@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,100 @@
|
|
|
1
|
+
// Pluggable CacheHandler (COMPAT - Next's classic cacheHandler config).
|
|
2
|
+
//
|
|
3
|
+
// Next's classic `cacheHandler` config points at a module exporting a class (CJS `module.exports =`,
|
|
4
|
+
// CJS `.default`, or ESM default) with `get(key, ctx)` / `set(key, data, ctx)` methods, backing the
|
|
5
|
+
// fetch data cache and the ISR page cache. pnext keeps an in-memory store of its own; when a handler is
|
|
6
|
+
// configured the store defers to it so the handler observes every get/set and can persist across the
|
|
7
|
+
// SWR window.
|
|
8
|
+
//
|
|
9
|
+
// Core owns the instance and the get/set/revalidate seams; compat loads the configured module and
|
|
10
|
+
// installs it via setCacheHandler, so core never imports compat.
|
|
11
|
+
|
|
12
|
+
export interface CacheHandlerEntry {
|
|
13
|
+
value: unknown;
|
|
14
|
+
lastModified: number;
|
|
15
|
+
tags?: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CacheHandlerGetContext {
|
|
19
|
+
tags?: readonly string[];
|
|
20
|
+
softTags?: readonly string[];
|
|
21
|
+
kind?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CacheHandlerSetContext {
|
|
25
|
+
tags?: readonly string[];
|
|
26
|
+
revalidate?: number | false;
|
|
27
|
+
kind?: string;
|
|
28
|
+
/** Set for fetch data-cache writes (Next's incremental-cache fetch ctx). */
|
|
29
|
+
fetchCache?: boolean;
|
|
30
|
+
fetchUrl?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The subset of Next's IncrementalCacheHandler contract pnext drives. */
|
|
34
|
+
export interface CacheHandler {
|
|
35
|
+
get(
|
|
36
|
+
key: string,
|
|
37
|
+
ctx?: CacheHandlerGetContext,
|
|
38
|
+
): Promise<CacheHandlerEntry | undefined | null> | CacheHandlerEntry | undefined | null;
|
|
39
|
+
set(
|
|
40
|
+
key: string,
|
|
41
|
+
data: unknown,
|
|
42
|
+
ctx?: CacheHandlerSetContext,
|
|
43
|
+
): Promise<void> | void;
|
|
44
|
+
revalidateTag?(tag: string | string[]): Promise<void> | void;
|
|
45
|
+
resetRequestCache?(): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let handler: CacheHandler | undefined;
|
|
49
|
+
|
|
50
|
+
/** Install the configured handler (compat calls this from an init hook). */
|
|
51
|
+
export function setCacheHandler(instance: CacheHandler | undefined): void {
|
|
52
|
+
handler = instance;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getCacheHandler(): CacheHandler | undefined {
|
|
56
|
+
return handler;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function hasCacheHandler(): boolean {
|
|
60
|
+
return handler !== undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Read an entry from the configured handler, tolerating sync/async + throws. */
|
|
64
|
+
export async function cacheHandlerGet(
|
|
65
|
+
key: string,
|
|
66
|
+
ctx?: CacheHandlerGetContext,
|
|
67
|
+
): Promise<CacheHandlerEntry | undefined> {
|
|
68
|
+
if (!handler) return undefined;
|
|
69
|
+
try {
|
|
70
|
+
const entry = await handler.get(key, ctx);
|
|
71
|
+
return entry ?? undefined;
|
|
72
|
+
} catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Write an entry to the configured handler, tolerating sync/async + throws. */
|
|
78
|
+
export async function cacheHandlerSet(
|
|
79
|
+
key: string,
|
|
80
|
+
data: unknown,
|
|
81
|
+
ctx?: CacheHandlerSetContext,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
if (!handler) return;
|
|
84
|
+
try {
|
|
85
|
+
await handler.set(key, data, ctx);
|
|
86
|
+
} catch {
|
|
87
|
+
// A handler failure must not break the response; the in-memory store is
|
|
88
|
+
// authoritative for correctness, the handler is a persistence layer.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Forward on-demand tag invalidation to the handler if it supports it. */
|
|
93
|
+
export async function cacheHandlerRevalidateTag(tag: string): Promise<void> {
|
|
94
|
+
if (!handler?.revalidateTag) return;
|
|
95
|
+
try {
|
|
96
|
+
await handler.revalidateTag(tag);
|
|
97
|
+
} catch {
|
|
98
|
+
// best-effort
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { format } from 'node:util';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
import { currentRequest } from '../../request/context';
|
|
7
|
+
import { realNow } from '../../ppr';
|
|
8
|
+
import { getNextConfig } from '../next/config-loader';
|
|
9
|
+
|
|
10
|
+
interface ModernCacheEntry {
|
|
11
|
+
value: ReadableStream<Uint8Array>;
|
|
12
|
+
tags: string[];
|
|
13
|
+
stale: number;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
expire: number;
|
|
16
|
+
revalidate: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ModernCacheHandler {
|
|
20
|
+
get?: (cacheKey: string, softTags: string[]) => Promise<ModernCacheEntry | undefined> | ModernCacheEntry | undefined;
|
|
21
|
+
set?: (cacheKey: string, pendingEntry: Promise<ModernCacheEntry>) => Promise<void> | void;
|
|
22
|
+
refreshTags?: () => Promise<void> | void;
|
|
23
|
+
getExpiration?: (tags: string[]) => Promise<number> | number;
|
|
24
|
+
updateTags?: (tags: string[], durations?: { expire?: number }) => Promise<void> | void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ModernCacheHandlerState {
|
|
28
|
+
handler?: ModernCacheHandler;
|
|
29
|
+
installedFor?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MODERN_CACHE_HANDLER_STATE = Symbol.for('pnext.compat.modernCacheHandler');
|
|
33
|
+
|
|
34
|
+
function state(): ModernCacheHandlerState {
|
|
35
|
+
const global = globalThis as typeof globalThis & {
|
|
36
|
+
[MODERN_CACHE_HANDLER_STATE]?: ModernCacheHandlerState;
|
|
37
|
+
};
|
|
38
|
+
return (global[MODERN_CACHE_HANDLER_STATE] ??= {});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function installModernCacheHandlers(root: string): void {
|
|
42
|
+
const configured = defaultCacheHandlerPath();
|
|
43
|
+
const current = state();
|
|
44
|
+
if (!configured) {
|
|
45
|
+
current.handler = undefined;
|
|
46
|
+
current.installedFor = undefined;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const filePath = resolveHandlerPath(configured, root);
|
|
50
|
+
if (current.installedFor === filePath) return;
|
|
51
|
+
const loaded = loadHandler(filePath, root);
|
|
52
|
+
if (!loaded) return;
|
|
53
|
+
current.handler = loaded;
|
|
54
|
+
current.installedFor = filePath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function hasModernCacheHandler(): boolean {
|
|
58
|
+
return state().handler !== undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function modernCacheRefreshTags(): Promise<void> {
|
|
62
|
+
const refreshTags = state().handler?.refreshTags;
|
|
63
|
+
const handler = state().handler;
|
|
64
|
+
if (!handler || !refreshTags) return;
|
|
65
|
+
try {
|
|
66
|
+
await withNodeConsoleFormat(() => refreshTags.call(handler));
|
|
67
|
+
} catch {
|
|
68
|
+
// best-effort
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function modernCacheGet(key: string, softTags = implicitSoftTags()): Promise<ModernCacheEntry | undefined> {
|
|
73
|
+
const handler = state().handler;
|
|
74
|
+
const get = handler?.get;
|
|
75
|
+
if (!handler || !get) return undefined;
|
|
76
|
+
try {
|
|
77
|
+
return await withNodeConsoleFormat(() => get.call(handler, key, softTags));
|
|
78
|
+
} catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function modernCacheSet(key: string, pendingEntry: Promise<ModernCacheEntry>): Promise<void> {
|
|
84
|
+
const handler = state().handler;
|
|
85
|
+
const set = handler?.set;
|
|
86
|
+
if (!handler || !set) return;
|
|
87
|
+
try {
|
|
88
|
+
await withNodeConsoleFormat(() => set.call(handler, key, pendingEntry));
|
|
89
|
+
} catch {
|
|
90
|
+
// best-effort
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function modernCacheGetExpiration(tags = implicitSoftTags()): Promise<number | undefined> {
|
|
95
|
+
const handler = state().handler;
|
|
96
|
+
const getExpiration = handler?.getExpiration;
|
|
97
|
+
if (!handler || !getExpiration) return undefined;
|
|
98
|
+
try {
|
|
99
|
+
return await withNodeConsoleFormat(() => getExpiration.call(handler, tags));
|
|
100
|
+
} catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function modernCacheUpdateTags(tags: string[], durations?: { expire?: number }): Promise<void> {
|
|
106
|
+
const handler = state().handler;
|
|
107
|
+
const updateTags = handler?.updateTags;
|
|
108
|
+
if (!handler || !updateTags) return;
|
|
109
|
+
try {
|
|
110
|
+
await withNodeConsoleFormat(() => updateTags.call(handler, tags, durations));
|
|
111
|
+
} catch {
|
|
112
|
+
// best-effort
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Run a user cache-handler method with Node-style console formatting. Bun's
|
|
118
|
+
* console.log inspects arguments with double quotes and a narrow wrap width;
|
|
119
|
+
* handlers log their raw arguments and the Next e2e suites match Node's
|
|
120
|
+
* single-quoted util.inspect output verbatim.
|
|
121
|
+
*/
|
|
122
|
+
async function withNodeConsoleFormat<T>(fn: () => Promise<T> | T): Promise<T> {
|
|
123
|
+
const original = console.log.bind(console);
|
|
124
|
+
console.log = (...args: unknown[]) => {
|
|
125
|
+
original(format(...args));
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
return await fn();
|
|
129
|
+
} finally {
|
|
130
|
+
console.log = original;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function modernCacheKey(id: string, argsJson: string): string {
|
|
135
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
136
|
+
const buildId = process.env.NODE_ENV === 'development' ? 'development' : 'pnext';
|
|
137
|
+
// Fixed-width hex: Next's cache keys are whole-byte hex strings and the e2e
|
|
138
|
+
// suites match them with `([0-9a-f]{2})+` — an odd-length (leading-zero
|
|
139
|
+
// trimmed) hash would never match.
|
|
140
|
+
const hash = Bun.hash.wyhash(`${id}:${argsJson}`).toString(16).padStart(16, '0');
|
|
141
|
+
return JSON.stringify([buildId, hash, JSON.parse(argsJson)]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function modernCacheEntry(input: {
|
|
145
|
+
value: unknown;
|
|
146
|
+
tags: string[];
|
|
147
|
+
staleSeconds?: number;
|
|
148
|
+
expireSeconds?: number;
|
|
149
|
+
revalidateSeconds?: number;
|
|
150
|
+
storedAt: number;
|
|
151
|
+
}): ModernCacheEntry {
|
|
152
|
+
return {
|
|
153
|
+
value: new ReadableStream<Uint8Array>({
|
|
154
|
+
start(controller) {
|
|
155
|
+
controller.enqueue(new TextEncoder().encode(JSON.stringify(input.value)));
|
|
156
|
+
controller.close();
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
tags: input.tags,
|
|
160
|
+
stale: input.staleSeconds ?? 300,
|
|
161
|
+
timestamp: input.storedAt,
|
|
162
|
+
expire: input.expireSeconds ?? 31536000,
|
|
163
|
+
revalidate: input.revalidateSeconds ?? 900,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function clearModernCacheHandlerForTest(): void {
|
|
168
|
+
const current = state();
|
|
169
|
+
current.handler = undefined;
|
|
170
|
+
current.installedFor = undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function defaultCacheHandlerPath(): string | undefined {
|
|
174
|
+
const cacheHandlers = getNextConfig().cacheHandlers;
|
|
175
|
+
if (!cacheHandlers || typeof cacheHandlers !== 'object' || Array.isArray(cacheHandlers)) return undefined;
|
|
176
|
+
const configured = (cacheHandlers as Record<string, unknown>).default;
|
|
177
|
+
return typeof configured === 'string' && configured.length > 0 ? configured : undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function resolveHandlerPath(configured: string, root: string): string {
|
|
181
|
+
const resolved = configured.startsWith('file:') ? fileURLToPath(configured) : configured;
|
|
182
|
+
if (path.isAbsolute(resolved)) return resolved;
|
|
183
|
+
return path.resolve(root, resolved);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function loadHandler(filePath: string, root: string): ModernCacheHandler | undefined {
|
|
187
|
+
if (!existsSync(filePath)) return undefined;
|
|
188
|
+
try {
|
|
189
|
+
const require = createRequire(pathToFileURL(`${root}/index.js`).href);
|
|
190
|
+
try {
|
|
191
|
+
return pickHandler(require(filePath));
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (!isNextDistMissing(error)) return undefined;
|
|
194
|
+
return loadCommonJsHandlerWithNextStubs(filePath, require);
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function loadCommonJsHandlerWithNextStubs(
|
|
202
|
+
filePath: string,
|
|
203
|
+
rootRequire: NodeRequire,
|
|
204
|
+
): ModernCacheHandler | undefined {
|
|
205
|
+
try {
|
|
206
|
+
const module = { exports: {} as unknown };
|
|
207
|
+
const localRequire = (specifier: string): unknown => {
|
|
208
|
+
if (specifier === 'next/dist/server/lib/cache-handlers/default.external') {
|
|
209
|
+
return { default: createDefaultExternalHandler() };
|
|
210
|
+
}
|
|
211
|
+
if (specifier.startsWith('next/dist/')) return {};
|
|
212
|
+
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
213
|
+
return rootRequire(path.resolve(path.dirname(filePath), specifier));
|
|
214
|
+
}
|
|
215
|
+
return rootRequire(specifier);
|
|
216
|
+
};
|
|
217
|
+
const source = readFileSync(filePath, 'utf8');
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
219
|
+
const evaluate = new Function('require', 'module', 'exports', '__filename', '__dirname', source) as (
|
|
220
|
+
require: (specifier: string) => unknown,
|
|
221
|
+
module: { exports: unknown },
|
|
222
|
+
exports: unknown,
|
|
223
|
+
filename: string,
|
|
224
|
+
dirname: string,
|
|
225
|
+
) => void;
|
|
226
|
+
evaluate(localRequire, module, module.exports, filePath, path.dirname(filePath));
|
|
227
|
+
return pickHandler(module.exports);
|
|
228
|
+
} catch {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function isNextDistMissing(error: unknown): boolean {
|
|
234
|
+
const message = (error as { message?: unknown }).message;
|
|
235
|
+
return typeof message === 'string' && message.includes('next/dist/');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function pickHandler(mod: unknown): ModernCacheHandler | undefined {
|
|
239
|
+
const candidate =
|
|
240
|
+
mod && typeof mod === 'object' && 'default' in mod
|
|
241
|
+
? (mod as { default?: unknown }).default
|
|
242
|
+
: mod;
|
|
243
|
+
return candidate && typeof candidate === 'object' ? candidate : undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function createDefaultExternalHandler(): ModernCacheHandler {
|
|
247
|
+
const entries = new Map<string, ModernCacheEntry>();
|
|
248
|
+
const tagExpirations = new Map<string, number>();
|
|
249
|
+
return {
|
|
250
|
+
get(cacheKey) {
|
|
251
|
+
return entries.get(cacheKey);
|
|
252
|
+
},
|
|
253
|
+
async set(cacheKey, pendingEntry) {
|
|
254
|
+
entries.set(cacheKey, await pendingEntry);
|
|
255
|
+
},
|
|
256
|
+
refreshTags() {
|
|
257
|
+
return undefined;
|
|
258
|
+
},
|
|
259
|
+
getExpiration(tags) {
|
|
260
|
+
let value = 0;
|
|
261
|
+
for (const tag of tags) value = Math.max(value, tagExpirations.get(tag) ?? 0);
|
|
262
|
+
return value;
|
|
263
|
+
},
|
|
264
|
+
updateTags(tags) {
|
|
265
|
+
const now = realNow();
|
|
266
|
+
for (const tag of tags) tagExpirations.set(tag, now);
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function implicitSoftTags(): string[] {
|
|
272
|
+
const pathname = new URL(currentRequest()?.url ?? 'http://pnext.local/').pathname || '/';
|
|
273
|
+
const leaf = pathname === '/' ? '/index' : pathname;
|
|
274
|
+
return ['_N_T_/layout', '_N_T_/page', '_N_T_/', `_N_T_${leaf}`];
|
|
275
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Resume-data-cache (RDC) - COMPAT (may import core freely). Composes the per-cache capture/seed
|
|
2
|
+
// helpers into the opaque render-lifecycle capability core drives; core never imports this file.
|
|
3
|
+
//
|
|
4
|
+
// Artifact format (the `.rdc` file emitted next to a PPR shell, plain JSON):
|
|
5
|
+
// { "v": 1,
|
|
6
|
+
// "useCache": [ { key, value, tags, revalidateSeconds?, expireSeconds?, staleSeconds?, storedAt,
|
|
7
|
+
// tagSeq } ... ],
|
|
8
|
+
// "fetch": [ { key, snapshot: { status, statusText, headers, body(b64), url }, tags,
|
|
9
|
+
// revalidateSeconds?, storedAt } ... ] }
|
|
10
|
+
//
|
|
11
|
+
// `key` is the exact in-memory cache key so a serving-process read lands on the same key the dynamic
|
|
12
|
+
// render computes. Values are plain data only; anything else was skipped at capture and is recomputed
|
|
13
|
+
// at request time.
|
|
14
|
+
|
|
15
|
+
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import {
|
|
18
|
+
getRequestExtensions,
|
|
19
|
+
type PrerenderSidecarContext,
|
|
20
|
+
type PrerenderSidecarExtension,
|
|
21
|
+
} from '../../extensions';
|
|
22
|
+
import { pathRevalidatedSince, tagsRevalidatedSince, tagsStaleSince } from './revalidate';
|
|
23
|
+
import {
|
|
24
|
+
beginUseCacheRdcCapture,
|
|
25
|
+
collectUseCacheRdcRecords,
|
|
26
|
+
collectUseCacheRdcTags,
|
|
27
|
+
seedUseCacheRdcRecords,
|
|
28
|
+
type UseCacheRdcRecord,
|
|
29
|
+
} from './use-cache';
|
|
30
|
+
import {
|
|
31
|
+
beginFetchRdcCapture,
|
|
32
|
+
collectFetchRdcRecords,
|
|
33
|
+
seedFetchRdcRecords,
|
|
34
|
+
type FetchRdcRecord,
|
|
35
|
+
} from './fetch-patch';
|
|
36
|
+
|
|
37
|
+
export interface ResumeDataArtifact {
|
|
38
|
+
v: 1;
|
|
39
|
+
useCache: UseCacheRdcRecord[];
|
|
40
|
+
fetch: FetchRdcRecord[];
|
|
41
|
+
/**
|
|
42
|
+
* Union of every captured cache tag - including tags of entries too complex to serialize as records
|
|
43
|
+
* (page-level `use cache` trees). isStale() must see them or updateTag()/revalidateTag() can never
|
|
44
|
+
* stale the prebuilt shell.
|
|
45
|
+
*/
|
|
46
|
+
tags?: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isArtifact(value: unknown): value is ResumeDataArtifact {
|
|
50
|
+
const artifact = value as Partial<ResumeDataArtifact> | null;
|
|
51
|
+
return (
|
|
52
|
+
typeof value === 'object' &&
|
|
53
|
+
value !== null &&
|
|
54
|
+
artifact?.v === 1 &&
|
|
55
|
+
Array.isArray(artifact.useCache) &&
|
|
56
|
+
Array.isArray(artifact.fetch)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function artifactPath(context: PrerenderSidecarContext): string {
|
|
61
|
+
return path.join(context.outPath, 'ppr', `${context.routeId}.rdc`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function shellPath(context: PrerenderSidecarContext): string {
|
|
65
|
+
return path.join(context.outPath, 'ppr', `${context.routeId}.html`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function sidecarFreshnessAt(context: PrerenderSidecarContext): Promise<number | undefined> {
|
|
69
|
+
for (const file of [shellPath(context), artifactPath(context)]) {
|
|
70
|
+
try {
|
|
71
|
+
return (await stat(file)).mtimeMs;
|
|
72
|
+
} catch {
|
|
73
|
+
// Try the fallback artifact when unit-testing the extension directly.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function readArtifact(context: PrerenderSidecarContext): Promise<ResumeDataArtifact | undefined> {
|
|
80
|
+
try {
|
|
81
|
+
const artifact = JSON.parse(await readFile(artifactPath(context), 'utf8')) as unknown;
|
|
82
|
+
return isArtifact(artifact) ? artifact : undefined;
|
|
83
|
+
} catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const resumeDataCacheExtension: PrerenderSidecarExtension = {
|
|
89
|
+
begin() {
|
|
90
|
+
beginUseCacheRdcCapture();
|
|
91
|
+
beginFetchRdcCapture();
|
|
92
|
+
},
|
|
93
|
+
async collect() {
|
|
94
|
+
const useCache = await collectUseCacheRdcRecords();
|
|
95
|
+
const fetch = await collectFetchRdcRecords();
|
|
96
|
+
const tags = await collectUseCacheRdcTags();
|
|
97
|
+
if (useCache.length === 0 && fetch.length === 0 && tags.length === 0) return undefined;
|
|
98
|
+
return {
|
|
99
|
+
v: 1,
|
|
100
|
+
useCache,
|
|
101
|
+
fetch,
|
|
102
|
+
...(tags.length > 0 ? { tags } : {}),
|
|
103
|
+
} satisfies ResumeDataArtifact;
|
|
104
|
+
},
|
|
105
|
+
async persist(context, artifact) {
|
|
106
|
+
if (!isArtifact(artifact)) return;
|
|
107
|
+
const file = artifactPath(context);
|
|
108
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
109
|
+
await writeFile(file, JSON.stringify(artifact));
|
|
110
|
+
},
|
|
111
|
+
async seed(context) {
|
|
112
|
+
const artifact = await readArtifact(context);
|
|
113
|
+
if (!artifact) return;
|
|
114
|
+
seedUseCacheRdcRecords(artifact.useCache, context.routePath);
|
|
115
|
+
seedFetchRdcRecords(artifact.fetch, context.routePath, artifact.tags ?? []);
|
|
116
|
+
},
|
|
117
|
+
async isStale(context) {
|
|
118
|
+
const freshnessAt = await sidecarFreshnessAt(context);
|
|
119
|
+
if (freshnessAt !== undefined && pathRevalidatedSince(context.routePath, freshnessAt)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
const artifact = await readArtifact(context);
|
|
123
|
+
if (!artifact) return false;
|
|
124
|
+
const tags = [
|
|
125
|
+
...(artifact.tags ?? []),
|
|
126
|
+
...artifact.useCache.flatMap(record => record.tags),
|
|
127
|
+
...artifact.fetch.flatMap(record => record.tags),
|
|
128
|
+
];
|
|
129
|
+
if (
|
|
130
|
+
freshnessAt !== undefined &&
|
|
131
|
+
getRequestExtensions().staticStaleness(context.routePath, freshnessAt, tags)
|
|
132
|
+
) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
// Soft revalidations (revalidateTag with a nonzero-expiry profile) mark tags stale rather than
|
|
136
|
+
// hard-revalidated. A PPR shell whose data carries a stale tag must regenerate on the next request -
|
|
137
|
+
// the shell IS the data, since the values are baked into the prerendered HTML, so unlike ISR there
|
|
138
|
+
// is no softer way to refresh it.
|
|
139
|
+
if (freshnessAt === undefined) return false;
|
|
140
|
+
if (tagsRevalidatedSince(tags, freshnessAt)) return true;
|
|
141
|
+
return tagsStaleSince(tags, freshnessAt) ? 'soft' : false;
|
|
142
|
+
},
|
|
143
|
+
};
|