@wular/pnext 0.0.3 → 0.0.6
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 +11 -11
- package/bin/pnext +1 -1
- package/config/lint/base.js +7 -7
- package/config/ts/base.json +2 -4
- package/config/ts/react.json +2 -6
- package/package.json +23 -2
- package/reference/compat.md +1 -1
- package/reference/config.md +2 -2
- package/reference/css.md +13 -9
- package/reference/dev.md +1 -1
- package/reference/metadata.md +7 -7
- package/reference/navigation.md +35 -28
- package/reference/performance.md +70 -70
- package/reference/rendering.md +17 -17
- package/reference/routing.md +17 -17
- package/reference/typegen.md +12 -8
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +113 -109
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +49 -52
- package/src/api/navigation.ts +59 -58
- package/src/api/server.ts +152 -144
- package/src/api/suspense.ts +4 -4
- package/src/cli/adapters/vercel-warm.ts +126 -121
- package/src/cli/adapters/vercel.ts +437 -443
- package/src/cli/analyze.ts +333 -144
- package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
- package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
- package/src/cli/build.ts +976 -978
- package/src/cli/create.ts +71 -63
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +88 -85
- package/src/cli/migrate/package-json.ts +73 -71
- package/src/cli/migrate/report.ts +32 -33
- package/src/cli/migrate/{index.ts → run.ts} +48 -49
- package/src/cli/migrate/scan.ts +46 -46
- package/src/cli/migrate/spinner.ts +9 -9
- package/src/cli/migrate/tsconfig.ts +35 -35
- package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
- package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
- package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
- package/src/cli/start.ts +65 -66
- package/src/{typegen.ts → cli/typegen.ts} +59 -59
- package/src/client/build.ts +794 -768
- package/src/client/chunk-fold.ts +245 -240
- package/src/client/{paths.ts → chunk-name.ts} +6 -6
- package/src/client/entry.ts +162 -147
- package/src/client/prebuilt.ts +231 -223
- package/src/client/profile.ts +29 -29
- package/src/client/react-compiler.ts +20 -15
- package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
- package/src/client/reference-stub.ts +51 -51
- package/src/client/reference.ts +19 -19
- package/src/{api → client}/router/events.ts +17 -17
- package/src/{api → client}/router/history.ts +16 -16
- package/src/{api → client}/router/hub.ts +52 -53
- package/src/{api/router.ts → client/router/index.ts} +61 -60
- package/src/{api → client}/router/policies.ts +24 -24
- package/src/{api → client}/router/runtime.ts +1988 -1961
- package/src/{api → client}/router/types.ts +93 -98
- package/src/compat/actions/client-plugin.ts +42 -43
- package/src/compat/actions/client-stub.ts +14 -14
- package/src/compat/actions/{action-client.ts → client.ts} +194 -193
- package/src/compat/actions/config.ts +63 -64
- package/src/compat/actions/detect.ts +68 -68
- package/src/compat/actions/discovery.ts +105 -105
- package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
- package/src/compat/actions/early-submit.ts +1 -1
- package/src/compat/actions/endpoint.ts +198 -198
- package/src/compat/actions/extensions.ts +811 -0
- package/src/compat/actions/flight.ts +23 -23
- package/src/compat/actions/form-state.ts +34 -34
- package/src/compat/actions/hoist.ts +382 -236
- package/src/compat/actions/ids.ts +7 -12
- package/src/compat/actions/index.ts +8 -8
- package/src/compat/actions/instances.ts +46 -46
- package/src/compat/actions/origin.ts +47 -48
- package/src/compat/actions/protocol.ts +22 -22
- package/src/compat/actions/registry.ts +19 -19
- package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
- package/src/compat/actions/rewrite.ts +125 -125
- package/src/compat/actions/{action-router.ts → router.ts} +10 -10
- package/src/compat/actions/serve.ts +132 -136
- package/src/compat/actions/server-tag.ts +4 -4
- package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
- package/src/compat/actions/unrecognized-error.ts +4 -4
- package/src/compat/{index.ts → aliases.ts} +112 -109
- package/src/compat/bundler/bun-externals.ts +18 -18
- package/src/compat/bundler/cjs-exports.ts +271 -223
- package/src/compat/bundler/config.ts +116 -111
- package/src/compat/bundler/externals.ts +12 -12
- package/src/compat/bundler/import-meta-url.ts +19 -19
- package/src/compat/bundler/modularize-imports.ts +36 -33
- package/src/compat/bundler/new-url-asset.ts +22 -24
- package/src/compat/bundler/optimize-package-imports.ts +111 -107
- package/src/compat/bundler/polyfill.ts +28 -28
- package/src/compat/bundler/react-compiler.ts +35 -29
- package/src/compat/bundler/react-profiler.tsx +11 -11
- package/src/compat/bundler/relay-transform.ts +48 -47
- package/src/compat/bundler/require-context.ts +119 -113
- package/src/compat/bundler/resolve-extensions.ts +19 -19
- package/src/compat/bundler/source-cache.ts +25 -25
- package/src/compat/bundler/static-imports.ts +7 -7
- package/src/compat/bundler/symlink-imports.ts +46 -46
- package/src/compat/bundler/tsconfig-paths.ts +13 -15
- package/src/compat/bundler/wasm.ts +58 -60
- package/src/compat/bundler/webpack-loaders.ts +254 -241
- package/src/compat/bundler/worker.ts +101 -104
- package/src/compat/cache/build-flags.ts +29 -29
- package/src/compat/cache/build-prerender-errors.ts +40 -44
- package/src/compat/cache/custom-handler.ts +60 -53
- package/src/compat/cache/fetch-patch.ts +240 -240
- package/src/compat/cache/handler.ts +27 -31
- package/src/compat/cache/modern-handler.ts +148 -126
- package/src/compat/cache/resume-data-cache.ts +47 -45
- package/src/compat/cache/revalidate.ts +233 -232
- package/src/compat/cache/runtime-error.ts +35 -35
- package/src/compat/cache/use-cache-transform.ts +442 -417
- package/src/compat/cache/use-cache.ts +650 -614
- package/src/compat/cache-control.ts +140 -142
- package/src/compat/client/base-path.ts +21 -20
- package/src/compat/client/css-order.ts +18 -18
- package/src/compat/client/errors/bare-boundary.ts +11 -11
- package/src/compat/client/errors/control-flow.ts +23 -23
- package/src/compat/client/errors/error-boundary.ts +61 -61
- package/src/compat/client/errors/global-error.ts +101 -91
- package/src/compat/client/errors/install.ts +71 -72
- package/src/compat/client/errors/lazy.ts +23 -23
- package/src/compat/client/errors/primitive-throw.ts +47 -45
- package/src/compat/client/errors/soft-refresh.ts +4 -4
- package/src/compat/client/link-status.ts +33 -33
- package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
- package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
- package/src/compat/client/navigation-scroll.ts +63 -63
- package/src/compat/client/optimistic-routing.ts +93 -88
- package/src/compat/client/prefetch-cache.ts +23 -24
- package/src/compat/client/route-announcer.ts +35 -35
- package/src/compat/client/segment-cache-policy.ts +20 -20
- package/src/compat/client/segment-cache.ts +309 -315
- package/src/compat/client/segment-prefetch.ts +127 -132
- package/src/compat/client/trailing-slash.ts +5 -4
- package/src/compat/css/chunking.ts +113 -116
- package/src/compat/css/inline-css.ts +21 -21
- package/src/compat/css/lightningcss.ts +37 -38
- package/src/compat/css/modules.ts +161 -175
- package/src/compat/css/nonce.ts +7 -7
- package/src/compat/css/sass-plugin.ts +18 -21
- package/src/compat/css/sass.ts +150 -152
- package/src/compat/css/styled-jsx-runtime.ts +27 -27
- package/src/compat/css/styled-jsx.ts +21 -21
- package/src/compat/edge-runtime.ts +27 -27
- package/src/compat/{adapter → export}/build-complete.ts +72 -75
- package/src/compat/export/client.ts +29 -31
- package/src/compat/export/{index.ts → emit.ts} +111 -110
- package/src/compat/export/standalone.ts +62 -54
- package/src/compat/image-optimizer/cache.ts +51 -49
- package/src/compat/image-optimizer/detect.ts +52 -52
- package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
- package/src/compat/image-optimizer/source.ts +77 -80
- package/src/compat/lifecycle/after-scope.ts +26 -26
- package/src/compat/lifecycle/after.ts +48 -45
- package/src/compat/lifecycle/error-funnel.ts +98 -102
- package/src/compat/lifecycle/error-serialize.ts +30 -26
- package/src/compat/lifecycle/error-ui.ts +67 -32
- package/src/compat/lifecycle/instrumentation-client.ts +36 -38
- package/src/compat/lifecycle/instrumentation.ts +85 -85
- package/src/compat/lifecycle/node-console.ts +11 -11
- package/src/compat/lifecycle/testmode.ts +160 -132
- package/src/compat/mdx/compile.ts +60 -57
- package/src/compat/mdx/plugin.ts +11 -11
- package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
- package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
- package/src/compat/metadata.ts +75 -81
- package/src/compat/next/cache.ts +68 -69
- package/src/compat/next/canonical-url.ts +9 -9
- package/src/compat/next/client-cache.ts +19 -19
- package/src/compat/next/client-navigation.ts +112 -116
- package/src/compat/next/client-only.ts +1 -1
- package/src/compat/next/client-script.tsx +99 -93
- package/src/compat/next/client-server.ts +10 -10
- package/src/compat/next/config-loader.ts +176 -173
- package/src/compat/next/config.ts +7 -7
- package/src/compat/next/constants.cjs +6 -6
- package/src/compat/next/constants.ts +6 -6
- package/src/compat/next/custom-server.ts +26 -24
- package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
- package/src/compat/next/dynamic.tsx +21 -18
- package/src/compat/next/error.tsx +52 -52
- package/src/compat/next/font/cache.ts +74 -74
- package/src/compat/next/font/google.ts +2 -2
- package/src/compat/next/font/index.ts +1 -1
- package/src/compat/next/font/local.ts +3 -3
- package/src/compat/next/font/runtime-client.ts +17 -15
- package/src/compat/next/font/runtime.ts +443 -408
- package/src/compat/next/font/shared.ts +120 -108
- package/src/compat/next/form.tsx +63 -63
- package/src/compat/next/head.tsx +2 -2
- package/src/compat/next/headers.ts +103 -95
- package/src/compat/next/image/client.tsx +220 -0
- package/src/compat/next/image/config.ts +56 -58
- package/src/compat/next/image/optimizer.ts +40 -36
- package/src/compat/next/image/patterns.ts +37 -40
- package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
- package/src/compat/next/image/shared.ts +65 -57
- package/src/compat/next/image/static-metadata.ts +98 -107
- package/src/compat/next/image/validate.ts +79 -88
- package/src/compat/next/image.tsx +19 -23
- package/src/compat/next/index.ts +1 -1
- package/src/compat/next/legacy-image.tsx +59 -60
- package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
- package/src/compat/next/link.tsx +158 -158
- package/src/compat/next/navigation.cjs +12 -3
- package/src/compat/next/navigation.ts +48 -50
- package/src/compat/next/offline.ts +27 -27
- package/src/compat/next/og.ts +121 -124
- package/src/compat/next/preferred-region.ts +13 -14
- package/src/compat/next/redirects.ts +58 -56
- package/src/compat/next/resource-hints.ts +73 -76
- package/src/compat/next/rewrites.ts +130 -133
- package/src/compat/next/root-params.ts +45 -45
- package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
- package/src/compat/next/router.cjs +4 -2
- package/src/compat/next/router.ts +58 -61
- package/src/compat/next/script.tsx +108 -108
- package/src/compat/next/server-only.ts +1 -1
- package/src/compat/next/server.ts +19 -19
- package/src/compat/next/svgr.ts +18 -17
- package/src/compat/next/telemetry.ts +24 -24
- package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
- package/src/compat/next/user-agent.ts +53 -49
- package/src/compat/next/web-vitals.ts +22 -24
- package/src/compat/otel/api.ts +41 -41
- package/src/compat/otel/client-trace-metadata.ts +25 -27
- package/src/compat/otel/fetch-span.ts +29 -29
- package/src/compat/otel/tracer.ts +331 -331
- package/src/compat/pages/api.ts +456 -0
- package/src/compat/pages/client-plugin.ts +36 -36
- package/src/compat/pages/router-state.ts +34 -34
- package/src/compat/pages/{index.ts → router.ts} +130 -135
- package/src/compat/ppr/io.ts +12 -12
- package/src/compat/ppr/missing-root-params.ts +34 -38
- package/src/compat/ppr/root-params-scan.ts +66 -66
- package/src/compat/ppr/root-params-transform.ts +24 -26
- package/src/compat/ppr/root-params.ts +30 -30
- package/src/compat/ppr/segment-config-incompat.ts +6 -7
- package/src/compat/protocol.ts +71 -70
- package/src/compat/react/action-state.ts +47 -48
- package/src/compat/react/client-lite.ts +15 -15
- package/src/compat/react/client.ts +4 -4
- package/src/compat/react/compiler-runtime.ts +11 -11
- package/src/compat/react/dom-client.ts +44 -44
- package/src/compat/react/dom-react-server.ts +10 -16
- package/src/compat/react/dom-server.ts +10 -10
- package/src/compat/react/dom.ts +52 -52
- package/src/compat/react/hooks-extra.ts +34 -35
- package/src/compat/react/parity.ts +64 -61
- package/src/compat/react/preact.ts +81 -82
- package/src/compat/react/react-server.ts +28 -28
- package/src/compat/react/router-shim.ts +1 -1
- package/src/compat/react/server-component-use.ts +8 -8
- package/src/compat/react/server-inserted-html.ts +30 -31
- package/src/compat/react/server.ts +53 -55
- package/src/compat/react/use.ts +32 -32
- package/src/compat/react/view-transition.ts +20 -20
- package/src/compat/register/actions.ts +35 -824
- package/src/compat/register/boot.ts +41 -41
- package/src/compat/register/build-tier.ts +5 -5
- package/src/compat/register/build.ts +74 -70
- package/src/compat/register/bundler.ts +161 -156
- package/src/compat/register/cache.ts +20 -20
- package/src/compat/register/client-errors.ts +3 -3
- package/src/compat/register/config.ts +6 -6
- package/src/compat/register/css-extras.ts +30 -34
- package/src/compat/register/edge-runtime.ts +3 -3
- package/src/compat/register/errors.ts +10 -12
- package/src/compat/register/export.ts +16 -16
- package/src/compat/register/font.ts +11 -11
- package/src/compat/register/hooks.ts +2 -2
- package/src/compat/register/image.ts +40 -40
- package/src/compat/register/index.ts +59 -62
- package/src/compat/register/instrumentation-client.ts +10 -10
- package/src/compat/register/lifecycle.ts +25 -28
- package/src/compat/register/mdx.ts +10 -10
- package/src/compat/register/middleware.ts +226 -16
- package/src/compat/register/otel.ts +80 -88
- package/src/compat/register/pages-api.ts +10 -463
- package/src/compat/register/ppr.ts +16 -16
- package/src/compat/register/protocol.ts +17 -18
- package/src/compat/register/proxy.ts +49 -51
- package/src/compat/register/render.ts +79 -76
- package/src/compat/register/routing.ts +162 -159
- package/src/compat/register/segment.ts +24 -1887
- package/src/compat/register/static-image.ts +3 -3
- package/src/compat/register/{misc.ts → taint.ts} +9 -9
- package/src/compat/register/typed-routes.ts +14 -14
- package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
- package/src/compat/register/validation.ts +18 -18
- package/src/compat/segment/loading-boundary.ts +43 -45
- package/src/compat/segment/page-slot.ts +69 -69
- package/src/compat/segment/serve.ts +1884 -0
- package/src/compat/segment/tree.ts +113 -112
- package/src/compat/segment/vary-key.ts +38 -38
- package/src/compat/segment/vary-params.ts +138 -142
- package/src/compat/static-params.ts +14 -12
- package/src/compat/tsconfig-defaults.ts +87 -91
- package/src/compat/typecheck/{index.ts → check.ts} +234 -212
- package/src/compat/typecheck/worker.ts +15 -12
- package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
- package/src/compat/typed-routes/manifest.ts +174 -170
- package/src/compat/typed-routes/typegen.ts +127 -110
- package/src/compat/validation/errors.ts +16 -19
- package/src/compat/validation/prerender-diagnostics.ts +521 -504
- package/src/compat/validation/{index.ts → validate.ts} +647 -648
- package/src/compat-bootstrap.ts +16 -16
- package/src/config.ts +69 -69
- package/src/css/build.ts +230 -227
- package/src/css/postcss.ts +79 -80
- package/src/css/worker.ts +14 -15
- package/src/dev/client-actions.ts +10 -10
- package/src/dev/client-chunk-store.ts +27 -27
- package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
- package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
- package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
- package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
- package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
- package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
- package/src/dev/server.ts +804 -820
- package/src/env.ts +46 -43
- package/src/extensions.ts +491 -478
- package/src/index.ts +8 -8
- package/src/internal.ts +20 -23
- package/src/{islands → render}/boundary-error.ts +3 -3
- package/src/render/hooks.ts +71 -71
- package/src/render/island-context.ts +14 -14
- package/src/render/metadata.ts +310 -310
- package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
- package/src/{ppr.ts → render/ppr.ts} +244 -245
- package/src/render/renderer.ts +2076 -2082
- package/src/render/resource-hints.ts +16 -17
- package/src/render/slots.tsx +224 -235
- package/src/{islands → render}/static-children.ts +9 -12
- package/src/{islands → render}/static-slots.ts +37 -37
- package/src/{cache/context.ts → request/cache.ts} +20 -20
- package/src/request/context.ts +107 -107
- package/src/{dynamic/source.ts → resolve/dynamic.ts} +152 -143
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +442 -186
- package/src/resolve/source-text.ts +37 -37
- package/src/{dynamic → resolve}/tree-shake.ts +132 -128
- package/src/routing/forwarded.ts +19 -19
- package/src/routing/handler.ts +84 -91
- package/src/routing/href.ts +69 -70
- package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
- package/src/{proxy.ts → routing/proxy.ts} +306 -312
- package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
- package/src/routing/routes.ts +833 -848
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +952 -0
- package/src/{dev → runtime}/module-cache.ts +309 -287
- package/src/{dev → runtime}/module-generations.ts +9 -9
- package/src/{dev → runtime}/module-transform.ts +81 -72
- package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
- package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
- package/src/runtime/vendor.ts +425 -404
- package/src/styles.d.ts +9 -0
- package/src/types.ts +320 -335
- package/src/utils/ansi.ts +5 -5
- package/src/utils/{source.ts → code.ts} +15 -12
- package/src/utils/content-type.ts +3 -3
- package/src/utils/decode.ts +2 -2
- package/src/utils/dev-profile.ts +13 -13
- package/src/utils/error-log.ts +6 -6
- package/src/utils/esbuild.ts +18 -18
- package/src/utils/fs-cache.ts +13 -13
- package/src/utils/fs.ts +57 -49
- package/src/utils/html.ts +20 -24
- package/src/utils/native-require.ts +8 -8
- package/src/utils/serialize.ts +139 -146
- package/src/utils/verbose.ts +18 -18
- package/src/cli/analyze-print.ts +0 -181
- package/src/compat/middleware/manifest.ts +0 -210
- package/src/compat/next/image-client.tsx +0 -215
- package/src/compat/next/link-usage.ts +0 -29
- package/src/css/index.ts +0 -2
- package/src/render/index.ts +0 -1
- package/src/style-modules.d.ts +0 -9
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* serialization boundary), and named custom cache handler kinds fall back to
|
|
12
12
|
* the default handler.
|
|
13
13
|
*/
|
|
14
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
15
|
-
import { Fragment, h, type ComponentChildren } from 'preact'
|
|
16
|
-
import { Suspense as CoreSuspense } from '../../api/suspense'
|
|
17
|
-
import { currentParams, currentRequest, getWorkUnit } from '../../request/context'
|
|
14
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
15
|
+
import { Fragment, h, type ComponentChildren } from 'preact'
|
|
16
|
+
import { Suspense as CoreSuspense } from '../../api/suspense'
|
|
17
|
+
import { currentParams, currentRequest, getWorkUnit } from '../../request/context'
|
|
18
18
|
import {
|
|
19
19
|
hangingPromise,
|
|
20
20
|
isPrefetchShellPrerender,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
PostponeError,
|
|
25
25
|
realNow,
|
|
26
26
|
trackPrerenderCacheFill,
|
|
27
|
-
} from '../../ppr'
|
|
27
|
+
} from '../../render/ppr'
|
|
28
28
|
import {
|
|
29
29
|
currentRenderCacheMeta,
|
|
30
30
|
currentTagRevalidationSeq,
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
setUseCacheRevalidateSink,
|
|
42
42
|
tagsRevalidatedAfterSeq,
|
|
43
43
|
tagsStaleSince,
|
|
44
|
-
} from './revalidate'
|
|
44
|
+
} from './revalidate'
|
|
45
45
|
import {
|
|
46
46
|
hasModernCacheHandler,
|
|
47
47
|
modernCacheEntry,
|
|
@@ -50,8 +50,8 @@ import {
|
|
|
50
50
|
modernCacheKey,
|
|
51
51
|
modernCacheRefreshTags,
|
|
52
52
|
modernCacheSet,
|
|
53
|
-
} from './modern-handler'
|
|
54
|
-
import { funnelCacheRuntimeError } from './runtime-error'
|
|
53
|
+
} from './modern-handler'
|
|
54
|
+
import { funnelCacheRuntimeError } from './runtime-error'
|
|
55
55
|
import {
|
|
56
56
|
prerenderErrorCollectionActive,
|
|
57
57
|
recordPrerenderError,
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
USE_CACHE_CLOSE_OVER_FUNCTION_BLOCK,
|
|
61
61
|
USE_CACHE_CLOSE_OVER_FUNCTION_MESSAGE,
|
|
62
62
|
USE_CACHE_HANGING_MESSAGE,
|
|
63
|
-
} from './build-prerender-errors'
|
|
63
|
+
} from './build-prerender-errors'
|
|
64
64
|
|
|
65
65
|
// ---------------------------------------------------------------------------
|
|
66
66
|
// Build-prerender `use cache` fill timeout (E236 hanging-input detection).
|
|
@@ -70,155 +70,161 @@ import {
|
|
|
70
70
|
// (seconds) and, on timeout, rejects with the E236 message. We mirror that only
|
|
71
71
|
// while a compat build prerender is armed (see build-prerender-errors); the
|
|
72
72
|
// serving runtime and pure-core builds are untouched. The value is seeded from
|
|
73
|
-
// next.config by register-
|
|
73
|
+
// next.config by register/use-cache at build start.
|
|
74
74
|
// ---------------------------------------------------------------------------
|
|
75
75
|
|
|
76
76
|
// Anchored on globalThis: begin/seed runs in the build process's copy of this
|
|
77
77
|
// module, but `withFillTimeout` runs in the bundled compat-server's copy — a
|
|
78
78
|
// module-local `let` would leave the bundle reading the stale default (see the
|
|
79
79
|
// same duplication note in build-prerender-errors.ts).
|
|
80
|
-
const USE_CACHE_FILL_TIMEOUT = Symbol.for('pnext.compat.useCacheFillTimeout')
|
|
80
|
+
const USE_CACHE_FILL_TIMEOUT = Symbol.for('pnext.compat.useCacheFillTimeout')
|
|
81
81
|
|
|
82
82
|
function fillTimeoutHolder(): { ms: number } {
|
|
83
|
-
const root = globalThis as Record<PropertyKey, unknown
|
|
84
|
-
return (root[USE_CACHE_FILL_TIMEOUT] ??= { ms: 50_000 }) as { ms: number }
|
|
83
|
+
const root = globalThis as Record<PropertyKey, unknown>
|
|
84
|
+
return (root[USE_CACHE_FILL_TIMEOUT] ??= { ms: 50_000 }) as { ms: number }
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Seed the build-time `use cache` fill timeout (milliseconds). Next derives it
|
|
89
89
|
* from `experimental.useCacheTimeout`, clamped during a build to just under
|
|
90
|
-
* `staticPageGenerationTimeout` (register-
|
|
90
|
+
* `staticPageGenerationTimeout` (register/use-cache passes the already-resolved
|
|
91
91
|
* value). Ignored (left at the 50s default) when undefined.
|
|
92
92
|
*/
|
|
93
93
|
export function setUseCacheFillTimeout(ms: number | undefined): void {
|
|
94
94
|
if (typeof ms === 'number' && Number.isFinite(ms) && ms > 0) {
|
|
95
|
-
fillTimeoutHolder().ms = ms
|
|
95
|
+
fillTimeoutHolder().ms = ms
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
interface UseCacheEntry {
|
|
100
|
-
value: Promise<unknown
|
|
101
|
-
storedAt: number
|
|
102
|
-
tags: string[]
|
|
103
|
-
revalidateSeconds?: number
|
|
104
|
-
expireSeconds?: number
|
|
105
|
-
staleSeconds?: number
|
|
100
|
+
value: Promise<unknown>
|
|
101
|
+
storedAt: number
|
|
102
|
+
tags: string[]
|
|
103
|
+
revalidateSeconds?: number
|
|
104
|
+
expireSeconds?: number
|
|
105
|
+
staleSeconds?: number
|
|
106
106
|
/** A background stale-while-revalidate refresh is already in flight. */
|
|
107
|
-
refreshing?: boolean
|
|
108
|
-
modernKey?: string
|
|
107
|
+
refreshing?: boolean
|
|
108
|
+
modernKey?: string
|
|
109
109
|
/** Pathname of the render that produced this entry (path-revalidation scope). */
|
|
110
|
-
route?: string
|
|
110
|
+
route?: string
|
|
111
111
|
/** Hard-tag-revalidation sequence sampled when production began. */
|
|
112
|
-
tagSeq: number
|
|
112
|
+
tagSeq: number
|
|
113
113
|
/**
|
|
114
114
|
* The render cache-meta this entry was produced under. An on-demand
|
|
115
115
|
* regeneration render (refreshFetches) re-produces each entry ONCE and then
|
|
116
116
|
* reuses it, so repeated reads within the regen dedupe (Next refreshes per
|
|
117
117
|
* unique cache, not per call).
|
|
118
118
|
*/
|
|
119
|
-
refreshedIn?: object
|
|
119
|
+
refreshedIn?: object
|
|
120
120
|
/**
|
|
121
121
|
* Nested `use cache` wrapper vnodes embedded in this entry's produced tree
|
|
122
122
|
* that have not yet rendered. Their scopes fold tags + min cacheLife into
|
|
123
123
|
* this entry when they settle (see attributeNestedCacheWrapper); persisting
|
|
124
124
|
* the entry to a modern cache handler waits for the count to drain.
|
|
125
125
|
*/
|
|
126
|
-
nestedPending?: number
|
|
126
|
+
nestedPending?: number
|
|
127
127
|
/** Invoked each time a pending nested wrapper settles. */
|
|
128
|
-
onNestedSettled?: () => void
|
|
128
|
+
onNestedSettled?: () => void
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
type RdcValue = null | boolean | number | string | RdcValue[] | { [key: string]: RdcValue }
|
|
131
|
+
type RdcValue = null | boolean | number | string | RdcValue[] | { [key: string]: RdcValue }
|
|
132
132
|
|
|
133
133
|
export interface UseCacheRdcRecord {
|
|
134
|
-
key: string
|
|
135
|
-
value: RdcValue
|
|
136
|
-
tags: string[]
|
|
137
|
-
storedAt: number
|
|
138
|
-
revalidateSeconds?: number
|
|
139
|
-
expireSeconds?: number
|
|
140
|
-
staleSeconds?: number
|
|
141
|
-
tagSeq: number
|
|
134
|
+
key: string
|
|
135
|
+
value: RdcValue
|
|
136
|
+
tags: string[]
|
|
137
|
+
storedAt: number
|
|
138
|
+
revalidateSeconds?: number
|
|
139
|
+
expireSeconds?: number
|
|
140
|
+
staleSeconds?: number
|
|
141
|
+
tagSeq: number
|
|
142
142
|
/** Process that produced this record — `tagSeq` only means anything there. */
|
|
143
|
-
pid?: string
|
|
144
|
-
route?: string
|
|
143
|
+
pid?: string
|
|
144
|
+
route?: string
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
interface UseCacheScope {
|
|
148
|
-
tags: Set<string
|
|
149
|
-
revalidateSeconds?: number
|
|
150
|
-
expireSeconds?: number
|
|
151
|
-
staleSeconds?: number
|
|
148
|
+
tags: Set<string>
|
|
149
|
+
revalidateSeconds?: number
|
|
150
|
+
expireSeconds?: number
|
|
151
|
+
staleSeconds?: number
|
|
152
152
|
/**
|
|
153
153
|
* The cached-function id producing inside this scope. Root-param reads made
|
|
154
154
|
* during production attribute to this id so the id's read-set (see
|
|
155
155
|
* knownRootParamsByFunctionId) widens and later calls key on those params.
|
|
156
156
|
*/
|
|
157
|
-
functionId?: string
|
|
157
|
+
functionId?: string
|
|
158
158
|
/** Root param NAMES read (via next/root-params) while producing this entry. */
|
|
159
|
-
rootParamsRead?: Set<string
|
|
159
|
+
rootParamsRead?: Set<string>
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
const USE_CACHE_ENTRIES = Symbol.for('pnext.compat.useCacheEntries')
|
|
163
|
-
const USE_CACHE_SCOPE_STORAGE = Symbol.for('pnext.compat.useCacheScopeStorage')
|
|
164
|
-
const USE_CACHE_KNOWN_ROOT_PARAMS = Symbol.for('pnext.compat.knownRootParamsByFunctionId')
|
|
165
|
-
const USE_CACHE_REQUEST_TOKENS = Symbol.for('pnext.compat.useCacheRequestTokens')
|
|
162
|
+
const USE_CACHE_ENTRIES = Symbol.for('pnext.compat.useCacheEntries')
|
|
163
|
+
const USE_CACHE_SCOPE_STORAGE = Symbol.for('pnext.compat.useCacheScopeStorage')
|
|
164
|
+
const USE_CACHE_KNOWN_ROOT_PARAMS = Symbol.for('pnext.compat.knownRootParamsByFunctionId')
|
|
165
|
+
const USE_CACHE_REQUEST_TOKENS = Symbol.for('pnext.compat.useCacheRequestTokens')
|
|
166
166
|
|
|
167
167
|
function useCacheGlobal<T>(key: symbol, create: () => T): T {
|
|
168
|
-
const state = globalThis as Record<PropertyKey, unknown
|
|
169
|
-
const existing = state[key] as T | undefined
|
|
170
|
-
if (existing !== undefined) return existing
|
|
171
|
-
const value = create()
|
|
172
|
-
state[key] = value
|
|
173
|
-
return value
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const entries = useCacheGlobal(USE_CACHE_ENTRIES, () => new Map<string, UseCacheEntry>())
|
|
177
|
-
const entriesLimit = 4096
|
|
178
|
-
const scopeStorage = useCacheGlobal(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
168
|
+
const state = globalThis as Record<PropertyKey, unknown>
|
|
169
|
+
const existing = state[key] as T | undefined
|
|
170
|
+
if (existing !== undefined) return existing
|
|
171
|
+
const value = create()
|
|
172
|
+
state[key] = value
|
|
173
|
+
return value
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const entries = useCacheGlobal(USE_CACHE_ENTRIES, () => new Map<string, UseCacheEntry>())
|
|
177
|
+
const entriesLimit = 4096
|
|
178
|
+
const scopeStorage = useCacheGlobal(
|
|
179
|
+
USE_CACHE_SCOPE_STORAGE,
|
|
180
|
+
() => new AsyncLocalStorage<UseCacheScope>(),
|
|
181
|
+
)
|
|
182
|
+
const requestTokens = useCacheGlobal(USE_CACHE_REQUEST_TOKENS, () => new WeakMap<object, object>())
|
|
183
|
+
const USE_CACHE_RDC_CAPTURE = Symbol.for('pnext.compat.useCacheRdcCapture')
|
|
184
|
+
const USE_CACHE_RDC_RECORDS = Symbol.for('pnext.compat.useCacheRdcRecords')
|
|
182
185
|
|
|
183
186
|
function useCacheRdcRecords(): Map<string, UseCacheRdcRecord> {
|
|
184
|
-
return useCacheGlobal(USE_CACHE_RDC_RECORDS, () => new Map<string, UseCacheRdcRecord>())
|
|
187
|
+
return useCacheGlobal(USE_CACHE_RDC_RECORDS, () => new Map<string, UseCacheRdcRecord>())
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
function useCacheRdcCapture(): Set<string> | undefined {
|
|
188
|
-
return (globalThis as Record<PropertyKey, unknown>)[USE_CACHE_RDC_CAPTURE] as
|
|
191
|
+
return (globalThis as Record<PropertyKey, unknown>)[USE_CACHE_RDC_CAPTURE] as
|
|
192
|
+
Set<string> | undefined
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
function captureUseCacheRdcEntry(key: string) {
|
|
192
|
-
useCacheRdcCapture()?.add(key)
|
|
196
|
+
useCacheRdcCapture()?.add(key)
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
export function beginUseCacheRdcCapture() {
|
|
196
|
-
(globalThis as Record<PropertyKey, unknown>)[USE_CACHE_RDC_CAPTURE] = new Set<string>()
|
|
200
|
+
;(globalThis as Record<PropertyKey, unknown>)[USE_CACHE_RDC_CAPTURE] = new Set<string>()
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
export async function collectUseCacheRdcRecords(): Promise<UseCacheRdcRecord[]> {
|
|
200
|
-
const capture = useCacheRdcCapture()
|
|
201
|
-
if (!capture) return []
|
|
202
|
-
const records: UseCacheRdcRecord[] = []
|
|
204
|
+
const capture = useCacheRdcCapture()
|
|
205
|
+
if (!capture) return []
|
|
206
|
+
const records: UseCacheRdcRecord[] = []
|
|
203
207
|
for (const key of capture) {
|
|
204
|
-
const entry = entries.get(key)
|
|
205
|
-
if (!entry) continue
|
|
206
|
-
const value = toRdcValue(await entry.value)
|
|
207
|
-
if (value === undefined) continue
|
|
208
|
+
const entry = entries.get(key)
|
|
209
|
+
if (!entry) continue
|
|
210
|
+
const value = toRdcValue(await entry.value)
|
|
211
|
+
if (value === undefined) continue
|
|
208
212
|
records.push({
|
|
209
213
|
key,
|
|
210
214
|
value,
|
|
211
215
|
tags: [...entry.tags],
|
|
212
216
|
storedAt: entry.storedAt,
|
|
213
|
-
...(entry.revalidateSeconds !== undefined
|
|
217
|
+
...(entry.revalidateSeconds !== undefined
|
|
218
|
+
? { revalidateSeconds: entry.revalidateSeconds }
|
|
219
|
+
: {}),
|
|
214
220
|
...(entry.expireSeconds !== undefined ? { expireSeconds: entry.expireSeconds } : {}),
|
|
215
221
|
...(entry.staleSeconds !== undefined ? { staleSeconds: entry.staleSeconds } : {}),
|
|
216
222
|
tagSeq: entry.tagSeq,
|
|
217
223
|
pid: rdcProcessId(),
|
|
218
224
|
...(entry.route !== undefined ? { route: entry.route } : {}),
|
|
219
|
-
})
|
|
225
|
+
})
|
|
220
226
|
}
|
|
221
|
-
return records
|
|
227
|
+
return records
|
|
222
228
|
}
|
|
223
229
|
|
|
224
230
|
/**
|
|
@@ -227,29 +233,29 @@ export async function collectUseCacheRdcRecords(): Promise<UseCacheRdcRecord[]>
|
|
|
227
233
|
* unserializable but its cacheTag()s must still reach the shell's staleness check.
|
|
228
234
|
*/
|
|
229
235
|
export async function collectUseCacheRdcTags(): Promise<string[]> {
|
|
230
|
-
const capture = useCacheRdcCapture()
|
|
231
|
-
if (!capture) return []
|
|
232
|
-
const tags = new Set<string>()
|
|
236
|
+
const capture = useCacheRdcCapture()
|
|
237
|
+
if (!capture) return []
|
|
238
|
+
const tags = new Set<string>()
|
|
233
239
|
for (const key of capture) {
|
|
234
|
-
const entry = entries.get(key)
|
|
235
|
-
if (!entry) continue
|
|
240
|
+
const entry = entries.get(key)
|
|
241
|
+
if (!entry) continue
|
|
236
242
|
// Tags are applied to the entry once its production settles.
|
|
237
|
-
await entry.value.catch(() => undefined)
|
|
238
|
-
for (const tag of entry.tags) tags.add(tag)
|
|
243
|
+
await entry.value.catch(() => undefined)
|
|
244
|
+
for (const tag of entry.tags) tags.add(tag)
|
|
239
245
|
}
|
|
240
|
-
return [...tags]
|
|
246
|
+
return [...tags]
|
|
241
247
|
}
|
|
242
248
|
|
|
243
249
|
export function seedUseCacheRdcRecords(records: readonly UseCacheRdcRecord[], route?: string) {
|
|
244
250
|
for (const record of records) {
|
|
245
|
-
if (!isUseCacheRdcRecord(record)) continue
|
|
251
|
+
if (!isUseCacheRdcRecord(record)) continue
|
|
246
252
|
// Foreign-process sequences are meaningless here — see rebaseRdcTagSeq.
|
|
247
|
-
const tagSeq = rebaseRdcTagSeq(record)
|
|
253
|
+
const tagSeq = rebaseRdcTagSeq(record)
|
|
248
254
|
// An RDC artifact is a snapshot: seeding it must never regress a live
|
|
249
255
|
// entry produced more recently (each regen seeds the stale .rdc first).
|
|
250
|
-
const live = entries.get(record.key)
|
|
251
|
-
if (live !== undefined && live.storedAt > record.storedAt) continue
|
|
252
|
-
useCacheRdcRecords().set(record.key, { ...record, tagSeq })
|
|
256
|
+
const live = entries.get(record.key)
|
|
257
|
+
if (live !== undefined && live.storedAt > record.storedAt) continue
|
|
258
|
+
useCacheRdcRecords().set(record.key, { ...record, tagSeq })
|
|
253
259
|
entries.set(record.key, {
|
|
254
260
|
value: Promise.resolve(record.value),
|
|
255
261
|
storedAt: record.storedAt,
|
|
@@ -260,14 +266,18 @@ export function seedUseCacheRdcRecords(records: readonly UseCacheRdcRecord[], ro
|
|
|
260
266
|
...(record.expireSeconds !== undefined ? { expireSeconds: record.expireSeconds } : {}),
|
|
261
267
|
...(record.staleSeconds !== undefined ? { staleSeconds: record.staleSeconds } : {}),
|
|
262
268
|
tagSeq,
|
|
263
|
-
...(route !== undefined
|
|
264
|
-
|
|
269
|
+
...(route !== undefined
|
|
270
|
+
? { route }
|
|
271
|
+
: record.route !== undefined
|
|
272
|
+
? { route: record.route }
|
|
273
|
+
: {}),
|
|
274
|
+
})
|
|
265
275
|
}
|
|
266
276
|
}
|
|
267
277
|
|
|
268
278
|
function isUseCacheRdcRecord(value: unknown): value is UseCacheRdcRecord {
|
|
269
|
-
if (!value || typeof value !== 'object') return false
|
|
270
|
-
const record = value as Partial<UseCacheRdcRecord
|
|
279
|
+
if (!value || typeof value !== 'object') return false
|
|
280
|
+
const record = value as Partial<UseCacheRdcRecord>
|
|
271
281
|
return (
|
|
272
282
|
typeof record.key === 'string' &&
|
|
273
283
|
Array.isArray(record.tags) &&
|
|
@@ -277,26 +287,26 @@ function isUseCacheRdcRecord(value: unknown): value is UseCacheRdcRecord {
|
|
|
277
287
|
typeof record.tagSeq === 'number' &&
|
|
278
288
|
Number.isInteger(record.tagSeq) &&
|
|
279
289
|
isRdcValue(record.value)
|
|
280
|
-
)
|
|
290
|
+
)
|
|
281
291
|
}
|
|
282
292
|
|
|
283
293
|
function toRdcValue(value: unknown): RdcValue | undefined {
|
|
284
|
-
if (!isRdcValue(value)) return undefined
|
|
285
|
-
return JSON.parse(JSON.stringify(value)) as RdcValue
|
|
294
|
+
if (!isRdcValue(value)) return undefined
|
|
295
|
+
return JSON.parse(JSON.stringify(value)) as RdcValue
|
|
286
296
|
}
|
|
287
297
|
|
|
288
298
|
function isRdcValue(value: unknown, seen = new WeakSet<object>()): value is RdcValue {
|
|
289
|
-
if (value === null || typeof value === 'string' || typeof value === 'boolean') return true
|
|
290
|
-
if (typeof value === 'number') return Number.isFinite(value)
|
|
291
|
-
if (typeof value !== 'object' || seen.has(value)) return false
|
|
299
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return true
|
|
300
|
+
if (typeof value === 'number') return Number.isFinite(value)
|
|
301
|
+
if (typeof value !== 'object' || seen.has(value)) return false
|
|
292
302
|
if (Array.isArray(value)) {
|
|
293
|
-
seen.add(value)
|
|
294
|
-
return value.every(item => isRdcValue(item, seen))
|
|
303
|
+
seen.add(value)
|
|
304
|
+
return value.every(item => isRdcValue(item, seen))
|
|
295
305
|
}
|
|
296
|
-
const prototype = Object.getPrototypeOf(value) as object | null
|
|
297
|
-
if (prototype !== Object.prototype && prototype !== null) return false
|
|
298
|
-
seen.add(value)
|
|
299
|
-
return Object.values(value).every(item => isRdcValue(item, seen))
|
|
306
|
+
const prototype = Object.getPrototypeOf(value) as object | null
|
|
307
|
+
if (prototype !== Object.prototype && prototype !== null) return false
|
|
308
|
+
seen.add(value)
|
|
309
|
+
return Object.values(value).every(item => isRdcValue(item, seen))
|
|
300
310
|
}
|
|
301
311
|
|
|
302
312
|
// ---------------------------------------------------------------------------
|
|
@@ -320,24 +330,23 @@ function isRdcValue(value: unknown, seen = new WeakSet<object>()): value is RdcV
|
|
|
320
330
|
const knownRootParamsByFunctionId = useCacheGlobal(
|
|
321
331
|
USE_CACHE_KNOWN_ROOT_PARAMS,
|
|
322
332
|
() => new Map<string, Set<string>>(),
|
|
323
|
-
)
|
|
333
|
+
)
|
|
324
334
|
|
|
325
335
|
// The app's ROOT param names (segments at/above a root layout), seeded from the
|
|
326
336
|
// build-time scan at registration. Anchored on globalThis so the build process
|
|
327
337
|
// and the built server bundle read one copy. Only these names may join a cache
|
|
328
338
|
// key: a non-root route param must never discriminate entries, and a fallback
|
|
329
339
|
// prerender's unknown params must never poison the key.
|
|
330
|
-
const USE_CACHE_ROOT_PARAM_NAMES = Symbol.for('pnext.compat.rootParamNames')
|
|
340
|
+
const USE_CACHE_ROOT_PARAM_NAMES = Symbol.for('pnext.compat.rootParamNames')
|
|
331
341
|
|
|
332
|
-
/** Seed the scanned root-param name set (register-
|
|
342
|
+
/** Seed the scanned root-param name set (register/use-cache, build + serve). */
|
|
333
343
|
export function setUseCacheRootParamNames(names: Iterable<string>): void {
|
|
334
|
-
(globalThis as Record<PropertyKey, unknown>)[USE_CACHE_ROOT_PARAM_NAMES] = new Set(names)
|
|
344
|
+
;(globalThis as Record<PropertyKey, unknown>)[USE_CACHE_ROOT_PARAM_NAMES] = new Set(names)
|
|
335
345
|
}
|
|
336
346
|
|
|
337
347
|
function rootParamNames(): Set<string> | undefined {
|
|
338
348
|
return (globalThis as Record<PropertyKey, unknown>)[USE_CACHE_ROOT_PARAM_NAMES] as
|
|
339
|
-
|
|
340
|
-
| undefined;
|
|
349
|
+
Set<string> | undefined
|
|
341
350
|
}
|
|
342
351
|
|
|
343
352
|
/**
|
|
@@ -347,21 +356,21 @@ function rootParamNames(): Set<string> | undefined {
|
|
|
347
356
|
* participated in a cache key); false otherwise (plain request read).
|
|
348
357
|
*/
|
|
349
358
|
export function recordUseCacheRootParamRead(name: string): boolean {
|
|
350
|
-
const scope = scopeStorage.getStore()
|
|
351
|
-
if (!scope?.functionId) return false
|
|
352
|
-
(scope.rootParamsRead ??= new Set()).add(name)
|
|
353
|
-
let known = knownRootParamsByFunctionId.get(scope.functionId)
|
|
359
|
+
const scope = scopeStorage.getStore()
|
|
360
|
+
if (!scope?.functionId) return false
|
|
361
|
+
;(scope.rootParamsRead ??= new Set()).add(name)
|
|
362
|
+
let known = knownRootParamsByFunctionId.get(scope.functionId)
|
|
354
363
|
if (!known) {
|
|
355
|
-
known = new Set()
|
|
356
|
-
knownRootParamsByFunctionId.set(scope.functionId, known)
|
|
364
|
+
known = new Set()
|
|
365
|
+
knownRootParamsByFunctionId.set(scope.functionId, known)
|
|
357
366
|
}
|
|
358
|
-
known.add(name)
|
|
359
|
-
return true
|
|
367
|
+
known.add(name)
|
|
368
|
+
return true
|
|
360
369
|
}
|
|
361
370
|
|
|
362
371
|
/** True when a `use cache` producer is currently on the stack (E1140 guard). */
|
|
363
372
|
export function insideUseCacheProducer(): boolean {
|
|
364
|
-
return scopeStorage.getStore()?.functionId !== undefined
|
|
373
|
+
return scopeStorage.getStore()?.functionId !== undefined
|
|
365
374
|
}
|
|
366
375
|
|
|
367
376
|
/**
|
|
@@ -371,57 +380,53 @@ export function insideUseCacheProducer(): boolean {
|
|
|
371
380
|
* when the id has no known root-param reads.
|
|
372
381
|
*/
|
|
373
382
|
function withRootParamKeyArgs(id: string, args: unknown[]): unknown[] {
|
|
374
|
-
const known = knownRootParamsByFunctionId.get(id)
|
|
375
|
-
const params = currentParams()
|
|
383
|
+
const known = knownRootParamsByFunctionId.get(id)
|
|
384
|
+
const params = currentParams()
|
|
376
385
|
// The first call has no observed read-set yet, but it must still not seed a cross-root entry. Once
|
|
377
386
|
// reads are known, narrow back to that set; until then every KNOWN (resolved) root param is a
|
|
378
387
|
// conservative cache discriminator - never non-root route params (they must not split entries) and
|
|
379
388
|
// never a fallback prerender's unfilled params (they must not poison the key). Over-discrimination
|
|
380
389
|
// costs a duplicate entry, under-discrimination shares a value across roots.
|
|
381
|
-
const roots = rootParamNames()
|
|
390
|
+
const roots = rootParamNames()
|
|
382
391
|
const names = known?.size
|
|
383
392
|
? [...known]
|
|
384
|
-
: Object.keys(params).filter(
|
|
385
|
-
|
|
386
|
-
);
|
|
387
|
-
if (names.length === 0) return args;
|
|
393
|
+
: Object.keys(params).filter(name => (!roots || roots.has(name)) && params[name] !== undefined)
|
|
394
|
+
if (names.length === 0) return args
|
|
388
395
|
// Stable order (sorted names) so the key is deterministic regardless of the
|
|
389
396
|
// order reads were discovered in.
|
|
390
|
-
const rootParamEntries = names
|
|
391
|
-
|
|
392
|
-
.map(name => [name, params[name]] as const);
|
|
393
|
-
return [{ $pnextRootParams: rootParamEntries }, ...args];
|
|
397
|
+
const rootParamEntries = names.sort().map(name => [name, params[name]] as const)
|
|
398
|
+
return [{ $pnextRootParams: rootParamEntries }, ...args]
|
|
394
399
|
}
|
|
395
400
|
|
|
396
401
|
/** Test hook. */
|
|
397
402
|
export function clearKnownRootParams() {
|
|
398
|
-
knownRootParamsByFunctionId.clear()
|
|
403
|
+
knownRootParamsByFunctionId.clear()
|
|
399
404
|
}
|
|
400
405
|
|
|
401
406
|
// Next's `default` cacheLife profile, applied to a `use cache` entry that never
|
|
402
407
|
// calls cacheLife(). Kept here (not read from config) so the runtime has no
|
|
403
408
|
// dependency on the compat config loader; a config `expireTime`/custom default
|
|
404
409
|
// override only affects explicit cacheLife() calls (see compat/next/cache.ts).
|
|
405
|
-
const DEFAULT_REVALIDATE_SECONDS = 900
|
|
406
|
-
const DEFAULT_EXPIRE_SECONDS = 31536000
|
|
407
|
-
const DEFAULT_STALE_SECONDS = 300
|
|
410
|
+
const DEFAULT_REVALIDATE_SECONDS = 900
|
|
411
|
+
const DEFAULT_EXPIRE_SECONDS = 31536000
|
|
412
|
+
const DEFAULT_STALE_SECONDS = 300
|
|
408
413
|
|
|
409
414
|
// A runtime prefetch (`unstable_instant`) includes a `use cache` scope only when its effective STALE
|
|
410
415
|
// window is at least this long; a shorter-lived cache stays a postponed hole. The threshold applies to
|
|
411
416
|
// `stale` ONLY, never `expire` - a public cache with a short expire but a long stale is still included.
|
|
412
|
-
const RUNTIME_PREFETCH_DYNAMIC_STALE = 30
|
|
417
|
+
const RUNTIME_PREFETCH_DYNAMIC_STALE = 30
|
|
413
418
|
|
|
414
419
|
// A STATIC prerender whose output the client prefetches omits a `use cache` scope that expires inside
|
|
415
420
|
// this window: data that short-lived is not worth prerendering, so its boundary stays a hole the
|
|
416
421
|
// navigation fills live. Note the two thresholds read DIFFERENT fields: a runtime prefetch gates on
|
|
417
422
|
// `stale` (a short-expire/long-stale cache is still prefetchable), a static prefetch shell on `expire`.
|
|
418
|
-
const PREFETCH_SHELL_DYNAMIC_EXPIRE = 300
|
|
423
|
+
const PREFETCH_SHELL_DYNAMIC_EXPIRE = 300
|
|
419
424
|
|
|
420
425
|
/** How the active prerender gates a short-lived `use cache` scope, if at all. */
|
|
421
426
|
function prefetchCacheGate(): 'stale' | 'expire' | null {
|
|
422
|
-
if (isRuntimePrefetchPrerender()) return 'stale'
|
|
423
|
-
if (isPrefetchShellPrerender()) return 'expire'
|
|
424
|
-
return null
|
|
427
|
+
if (isRuntimePrefetchPrerender()) return 'stale'
|
|
428
|
+
if (isPrefetchShellPrerender()) return 'expire'
|
|
429
|
+
return null
|
|
425
430
|
}
|
|
426
431
|
|
|
427
432
|
/**
|
|
@@ -439,22 +444,22 @@ function gatePrefetchCacheLife<T>(
|
|
|
439
444
|
// same way awaiting cookies()/params does (identity for the shell gate).
|
|
440
445
|
return markRuntimeApiOnAwait(
|
|
441
446
|
Promise.resolve(result).then(value => {
|
|
442
|
-
const effective = life()
|
|
447
|
+
const effective = life()
|
|
443
448
|
const short =
|
|
444
449
|
gate === 'stale'
|
|
445
450
|
? (effective?.staleSeconds ?? DEFAULT_STALE_SECONDS) < RUNTIME_PREFETCH_DYNAMIC_STALE
|
|
446
|
-
: (effective?.expireSeconds ?? DEFAULT_EXPIRE_SECONDS) < PREFETCH_SHELL_DYNAMIC_EXPIRE
|
|
447
|
-
if (short) return hangingPromise<T>('use cache')
|
|
448
|
-
return value
|
|
451
|
+
: (effective?.expireSeconds ?? DEFAULT_EXPIRE_SECONDS) < PREFETCH_SHELL_DYNAMIC_EXPIRE
|
|
452
|
+
if (short) return hangingPromise<T>('use cache')
|
|
453
|
+
return value
|
|
449
454
|
}),
|
|
450
|
-
)
|
|
455
|
+
)
|
|
451
456
|
}
|
|
452
457
|
|
|
453
458
|
// ---------------------------------------------------------------------------
|
|
454
459
|
// cacheLife/response-header plumbing.
|
|
455
460
|
//
|
|
456
461
|
// A `use cache` entry's cacheLife (revalidate/expire/stale) is stashed on the
|
|
457
|
-
// request work unit as it resolves during a render; the register-
|
|
462
|
+
// request work unit as it resolves during a render; the register/use-cache
|
|
458
463
|
// responseFinalizer reads the aggregate and emits the SWR `cache-control` +
|
|
459
464
|
// `x-nextjs-stale-time` headers (mirroring how the font runtime stashes
|
|
460
465
|
// preloads for a `Link` header). Only the LONGEST-lived cache scope on the
|
|
@@ -463,12 +468,12 @@ function gatePrefetchCacheLife<T>(
|
|
|
463
468
|
// ---------------------------------------------------------------------------
|
|
464
469
|
|
|
465
470
|
/** Well-known key for the per-request cache-life aggregate stash. */
|
|
466
|
-
export const USE_CACHE_LIFE = Symbol.for('pnext.compat.useCacheLife')
|
|
471
|
+
export const USE_CACHE_LIFE = Symbol.for('pnext.compat.useCacheLife')
|
|
467
472
|
|
|
468
473
|
export interface CacheLifeStash {
|
|
469
|
-
revalidateSeconds?: number
|
|
470
|
-
expireSeconds?: number
|
|
471
|
-
staleSeconds?: number
|
|
474
|
+
revalidateSeconds?: number
|
|
475
|
+
expireSeconds?: number
|
|
476
|
+
staleSeconds?: number
|
|
472
477
|
}
|
|
473
478
|
|
|
474
479
|
function stashCacheLife(life: CacheLifeStash): void {
|
|
@@ -477,121 +482,123 @@ function stashCacheLife(life: CacheLifeStash): void {
|
|
|
477
482
|
life.expireSeconds === undefined &&
|
|
478
483
|
life.staleSeconds === undefined
|
|
479
484
|
) {
|
|
480
|
-
return
|
|
485
|
+
return
|
|
481
486
|
}
|
|
482
|
-
const unit = getWorkUnit()
|
|
483
|
-
if (!unit) return
|
|
484
|
-
const compat = (unit.compat ??= {})
|
|
485
|
-
const existing = (compat[USE_CACHE_LIFE] as CacheLifeStash | undefined) ?? {}
|
|
487
|
+
const unit = getWorkUnit()
|
|
488
|
+
if (!unit) return
|
|
489
|
+
const compat = (unit.compat ??= {})
|
|
490
|
+
const existing = (compat[USE_CACHE_LIFE] as CacheLifeStash | undefined) ?? {}
|
|
486
491
|
// Track the minimum revalidate (Next lowers the page revalidate to the
|
|
487
492
|
// shortest cache used) but pair it with that same entry's expire so the
|
|
488
493
|
// emitted SWR window is self-consistent.
|
|
489
|
-
const nextRevalidate = minDefined(existing.revalidateSeconds, life.revalidateSeconds)
|
|
494
|
+
const nextRevalidate = minDefined(existing.revalidateSeconds, life.revalidateSeconds)
|
|
490
495
|
const drivenByThis =
|
|
491
|
-
life.revalidateSeconds !== undefined && life.revalidateSeconds === nextRevalidate
|
|
496
|
+
life.revalidateSeconds !== undefined && life.revalidateSeconds === nextRevalidate
|
|
492
497
|
// `staleSeconds` is its OWN minimum, NOT the min-revalidate entry's: the
|
|
493
498
|
// client router cache is only as fresh as the shortest-lived content on the
|
|
494
499
|
// page, and coupling it let whichever scope rendered first win the window
|
|
495
500
|
// whenever the revalidates tied (the default case).
|
|
496
|
-
const nextStale = minDefined(existing.staleSeconds, life.staleSeconds)
|
|
501
|
+
const nextStale = minDefined(existing.staleSeconds, life.staleSeconds)
|
|
497
502
|
compat[USE_CACHE_LIFE] = {
|
|
498
503
|
...(nextRevalidate !== undefined ? { revalidateSeconds: nextRevalidate } : {}),
|
|
499
504
|
...(drivenByThis
|
|
500
505
|
? { ...(life.expireSeconds !== undefined ? { expireSeconds: life.expireSeconds } : {}) }
|
|
501
506
|
: {
|
|
502
|
-
...(existing.expireSeconds !== undefined
|
|
507
|
+
...(existing.expireSeconds !== undefined
|
|
508
|
+
? { expireSeconds: existing.expireSeconds }
|
|
509
|
+
: {}),
|
|
503
510
|
}),
|
|
504
511
|
...(nextStale !== undefined ? { staleSeconds: nextStale } : {}),
|
|
505
|
-
}
|
|
512
|
+
}
|
|
506
513
|
}
|
|
507
514
|
|
|
508
515
|
/** Read + clear the cache-life aggregate stashed during the current render. */
|
|
509
516
|
export function takeCacheLifeStash(): CacheLifeStash | undefined {
|
|
510
|
-
const unit = getWorkUnit()
|
|
511
|
-
const compat = unit?.compat
|
|
512
|
-
if (!compat) return undefined
|
|
513
|
-
const life = compat[USE_CACHE_LIFE] as CacheLifeStash | undefined
|
|
514
|
-
compat[USE_CACHE_LIFE] = undefined
|
|
515
|
-
return life
|
|
517
|
+
const unit = getWorkUnit()
|
|
518
|
+
const compat = unit?.compat
|
|
519
|
+
if (!compat) return undefined
|
|
520
|
+
const life = compat[USE_CACHE_LIFE] as CacheLifeStash | undefined
|
|
521
|
+
compat[USE_CACHE_LIFE] = undefined
|
|
522
|
+
return life
|
|
516
523
|
}
|
|
517
524
|
|
|
518
525
|
function minDefined(a: number | undefined, b: number | undefined): number | undefined {
|
|
519
|
-
if (a === undefined) return b
|
|
520
|
-
if (b === undefined) return a
|
|
521
|
-
return Math.min(a, b)
|
|
526
|
+
if (a === undefined) return b
|
|
527
|
+
if (b === undefined) return a
|
|
528
|
+
return Math.min(a, b)
|
|
522
529
|
}
|
|
523
530
|
|
|
524
531
|
// An explicit data-cache revalidate (e.g. `fetch(..., { next: { revalidate } })`
|
|
525
532
|
// or a nested unstable_cache read) recorded while a `use cache` entry is
|
|
526
533
|
// producing lowers the producing entry's own cacheLife, matching Next.
|
|
527
534
|
setUseCacheRevalidateSink(seconds => {
|
|
528
|
-
recordUseCacheLife(seconds)
|
|
529
|
-
})
|
|
535
|
+
recordUseCacheLife(seconds)
|
|
536
|
+
})
|
|
530
537
|
|
|
531
538
|
/** cacheTag() inside a 'use cache' function: tag the active entry. */
|
|
532
539
|
export function recordUseCacheTags(tags: readonly string[]): boolean {
|
|
533
|
-
const scope = scopeStorage.getStore()
|
|
534
|
-
if (!scope) return false
|
|
535
|
-
for (const tag of tags) scope.tags.add(tag)
|
|
536
|
-
return true
|
|
540
|
+
const scope = scopeStorage.getStore()
|
|
541
|
+
if (!scope) return false
|
|
542
|
+
for (const tag of tags) scope.tags.add(tag)
|
|
543
|
+
return true
|
|
537
544
|
}
|
|
538
545
|
|
|
539
546
|
/** cacheLife() inside a 'use cache' function: bound the active entry's TTL. */
|
|
540
547
|
export function recordUseCacheLife(life: number | CacheLifeStash): boolean {
|
|
541
|
-
const scope = scopeStorage.getStore()
|
|
542
|
-
if (!scope) return false
|
|
543
|
-
const revalidate = typeof life === 'number' ? life : life.revalidateSeconds
|
|
544
|
-
const expire = typeof life === 'number' ? undefined : life.expireSeconds
|
|
545
|
-
const stale = typeof life === 'number' ? undefined : life.staleSeconds
|
|
548
|
+
const scope = scopeStorage.getStore()
|
|
549
|
+
if (!scope) return false
|
|
550
|
+
const revalidate = typeof life === 'number' ? life : life.revalidateSeconds
|
|
551
|
+
const expire = typeof life === 'number' ? undefined : life.expireSeconds
|
|
552
|
+
const stale = typeof life === 'number' ? undefined : life.staleSeconds
|
|
546
553
|
if (revalidate !== undefined) {
|
|
547
554
|
scope.revalidateSeconds =
|
|
548
555
|
scope.revalidateSeconds === undefined
|
|
549
556
|
? revalidate
|
|
550
|
-
: Math.min(scope.revalidateSeconds, revalidate)
|
|
557
|
+
: Math.min(scope.revalidateSeconds, revalidate)
|
|
551
558
|
}
|
|
552
559
|
if (expire !== undefined) {
|
|
553
560
|
scope.expireSeconds =
|
|
554
|
-
scope.expireSeconds === undefined ? expire : Math.min(scope.expireSeconds, expire)
|
|
561
|
+
scope.expireSeconds === undefined ? expire : Math.min(scope.expireSeconds, expire)
|
|
555
562
|
}
|
|
556
563
|
if (stale !== undefined) {
|
|
557
564
|
scope.staleSeconds =
|
|
558
|
-
scope.staleSeconds === undefined ? stale : Math.min(scope.staleSeconds, stale)
|
|
565
|
+
scope.staleSeconds === undefined ? stale : Math.min(scope.staleSeconds, stale)
|
|
559
566
|
}
|
|
560
|
-
return true
|
|
567
|
+
return true
|
|
561
568
|
}
|
|
562
569
|
|
|
563
570
|
function entryExpired(entry: UseCacheEntry) {
|
|
564
571
|
return (
|
|
565
572
|
entry.revalidateSeconds !== undefined &&
|
|
566
573
|
realNow() - entry.storedAt >= entry.revalidateSeconds * 1000
|
|
567
|
-
)
|
|
574
|
+
)
|
|
568
575
|
}
|
|
569
576
|
|
|
570
577
|
/** Propagate an entry's cache meta to the surrounding render/'use cache' scopes. */
|
|
571
578
|
function propagate(entry: UseCacheEntry, parent?: UseCacheScope) {
|
|
572
|
-
const { tags, revalidateSeconds, expireSeconds, staleSeconds } = entry
|
|
573
|
-
recordCacheTags(tags)
|
|
574
|
-
if (revalidateSeconds !== undefined) recordCacheRevalidate(revalidateSeconds)
|
|
579
|
+
const { tags, revalidateSeconds, expireSeconds, staleSeconds } = entry
|
|
580
|
+
recordCacheTags(tags)
|
|
581
|
+
if (revalidateSeconds !== undefined) recordCacheRevalidate(revalidateSeconds)
|
|
575
582
|
// Also aggregate the expire/stale windows onto the render meta so a build
|
|
576
583
|
// shell prerender can persist the route's SWR headers (the work-unit stash
|
|
577
584
|
// below is not in scope during this async propagation — see build.ts).
|
|
578
|
-
if (expireSeconds !== undefined) recordCacheExpire(expireSeconds)
|
|
579
|
-
if (staleSeconds !== undefined) recordCacheStale(staleSeconds)
|
|
585
|
+
if (expireSeconds !== undefined) recordCacheExpire(expireSeconds)
|
|
586
|
+
if (staleSeconds !== undefined) recordCacheStale(staleSeconds)
|
|
580
587
|
// Stash the effective cache-life on the request work unit so the finalizer
|
|
581
588
|
// can emit the SWR cache-control + x-nextjs-stale-time headers.
|
|
582
589
|
stashCacheLife({
|
|
583
590
|
...(revalidateSeconds !== undefined ? { revalidateSeconds } : {}),
|
|
584
591
|
...(expireSeconds !== undefined ? { expireSeconds } : {}),
|
|
585
592
|
...(staleSeconds !== undefined ? { staleSeconds } : {}),
|
|
586
|
-
})
|
|
587
|
-
if (parent) propagateToParent(entry, parent)
|
|
593
|
+
})
|
|
594
|
+
if (parent) propagateToParent(entry, parent)
|
|
588
595
|
}
|
|
589
596
|
|
|
590
597
|
function propagateToParent(entry: UseCacheEntry, parent: UseCacheScope): void {
|
|
591
|
-
for (const tag of entry.tags) parent.tags.add(tag)
|
|
592
|
-
parent.revalidateSeconds = minDefined(parent.revalidateSeconds, entry.revalidateSeconds)
|
|
593
|
-
parent.expireSeconds = minDefined(parent.expireSeconds, entry.expireSeconds)
|
|
594
|
-
parent.staleSeconds = minDefined(parent.staleSeconds, entry.staleSeconds)
|
|
598
|
+
for (const tag of entry.tags) parent.tags.add(tag)
|
|
599
|
+
parent.revalidateSeconds = minDefined(parent.revalidateSeconds, entry.revalidateSeconds)
|
|
600
|
+
parent.expireSeconds = minDefined(parent.expireSeconds, entry.expireSeconds)
|
|
601
|
+
parent.staleSeconds = minDefined(parent.staleSeconds, entry.staleSeconds)
|
|
595
602
|
}
|
|
596
603
|
|
|
597
604
|
export function pnextUseCache<Args extends unknown[], Result>(
|
|
@@ -599,20 +606,20 @@ export function pnextUseCache<Args extends unknown[], Result>(
|
|
|
599
606
|
fn: (...args: Args) => Result,
|
|
600
607
|
): (...args: Args) => Result {
|
|
601
608
|
const wrapper = (...args: Args): Result => {
|
|
602
|
-
const serializedArgs = installSearchParamsTripwire(serializeCacheBoundaryArgs(args), id) as Args
|
|
609
|
+
const serializedArgs = installSearchParamsTripwire(serializeCacheBoundaryArgs(args), id) as Args
|
|
603
610
|
// Draft mode skips cache reads/writes but KEEPS the cache-scope rules:
|
|
604
611
|
// dynamic APIs (cookies()/headers()) inside 'use cache' still throw.
|
|
605
|
-
if (draftModeBypassActive()) return draftBypassRun(id, fn, serializedArgs)
|
|
612
|
+
if (draftModeBypassActive()) return draftBypassRun(id, fn, serializedArgs)
|
|
606
613
|
// Element-like arguments become indexed holes: they never affect the key
|
|
607
614
|
// (an element keys as an opaque token either way), the produced tree embeds
|
|
608
615
|
// the hole markers, and each invocation re-instantiates it with ITS fills.
|
|
609
|
-
const holes = extractElementHoles(serializedArgs)
|
|
610
|
-
const keyArgs = declaredArgs(fn, serializedArgs)
|
|
611
|
-
const key = cacheKeyFor(id, withRootParamKeyArgs(id, keyArgs))
|
|
616
|
+
const holes = extractElementHoles(serializedArgs)
|
|
617
|
+
const keyArgs = declaredArgs(fn, serializedArgs)
|
|
618
|
+
const key = cacheKeyFor(id, withRootParamKeyArgs(id, keyArgs))
|
|
612
619
|
if (isPromiseLike(key)) {
|
|
613
620
|
return key.then(
|
|
614
621
|
resolvedKey => {
|
|
615
|
-
if (resolvedKey === null) return fn(...serializedArgs)
|
|
622
|
+
if (resolvedKey === null) return fn(...serializedArgs)
|
|
616
623
|
return readOrProduceEntry(
|
|
617
624
|
resolvedKey,
|
|
618
625
|
id,
|
|
@@ -620,17 +627,24 @@ export function pnextUseCache<Args extends unknown[], Result>(
|
|
|
620
627
|
fn,
|
|
621
628
|
holes.args as Args,
|
|
622
629
|
holes.fills,
|
|
623
|
-
)
|
|
630
|
+
)
|
|
624
631
|
},
|
|
625
632
|
() => fn(...serializedArgs),
|
|
626
|
-
) as Result
|
|
633
|
+
) as Result
|
|
627
634
|
}
|
|
628
|
-
if (key === null) return fn(...serializedArgs)
|
|
635
|
+
if (key === null) return fn(...serializedArgs)
|
|
629
636
|
|
|
630
|
-
return readOrProduceEntry(
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
637
|
+
return readOrProduceEntry(
|
|
638
|
+
key,
|
|
639
|
+
id,
|
|
640
|
+
argsJsonFromKey(id, key),
|
|
641
|
+
fn,
|
|
642
|
+
holes.args as Args,
|
|
643
|
+
holes.fills,
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
;(wrapper as unknown as Record<symbol, unknown>)[USE_CACHE_WRAPPER] = true
|
|
647
|
+
return wrapper
|
|
634
648
|
}
|
|
635
649
|
|
|
636
650
|
// ---------------------------------------------------------------------------
|
|
@@ -649,9 +663,9 @@ export function pnextUseCache<Args extends unknown[], Result>(
|
|
|
649
663
|
// ---------------------------------------------------------------------------
|
|
650
664
|
|
|
651
665
|
function installSearchParamsTripwire(args: unknown[], id: string): unknown[] {
|
|
652
|
-
if (!prerenderErrorCollectionActive()) return args
|
|
653
|
-
const route = routeFromCacheId(id)
|
|
654
|
-
let changed = false
|
|
666
|
+
if (!prerenderErrorCollectionActive()) return args
|
|
667
|
+
const route = routeFromCacheId(id)
|
|
668
|
+
let changed = false
|
|
655
669
|
const out = args.map(arg => {
|
|
656
670
|
if (
|
|
657
671
|
arg !== null &&
|
|
@@ -661,15 +675,15 @@ function installSearchParamsTripwire(args: unknown[], id: string): unknown[] {
|
|
|
661
675
|
!isElementLike(arg) &&
|
|
662
676
|
isPromiseLike((arg as Record<string, unknown>).searchParams)
|
|
663
677
|
) {
|
|
664
|
-
changed = true
|
|
678
|
+
changed = true
|
|
665
679
|
return {
|
|
666
680
|
...arg,
|
|
667
681
|
searchParams: searchParamsTripwire(route, (arg as Record<string, unknown>).searchParams),
|
|
668
|
-
}
|
|
682
|
+
}
|
|
669
683
|
}
|
|
670
|
-
return arg
|
|
671
|
-
})
|
|
672
|
-
return changed ? out : args
|
|
684
|
+
return arg
|
|
685
|
+
})
|
|
686
|
+
return changed ? out : args
|
|
673
687
|
}
|
|
674
688
|
|
|
675
689
|
function withFillTimeout(value: Promise<unknown>, route: string | undefined): Promise<unknown> {
|
|
@@ -679,14 +693,14 @@ function withFillTimeout(value: Promise<unknown>, route: string | undefined): Pr
|
|
|
679
693
|
kind: 'hanging',
|
|
680
694
|
route: route ?? '',
|
|
681
695
|
consoleBlock: `Error: ${USE_CACHE_HANGING_MESSAGE}`,
|
|
682
|
-
})
|
|
683
|
-
reject(new Error(USE_CACHE_HANGING_MESSAGE))
|
|
684
|
-
}, fillTimeoutHolder().ms)
|
|
696
|
+
})
|
|
697
|
+
reject(new Error(USE_CACHE_HANGING_MESSAGE))
|
|
698
|
+
}, fillTimeoutHolder().ms)
|
|
685
699
|
// The timer must not keep the build process alive past the fill settling.
|
|
686
|
-
if (typeof timer.unref === 'function') timer.unref()
|
|
687
|
-
const clear = () => clearTimeout(timer)
|
|
688
|
-
void value.then(resolve, reject).then(clear, clear)
|
|
689
|
-
})
|
|
700
|
+
if (typeof timer.unref === 'function') timer.unref()
|
|
701
|
+
const clear = () => clearTimeout(timer)
|
|
702
|
+
void value.then(resolve, reject).then(clear, clear)
|
|
703
|
+
})
|
|
690
704
|
}
|
|
691
705
|
|
|
692
706
|
function searchParamsTripwire(route: string, real: unknown): PromiseLike<unknown> {
|
|
@@ -696,13 +710,13 @@ function searchParamsTripwire(route: string, real: unknown): PromiseLike<unknown
|
|
|
696
710
|
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
|
|
697
711
|
): PromiseLike<TResult1 | TResult2> {
|
|
698
712
|
if (insideUseCacheProducer()) {
|
|
699
|
-
const message = useCacheSearchParamsMessage(route)
|
|
700
|
-
recordPrerenderError({ kind: 'search-params', route, consoleBlock: `Error: ${message}` })
|
|
701
|
-
return Promise.reject(new Error(message)).then(onFulfilled, onRejected)
|
|
713
|
+
const message = useCacheSearchParamsMessage(route)
|
|
714
|
+
recordPrerenderError({ kind: 'search-params', route, consoleBlock: `Error: ${message}` })
|
|
715
|
+
return Promise.reject(new Error(message)).then(onFulfilled, onRejected)
|
|
702
716
|
}
|
|
703
|
-
return Promise.resolve(real).then(onFulfilled, onRejected)
|
|
717
|
+
return Promise.resolve(real).then(onFulfilled, onRejected)
|
|
704
718
|
},
|
|
705
|
-
}
|
|
719
|
+
}
|
|
706
720
|
}
|
|
707
721
|
|
|
708
722
|
/**
|
|
@@ -718,13 +732,13 @@ export function pnextUseCacheBound<Args extends unknown[], Result>(
|
|
|
718
732
|
capture: () => unknown[],
|
|
719
733
|
): (...args: Args) => Result {
|
|
720
734
|
const wrapper = (...args: Args): Result => {
|
|
721
|
-
const serializedArgs = serializeCacheBoundaryArgs(args) as Args
|
|
722
|
-
if (draftModeBypassActive()) return draftBypassRun(id, fn, serializedArgs)
|
|
723
|
-
let captured: unknown[]
|
|
735
|
+
const serializedArgs = serializeCacheBoundaryArgs(args) as Args
|
|
736
|
+
if (draftModeBypassActive()) return draftBypassRun(id, fn, serializedArgs)
|
|
737
|
+
let captured: unknown[]
|
|
724
738
|
try {
|
|
725
|
-
captured = serializeCacheBoundaryArgs(capture())
|
|
739
|
+
captured = serializeCacheBoundaryArgs(capture())
|
|
726
740
|
} catch {
|
|
727
|
-
return fn(...serializedArgs)
|
|
741
|
+
return fn(...serializedArgs)
|
|
728
742
|
}
|
|
729
743
|
// Close-over-function detection: a `use cache` closure that captures a non-serializable function
|
|
730
744
|
// cannot cross the cache/RSC boundary. During a build prerender, reject with Next's "Functions
|
|
@@ -735,16 +749,16 @@ export function pnextUseCacheBound<Args extends unknown[], Result>(
|
|
|
735
749
|
kind: 'close-over',
|
|
736
750
|
route: routeFromCacheId(id),
|
|
737
751
|
consoleBlock: USE_CACHE_CLOSE_OVER_FUNCTION_BLOCK,
|
|
738
|
-
})
|
|
739
|
-
throw new Error(USE_CACHE_CLOSE_OVER_FUNCTION_MESSAGE)
|
|
752
|
+
})
|
|
753
|
+
throw new Error(USE_CACHE_CLOSE_OVER_FUNCTION_MESSAGE)
|
|
740
754
|
}
|
|
741
|
-
const holes = extractElementHoles(serializedArgs)
|
|
742
|
-
const keyArgs = [captured, ...declaredArgs(fn, serializedArgs)]
|
|
743
|
-
const key = cacheKeyFor(id, withRootParamKeyArgs(id, keyArgs))
|
|
755
|
+
const holes = extractElementHoles(serializedArgs)
|
|
756
|
+
const keyArgs = [captured, ...declaredArgs(fn, serializedArgs)]
|
|
757
|
+
const key = cacheKeyFor(id, withRootParamKeyArgs(id, keyArgs))
|
|
744
758
|
if (isPromiseLike(key)) {
|
|
745
759
|
return key.then(
|
|
746
760
|
resolvedKey => {
|
|
747
|
-
if (resolvedKey === null) return fn(...serializedArgs)
|
|
761
|
+
if (resolvedKey === null) return fn(...serializedArgs)
|
|
748
762
|
return readOrProduceEntry(
|
|
749
763
|
resolvedKey,
|
|
750
764
|
id,
|
|
@@ -752,16 +766,23 @@ export function pnextUseCacheBound<Args extends unknown[], Result>(
|
|
|
752
766
|
fn,
|
|
753
767
|
holes.args as Args,
|
|
754
768
|
holes.fills,
|
|
755
|
-
)
|
|
769
|
+
)
|
|
756
770
|
},
|
|
757
771
|
() => fn(...serializedArgs),
|
|
758
|
-
) as Result
|
|
772
|
+
) as Result
|
|
759
773
|
}
|
|
760
|
-
if (key === null) return fn(...serializedArgs)
|
|
761
|
-
return readOrProduceEntry(
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
774
|
+
if (key === null) return fn(...serializedArgs)
|
|
775
|
+
return readOrProduceEntry(
|
|
776
|
+
key,
|
|
777
|
+
id,
|
|
778
|
+
argsJsonFromKey(id, key),
|
|
779
|
+
fn,
|
|
780
|
+
holes.args as Args,
|
|
781
|
+
holes.fills,
|
|
782
|
+
)
|
|
783
|
+
}
|
|
784
|
+
;(wrapper as unknown as Record<symbol, unknown>)[USE_CACHE_WRAPPER] = true
|
|
785
|
+
return wrapper
|
|
765
786
|
}
|
|
766
787
|
|
|
767
788
|
/**
|
|
@@ -778,42 +799,42 @@ export function pnextUseCachePrivate<Args extends unknown[], Result>(
|
|
|
778
799
|
fn: (...args: Args) => Result,
|
|
779
800
|
): (...args: Args) => Result {
|
|
780
801
|
const wrapper = (...args: Args): Result => {
|
|
781
|
-
recordCacheNoStore()
|
|
802
|
+
recordCacheNoStore()
|
|
782
803
|
// Private caches are EXCLUDED from prerenders: they may read per-request data, so a build/shell
|
|
783
804
|
// prerender postpones the scope (a dynamic hole under the nearest Suspense boundary; a classic
|
|
784
805
|
// static prerender skips the route to dynamic) and the function is evaluated per request instead.
|
|
785
806
|
// EXCEPTION: a runtime prefetch samples the real request, so a private cache with a long enough
|
|
786
807
|
// stale window IS included; a shorter-lived one stays a postponed hole.
|
|
787
808
|
if (isPrerendering()) {
|
|
788
|
-
if (!isRuntimePrefetchPrerender()) throw new PostponeError('use cache: private')
|
|
789
|
-
return runtimePrefetchPrivate(id, fn, serializeCacheBoundaryArgs(args) as Args) as Result
|
|
809
|
+
if (!isRuntimePrefetchPrerender()) throw new PostponeError('use cache: private')
|
|
810
|
+
return runtimePrefetchPrivate(id, fn, serializeCacheBoundaryArgs(args) as Args) as Result
|
|
790
811
|
}
|
|
791
|
-
const serializedArgs = serializeCacheBoundaryArgs(args) as Args
|
|
792
|
-
if (draftModeBypassActive()) return draftBypassRun(id, fn, serializedArgs)
|
|
812
|
+
const serializedArgs = serializeCacheBoundaryArgs(args) as Args
|
|
813
|
+
if (draftModeBypassActive()) return draftBypassRun(id, fn, serializedArgs)
|
|
793
814
|
// Run the REQUEST-TIME body inside a cache scope too (the prerender path
|
|
794
815
|
// already does, via runtimePrefetchPrivate): without one, a cacheLife()
|
|
795
816
|
// inside a `use cache: private` body is a no-op for the response aggregate,
|
|
796
817
|
// so its stale window never reaches x-nextjs-stale-time and the client
|
|
797
818
|
// reuses the navigation for the page's LONGEST window instead.
|
|
798
819
|
const runScoped = (call: () => Result): Result => {
|
|
799
|
-
const scope: UseCacheScope = { tags: new Set(), functionId: id }
|
|
800
|
-
const result = scopeStorage.run(scope, call)
|
|
820
|
+
const scope: UseCacheScope = { tags: new Set(), functionId: id }
|
|
821
|
+
const result = scopeStorage.run(scope, call)
|
|
801
822
|
const settle = () => {
|
|
802
|
-
if (scope.staleSeconds !== undefined) stashCacheLife({ staleSeconds: scope.staleSeconds })
|
|
803
|
-
}
|
|
823
|
+
if (scope.staleSeconds !== undefined) stashCacheLife({ staleSeconds: scope.staleSeconds })
|
|
824
|
+
}
|
|
804
825
|
if (isPromiseLike(result)) {
|
|
805
826
|
return (result as PromiseLike<unknown>).then(value => {
|
|
806
|
-
settle()
|
|
807
|
-
return value
|
|
808
|
-
}) as Result
|
|
827
|
+
settle()
|
|
828
|
+
return value
|
|
829
|
+
}) as Result
|
|
809
830
|
}
|
|
810
|
-
settle()
|
|
811
|
-
return result
|
|
812
|
-
}
|
|
813
|
-
const store = privateCacheStore()
|
|
814
|
-
if (!store) return runScoped(() => fn(...serializedArgs))
|
|
815
|
-
const keyArgs = declaredArgs(fn, serializedArgs)
|
|
816
|
-
const key = cacheKeyFor(id, keyArgs)
|
|
831
|
+
settle()
|
|
832
|
+
return result
|
|
833
|
+
}
|
|
834
|
+
const store = privateCacheStore()
|
|
835
|
+
if (!store) return runScoped(() => fn(...serializedArgs))
|
|
836
|
+
const keyArgs = declaredArgs(fn, serializedArgs)
|
|
837
|
+
const key = cacheKeyFor(id, keyArgs)
|
|
817
838
|
if (isPromiseLike(key)) {
|
|
818
839
|
return key.then(
|
|
819
840
|
resolvedKey =>
|
|
@@ -821,13 +842,13 @@ export function pnextUseCachePrivate<Args extends unknown[], Result>(
|
|
|
821
842
|
? runScoped(() => fn(...serializedArgs))
|
|
822
843
|
: runScoped(() => privateMemo(store, resolvedKey, fn, serializedArgs)),
|
|
823
844
|
() => runScoped(() => fn(...serializedArgs)),
|
|
824
|
-
) as Result
|
|
845
|
+
) as Result
|
|
825
846
|
}
|
|
826
|
-
if (key === null) return runScoped(() => fn(...serializedArgs))
|
|
827
|
-
return runScoped(() => privateMemo(store, key, fn, serializedArgs))
|
|
828
|
-
}
|
|
829
|
-
(wrapper as unknown as Record<symbol, unknown>)[USE_CACHE_WRAPPER] = true
|
|
830
|
-
return wrapper
|
|
847
|
+
if (key === null) return runScoped(() => fn(...serializedArgs))
|
|
848
|
+
return runScoped(() => privateMemo(store, key, fn, serializedArgs))
|
|
849
|
+
}
|
|
850
|
+
;(wrapper as unknown as Record<symbol, unknown>)[USE_CACHE_WRAPPER] = true
|
|
851
|
+
return wrapper
|
|
831
852
|
}
|
|
832
853
|
|
|
833
854
|
/**
|
|
@@ -844,59 +865,59 @@ function runtimePrefetchPrivate<Args extends unknown[], Result>(
|
|
|
844
865
|
fn: (...args: Args) => Result,
|
|
845
866
|
args: Args,
|
|
846
867
|
): Promise<unknown> {
|
|
847
|
-
const scope: UseCacheScope = { tags: new Set(), functionId: id }
|
|
848
|
-
const produced = (async () => await scopeStorage.run(scope, () => fn(...args)))()
|
|
868
|
+
const scope: UseCacheScope = { tags: new Set(), functionId: id }
|
|
869
|
+
const produced = (async () => await scopeStorage.run(scope, () => fn(...args)))()
|
|
849
870
|
// Awaiting the private cache arms the sync-IO abort (see gatePrefetchCacheLife).
|
|
850
871
|
return markRuntimeApiOnAwait(
|
|
851
872
|
produced.then(value => {
|
|
852
|
-
const stale = scope.staleSeconds ?? DEFAULT_STALE_SECONDS
|
|
853
|
-
if (stale < RUNTIME_PREFETCH_DYNAMIC_STALE) return hangingPromise('use cache: private')
|
|
873
|
+
const stale = scope.staleSeconds ?? DEFAULT_STALE_SECONDS
|
|
874
|
+
if (stale < RUNTIME_PREFETCH_DYNAMIC_STALE) return hangingPromise('use cache: private')
|
|
854
875
|
// The sampled value IS in this response, so the response is only as fresh
|
|
855
876
|
// as that private window — the runtime-prefetch payload must expire with
|
|
856
877
|
// it, not with the (longer) public `use cache` window baked into the
|
|
857
878
|
// route's segment meta.
|
|
858
|
-
recordCacheStale(stale)
|
|
859
|
-
return value
|
|
879
|
+
recordCacheStale(stale)
|
|
880
|
+
return value
|
|
860
881
|
}),
|
|
861
|
-
)
|
|
882
|
+
)
|
|
862
883
|
}
|
|
863
884
|
|
|
864
885
|
/** Well-known key for the per-request private-cache dedup store. */
|
|
865
|
-
const PRIVATE_CACHE_STORE = Symbol.for('pnext.compat.privateCacheStore')
|
|
886
|
+
const PRIVATE_CACHE_STORE = Symbol.for('pnext.compat.privateCacheStore')
|
|
866
887
|
|
|
867
888
|
// Fallback per-request private-cache store, used when a private `use cache`
|
|
868
889
|
// resolves outside the work-unit ALS but still inside the request ALS. Keyed on
|
|
869
890
|
// the request object (WeakMap → auto-evicts when the request is GC'd), anchored
|
|
870
891
|
// on globalThis so the build orchestrator and the built server bundle share it.
|
|
871
|
-
const PRIVATE_STORE_BY_REQUEST = Symbol.for('pnext.compat.privateStoreByRequest')
|
|
892
|
+
const PRIVATE_STORE_BY_REQUEST = Symbol.for('pnext.compat.privateStoreByRequest')
|
|
872
893
|
const privateStoreByRequest = useCacheGlobal(
|
|
873
894
|
PRIVATE_STORE_BY_REQUEST,
|
|
874
895
|
() => new WeakMap<object, Map<string, unknown>>(),
|
|
875
|
-
)
|
|
896
|
+
)
|
|
876
897
|
|
|
877
898
|
function privateCacheStore(): Map<string, unknown> | undefined {
|
|
878
|
-
const unit = getWorkUnit()
|
|
899
|
+
const unit = getWorkUnit()
|
|
879
900
|
if (unit) {
|
|
880
|
-
const compat = (unit.compat ??= {})
|
|
881
|
-
let store = compat[PRIVATE_CACHE_STORE] as Map<string, unknown> | undefined
|
|
901
|
+
const compat = (unit.compat ??= {})
|
|
902
|
+
let store = compat[PRIVATE_CACHE_STORE] as Map<string, unknown> | undefined
|
|
882
903
|
if (!store) {
|
|
883
|
-
store = new Map<string, unknown>()
|
|
884
|
-
compat[PRIVATE_CACHE_STORE] = store
|
|
904
|
+
store = new Map<string, unknown>()
|
|
905
|
+
compat[PRIVATE_CACHE_STORE] = store
|
|
885
906
|
}
|
|
886
|
-
return store
|
|
907
|
+
return store
|
|
887
908
|
}
|
|
888
909
|
// Async server components may resolve outside the work-unit ALS (the renderer re-enters the request
|
|
889
910
|
// ALS, but not always the work unit), and Next still dedupes identical private-cache invocations
|
|
890
911
|
// WITHIN one request. Fall back to a per-request store keyed on the live request object, so two
|
|
891
912
|
// placements in one render join one value while concurrent requests stay isolated.
|
|
892
|
-
const request = currentRequest() as object | undefined
|
|
893
|
-
if (!request) return undefined
|
|
894
|
-
let store = privateStoreByRequest.get(request)
|
|
913
|
+
const request = currentRequest() as object | undefined
|
|
914
|
+
if (!request) return undefined
|
|
915
|
+
let store = privateStoreByRequest.get(request)
|
|
895
916
|
if (!store) {
|
|
896
|
-
store = new Map<string, unknown>()
|
|
897
|
-
privateStoreByRequest.set(request, store)
|
|
917
|
+
store = new Map<string, unknown>()
|
|
918
|
+
privateStoreByRequest.set(request, store)
|
|
898
919
|
}
|
|
899
|
-
return store
|
|
920
|
+
return store
|
|
900
921
|
}
|
|
901
922
|
|
|
902
923
|
function privateMemo<Args extends unknown[], Result>(
|
|
@@ -905,52 +926,54 @@ function privateMemo<Args extends unknown[], Result>(
|
|
|
905
926
|
fn: (...args: Args) => Result,
|
|
906
927
|
args: Args,
|
|
907
928
|
): Result {
|
|
908
|
-
if (store.has(key)) return store.get(key) as Result
|
|
909
|
-
const value = fn(...args)
|
|
910
|
-
store.set(key, value)
|
|
911
|
-
return value
|
|
929
|
+
if (store.has(key)) return store.get(key) as Result
|
|
930
|
+
const value = fn(...args)
|
|
931
|
+
store.set(key, value)
|
|
932
|
+
return value
|
|
912
933
|
}
|
|
913
934
|
|
|
914
|
-
function declaredArgs<Args extends unknown[]>(
|
|
915
|
-
|
|
935
|
+
function declaredArgs<Args extends unknown[]>(
|
|
936
|
+
fn: (...args: Args) => unknown,
|
|
937
|
+
args: Args,
|
|
938
|
+
): unknown[] {
|
|
939
|
+
return args.slice(0, fn.length)
|
|
916
940
|
}
|
|
917
941
|
|
|
918
942
|
function serializeCacheBoundaryArgs(args: unknown[]): unknown[] {
|
|
919
|
-
return args.map(arg => serializeCacheBoundaryValue(arg, new WeakSet<object>()))
|
|
943
|
+
return args.map(arg => serializeCacheBoundaryValue(arg, new WeakSet<object>()))
|
|
920
944
|
}
|
|
921
945
|
|
|
922
946
|
function serializeCacheBoundaryValue(value: unknown, stack: WeakSet<object>): unknown {
|
|
923
947
|
if (typeof value !== 'object' || value === null || isPromiseLike(value) || isElementLike(value)) {
|
|
924
|
-
return value
|
|
948
|
+
return value
|
|
925
949
|
}
|
|
926
|
-
if (stack.has(value)) return value
|
|
927
|
-
stack.add(value)
|
|
950
|
+
if (stack.has(value)) return value
|
|
951
|
+
stack.add(value)
|
|
928
952
|
try {
|
|
929
|
-
if (Array.isArray(value)) return value.map(item => serializeCacheBoundaryValue(item, stack))
|
|
930
|
-
if (value instanceof Date) return value.toJSON()
|
|
953
|
+
if (Array.isArray(value)) return value.map(item => serializeCacheBoundaryValue(item, stack))
|
|
954
|
+
if (value instanceof Date) return value.toJSON()
|
|
931
955
|
if (value instanceof Map) {
|
|
932
956
|
return [...value].map(([key, nested]) => [
|
|
933
957
|
serializeCacheBoundaryValue(key, stack),
|
|
934
958
|
serializeCacheBoundaryValue(nested, stack),
|
|
935
|
-
])
|
|
959
|
+
])
|
|
936
960
|
}
|
|
937
961
|
if (value instanceof Set) {
|
|
938
|
-
return [...value].map(item => serializeCacheBoundaryValue(item, stack))
|
|
962
|
+
return [...value].map(item => serializeCacheBoundaryValue(item, stack))
|
|
939
963
|
}
|
|
940
|
-
const iterator = (value as { [Symbol.iterator]?: unknown })[Symbol.iterator]
|
|
964
|
+
const iterator = (value as { [Symbol.iterator]?: unknown })[Symbol.iterator]
|
|
941
965
|
if (typeof iterator === 'function') {
|
|
942
|
-
return Array.from(value as Iterable<unknown>, item =>
|
|
966
|
+
return Array.from(value as Iterable<unknown>, item =>
|
|
967
|
+
serializeCacheBoundaryValue(item, stack),
|
|
968
|
+
)
|
|
943
969
|
}
|
|
944
|
-
const entriesForObject = Object.entries(value)
|
|
945
|
-
if (entriesForObject.length === 0) return value
|
|
970
|
+
const entriesForObject = Object.entries(value)
|
|
971
|
+
if (entriesForObject.length === 0) return value
|
|
946
972
|
return Object.fromEntries(
|
|
947
|
-
entriesForObject.map(([key, nested]) => [
|
|
948
|
-
|
|
949
|
-
serializeCacheBoundaryValue(nested, stack),
|
|
950
|
-
]),
|
|
951
|
-
);
|
|
973
|
+
entriesForObject.map(([key, nested]) => [key, serializeCacheBoundaryValue(nested, stack)]),
|
|
974
|
+
)
|
|
952
975
|
} finally {
|
|
953
|
-
stack.delete(value)
|
|
976
|
+
stack.delete(value)
|
|
954
977
|
}
|
|
955
978
|
}
|
|
956
979
|
|
|
@@ -962,8 +985,8 @@ function draftBypassRun<Args extends unknown[], Result>(
|
|
|
962
985
|
fn: (...args: Args) => Result,
|
|
963
986
|
args: Args,
|
|
964
987
|
): Result {
|
|
965
|
-
const scope: UseCacheScope = { tags: new Set(), functionId: id }
|
|
966
|
-
return runInsideDataCacheProducer(() => scopeStorage.run(scope, () => fn(...args)))
|
|
988
|
+
const scope: UseCacheScope = { tags: new Set(), functionId: id }
|
|
989
|
+
return runInsideDataCacheProducer(() => scopeStorage.run(scope, () => fn(...args)))
|
|
967
990
|
}
|
|
968
991
|
|
|
969
992
|
function readOrProduceEntry<Args extends unknown[], Result>(
|
|
@@ -974,9 +997,9 @@ function readOrProduceEntry<Args extends unknown[], Result>(
|
|
|
974
997
|
args: Args,
|
|
975
998
|
fills: unknown[],
|
|
976
999
|
): Result {
|
|
977
|
-
const parent = scopeStorage.getStore()
|
|
978
|
-
const modernKey = hasModernCacheHandler() ? modernCacheKey(id, argsJson) : undefined
|
|
979
|
-
const resumeRecord = useCacheRdcRecords().get(key)
|
|
1000
|
+
const parent = scopeStorage.getStore()
|
|
1001
|
+
const modernKey = hasModernCacheHandler() ? modernCacheKey(id, argsJson) : undefined
|
|
1002
|
+
const resumeRecord = useCacheRdcRecords().get(key)
|
|
980
1003
|
if (
|
|
981
1004
|
resumeRecord &&
|
|
982
1005
|
!tagsRevalidatedAfterSeq(resumeRecord.tags, resumeRecord.tagSeq) &&
|
|
@@ -990,25 +1013,28 @@ function readOrProduceEntry<Args extends unknown[], Result>(
|
|
|
990
1013
|
...(resumeRecord.revalidateSeconds !== undefined
|
|
991
1014
|
? { revalidateSeconds: resumeRecord.revalidateSeconds }
|
|
992
1015
|
: {}),
|
|
993
|
-
...(resumeRecord.expireSeconds !== undefined
|
|
994
|
-
|
|
1016
|
+
...(resumeRecord.expireSeconds !== undefined
|
|
1017
|
+
? { expireSeconds: resumeRecord.expireSeconds }
|
|
1018
|
+
: {}),
|
|
1019
|
+
...(resumeRecord.staleSeconds !== undefined
|
|
1020
|
+
? { staleSeconds: resumeRecord.staleSeconds }
|
|
1021
|
+
: {}),
|
|
995
1022
|
tagSeq: resumeRecord.tagSeq,
|
|
996
1023
|
...(resumeRecord.route !== undefined ? { route: resumeRecord.route } : {}),
|
|
997
|
-
})
|
|
1024
|
+
})
|
|
998
1025
|
}
|
|
999
|
-
const existing = entries.get(key)
|
|
1000
|
-
const meta = currentRenderCacheMeta()
|
|
1001
|
-
const expired = existing !== undefined && entryExpired(existing)
|
|
1026
|
+
const existing = entries.get(key)
|
|
1027
|
+
const meta = currentRenderCacheMeta()
|
|
1028
|
+
const expired = existing !== undefined && entryExpired(existing)
|
|
1002
1029
|
// Hard on-demand invalidation: a tag update (revalidateTag/updateTag) after
|
|
1003
1030
|
// this entry was produced, or a revalidatePath covering the entry's owning
|
|
1004
1031
|
// route. Both block and re-produce so the read observes the fresh value
|
|
1005
1032
|
// (read-your-writes within an action; path-scoped revalidation).
|
|
1006
|
-
const tagStale =
|
|
1007
|
-
existing !== undefined && tagsRevalidatedAfterSeq(existing.tags, existing.tagSeq);
|
|
1033
|
+
const tagStale = existing !== undefined && tagsRevalidatedAfterSeq(existing.tags, existing.tagSeq)
|
|
1008
1034
|
const pathStale =
|
|
1009
|
-
existing !== undefined && pathScopeRevalidated(existing.route, existing.storedAt)
|
|
1010
|
-
const invalidated = tagStale || pathStale
|
|
1011
|
-
const swrStale = existing !== undefined && tagsStaleSince(existing.tags, existing.storedAt)
|
|
1035
|
+
existing !== undefined && pathScopeRevalidated(existing.route, existing.storedAt)
|
|
1036
|
+
const invalidated = tagStale || pathStale
|
|
1037
|
+
const swrStale = existing !== undefined && tagsStaleSince(existing.tags, existing.storedAt)
|
|
1012
1038
|
// On-demand revalidation re-produces each entry ONCE per render, deduped via refreshedIn - but only
|
|
1013
1039
|
// entries the revalidation actually reached. A revalidation mark outlives a single render (a soft-nav
|
|
1014
1040
|
// render never writes the prebuilt HTML back), so refreshing unconditionally churned every cache
|
|
@@ -1016,15 +1042,15 @@ function readOrProduceEntry<Args extends unknown[], Result>(
|
|
|
1016
1042
|
const refresh =
|
|
1017
1043
|
meta?.refreshFetches === true &&
|
|
1018
1044
|
existing?.refreshedIn !== meta &&
|
|
1019
|
-
(invalidated || swrStale || expired)
|
|
1020
|
-
let modernGet: Promise<unknown> | undefined
|
|
1045
|
+
(invalidated || swrStale || expired)
|
|
1046
|
+
let modernGet: Promise<unknown> | undefined
|
|
1021
1047
|
if (modernKey) {
|
|
1022
|
-
void modernCacheRefreshTags()
|
|
1048
|
+
void modernCacheRefreshTags()
|
|
1023
1049
|
// Keep the handler read's promise: serving an existing entry awaits it, so the handler's get() -
|
|
1024
1050
|
// whose logs are asserted - lands on the critical path like Next's, where a custom handler IS the
|
|
1025
1051
|
// cache. A fire-and-forget read could otherwise log only after the response already flushed.
|
|
1026
|
-
modernGet = modernCacheGet(modernKey)
|
|
1027
|
-
if (existing && !invalidated) void modernCacheGetExpiration()
|
|
1052
|
+
modernGet = modernCacheGet(modernKey)
|
|
1053
|
+
if (existing && !invalidated) void modernCacheGetExpiration()
|
|
1028
1054
|
}
|
|
1029
1055
|
// A regeneration render (blockingStaleFetches) must re-produce not only
|
|
1030
1056
|
// TTL-expired entries but also soft-stale ones (revalidateTag with a
|
|
@@ -1039,32 +1065,32 @@ function readOrProduceEntry<Args extends unknown[], Result>(
|
|
|
1039
1065
|
if ((expired || swrStale) && !existing.refreshing) {
|
|
1040
1066
|
// TTL expiry outside an ISR regen: serve stale while refreshing in
|
|
1041
1067
|
// the background (Next's SWR data-cache semantics).
|
|
1042
|
-
existing.refreshing = true
|
|
1043
|
-
void produceEntry(key, id, argsJson, fn, args, parent, existing, modernKey)
|
|
1068
|
+
existing.refreshing = true
|
|
1069
|
+
void produceEntry(key, id, argsJson, fn, args, parent, existing, modernKey)
|
|
1044
1070
|
}
|
|
1045
1071
|
if (modernKey && parent && existing.revalidateSeconds === undefined) {
|
|
1046
1072
|
void existing.value.then(
|
|
1047
1073
|
() => propagateToParent(existing, parent),
|
|
1048
1074
|
() => undefined,
|
|
1049
|
-
)
|
|
1075
|
+
)
|
|
1050
1076
|
}
|
|
1051
|
-
propagate(existing, parent)
|
|
1052
|
-
captureUseCacheRdcEntry(key)
|
|
1053
|
-
const served = withHoleFills(existing.value, fills)
|
|
1054
|
-
const out = modernGet ? modernGet.then(() => served) : served
|
|
1055
|
-
const servedGate = prefetchCacheGate()
|
|
1056
|
-
return (servedGate ? gatePrefetchCacheLife(out, servedGate, () => existing) : out) as Result
|
|
1077
|
+
propagate(existing, parent)
|
|
1078
|
+
captureUseCacheRdcEntry(key)
|
|
1079
|
+
const served = withHoleFills(existing.value, fills)
|
|
1080
|
+
const out = modernGet ? modernGet.then(() => served) : served
|
|
1081
|
+
const servedGate = prefetchCacheGate()
|
|
1082
|
+
return (servedGate ? gatePrefetchCacheLife(out, servedGate, () => existing) : out) as Result
|
|
1057
1083
|
}
|
|
1058
1084
|
|
|
1059
|
-
const value = produceEntry(key, id, argsJson, fn, args, parent, undefined, modernKey)
|
|
1060
|
-
captureUseCacheRdcEntry(key)
|
|
1061
|
-
const out = withHoleFills(value, fills)
|
|
1085
|
+
const value = produceEntry(key, id, argsJson, fn, args, parent, undefined, modernKey)
|
|
1086
|
+
captureUseCacheRdcEntry(key)
|
|
1087
|
+
const out = withHoleFills(value, fills)
|
|
1062
1088
|
// The producing entry's cacheLife is set once `value` settles (see
|
|
1063
1089
|
// produceEntry); `entries.get(key)` reads it back after production.
|
|
1064
|
-
const producedGate = prefetchCacheGate()
|
|
1090
|
+
const producedGate = prefetchCacheGate()
|
|
1065
1091
|
return (
|
|
1066
1092
|
producedGate ? gatePrefetchCacheLife(out, producedGate, () => entries.get(key)) : out
|
|
1067
|
-
) as Result
|
|
1093
|
+
) as Result
|
|
1068
1094
|
}
|
|
1069
1095
|
|
|
1070
1096
|
function produceEntry<Args extends unknown[], Result>(
|
|
@@ -1077,14 +1103,14 @@ function produceEntry<Args extends unknown[], Result>(
|
|
|
1077
1103
|
serveStale?: UseCacheEntry,
|
|
1078
1104
|
modernKey?: string,
|
|
1079
1105
|
): Promise<unknown> {
|
|
1080
|
-
const scope: UseCacheScope = { tags: new Set(), functionId: id }
|
|
1106
|
+
const scope: UseCacheScope = { tags: new Set(), functionId: id }
|
|
1081
1107
|
// Sample the invalidation-scope inputs at production start: the hard-tag
|
|
1082
1108
|
// sequence (read-your-writes) and the producing route (path revalidation),
|
|
1083
1109
|
// mirroring how the fetch/unstable_cache data caches tag entries with their
|
|
1084
1110
|
// owning route.
|
|
1085
|
-
const tagSeq = currentTagRevalidationSeq()
|
|
1086
|
-
const producingMeta = currentRenderCacheMeta()
|
|
1087
|
-
const route = producingMeta?.route
|
|
1111
|
+
const tagSeq = currentTagRevalidationSeq()
|
|
1112
|
+
const producingMeta = currentRenderCacheMeta()
|
|
1113
|
+
const route = producingMeta?.route
|
|
1088
1114
|
const entry: UseCacheEntry = {
|
|
1089
1115
|
value: undefined as unknown as Promise<unknown>,
|
|
1090
1116
|
storedAt: realNow(),
|
|
@@ -1092,8 +1118,12 @@ function produceEntry<Args extends unknown[], Result>(
|
|
|
1092
1118
|
modernKey,
|
|
1093
1119
|
tagSeq,
|
|
1094
1120
|
...(producingMeta !== undefined ? { refreshedIn: producingMeta } : {}),
|
|
1095
|
-
...(route !== undefined
|
|
1096
|
-
|
|
1121
|
+
...(route !== undefined
|
|
1122
|
+
? { route }
|
|
1123
|
+
: serveStale?.route !== undefined
|
|
1124
|
+
? { route: serveStale.route }
|
|
1125
|
+
: {}),
|
|
1126
|
+
}
|
|
1097
1127
|
const rawValue = (async () =>
|
|
1098
1128
|
await runInsideDataCacheProducer(() => scopeStorage.run(scope, () => fn(...args))))().then(
|
|
1099
1129
|
// Render-once semantics: nested server components inside the produced tree
|
|
@@ -1103,9 +1133,9 @@ function produceEntry<Args extends unknown[], Result>(
|
|
|
1103
1133
|
// before any caller sees it: at runtime that reshapes the error (and, in a
|
|
1104
1134
|
// plain production build, logs it here and redacts what escapes).
|
|
1105
1135
|
error => {
|
|
1106
|
-
throw prerenderErrorCollectionActive() ? error : funnelCacheRuntimeError(error)
|
|
1136
|
+
throw prerenderErrorCollectionActive() ? error : funnelCacheRuntimeError(error)
|
|
1107
1137
|
},
|
|
1108
|
-
)
|
|
1138
|
+
)
|
|
1109
1139
|
// E236 hanging-input guard: during a build prerender a fill that never
|
|
1110
1140
|
// resolves would hang the build; race it against the configured fill timeout
|
|
1111
1141
|
// and reject with Next's timeout message (recorded so the build fails). Gated
|
|
@@ -1113,31 +1143,33 @@ function produceEntry<Args extends unknown[], Result>(
|
|
|
1113
1143
|
// render cache-meta, so we can't rely on meta.prerender here).
|
|
1114
1144
|
const value = prerenderErrorCollectionActive()
|
|
1115
1145
|
? withFillTimeout(rawValue, routeFromCacheId(id))
|
|
1116
|
-
: rawValue
|
|
1146
|
+
: rawValue
|
|
1117
1147
|
// A build prerender's task boundary must not cut a cache fill short: the
|
|
1118
1148
|
// whole point of a cached read is that its (possibly task-settled) IO belongs
|
|
1119
1149
|
// in the static shell. Register it so the boundary waits (core ppr).
|
|
1120
|
-
void trackPrerenderCacheFill(value)
|
|
1121
|
-
entry.value = value
|
|
1122
|
-
let resolveModernEntry: ((entry: ReturnType<typeof modernCacheEntry>) => void) | undefined
|
|
1123
|
-
let rejectModernEntry: ((reason?: unknown) => void) | undefined
|
|
1150
|
+
void trackPrerenderCacheFill(value)
|
|
1151
|
+
entry.value = value
|
|
1152
|
+
let resolveModernEntry: ((entry: ReturnType<typeof modernCacheEntry>) => void) | undefined
|
|
1153
|
+
let rejectModernEntry: ((reason?: unknown) => void) | undefined
|
|
1124
1154
|
if (modernKey) {
|
|
1125
|
-
const pendingModernEntry = new Promise<ReturnType<typeof modernCacheEntry>>(
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1155
|
+
const pendingModernEntry = new Promise<ReturnType<typeof modernCacheEntry>>(
|
|
1156
|
+
(resolve, reject) => {
|
|
1157
|
+
resolveModernEntry = resolve
|
|
1158
|
+
rejectModernEntry = reject
|
|
1159
|
+
},
|
|
1160
|
+
)
|
|
1161
|
+
void modernCacheSet(modernKey, pendingModernEntry)
|
|
1130
1162
|
}
|
|
1131
1163
|
if (!serveStale) {
|
|
1132
1164
|
if (entries.size >= entriesLimit) {
|
|
1133
|
-
const oldest = entries.keys().next().value
|
|
1134
|
-
if (oldest !== undefined) entries.delete(oldest)
|
|
1165
|
+
const oldest = entries.keys().next().value
|
|
1166
|
+
if (oldest !== undefined) entries.delete(oldest)
|
|
1135
1167
|
}
|
|
1136
|
-
entries.set(key, entry)
|
|
1168
|
+
entries.set(key, entry)
|
|
1137
1169
|
}
|
|
1138
1170
|
value.then(
|
|
1139
1171
|
produced => {
|
|
1140
|
-
entry.tags = [...scope.tags]
|
|
1172
|
+
entry.tags = [...scope.tags]
|
|
1141
1173
|
// Conditional-read upgrade (Stage B-3): production may have read root
|
|
1142
1174
|
// params that were not in this entry's key (first-ever call for the id, or
|
|
1143
1175
|
// a newly-discovered branch-dependent read). Re-store the settled entry
|
|
@@ -1145,22 +1177,22 @@ function produceEntry<Args extends unknown[], Result>(
|
|
|
1145
1177
|
// on those root params finds this value instead of re-producing. The
|
|
1146
1178
|
// original key stays populated too (harmless; both point at one value).
|
|
1147
1179
|
if (scope.rootParamsRead && scope.rootParamsRead.size > 0) {
|
|
1148
|
-
const upgradedKey = cacheKeyFor(id, withRootParamKeyArgs(id, args))
|
|
1180
|
+
const upgradedKey = cacheKeyFor(id, withRootParamKeyArgs(id, args))
|
|
1149
1181
|
if (typeof upgradedKey === 'string' && upgradedKey !== key) {
|
|
1150
|
-
entries.set(upgradedKey, entry)
|
|
1182
|
+
entries.set(upgradedKey, entry)
|
|
1151
1183
|
}
|
|
1152
1184
|
}
|
|
1153
1185
|
// A `use cache` entry with no explicit cacheLife() takes Next's `default`
|
|
1154
1186
|
// profile (revalidate 900, expire one year, stale 300) so its route
|
|
1155
1187
|
// emits the SWR cache-control + x-nextjs-stale-time headers.
|
|
1156
|
-
entry.revalidateSeconds = scope.revalidateSeconds ?? DEFAULT_REVALIDATE_SECONDS
|
|
1157
|
-
entry.expireSeconds = scope.expireSeconds ?? DEFAULT_EXPIRE_SECONDS
|
|
1158
|
-
entry.staleSeconds = scope.staleSeconds ?? DEFAULT_STALE_SECONDS
|
|
1188
|
+
entry.revalidateSeconds = scope.revalidateSeconds ?? DEFAULT_REVALIDATE_SECONDS
|
|
1189
|
+
entry.expireSeconds = scope.expireSeconds ?? DEFAULT_EXPIRE_SECONDS
|
|
1190
|
+
entry.staleSeconds = scope.staleSeconds ?? DEFAULT_STALE_SECONDS
|
|
1159
1191
|
// Persisting to a modern cache handler waits for embedded nested `use cache` vnodes to render
|
|
1160
1192
|
// (their scopes fold tags and min cacheLife into this entry - Next resolves the entry only once
|
|
1161
1193
|
// the whole cached tree has serialized). A fallback timer guards against a tree that is never
|
|
1162
1194
|
// rendered, so the handler's pending set cannot deadlock.
|
|
1163
|
-
const resolveModern = resolveModernEntry
|
|
1195
|
+
const resolveModern = resolveModernEntry
|
|
1164
1196
|
if (resolveModern) {
|
|
1165
1197
|
const finalize = () =>
|
|
1166
1198
|
resolveModern(
|
|
@@ -1172,31 +1204,31 @@ function produceEntry<Args extends unknown[], Result>(
|
|
|
1172
1204
|
revalidateSeconds: entry.revalidateSeconds ?? DEFAULT_REVALIDATE_SECONDS,
|
|
1173
1205
|
storedAt: entry.storedAt,
|
|
1174
1206
|
}),
|
|
1175
|
-
)
|
|
1207
|
+
)
|
|
1176
1208
|
if (entry.nestedPending !== undefined && entry.nestedPending > 0) {
|
|
1177
|
-
const timer = setTimeout(finalize, 10_000)
|
|
1178
|
-
if (typeof timer.unref === 'function') timer.unref()
|
|
1209
|
+
const timer = setTimeout(finalize, 10_000)
|
|
1210
|
+
if (typeof timer.unref === 'function') timer.unref()
|
|
1179
1211
|
entry.onNestedSettled = () => {
|
|
1180
1212
|
if (entry.nestedPending === 0) {
|
|
1181
|
-
clearTimeout(timer)
|
|
1182
|
-
finalize()
|
|
1213
|
+
clearTimeout(timer)
|
|
1214
|
+
finalize()
|
|
1183
1215
|
}
|
|
1184
|
-
}
|
|
1216
|
+
}
|
|
1185
1217
|
} else {
|
|
1186
|
-
finalize()
|
|
1218
|
+
finalize()
|
|
1187
1219
|
}
|
|
1188
1220
|
}
|
|
1189
1221
|
// Background refresh replaces the stale entry only once settled.
|
|
1190
|
-
if (serveStale) entries.set(key, entry)
|
|
1191
|
-
propagate(entry, parent)
|
|
1222
|
+
if (serveStale) entries.set(key, entry)
|
|
1223
|
+
propagate(entry, parent)
|
|
1192
1224
|
},
|
|
1193
1225
|
() => {
|
|
1194
|
-
rejectModernEntry?.()
|
|
1195
|
-
if (serveStale) serveStale.refreshing = false
|
|
1196
|
-
else if (entries.get(key) === entry) entries.delete(key)
|
|
1226
|
+
rejectModernEntry?.()
|
|
1227
|
+
if (serveStale) serveStale.refreshing = false
|
|
1228
|
+
else if (entries.get(key) === entry) entries.delete(key)
|
|
1197
1229
|
},
|
|
1198
|
-
)
|
|
1199
|
-
return value
|
|
1230
|
+
)
|
|
1231
|
+
return value
|
|
1200
1232
|
}
|
|
1201
1233
|
|
|
1202
1234
|
/**
|
|
@@ -1206,34 +1238,34 @@ function produceEntry<Args extends unknown[], Result>(
|
|
|
1206
1238
|
*/
|
|
1207
1239
|
function cacheKeyFor(id: string, args: unknown[]): string | Promise<string | null> | null {
|
|
1208
1240
|
try {
|
|
1209
|
-
const keyValue = resolvePromisesForKey(args, new WeakSet<object>())
|
|
1241
|
+
const keyValue = resolvePromisesForKey(args, new WeakSet<object>())
|
|
1210
1242
|
if (isPromiseLike(keyValue)) {
|
|
1211
1243
|
return Promise.resolve(keyValue).then(
|
|
1212
1244
|
resolved => stringifyCacheKey(id, resolved),
|
|
1213
1245
|
() => null,
|
|
1214
|
-
)
|
|
1246
|
+
)
|
|
1215
1247
|
}
|
|
1216
|
-
return stringifyCacheKey(id, keyValue)
|
|
1248
|
+
return stringifyCacheKey(id, keyValue)
|
|
1217
1249
|
} catch {
|
|
1218
|
-
return null
|
|
1250
|
+
return null
|
|
1219
1251
|
}
|
|
1220
1252
|
}
|
|
1221
1253
|
|
|
1222
1254
|
function argsJsonFromKey(id: string, key: string): string {
|
|
1223
|
-
return key.startsWith(`${id}:`) ? key.slice(id.length + 1) : '[]'
|
|
1255
|
+
return key.startsWith(`${id}:`) ? key.slice(id.length + 1) : '[]'
|
|
1224
1256
|
}
|
|
1225
1257
|
|
|
1226
1258
|
function stringifyCacheKey(id: string, value: unknown): string | null {
|
|
1227
1259
|
try {
|
|
1228
1260
|
const json = JSON.stringify(value, (_key, nested: unknown) => {
|
|
1229
|
-
if (typeof nested === 'function' || typeof nested === 'symbol') return '$pnext:ref'
|
|
1230
|
-
if (typeof nested === 'bigint') return `$pnext:bigint:${nested}
|
|
1231
|
-
if (isElementLike(nested)) return '$pnext:element'
|
|
1232
|
-
return nested
|
|
1233
|
-
})
|
|
1234
|
-
return `${id}:${json}
|
|
1261
|
+
if (typeof nested === 'function' || typeof nested === 'symbol') return '$pnext:ref'
|
|
1262
|
+
if (typeof nested === 'bigint') return `$pnext:bigint:${nested}`
|
|
1263
|
+
if (isElementLike(nested)) return '$pnext:element'
|
|
1264
|
+
return nested
|
|
1265
|
+
})
|
|
1266
|
+
return `${id}:${json}`
|
|
1235
1267
|
} catch {
|
|
1236
|
-
return null
|
|
1268
|
+
return null
|
|
1237
1269
|
}
|
|
1238
1270
|
}
|
|
1239
1271
|
|
|
@@ -1241,32 +1273,33 @@ function resolvePromisesForKey(value: unknown, stack: WeakSet<object>): unknown
|
|
|
1241
1273
|
if (isPromiseLike(value)) {
|
|
1242
1274
|
return Promise.resolve(value).then(resolved =>
|
|
1243
1275
|
resolvePromisesForKey(resolved, new WeakSet<object>()),
|
|
1244
|
-
)
|
|
1276
|
+
)
|
|
1245
1277
|
}
|
|
1246
|
-
if (typeof value !== 'object' || value === null || isElementLike(value)) return value
|
|
1278
|
+
if (typeof value !== 'object' || value === null || isElementLike(value)) return value
|
|
1247
1279
|
|
|
1248
|
-
if (stack.has(value)) throw new Error('cyclic cache key')
|
|
1249
|
-
stack.add(value)
|
|
1280
|
+
if (stack.has(value)) throw new Error('cyclic cache key')
|
|
1281
|
+
stack.add(value)
|
|
1250
1282
|
try {
|
|
1251
1283
|
if (Array.isArray(value)) {
|
|
1252
|
-
const items = value.map(item => resolvePromisesForKey(item, stack))
|
|
1253
|
-
return items.some(isPromiseLike) ? Promise.all(items) : value
|
|
1284
|
+
const items = value.map(item => resolvePromisesForKey(item, stack))
|
|
1285
|
+
return items.some(isPromiseLike) ? Promise.all(items) : value
|
|
1254
1286
|
}
|
|
1255
1287
|
|
|
1256
|
-
const entriesForObject = Object.entries(value)
|
|
1257
|
-
if (entriesForObject.length === 0) return value
|
|
1288
|
+
const entriesForObject = Object.entries(value)
|
|
1289
|
+
if (entriesForObject.length === 0) return value
|
|
1258
1290
|
|
|
1259
|
-
const resolvedEntries = entriesForObject.map(
|
|
1260
|
-
key,
|
|
1261
|
-
|
|
1262
|
-
]
|
|
1263
|
-
if (!resolvedEntries.some(([, nested]) => isPromiseLike(nested))) return value;
|
|
1291
|
+
const resolvedEntries = entriesForObject.map(
|
|
1292
|
+
([key, nested]) => [key, resolvePromisesForKey(nested, stack)] as const,
|
|
1293
|
+
)
|
|
1294
|
+
if (!resolvedEntries.some(([, nested]) => isPromiseLike(nested))) return value
|
|
1264
1295
|
|
|
1265
1296
|
return Promise.all(
|
|
1266
|
-
resolvedEntries.map(([key, nested]) =>
|
|
1267
|
-
|
|
1297
|
+
resolvedEntries.map(([key, nested]) =>
|
|
1298
|
+
Promise.resolve(nested).then(resolved => [key, resolved] as const),
|
|
1299
|
+
),
|
|
1300
|
+
).then(entriesList => Object.fromEntries(entriesList))
|
|
1268
1301
|
} finally {
|
|
1269
|
-
stack.delete(value)
|
|
1302
|
+
stack.delete(value)
|
|
1270
1303
|
}
|
|
1271
1304
|
}
|
|
1272
1305
|
|
|
@@ -1276,7 +1309,7 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
|
1276
1309
|
value !== null &&
|
|
1277
1310
|
'then' in value &&
|
|
1278
1311
|
typeof (value as { then?: unknown }).then === 'function'
|
|
1279
|
-
)
|
|
1312
|
+
)
|
|
1280
1313
|
}
|
|
1281
1314
|
|
|
1282
1315
|
// Cached element trees: argument holes + render-once subtree semantics.
|
|
@@ -1303,30 +1336,30 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
|
1303
1336
|
// renderer's existing hook-dispatcher fallback still applies.
|
|
1304
1337
|
|
|
1305
1338
|
/** Marks pnextUseCache* wrappers so tree wrapping leaves them to self-memoize. */
|
|
1306
|
-
const USE_CACHE_WRAPPER = Symbol.for('pnext.compat.useCacheWrapper')
|
|
1307
|
-
const MEMO_WRAPPED = Symbol.for('pnext.compat.useCacheMemoWrapped')
|
|
1339
|
+
const USE_CACHE_WRAPPER = Symbol.for('pnext.compat.useCacheWrapper')
|
|
1340
|
+
const MEMO_WRAPPED = Symbol.for('pnext.compat.useCacheMemoWrapped')
|
|
1308
1341
|
// Renderer component-kind marks (global symbol registry; see render/renderer.ts,
|
|
1309
1342
|
// client/reference.ts, api/dynamic.tsx, render/slots.tsx).
|
|
1310
|
-
const clientReferenceSymbol = Symbol.for('pnext.clientReference')
|
|
1311
|
-
const dynamicReferenceSymbol = Symbol.for('pnext.dynamic')
|
|
1312
|
-
const paramsScopeSymbol = Symbol.for('pnext.paramsScope')
|
|
1313
|
-
const clientPageComponentSymbol = Symbol.for('pnext.clientPageComponent')
|
|
1314
|
-
const useCacheJoinerPromiseSymbol = Symbol.for('pnext.compat.useCacheJoinerPromise')
|
|
1343
|
+
const clientReferenceSymbol = Symbol.for('pnext.clientReference')
|
|
1344
|
+
const dynamicReferenceSymbol = Symbol.for('pnext.dynamic')
|
|
1345
|
+
const paramsScopeSymbol = Symbol.for('pnext.paramsScope')
|
|
1346
|
+
const clientPageComponentSymbol = Symbol.for('pnext.clientPageComponent')
|
|
1347
|
+
const useCacheJoinerPromiseSymbol = Symbol.for('pnext.compat.useCacheJoinerPromise')
|
|
1315
1348
|
|
|
1316
|
-
const HOLE_INDEX_PROP = '__pnextCacheHoleIndex'
|
|
1349
|
+
const HOLE_INDEX_PROP = '__pnextCacheHoleIndex'
|
|
1317
1350
|
|
|
1318
1351
|
/** Inert placeholder; instantiation replaces it before it can ever render. */
|
|
1319
1352
|
function CachedArgumentHole(): null {
|
|
1320
|
-
return null
|
|
1353
|
+
return null
|
|
1321
1354
|
}
|
|
1322
1355
|
|
|
1323
1356
|
function holeElement(index: number) {
|
|
1324
|
-
return h(CachedArgumentHole as never, { [HOLE_INDEX_PROP]: index } as never)
|
|
1357
|
+
return h(CachedArgumentHole as never, { [HOLE_INDEX_PROP]: index } as never)
|
|
1325
1358
|
}
|
|
1326
1359
|
|
|
1327
1360
|
interface ExtractedHoles {
|
|
1328
|
-
args: unknown[]
|
|
1329
|
-
fills: unknown[]
|
|
1361
|
+
args: unknown[]
|
|
1362
|
+
fills: unknown[]
|
|
1330
1363
|
}
|
|
1331
1364
|
|
|
1332
1365
|
/**
|
|
@@ -1336,97 +1369,97 @@ interface ExtractedHoles {
|
|
|
1336
1369
|
* returned unchanged with an empty fills list.
|
|
1337
1370
|
*/
|
|
1338
1371
|
function extractElementHoles(args: unknown[]): ExtractedHoles {
|
|
1339
|
-
const fills: unknown[] = []
|
|
1340
|
-
const stack = new WeakSet<object>()
|
|
1372
|
+
const fills: unknown[] = []
|
|
1373
|
+
const stack = new WeakSet<object>()
|
|
1341
1374
|
const walk = (value: unknown): unknown => {
|
|
1342
|
-
if (value === null || typeof value !== 'object' || isPromiseLike(value)) return value
|
|
1375
|
+
if (value === null || typeof value !== 'object' || isPromiseLike(value)) return value
|
|
1343
1376
|
if (isElementLike(value)) {
|
|
1344
|
-
fills.push(value)
|
|
1345
|
-
return holeElement(fills.length - 1)
|
|
1377
|
+
fills.push(value)
|
|
1378
|
+
return holeElement(fills.length - 1)
|
|
1346
1379
|
}
|
|
1347
|
-
if (stack.has(value)) return value
|
|
1348
|
-
stack.add(value)
|
|
1380
|
+
if (stack.has(value)) return value
|
|
1381
|
+
stack.add(value)
|
|
1349
1382
|
try {
|
|
1350
1383
|
if (Array.isArray(value)) {
|
|
1351
|
-
let changed = false
|
|
1384
|
+
let changed = false
|
|
1352
1385
|
const next = value.map(item => {
|
|
1353
|
-
const out = walk(item)
|
|
1354
|
-
if (out !== item) changed = true
|
|
1355
|
-
return out
|
|
1356
|
-
})
|
|
1357
|
-
return changed ? next : value
|
|
1386
|
+
const out = walk(item)
|
|
1387
|
+
if (out !== item) changed = true
|
|
1388
|
+
return out
|
|
1389
|
+
})
|
|
1390
|
+
return changed ? next : value
|
|
1358
1391
|
}
|
|
1359
|
-
const proto: unknown = Object.getPrototypeOf(value)
|
|
1360
|
-
if (proto !== Object.prototype && proto !== null) return value
|
|
1361
|
-
let changed = false
|
|
1392
|
+
const proto: unknown = Object.getPrototypeOf(value)
|
|
1393
|
+
if (proto !== Object.prototype && proto !== null) return value
|
|
1394
|
+
let changed = false
|
|
1362
1395
|
const entriesForObject = Object.entries(value).map(([key, nested]) => {
|
|
1363
|
-
const out = walk(nested)
|
|
1364
|
-
if (out !== nested) changed = true
|
|
1365
|
-
return [key, out] as const
|
|
1366
|
-
})
|
|
1367
|
-
return changed ? Object.fromEntries(entriesForObject) : value
|
|
1396
|
+
const out = walk(nested)
|
|
1397
|
+
if (out !== nested) changed = true
|
|
1398
|
+
return [key, out] as const
|
|
1399
|
+
})
|
|
1400
|
+
return changed ? Object.fromEntries(entriesForObject) : value
|
|
1368
1401
|
} finally {
|
|
1369
|
-
stack.delete(value)
|
|
1402
|
+
stack.delete(value)
|
|
1370
1403
|
}
|
|
1371
|
-
}
|
|
1372
|
-
const out = args.map(walk)
|
|
1373
|
-
return fills.length > 0 ? { args: out, fills } : { args, fills }
|
|
1404
|
+
}
|
|
1405
|
+
const out = args.map(walk)
|
|
1406
|
+
return fills.length > 0 ? { args: out, fills } : { args, fills }
|
|
1374
1407
|
}
|
|
1375
1408
|
|
|
1376
1409
|
/** Resolve a cached value for one invocation, substituting its hole fills. */
|
|
1377
1410
|
function withHoleFills(value: Promise<unknown>, fills: unknown[]): Promise<unknown> {
|
|
1378
|
-
if (fills.length === 0) return value
|
|
1379
|
-
return value.then(resolved => instantiateHoles(resolved, fills, new WeakSet<object>()))
|
|
1411
|
+
if (fills.length === 0) return value
|
|
1412
|
+
return value.then(resolved => instantiateHoles(resolved, fills, new WeakSet<object>()))
|
|
1380
1413
|
}
|
|
1381
1414
|
|
|
1382
1415
|
function instantiateHoles(value: unknown, fills: unknown[], stack: WeakSet<object>): unknown {
|
|
1383
|
-
if (value === null || typeof value !== 'object' || isPromiseLike(value)) return value
|
|
1384
|
-
if (stack.has(value)) return value
|
|
1385
|
-
stack.add(value)
|
|
1416
|
+
if (value === null || typeof value !== 'object' || isPromiseLike(value)) return value
|
|
1417
|
+
if (stack.has(value)) return value
|
|
1418
|
+
stack.add(value)
|
|
1386
1419
|
try {
|
|
1387
1420
|
if (isElementLike(value)) {
|
|
1388
1421
|
const vnode = value as {
|
|
1389
|
-
type?: unknown
|
|
1390
|
-
props?: Record<string, unknown> | null
|
|
1391
|
-
key?: unknown
|
|
1392
|
-
ref?: unknown
|
|
1393
|
-
}
|
|
1422
|
+
type?: unknown
|
|
1423
|
+
props?: Record<string, unknown> | null
|
|
1424
|
+
key?: unknown
|
|
1425
|
+
ref?: unknown
|
|
1426
|
+
}
|
|
1394
1427
|
if (vnode.type === CachedArgumentHole) {
|
|
1395
|
-
const index = vnode.props?.[HOLE_INDEX_PROP]
|
|
1396
|
-
return typeof index === 'number' ? (fills[index] ?? null) : null
|
|
1428
|
+
const index = vnode.props?.[HOLE_INDEX_PROP]
|
|
1429
|
+
return typeof index === 'number' ? (fills[index] ?? null) : null
|
|
1397
1430
|
}
|
|
1398
|
-
if (!vnode.props || typeof vnode.props !== 'object') return value
|
|
1399
|
-
let changed = false
|
|
1431
|
+
if (!vnode.props || typeof vnode.props !== 'object') return value
|
|
1432
|
+
let changed = false
|
|
1400
1433
|
const nextProps = Object.fromEntries(
|
|
1401
1434
|
Object.entries(vnode.props).map(([key, nested]) => {
|
|
1402
|
-
const out = instantiateHoles(nested, fills, stack)
|
|
1403
|
-
if (out !== nested) changed = true
|
|
1404
|
-
return [key, out] as const
|
|
1435
|
+
const out = instantiateHoles(nested, fills, stack)
|
|
1436
|
+
if (out !== nested) changed = true
|
|
1437
|
+
return [key, out] as const
|
|
1405
1438
|
}),
|
|
1406
|
-
)
|
|
1407
|
-
if (!changed) return value
|
|
1408
|
-
return cloneElementLike(vnode, vnode.type, nextProps)
|
|
1439
|
+
)
|
|
1440
|
+
if (!changed) return value
|
|
1441
|
+
return cloneElementLike(vnode, vnode.type, nextProps)
|
|
1409
1442
|
}
|
|
1410
1443
|
if (Array.isArray(value)) {
|
|
1411
|
-
let changed = false
|
|
1444
|
+
let changed = false
|
|
1412
1445
|
const next = value.map(item => {
|
|
1413
|
-
const out = instantiateHoles(item, fills, stack)
|
|
1414
|
-
if (out !== item) changed = true
|
|
1415
|
-
return out
|
|
1416
|
-
})
|
|
1417
|
-
return changed ? next : value
|
|
1446
|
+
const out = instantiateHoles(item, fills, stack)
|
|
1447
|
+
if (out !== item) changed = true
|
|
1448
|
+
return out
|
|
1449
|
+
})
|
|
1450
|
+
return changed ? next : value
|
|
1418
1451
|
}
|
|
1419
|
-
const proto: unknown = Object.getPrototypeOf(value)
|
|
1420
|
-
if (proto !== Object.prototype && proto !== null) return value
|
|
1421
|
-
let changed = false
|
|
1452
|
+
const proto: unknown = Object.getPrototypeOf(value)
|
|
1453
|
+
if (proto !== Object.prototype && proto !== null) return value
|
|
1454
|
+
let changed = false
|
|
1422
1455
|
const entriesForObject = Object.entries(value).map(([key, nested]) => {
|
|
1423
|
-
const out = instantiateHoles(nested, fills, stack)
|
|
1424
|
-
if (out !== nested) changed = true
|
|
1425
|
-
return [key, out] as const
|
|
1426
|
-
})
|
|
1427
|
-
return changed ? Object.fromEntries(entriesForObject) : value
|
|
1456
|
+
const out = instantiateHoles(nested, fills, stack)
|
|
1457
|
+
if (out !== nested) changed = true
|
|
1458
|
+
return [key, out] as const
|
|
1459
|
+
})
|
|
1460
|
+
return changed ? Object.fromEntries(entriesForObject) : value
|
|
1428
1461
|
} finally {
|
|
1429
|
-
stack.delete(value)
|
|
1462
|
+
stack.delete(value)
|
|
1430
1463
|
}
|
|
1431
1464
|
}
|
|
1432
1465
|
|
|
@@ -1436,21 +1469,24 @@ function cloneElementLike(
|
|
|
1436
1469
|
type: unknown,
|
|
1437
1470
|
props: Record<string, unknown>,
|
|
1438
1471
|
): unknown {
|
|
1439
|
-
return h(
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1472
|
+
return h(
|
|
1473
|
+
type as never,
|
|
1474
|
+
{
|
|
1475
|
+
...props,
|
|
1476
|
+
...(vnode.key !== undefined && vnode.key !== null ? { key: vnode.key } : {}),
|
|
1477
|
+
...(vnode.ref !== undefined && vnode.ref !== null ? { ref: vnode.ref } : {}),
|
|
1478
|
+
} as never,
|
|
1479
|
+
)
|
|
1444
1480
|
}
|
|
1445
1481
|
|
|
1446
1482
|
/** Wrap a settled cached value's server-component vnodes for render-once reuse. */
|
|
1447
1483
|
function wrapCachedValue(value: unknown, scope: UseCacheScope, entry: UseCacheEntry): unknown {
|
|
1448
|
-
return wrapCachedNode(value, scope, entry, new WeakSet<object>()).value
|
|
1484
|
+
return wrapCachedNode(value, scope, entry, new WeakSet<object>()).value
|
|
1449
1485
|
}
|
|
1450
1486
|
|
|
1451
1487
|
interface WrapNodeResult {
|
|
1452
|
-
value: unknown
|
|
1453
|
-
hasHole: boolean
|
|
1488
|
+
value: unknown
|
|
1489
|
+
hasHole: boolean
|
|
1454
1490
|
}
|
|
1455
1491
|
|
|
1456
1492
|
function wrapCachedNode(
|
|
@@ -1460,36 +1496,36 @@ function wrapCachedNode(
|
|
|
1460
1496
|
stack: WeakSet<object>,
|
|
1461
1497
|
): WrapNodeResult {
|
|
1462
1498
|
if (value === null || typeof value !== 'object' || isPromiseLike(value)) {
|
|
1463
|
-
return { value, hasHole: false }
|
|
1499
|
+
return { value, hasHole: false }
|
|
1464
1500
|
}
|
|
1465
|
-
if (stack.has(value)) return { value, hasHole: false }
|
|
1466
|
-
stack.add(value)
|
|
1501
|
+
if (stack.has(value)) return { value, hasHole: false }
|
|
1502
|
+
stack.add(value)
|
|
1467
1503
|
try {
|
|
1468
1504
|
if (isElementLike(value)) {
|
|
1469
1505
|
const vnode = value as {
|
|
1470
|
-
type?: unknown
|
|
1471
|
-
props?: Record<string, unknown> | null
|
|
1472
|
-
key?: unknown
|
|
1473
|
-
ref?: unknown
|
|
1474
|
-
}
|
|
1475
|
-
if (vnode.type === CachedArgumentHole) return { value, hasHole: true }
|
|
1476
|
-
let hasHole = false
|
|
1477
|
-
let changed = false
|
|
1478
|
-
let nextProps = vnode.props ?? undefined
|
|
1506
|
+
type?: unknown
|
|
1507
|
+
props?: Record<string, unknown> | null
|
|
1508
|
+
key?: unknown
|
|
1509
|
+
ref?: unknown
|
|
1510
|
+
}
|
|
1511
|
+
if (vnode.type === CachedArgumentHole) return { value, hasHole: true }
|
|
1512
|
+
let hasHole = false
|
|
1513
|
+
let changed = false
|
|
1514
|
+
let nextProps = vnode.props ?? undefined
|
|
1479
1515
|
if (vnode.props && typeof vnode.props === 'object') {
|
|
1480
1516
|
const entriesForProps = Object.entries(vnode.props).map(([key, nested]) => {
|
|
1481
|
-
const result = wrapCachedNode(nested, scope, entry, stack)
|
|
1482
|
-
hasHole ||= result.hasHole
|
|
1483
|
-
if (result.value !== nested) changed = true
|
|
1484
|
-
return [key, result.value] as const
|
|
1485
|
-
})
|
|
1486
|
-
if (changed) nextProps = Object.fromEntries(entriesForProps)
|
|
1517
|
+
const result = wrapCachedNode(nested, scope, entry, stack)
|
|
1518
|
+
hasHole ||= result.hasHole
|
|
1519
|
+
if (result.value !== nested) changed = true
|
|
1520
|
+
return [key, result.value] as const
|
|
1521
|
+
})
|
|
1522
|
+
if (changed) nextProps = Object.fromEntries(entriesForProps)
|
|
1487
1523
|
}
|
|
1488
1524
|
// A component whose props carry a hole re-executes per invocation (its
|
|
1489
1525
|
// props genuinely differ per call); everything else renders once.
|
|
1490
|
-
const type = vnode.type
|
|
1526
|
+
const type = vnode.type
|
|
1491
1527
|
if (!hasHole && isUseCacheWrapperType(type)) {
|
|
1492
|
-
entry.nestedPending = (entry.nestedPending ?? 0) + 1
|
|
1528
|
+
entry.nestedPending = (entry.nestedPending ?? 0) + 1
|
|
1493
1529
|
}
|
|
1494
1530
|
const nextType =
|
|
1495
1531
|
!hasHole && isUseCacheWrapperType(type)
|
|
@@ -1500,37 +1536,37 @@ function wrapCachedNode(
|
|
|
1500
1536
|
)
|
|
1501
1537
|
: !hasHole && shouldMemoWrapType(type)
|
|
1502
1538
|
? memoServerComponent(type as (props: Record<string, unknown>) => unknown, scope, entry)
|
|
1503
|
-
: type
|
|
1504
|
-
if (!changed && nextType === type) return { value, hasHole }
|
|
1539
|
+
: type
|
|
1540
|
+
if (!changed && nextType === type) return { value, hasHole }
|
|
1505
1541
|
return {
|
|
1506
1542
|
value: cloneElementLike(vnode, nextType, nextProps ?? {}),
|
|
1507
1543
|
hasHole,
|
|
1508
|
-
}
|
|
1544
|
+
}
|
|
1509
1545
|
}
|
|
1510
1546
|
if (Array.isArray(value)) {
|
|
1511
|
-
let hasHole = false
|
|
1512
|
-
let changed = false
|
|
1547
|
+
let hasHole = false
|
|
1548
|
+
let changed = false
|
|
1513
1549
|
const next = value.map(item => {
|
|
1514
|
-
const result = wrapCachedNode(item, scope, entry, stack)
|
|
1515
|
-
hasHole ||= result.hasHole
|
|
1516
|
-
if (result.value !== item) changed = true
|
|
1517
|
-
return result.value
|
|
1518
|
-
})
|
|
1519
|
-
return { value: changed ? next : value, hasHole }
|
|
1550
|
+
const result = wrapCachedNode(item, scope, entry, stack)
|
|
1551
|
+
hasHole ||= result.hasHole
|
|
1552
|
+
if (result.value !== item) changed = true
|
|
1553
|
+
return result.value
|
|
1554
|
+
})
|
|
1555
|
+
return { value: changed ? next : value, hasHole }
|
|
1520
1556
|
}
|
|
1521
|
-
const proto: unknown = Object.getPrototypeOf(value)
|
|
1522
|
-
if (proto !== Object.prototype && proto !== null) return { value, hasHole: false }
|
|
1523
|
-
let hasHole = false
|
|
1524
|
-
let changed = false
|
|
1557
|
+
const proto: unknown = Object.getPrototypeOf(value)
|
|
1558
|
+
if (proto !== Object.prototype && proto !== null) return { value, hasHole: false }
|
|
1559
|
+
let hasHole = false
|
|
1560
|
+
let changed = false
|
|
1525
1561
|
const entriesForObject = Object.entries(value).map(([key, nested]) => {
|
|
1526
|
-
const result = wrapCachedNode(nested, scope, entry, stack)
|
|
1527
|
-
hasHole ||= result.hasHole
|
|
1528
|
-
if (result.value !== nested) changed = true
|
|
1529
|
-
return [key, result.value] as const
|
|
1530
|
-
})
|
|
1531
|
-
return { value: changed ? Object.fromEntries(entriesForObject) : value, hasHole }
|
|
1562
|
+
const result = wrapCachedNode(nested, scope, entry, stack)
|
|
1563
|
+
hasHole ||= result.hasHole
|
|
1564
|
+
if (result.value !== nested) changed = true
|
|
1565
|
+
return [key, result.value] as const
|
|
1566
|
+
})
|
|
1567
|
+
return { value: changed ? Object.fromEntries(entriesForObject) : value, hasHole }
|
|
1532
1568
|
} finally {
|
|
1533
|
-
stack.delete(value)
|
|
1569
|
+
stack.delete(value)
|
|
1534
1570
|
}
|
|
1535
1571
|
}
|
|
1536
1572
|
|
|
@@ -1539,7 +1575,7 @@ function isUseCacheWrapperType(type: unknown): boolean {
|
|
|
1539
1575
|
typeof type === 'function' &&
|
|
1540
1576
|
Boolean((type as unknown as Record<symbol, unknown>)[USE_CACHE_WRAPPER]) &&
|
|
1541
1577
|
!(type as unknown as Record<symbol, unknown>)[MEMO_WRAPPED]
|
|
1542
|
-
)
|
|
1578
|
+
)
|
|
1543
1579
|
}
|
|
1544
1580
|
|
|
1545
1581
|
/**
|
|
@@ -1555,67 +1591,67 @@ function attributeNestedCacheWrapper(
|
|
|
1555
1591
|
scope: UseCacheScope,
|
|
1556
1592
|
entry: UseCacheEntry,
|
|
1557
1593
|
): (props: Record<string, unknown>) => unknown {
|
|
1558
|
-
let settledOnce = false
|
|
1594
|
+
let settledOnce = false
|
|
1559
1595
|
const settle = (fold: boolean) => {
|
|
1560
|
-
if (fold) foldScopeIntoEntry(scope, entry)
|
|
1561
|
-
if (settledOnce) return
|
|
1562
|
-
settledOnce = true
|
|
1596
|
+
if (fold) foldScopeIntoEntry(scope, entry)
|
|
1597
|
+
if (settledOnce) return
|
|
1598
|
+
settledOnce = true
|
|
1563
1599
|
if (entry.nestedPending !== undefined && entry.nestedPending > 0) {
|
|
1564
|
-
entry.nestedPending -= 1
|
|
1565
|
-
entry.onNestedSettled?.()
|
|
1600
|
+
entry.nestedPending -= 1
|
|
1601
|
+
entry.onNestedSettled?.()
|
|
1566
1602
|
}
|
|
1567
|
-
}
|
|
1603
|
+
}
|
|
1568
1604
|
const wrapper = function (this: unknown, props: Record<string, unknown>): unknown {
|
|
1569
|
-
if (this !== undefined) return fn.call(this, props)
|
|
1570
|
-
const result = scopeStorage.run(scope, () => fn(props))
|
|
1605
|
+
if (this !== undefined) return fn.call(this, props)
|
|
1606
|
+
const result = scopeStorage.run(scope, () => fn(props))
|
|
1571
1607
|
if (isPromiseLike(result)) {
|
|
1572
1608
|
return Promise.resolve(result).then(
|
|
1573
1609
|
value => {
|
|
1574
|
-
settle(true)
|
|
1575
|
-
return value
|
|
1610
|
+
settle(true)
|
|
1611
|
+
return value
|
|
1576
1612
|
},
|
|
1577
1613
|
(error: unknown) => {
|
|
1578
|
-
settle(false)
|
|
1579
|
-
throw error
|
|
1614
|
+
settle(false)
|
|
1615
|
+
throw error
|
|
1580
1616
|
},
|
|
1581
|
-
)
|
|
1617
|
+
)
|
|
1582
1618
|
}
|
|
1583
|
-
settle(true)
|
|
1584
|
-
return result
|
|
1585
|
-
}
|
|
1586
|
-
const marked = wrapper as unknown as Record<symbol, unknown
|
|
1587
|
-
marked[MEMO_WRAPPED] = true
|
|
1588
|
-
marked[USE_CACHE_WRAPPER] = true
|
|
1589
|
-
return wrapper
|
|
1619
|
+
settle(true)
|
|
1620
|
+
return result
|
|
1621
|
+
}
|
|
1622
|
+
const marked = wrapper as unknown as Record<symbol, unknown>
|
|
1623
|
+
marked[MEMO_WRAPPED] = true
|
|
1624
|
+
marked[USE_CACHE_WRAPPER] = true
|
|
1625
|
+
return wrapper
|
|
1590
1626
|
}
|
|
1591
1627
|
|
|
1592
1628
|
/** Fold the scope's accumulated tags + min cacheLife onto a settled entry. */
|
|
1593
1629
|
function foldScopeIntoEntry(scope: UseCacheScope, entry: UseCacheEntry): void {
|
|
1594
|
-
entry.tags = [...new Set([...entry.tags, ...scope.tags])]
|
|
1595
|
-
entry.revalidateSeconds = minDefined(entry.revalidateSeconds, scope.revalidateSeconds)
|
|
1596
|
-
entry.expireSeconds = minDefined(entry.expireSeconds, scope.expireSeconds)
|
|
1597
|
-
entry.staleSeconds = minDefined(entry.staleSeconds, scope.staleSeconds)
|
|
1630
|
+
entry.tags = [...new Set([...entry.tags, ...scope.tags])]
|
|
1631
|
+
entry.revalidateSeconds = minDefined(entry.revalidateSeconds, scope.revalidateSeconds)
|
|
1632
|
+
entry.expireSeconds = minDefined(entry.expireSeconds, scope.expireSeconds)
|
|
1633
|
+
entry.staleSeconds = minDefined(entry.staleSeconds, scope.staleSeconds)
|
|
1598
1634
|
}
|
|
1599
1635
|
|
|
1600
1636
|
function shouldMemoWrapType(type: unknown): boolean {
|
|
1601
|
-
if (typeof type !== 'function') return false
|
|
1602
|
-
if (type === Fragment || type === (CoreSuspense as unknown)) return false
|
|
1637
|
+
if (typeof type !== 'function') return false
|
|
1638
|
+
if (type === Fragment || type === (CoreSuspense as unknown)) return false
|
|
1603
1639
|
const component = type as unknown as { prototype?: { render?: unknown } } & Record<
|
|
1604
1640
|
symbol,
|
|
1605
1641
|
unknown
|
|
1606
|
-
|
|
1642
|
+
>
|
|
1607
1643
|
// Class components (incl. preact/compat Suspense) need a preact instance.
|
|
1608
|
-
if (component.prototype && typeof component.prototype.render === 'function') return false
|
|
1609
|
-
if (component[MEMO_WRAPPED] || component[USE_CACHE_WRAPPER]) return false
|
|
1644
|
+
if (component.prototype && typeof component.prototype.render === 'function') return false
|
|
1645
|
+
if (component[MEMO_WRAPPED] || component[USE_CACHE_WRAPPER]) return false
|
|
1610
1646
|
if (
|
|
1611
1647
|
component[clientReferenceSymbol] ||
|
|
1612
1648
|
component[dynamicReferenceSymbol] ||
|
|
1613
1649
|
component[paramsScopeSymbol] ||
|
|
1614
1650
|
component[clientPageComponentSymbol]
|
|
1615
1651
|
) {
|
|
1616
|
-
return false
|
|
1652
|
+
return false
|
|
1617
1653
|
}
|
|
1618
|
-
return true
|
|
1654
|
+
return true
|
|
1619
1655
|
}
|
|
1620
1656
|
|
|
1621
1657
|
/**
|
|
@@ -1630,66 +1666,66 @@ function memoServerComponent(
|
|
|
1630
1666
|
scope: UseCacheScope,
|
|
1631
1667
|
entry: UseCacheEntry,
|
|
1632
1668
|
): (props: Record<string, unknown>) => unknown {
|
|
1633
|
-
let memo: { value: unknown; requestToken?: object } | undefined
|
|
1669
|
+
let memo: { value: unknown; requestToken?: object } | undefined
|
|
1634
1670
|
const wrapper = function (this: unknown, props: Record<string, unknown>): unknown {
|
|
1635
|
-
if (this !== undefined) return fn.call(this, props)
|
|
1671
|
+
if (this !== undefined) return fn.call(this, props)
|
|
1636
1672
|
if (memo) {
|
|
1637
|
-
const requestToken = currentUseCacheRequestToken()
|
|
1673
|
+
const requestToken = currentUseCacheRequestToken()
|
|
1638
1674
|
if (
|
|
1639
1675
|
memo.requestToken &&
|
|
1640
1676
|
requestToken &&
|
|
1641
1677
|
memo.requestToken !== requestToken &&
|
|
1642
1678
|
isPromiseLike(memo.value)
|
|
1643
1679
|
) {
|
|
1644
|
-
const joined = Promise.resolve(memo.value).then(value => value)
|
|
1645
|
-
(joined as unknown as Record<symbol, unknown>)[useCacheJoinerPromiseSymbol] = true
|
|
1646
|
-
return joined
|
|
1680
|
+
const joined = Promise.resolve(memo.value).then(value => value)
|
|
1681
|
+
;(joined as unknown as Record<symbol, unknown>)[useCacheJoinerPromiseSymbol] = true
|
|
1682
|
+
return joined
|
|
1647
1683
|
}
|
|
1648
|
-
return memo.value
|
|
1684
|
+
return memo.value
|
|
1649
1685
|
}
|
|
1650
|
-
const requestToken = currentUseCacheRequestToken()
|
|
1651
|
-
const result = runInsideDataCacheProducer(() => scopeStorage.run(scope, () => fn(props)))
|
|
1686
|
+
const requestToken = currentUseCacheRequestToken()
|
|
1687
|
+
const result = runInsideDataCacheProducer(() => scopeStorage.run(scope, () => fn(props)))
|
|
1652
1688
|
if (isPromiseLike(result)) {
|
|
1653
1689
|
const settled = Promise.resolve(result).then(resolved => {
|
|
1654
1690
|
// Tags declared while the subtree rendered attribute to the entry.
|
|
1655
|
-
entry.tags = [...scope.tags]
|
|
1656
|
-
return wrapCachedValue(resolved, scope, entry) as ComponentChildren
|
|
1657
|
-
})
|
|
1658
|
-
memo = { value: settled, ...(requestToken ? { requestToken } : {}) }
|
|
1691
|
+
entry.tags = [...scope.tags]
|
|
1692
|
+
return wrapCachedValue(resolved, scope, entry) as ComponentChildren
|
|
1693
|
+
})
|
|
1694
|
+
memo = { value: settled, ...(requestToken ? { requestToken } : {}) }
|
|
1659
1695
|
settled.then(undefined, () => {
|
|
1660
1696
|
// Failures don't stick: the next placement re-executes.
|
|
1661
|
-
if (memo?.value === settled) memo = undefined
|
|
1662
|
-
})
|
|
1663
|
-
return settled
|
|
1697
|
+
if (memo?.value === settled) memo = undefined
|
|
1698
|
+
})
|
|
1699
|
+
return settled
|
|
1664
1700
|
}
|
|
1665
|
-
entry.tags = [...scope.tags]
|
|
1666
|
-
const wrapped = wrapCachedValue(result, scope, entry)
|
|
1667
|
-
memo = { value: wrapped, ...(requestToken ? { requestToken } : {}) }
|
|
1668
|
-
return wrapped
|
|
1669
|
-
}
|
|
1670
|
-
(wrapper as unknown as Record<symbol, unknown>)[MEMO_WRAPPED] = true
|
|
1671
|
-
return wrapper
|
|
1701
|
+
entry.tags = [...scope.tags]
|
|
1702
|
+
const wrapped = wrapCachedValue(result, scope, entry)
|
|
1703
|
+
memo = { value: wrapped, ...(requestToken ? { requestToken } : {}) }
|
|
1704
|
+
return wrapped
|
|
1705
|
+
}
|
|
1706
|
+
;(wrapper as unknown as Record<symbol, unknown>)[MEMO_WRAPPED] = true
|
|
1707
|
+
return wrapper
|
|
1672
1708
|
}
|
|
1673
1709
|
|
|
1674
1710
|
function currentUseCacheRequestToken(): object | undefined {
|
|
1675
|
-
const request = currentRequest() as object | undefined
|
|
1676
|
-
if (!request) return undefined
|
|
1677
|
-
let token = requestTokens.get(request)
|
|
1711
|
+
const request = currentRequest() as object | undefined
|
|
1712
|
+
if (!request) return undefined
|
|
1713
|
+
let token = requestTokens.get(request)
|
|
1678
1714
|
if (!token) {
|
|
1679
|
-
token = {}
|
|
1680
|
-
requestTokens.set(request, token)
|
|
1715
|
+
token = {}
|
|
1716
|
+
requestTokens.set(request, token)
|
|
1681
1717
|
}
|
|
1682
|
-
return token
|
|
1718
|
+
return token
|
|
1683
1719
|
}
|
|
1684
1720
|
|
|
1685
1721
|
function isElementLike(value: unknown): boolean {
|
|
1686
|
-
if (typeof value !== 'object' || value === null) return false
|
|
1687
|
-
const record = value as Record<string, unknown
|
|
1688
|
-
if ('$$typeof' in record) return true
|
|
1689
|
-
return 'type' in record && 'props' in record
|
|
1722
|
+
if (typeof value !== 'object' || value === null) return false
|
|
1723
|
+
const record = value as Record<string, unknown>
|
|
1724
|
+
if ('$$typeof' in record) return true
|
|
1725
|
+
return 'type' in record && 'props' in record
|
|
1690
1726
|
}
|
|
1691
1727
|
|
|
1692
1728
|
/** Test hook. */
|
|
1693
1729
|
export function clearUseCacheEntries() {
|
|
1694
|
-
entries.clear()
|
|
1730
|
+
entries.clear()
|
|
1695
1731
|
}
|