@timber-js/app 0.2.0-alpha.207 → 0.2.0-alpha.209
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/dist/_chunks/{primitives-DLAnvsrk.js → actions-BerlqoXA.js} +640 -410
- package/dist/_chunks/actions-BerlqoXA.js.map +1 -0
- package/dist/_chunks/{als-registry-C6kcfprT.js → als-registry-DaxkVjt5.js} +6 -2
- package/dist/_chunks/als-registry-DaxkVjt5.js.map +1 -0
- package/dist/_chunks/{build-manifest-DTmSGLRz.js → build-manifest-DWppEdLB.js} +2 -51
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
- package/dist/_chunks/{build-output-helper-9dqCv1pZ.js → build-output-helper-RV_BRcF1.js} +50 -53
- package/dist/_chunks/build-output-helper-RV_BRcF1.js.map +1 -0
- package/dist/_chunks/{cache-api-CUD9Ezaq.js → cache-api-CR23J_NC.js} +64 -32
- package/dist/_chunks/cache-api-CR23J_NC.js.map +1 -0
- package/dist/_chunks/{chains-B4bgOl2g.js → chains-CpFg56UB.js} +2 -2
- package/dist/_chunks/{chains-B4bgOl2g.js.map → chains-CpFg56UB.js.map} +1 -1
- package/dist/_chunks/{cli-check-G8o30ldg.js → cli-check-C6Ev6wBO.js} +11 -52
- package/dist/_chunks/cli-check-C6Ev6wBO.js.map +1 -0
- package/dist/_chunks/cli-context-By7k2xVo.js +70 -0
- package/dist/_chunks/cli-context-By7k2xVo.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync--LZKs8Zn.js → cli-schema-sync-CbT2AUUI.js} +3 -3
- package/dist/_chunks/{cli-schema-sync--LZKs8Zn.js.map → cli-schema-sync-CbT2AUUI.js.map} +1 -1
- package/dist/_chunks/{cloudflare-BFb__LYG.js → cloudflare-Byxl8FWP.js} +96 -53
- package/dist/_chunks/{cloudflare-BFb__LYG.js.map → cloudflare-Byxl8FWP.js.map} +1 -1
- package/dist/_chunks/{convention-lint-5KzoMg_A.js → convention-lint-jKTwKwPe.js} +21 -6
- package/dist/_chunks/convention-lint-jKTwKwPe.js.map +1 -0
- package/dist/_chunks/{dev-server-BzBxgYAV.js → dev-server-C4WZdB7L.js} +15 -167
- package/dist/_chunks/dev-server-C4WZdB7L.js.map +1 -0
- package/dist/_chunks/{error-boundary-BndF-3Td.js → error-boundary-tA7kVfs4.js} +84 -104
- package/dist/_chunks/error-boundary-tA7kVfs4.js.map +1 -0
- package/dist/_chunks/{fs-identity-D7vb6GdI.js → fs-identity-BRKuL215.js} +3 -4
- package/dist/_chunks/fs-identity-BRKuL215.js.map +1 -0
- package/dist/_chunks/{graph-cache-CP4GEmf9.js → graph-cache-CYiUS2aS.js} +4 -5
- package/dist/_chunks/graph-cache-CYiUS2aS.js.map +1 -0
- package/dist/_chunks/{json-lossy-check-ClNvBM_3.js → json-lossy-check-ip0Qi0MT.js} +6 -3
- package/dist/_chunks/json-lossy-check-ip0Qi0MT.js.map +1 -0
- package/dist/_chunks/{live-graph-Bm5WTvcr.js → live-graph-Dv-JJCZw.js} +3 -3
- package/dist/_chunks/{live-graph-Bm5WTvcr.js.map → live-graph-Dv-JJCZw.js.map} +1 -1
- package/dist/_chunks/{logger-k2DQ4EUf.js → logger-DiDt5ppH.js} +192 -51
- package/dist/_chunks/logger-DiDt5ppH.js.map +1 -0
- package/dist/_chunks/{navigation-root-mHSK9psY.js → navigation-root-BQfo1-kG.js} +2 -2
- package/dist/_chunks/{navigation-root-mHSK9psY.js.map → navigation-root-BQfo1-kG.js.map} +1 -1
- package/dist/_chunks/{plugin-context-8idfR3UZ.js → plugin-context-BPBPc3lL.js} +28 -4
- package/dist/_chunks/plugin-context-BPBPc3lL.js.map +1 -0
- package/dist/_chunks/{poison-scan-DG5eI2_z.js → poison-scan-CpeT6_OJ.js} +2 -2
- package/dist/_chunks/{poison-scan-DG5eI2_z.js.map → poison-scan-CpeT6_OJ.js.map} +1 -1
- package/dist/_chunks/render-timeout-DOf_BBcO.js +46 -0
- package/dist/_chunks/render-timeout-DOf_BBcO.js.map +1 -0
- package/dist/_chunks/{rsc-payload-path-B_LBodc2.js → rsc-payload-path-dffcs4jp.js} +9 -3
- package/dist/_chunks/{rsc-payload-path-B_LBodc2.js.map → rsc-payload-path-dffcs4jp.js.map} +1 -1
- package/dist/_chunks/{scanner-BQ5sPIJ7.js → scanner-Bw0oq1HB.js} +2 -2
- package/dist/_chunks/{scanner-BQ5sPIJ7.js.map → scanner-Bw0oq1HB.js.map} +1 -1
- package/dist/_chunks/{navigation-context-D0TU0Jog.js → segment-context-xtPUGfdq.js} +36 -34
- package/dist/_chunks/segment-context-xtPUGfdq.js.map +1 -0
- package/dist/_chunks/{singleflight-2lUWfcAk.js → singleflight-BeVMMKi0.js} +4 -3
- package/dist/_chunks/{singleflight-2lUWfcAk.js.map → singleflight-BeVMMKi0.js.map} +1 -1
- package/dist/_chunks/{sizeof-lXffQ1Vr.js → sizeof-UwzwB1uM.js} +3 -2
- package/dist/_chunks/sizeof-UwzwB1uM.js.map +1 -0
- package/dist/_chunks/ssr-data-nA3I70_n.js +49 -0
- package/dist/_chunks/ssr-data-nA3I70_n.js.map +1 -0
- package/dist/_chunks/{state-FippDgxN.js → state-BhNGPsdi.js} +6 -8
- package/dist/_chunks/state-BhNGPsdi.js.map +1 -0
- package/dist/_chunks/{status-page-marker-DwQBrLBz.js → status-page-marker-gaihi0KZ.js} +140 -28
- package/dist/_chunks/status-page-marker-gaihi0KZ.js.map +1 -0
- package/dist/_chunks/{walkers-CN2N_bA-.js → walkers-BXExhzzk.js} +2 -2
- package/dist/_chunks/{walkers-CN2N_bA-.js.map → walkers-BXExhzzk.js.map} +1 -1
- package/dist/_chunks/write-file-atomic-D1qWqHb4.js +32 -0
- package/dist/_chunks/write-file-atomic-D1qWqHb4.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +7 -7
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/canonicalize-module.d.ts +8 -0
- package/dist/adapters/canonicalize-module.d.ts.map +1 -0
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +2 -2
- package/dist/adapters/cloudflare.d.ts +30 -8
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +2 -2
- package/dist/adapters/compress-module.d.ts +8 -0
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/fs-identity.d.ts +2 -3
- package/dist/adapters/fs-identity.d.ts.map +1 -1
- package/dist/adapters/nitro-presets.d.ts +0 -6
- package/dist/adapters/nitro-presets.d.ts.map +1 -1
- package/dist/adapters/nitro-preview.d.ts +43 -11
- package/dist/adapters/nitro-preview.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +1 -13
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +291 -176
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +7 -2
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +105 -21
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js +0 -0
- package/dist/analyze/crawl-entry.js +3 -3
- package/dist/analyze/graph-cache.d.ts.map +1 -1
- package/dist/analyze/graph-command.js +4 -4
- package/dist/cache/index.js +2 -2
- package/dist/cache/json-lossy-check.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/sizeof.d.ts.map +1 -1
- package/dist/cache/stores/memory.js +1 -1
- package/dist/cache/tag-aware-handler.d.ts.map +1 -1
- package/dist/cdn/workers-cache-purge.js +1 -1
- package/dist/cli-check.d.ts +7 -37
- package/dist/cli-check.d.ts.map +1 -1
- package/dist/cli-context.d.ts +54 -0
- package/dist/cli-context.d.ts.map +1 -0
- package/dist/cli.d.ts +9 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +20 -24
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-dev.d.ts +1 -1
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts +19 -29
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/index.d.ts +7 -7
- package/dist/client/browser-entry/post-hydration.d.ts +6 -9
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +6 -11
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +4 -3
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/error-reconstituter.d.ts +3 -9
- package/dist/client/error-reconstituter.d.ts.map +1 -1
- package/dist/client/history.d.ts +20 -11
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +0 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +5 -6
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +3 -3
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +95 -108
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-commit.d.ts +20 -7
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +3 -5
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/react-root.d.ts +3 -2
- package/dist/client/react-root.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +3 -8
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +45 -39
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router-url.d.ts +24 -0
- package/dist/client/router-url.d.ts.map +1 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +8 -4
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +44 -4
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/ssr-data.d.ts +18 -64
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/state.d.ts +2 -5
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/use-pathname.d.ts +1 -1
- package/dist/config-types.d.ts +2 -1
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/dev-page-shell.d.ts +17 -0
- package/dist/dev-tools/dev-page-shell.d.ts.map +1 -1
- package/dist/dev-tools/index.d.ts +1 -1
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/dev-tools/instrumentation.d.ts +17 -2
- package/dist/dev-tools/instrumentation.d.ts.map +1 -1
- package/dist/dev-tools/overlay.d.ts +6 -27
- package/dist/dev-tools/overlay.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +632 -373
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +26 -40
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +15 -2
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +19 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/post-build.d.ts +110 -0
- package/dist/plugins/post-build.d.ts.map +1 -0
- package/dist/plugins/prebuilt-capture.d.ts +11 -4
- package/dist/plugins/prebuilt-capture.d.ts.map +1 -1
- package/dist/plugins/prebuilt.d.ts +17 -1
- package/dist/plugins/prebuilt.d.ts.map +1 -1
- package/dist/plugins/prerender-sugar.d.ts.map +1 -1
- package/dist/plugins/react-resolve.d.ts +49 -0
- package/dist/plugins/react-resolve.d.ts.map +1 -0
- package/dist/plugins/rsc-assets-manifest.d.ts +33 -0
- package/dist/plugins/rsc-assets-manifest.d.ts.map +1 -0
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +32 -9
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts +0 -9
- package/dist/routing/codegen-write.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/routing/segment-codegen.d.ts.map +1 -1
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts +5 -4
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +46 -7
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +58 -5
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/children-interception.d.ts.map +1 -1
- package/dist/server/cookie-context.d.ts +2 -23
- package/dist/server/cookie-context.d.ts.map +1 -1
- package/dist/server/csp-nonce.d.ts +0 -9
- package/dist/server/csp-nonce.d.ts.map +1 -1
- package/dist/server/csrf.d.ts +27 -4
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/debug.d.ts +0 -1
- package/dist/server/debug.d.ts.map +1 -1
- package/dist/server/default-logger.d.ts +2 -1
- package/dist/server/default-logger.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +12 -6
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +2 -2
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +7 -3
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +9 -2
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/head-response.d.ts +4 -4
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/instrumentation.d.ts +10 -3
- package/dist/server/instrumentation.d.ts.map +1 -1
- package/dist/server/internal.d.ts +1 -4
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +961 -581
- package/dist/server/internal.js.map +1 -1
- package/dist/server/json-status-response.d.ts +34 -0
- package/dist/server/json-status-response.d.ts.map +1 -0
- package/dist/server/logger.d.ts +26 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts +11 -6
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +95 -7
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-interception.d.ts +11 -0
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts +11 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +2 -2
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +23 -35
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/prebuilt/capture-render.d.ts +25 -0
- package/dist/server/prebuilt/capture-render.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-routes.d.ts +37 -0
- package/dist/server/prebuilt/capture-routes.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-state.d.ts +79 -35
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -1
- package/dist/server/prebuilt/lifetime.d.ts +38 -0
- package/dist/server/prebuilt/lifetime.d.ts.map +1 -0
- package/dist/server/prebuilt/slots.d.ts +7 -0
- package/dist/server/prebuilt/slots.d.ts.map +1 -1
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
- package/dist/server/prebuilt-builder.d.ts.map +1 -1
- package/dist/server/prebuilt-runtime.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +6 -81
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/react-cache-bridge.d.ts +59 -0
- package/dist/server/react-cache-bridge.d.ts.map +1 -0
- package/dist/server/react-cache-scope.d.ts +49 -0
- package/dist/server/react-cache-scope.d.ts.map +1 -0
- package/dist/server/render-timeout.d.ts +15 -0
- package/dist/server/render-timeout.d.ts.map +1 -1
- package/dist/server/render-utils.d.ts +9 -0
- package/dist/server/render-utils.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +26 -5
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +7 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +22 -22
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
- package/dist/server/rsc-entry/api-handler.d.ts +7 -0
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts +32 -6
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +11 -18
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -1
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +5 -15
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +5 -6
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-signals.d.ts +65 -0
- package/dist/server/rsc-entry/render-signals.d.ts.map +1 -0
- package/dist/server/rsc-entry/revalidate-renderer.d.ts +6 -16
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +7 -3
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +8 -53
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts +2 -2
- package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +9 -4
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
- package/dist/server/signal-identity.d.ts +43 -0
- package/dist/server/signal-identity.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +4 -3
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/slot-subtree-contain.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +91 -53
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +8 -5
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +26 -40
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +1 -1
- package/dist/server/static-generator.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts +12 -0
- package/dist/server/stream-utils.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +12 -16
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -1
- package/dist/server/version-skew.d.ts +3 -3
- package/dist/shared/csrf-reject.d.ts +16 -0
- package/dist/shared/csrf-reject.d.ts.map +1 -0
- package/dist/shared/error-digest.d.ts +28 -0
- package/dist/shared/error-digest.d.ts.map +1 -0
- package/dist/shared/payload-root.d.ts +11 -10
- package/dist/shared/payload-root.d.ts.map +1 -1
- package/dist/shared/rsc-payload-path.d.ts +6 -0
- package/dist/shared/rsc-payload-path.d.ts.map +1 -1
- package/dist/utils/write-file-atomic.d.ts +17 -0
- package/dist/utils/write-file-atomic.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +17 -14
- package/docs/api/32-api-cache.mdx +51 -23
- package/docs/api/34-api-config.mdx +34 -6
- package/docs/api/36-cli.mdx +11 -1
- package/docs/learn/03-fetching-data.mdx +17 -1
- package/docs/learn/04-access-control.mdx +1 -1
- package/docs/learn/10-caching.mdx +29 -11
- package/docs/learn/11-middleware.mdx +8 -2
- package/docs/learn/12-error-handling.mdx +6 -1
- package/package.json +7 -3
- package/src/adapters/build-output-helper.ts +55 -53
- package/src/adapters/canonicalize-module.ts +29 -0
- package/src/adapters/cloudflare.ts +105 -63
- package/src/adapters/compress-module.ts +16 -5
- package/src/adapters/fs-identity.ts +2 -3
- package/src/adapters/nitro-presets.ts +0 -15
- package/src/adapters/nitro-preview.ts +272 -136
- package/src/adapters/nitro.ts +119 -82
- package/src/adapters/shared.ts +23 -14
- package/src/adapters/types.ts +110 -21
- package/src/analyze/graph-cache.ts +3 -4
- package/src/cache/json-lossy-check.ts +3 -9
- package/src/cache/singleflight.ts +5 -2
- package/src/cache/sizeof.ts +3 -1
- package/src/cache/tag-aware-handler.ts +43 -26
- package/src/cli-check.ts +8 -98
- package/src/cli-context.ts +119 -0
- package/src/cli.ts +19 -28
- package/src/client/browser-dev.ts +1 -1
- package/src/client/browser-entry/action-dispatch.ts +26 -4
- package/src/client/browser-entry/hydrate.ts +53 -58
- package/src/client/browser-entry/index.ts +16 -27
- package/src/client/browser-entry/post-hydration.ts +6 -40
- package/src/client/browser-entry/router-init.ts +66 -84
- package/src/client/error-boundary.tsx +9 -41
- package/src/client/error-reconstituter.tsx +10 -5
- package/src/client/history.ts +35 -21
- package/src/client/index.ts +0 -1
- package/src/client/internal.ts +2 -7
- package/src/client/navigation-commit.ts +41 -18
- package/src/client/navigation-context.ts +3 -39
- package/src/client/react-root.ts +3 -2
- package/src/client/router-pipeline.ts +96 -132
- package/src/client/router-types.ts +46 -44
- package/src/client/router-url.ts +32 -0
- package/src/client/router.ts +62 -21
- package/src/client/rsc-fetch.ts +117 -155
- package/src/client/segment-cache.ts +110 -11
- package/src/client/ssr-data.ts +19 -80
- package/src/client/state.ts +2 -9
- package/src/client/use-pathname.ts +1 -1
- package/src/config-types.ts +2 -1
- package/src/config-validation.ts +2 -1
- package/src/dev-tools/dev-page-shell.ts +18 -0
- package/src/dev-tools/index.ts +0 -2
- package/src/dev-tools/instrumentation.ts +50 -2
- package/src/dev-tools/overlay.ts +8 -238
- package/src/index.ts +26 -14
- package/src/plugin-context.ts +39 -40
- package/src/plugins/build-manifest.ts +4 -11
- package/src/plugins/build-report.ts +70 -70
- package/src/plugins/dev-server.ts +24 -44
- package/src/plugins/entries.ts +2 -1
- package/src/plugins/fonts.ts +35 -7
- package/src/plugins/post-build.ts +327 -0
- package/src/plugins/prebuilt-capture.ts +28 -32
- package/src/plugins/prebuilt.ts +54 -39
- package/src/plugins/prerender-sugar.ts +0 -4
- package/src/plugins/react-resolve.ts +149 -0
- package/src/plugins/{adapter-build.ts → rsc-assets-manifest.ts} +20 -109
- package/src/plugins/shims.ts +1 -0
- package/src/plugins/static-build.ts +42 -74
- package/src/routing/codegen-write.ts +25 -3
- package/src/routing/segment-codegen.ts +3 -2
- package/src/rsc-runtime/vendor-types.d.ts +8 -0
- package/src/server/access-gate.tsx +2 -1
- package/src/server/action-client.ts +1 -1
- package/src/server/action-handler.ts +253 -110
- package/src/server/actions.ts +73 -25
- package/src/server/als-registry.ts +73 -5
- package/src/server/chain-url-parts.ts +1 -1
- package/src/server/children-interception.ts +2 -1
- package/src/server/cookie-context.ts +18 -48
- package/src/server/csp-nonce.ts +0 -12
- package/src/server/csrf.ts +140 -8
- package/src/server/debug.ts +0 -1
- package/src/server/default-logger.ts +2 -1
- package/src/server/deny-boundary.ts +25 -13
- package/src/server/deny-renderer.ts +30 -97
- package/src/server/error-boundary-wrapper.ts +7 -3
- package/src/server/fallback-error.ts +26 -11
- package/src/server/flight-scripts.ts +1 -1
- package/src/server/head-response.ts +4 -4
- package/src/server/html-injector-core.ts +2 -2
- package/src/server/html-injectors.ts +2 -4
- package/src/server/index.ts +0 -4
- package/src/server/instrumentation.ts +10 -3
- package/src/server/internal.ts +1 -6
- package/src/server/json-status-response.ts +105 -0
- package/src/server/logger.ts +39 -3
- package/src/server/param-coercion.ts +15 -12
- package/src/server/pipeline-helpers.ts +257 -12
- package/src/server/pipeline-interception.ts +133 -0
- package/src/server/pipeline-outcome.ts +103 -31
- package/src/server/pipeline-phases.ts +127 -237
- package/src/server/pipeline.ts +204 -207
- package/src/server/prebuilt/capture-render.ts +118 -0
- package/src/server/prebuilt/capture-routes.ts +104 -0
- package/src/server/prebuilt/capture-state.ts +155 -48
- package/src/server/prebuilt/lifetime.ts +151 -0
- package/src/server/prebuilt/slots.ts +14 -2
- package/src/server/prebuilt/synthetic-store.ts +3 -0
- package/src/server/prebuilt-builder.ts +30 -320
- package/src/server/prebuilt-runtime.ts +125 -129
- package/src/server/primitives.ts +8 -168
- package/src/server/react-cache-bridge.ts +124 -0
- package/src/server/react-cache-scope.ts +104 -0
- package/src/server/render-timeout.ts +20 -0
- package/src/server/render-utils.ts +11 -1
- package/src/server/request-context.ts +36 -9
- package/src/server/route-element-builder.ts +11 -6
- package/src/server/route-handler.ts +36 -23
- package/src/server/route-matcher.ts +15 -1
- package/src/server/rsc-entry/action-middleware-runner.ts +79 -123
- package/src/server/rsc-entry/api-handler.ts +27 -103
- package/src/server/rsc-entry/deny-fallback.ts +93 -26
- package/src/server/rsc-entry/error-renderer.ts +39 -91
- package/src/server/rsc-entry/helpers.ts +2 -4
- package/src/server/rsc-entry/index.ts +64 -58
- package/src/server/rsc-entry/render-route.ts +15 -80
- package/src/server/rsc-entry/render-signals.ts +108 -0
- package/src/server/rsc-entry/revalidate-renderer.ts +56 -57
- package/src/server/rsc-entry/rsc-payload.ts +28 -61
- package/src/server/rsc-entry/rsc-stream.ts +113 -213
- package/src/server/rsc-entry/ssr-bridge.ts +2 -2
- package/src/server/rsc-entry/ssr-renderer.ts +182 -263
- package/src/server/rsc-entry/wrap-action-dispatch.ts +12 -14
- package/src/server/signal-identity.ts +112 -0
- package/src/server/slot-resolver.ts +18 -18
- package/src/server/slot-subtree-contain.ts +2 -1
- package/src/server/ssr-bridge-types.ts +92 -56
- package/src/server/ssr-entry.ts +87 -111
- package/src/server/ssr-render.ts +55 -64
- package/src/server/ssr-wrappers.tsx +2 -2
- package/src/server/static-generator.ts +7 -42
- package/src/server/static-not-found.ts +3 -3
- package/src/server/stream-utils.ts +51 -0
- package/src/server/tracing.ts +58 -75
- package/src/server/types.ts +1 -1
- package/src/server/version-skew.ts +5 -5
- package/src/shared/csrf-reject.ts +15 -0
- package/src/shared/error-digest.ts +46 -0
- package/src/shared/payload-root.ts +11 -10
- package/src/shared/rsc-payload-path.ts +8 -1
- package/src/utils/write-file-atomic.ts +32 -0
- package/dist/_chunks/actions-jNrXdzgs.js +0 -346
- package/dist/_chunks/actions-jNrXdzgs.js.map +0 -1
- package/dist/_chunks/als-registry-C6kcfprT.js.map +0 -1
- package/dist/_chunks/build-manifest-DTmSGLRz.js.map +0 -1
- package/dist/_chunks/build-output-helper-9dqCv1pZ.js.map +0 -1
- package/dist/_chunks/cache-api-CUD9Ezaq.js.map +0 -1
- package/dist/_chunks/cli-check-G8o30ldg.js.map +0 -1
- package/dist/_chunks/convention-lint-5KzoMg_A.js.map +0 -1
- package/dist/_chunks/dev-server-BzBxgYAV.js.map +0 -1
- package/dist/_chunks/error-boundary-BndF-3Td.js.map +0 -1
- package/dist/_chunks/fs-identity-D7vb6GdI.js.map +0 -1
- package/dist/_chunks/graph-cache-CP4GEmf9.js.map +0 -1
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +0 -1
- package/dist/_chunks/logger-k2DQ4EUf.js.map +0 -1
- package/dist/_chunks/navigation-context-D0TU0Jog.js.map +0 -1
- package/dist/_chunks/plugin-context-8idfR3UZ.js.map +0 -1
- package/dist/_chunks/primitives-DLAnvsrk.js.map +0 -1
- package/dist/_chunks/segment-context-D9_89u34.js +0 -34
- package/dist/_chunks/segment-context-D9_89u34.js.map +0 -1
- package/dist/_chunks/sizeof-lXffQ1Vr.js.map +0 -1
- package/dist/_chunks/ssr-data-D6T6Y3ef.js +0 -60
- package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +0 -1
- package/dist/_chunks/state-FippDgxN.js.map +0 -1
- package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +0 -1
- package/dist/client/types.d.ts +0 -14
- package/dist/client/types.d.ts.map +0 -1
- package/dist/plugins/adapter-build.d.ts +0 -38
- package/dist/plugins/adapter-build.d.ts.map +0 -1
- package/dist/server/asset-headers.d.ts +0 -32
- package/dist/server/asset-headers.d.ts.map +0 -1
- package/dist/server/dev-source-map.d.ts +0 -22
- package/dist/server/dev-source-map.d.ts.map +0 -1
- package/src/client/types.ts +0 -21
- package/src/server/asset-headers.ts +0 -59
- package/src/server/dev-source-map.ts +0 -31
package/src/server/logger.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* See design/17-logging.md §"Production Logging"
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { getTraceStore } from './tracing.ts';
|
|
11
|
+
import { generateTraceId, getTraceStore } from './tracing.ts';
|
|
12
12
|
import { createDefaultLogger } from './default-logger.ts';
|
|
13
13
|
import { isDevMode } from './debug.ts';
|
|
14
|
-
import { isControlFlowSignal } from './
|
|
14
|
+
import { isControlFlowSignal } from './signal-identity.ts';
|
|
15
|
+
import type { CsrfRejectReason } from './csrf.ts';
|
|
15
16
|
|
|
16
17
|
// ─── Logger Interface ─────────────────────────────────────────────────────
|
|
17
18
|
|
|
@@ -56,7 +57,7 @@ function withTraceContext(data?: Record<string, unknown>): Record<string, unknow
|
|
|
56
57
|
const store = getTraceStore();
|
|
57
58
|
const enriched: Record<string, unknown> = { ...data };
|
|
58
59
|
if (store) {
|
|
59
|
-
enriched.trace_id = store.traceId;
|
|
60
|
+
enriched.trace_id = store.request.traceId;
|
|
60
61
|
if (store.spanId) {
|
|
61
62
|
enriched.span_id = store.spanId;
|
|
62
63
|
}
|
|
@@ -66,6 +67,24 @@ function withTraceContext(data?: Record<string, unknown>): Record<string, unknow
|
|
|
66
67
|
|
|
67
68
|
// ─── Framework Event Emitters ─────────────────────────────────────────────
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Log a CSRF rejection. Level: warn — the 403 body is deliberately terse,
|
|
72
|
+
* so this line is the only place the rejected origin is visible (TIM-1459).
|
|
73
|
+
* `detail` names the observed origin and, where one exists, the expected one.
|
|
74
|
+
*
|
|
75
|
+
* The CSRF gate runs at the pipeline boundary, before the pipeline opens its
|
|
76
|
+
* trace scope, so a rejection mints its own `trace_id` — the field is always
|
|
77
|
+
* present (design/17-logging.md §"trace_id is Always Set").
|
|
78
|
+
*/
|
|
79
|
+
export function logCsrfRejected(
|
|
80
|
+
detail: string,
|
|
81
|
+
data: { method: string; path: string; reason: CsrfRejectReason }
|
|
82
|
+
): void {
|
|
83
|
+
const fields = withTraceContext(data);
|
|
84
|
+
fields.trace_id ??= generateTraceId();
|
|
85
|
+
_logger.warn(`CSRF origin check failed: ${detail}`, fields);
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
/** Log a completed request. Level: info. */
|
|
70
89
|
export function logRequestCompleted(data: {
|
|
71
90
|
method: string;
|
|
@@ -110,6 +129,12 @@ export function logMiddlewareError(data: { method: string; path: string; error:
|
|
|
110
129
|
_logger.error('unhandled error in middleware phase', withTraceContext(data));
|
|
111
130
|
}
|
|
112
131
|
|
|
132
|
+
/** Log unhandled error in route setup (matching, param coercion). Level: error. */
|
|
133
|
+
export function logRouteSetupError(data: { method: string; path: string; error: unknown }): void {
|
|
134
|
+
if (isControlFlowSignal(data.error)) return;
|
|
135
|
+
_logger.error('unhandled error in route setup', withTraceContext(data));
|
|
136
|
+
}
|
|
137
|
+
|
|
113
138
|
/** Log unhandled render-phase error. Level: error. */
|
|
114
139
|
export function logRenderError(data: {
|
|
115
140
|
method: string;
|
|
@@ -158,6 +183,17 @@ export function logSwrRefetchFailed(data: { cacheKey: string; error: unknown }):
|
|
|
158
183
|
_logger.warn('staleWhileRevalidate refetch failed', withTraceContext(data));
|
|
159
184
|
}
|
|
160
185
|
|
|
186
|
+
/** Log a cache value that loses fidelity through JSON serialization. Level: warn. */
|
|
187
|
+
export function logLossyCacheValue(data: { cacheKey: string; types: string }): void {
|
|
188
|
+
_logger.warn(
|
|
189
|
+
'cache value contains types that lose fidelity through JSON serialization in ' +
|
|
190
|
+
'Redis/KV cache handlers: Date becomes an ISO string, Map/Set become {}, BigInt throws. ' +
|
|
191
|
+
'The MemoryCacheHandler preserves them. Transform values to JSON-safe types before ' +
|
|
192
|
+
'caching, or use MemoryCacheHandler for values that require type preservation.',
|
|
193
|
+
withTraceContext(data)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
161
197
|
/** Log cache miss. Level: debug. */
|
|
162
198
|
export function logCacheMiss(data: { cacheKey: string }): void {
|
|
163
199
|
_logger.debug('timber.cache MISS', withTraceContext(data));
|
|
@@ -118,7 +118,12 @@ export function coerceSlotParams(
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
121
|
+
* Coerce the matched route's raw segment params and return the result.
|
|
122
|
+
*
|
|
123
|
+
* Reads `match.rawSegmentParams` and never writes to `match`: the caller
|
|
124
|
+
* decides where the coerced params go. Reading the raw record makes the call
|
|
125
|
+
* repeatable — coercing a match twice runs every codec on the URL value both
|
|
126
|
+
* times, never on its own output.
|
|
122
127
|
*
|
|
123
128
|
* Codecs come from `app/schema.ts` and nowhere else (TIM-1342 completed
|
|
124
129
|
* design/41-global-params.md migration step 9). A route with no schema, or
|
|
@@ -132,24 +137,21 @@ export function coerceSlotParams(
|
|
|
132
137
|
* See design/41-global-params.md §"Pipeline Integration"
|
|
133
138
|
*
|
|
134
139
|
* Stays `async` with nothing to await: it is injected as a
|
|
135
|
-
* `CoerceSegmentParamsFn` into the revalidation renderer
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
140
|
+
* `CoerceSegmentParamsFn` into the revalidation renderer, which awaits
|
|
141
|
+
* it. Making it synchronous would
|
|
142
|
+
* change that contract and remove a microtask boundary the pipeline's
|
|
143
|
+
* ordering has always had, for no gain.
|
|
139
144
|
*/
|
|
140
145
|
// eslint-disable-next-line @typescript-eslint/require-await -- see above
|
|
141
|
-
export async function coerceSegmentParams(match: RouteMatch): Promise<
|
|
146
|
+
export async function coerceSegmentParams(match: RouteMatch): Promise<CoercedParams> {
|
|
142
147
|
const globalCodecs = _globalCodecs;
|
|
143
|
-
//
|
|
144
|
-
// "match.segmentParams is null-prototype" holds from the first line,
|
|
145
|
-
// regardless of whether any segment has a codec.
|
|
148
|
+
// Always a null-prototype record, whether or not any segment has a codec.
|
|
146
149
|
const mergeTarget: CoercedParams = Object.create(null);
|
|
147
|
-
for (const key of Object.keys(match.
|
|
150
|
+
for (const key of Object.keys(match.rawSegmentParams)) {
|
|
148
151
|
if (key !== '__proto__') {
|
|
149
|
-
mergeTarget[key] = match.
|
|
152
|
+
mergeTarget[key] = match.rawSegmentParams[key];
|
|
150
153
|
}
|
|
151
154
|
}
|
|
152
|
-
match.segmentParams = mergeTarget;
|
|
153
155
|
|
|
154
156
|
// TIM-931/TIM-936: When a global schema is available, use it for coercion
|
|
155
157
|
// instead of per-segment params.ts files. The global codec map is keyed
|
|
@@ -195,4 +197,5 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
|
|
|
195
197
|
mergeTarget[key] = normalizeParamValue(parsed, key);
|
|
196
198
|
}
|
|
197
199
|
}
|
|
200
|
+
return mergeTarget;
|
|
198
201
|
}
|
|
@@ -11,11 +11,22 @@
|
|
|
11
11
|
|
|
12
12
|
import type { ProxyExport } from './proxy.ts';
|
|
13
13
|
import { getSetCookieHeaders } from './cookie-context.ts';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
callOnRequestError,
|
|
16
|
+
hasOnRequestError,
|
|
17
|
+
type InstrumentationErrorContext,
|
|
18
|
+
} from './instrumentation.ts';
|
|
15
19
|
import { getTraceId } from './tracing.ts';
|
|
20
|
+
import { requestContextAls } from './als-registry.ts';
|
|
16
21
|
import { RedirectSignal } from './primitives.ts';
|
|
22
|
+
import { isControlFlowSignal } from './signal-identity.ts';
|
|
23
|
+
import { logRenderError, swallow } from './logger.ts';
|
|
24
|
+
import { isAbortError } from './render-utils.ts';
|
|
25
|
+
import { loadModule, ModuleLoadError, type ManifestLoader } from './safe-load.ts';
|
|
26
|
+
import { getWaitUntil } from './waituntil-bridge.ts';
|
|
27
|
+
import { isApiRouteChain, type ManifestSegmentNode } from './route-matcher.ts';
|
|
17
28
|
import type { ProxyConfig } from './pipeline.ts';
|
|
18
|
-
import {
|
|
29
|
+
import type { PhaseName } from './pipeline-outcome.ts';
|
|
19
30
|
|
|
20
31
|
// ─── Prototype-Pollution-Safe Sanitizer ────────────────────────────────────
|
|
21
32
|
|
|
@@ -70,6 +81,32 @@ export function applyCookieJar(headers: Headers): void {
|
|
|
70
81
|
}
|
|
71
82
|
}
|
|
72
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Bring a bag the jar was already applied to up to date with the jar.
|
|
86
|
+
*
|
|
87
|
+
* `applied` is what `getSetCookieHeaders()` returned when the jar was
|
|
88
|
+
* applied to `headers`. Values the jar no longer holds (a later write to the
|
|
89
|
+
* same name replaced them) are removed, and values it gained are appended.
|
|
90
|
+
* Every cookie then appears once, with its last write (design/29-cookies.md
|
|
91
|
+
* §"Last-Write-Wins for Same Name"). `Set-Cookie` values that did not come
|
|
92
|
+
* from the jar, such as a middleware appending to `ctx.headers` directly,
|
|
93
|
+
* are left alone.
|
|
94
|
+
*/
|
|
95
|
+
export function syncCookieJar(headers: Headers, applied: readonly string[]): void {
|
|
96
|
+
const current = getSetCookieHeaders();
|
|
97
|
+
const stale = applied.filter((value) => !current.includes(value));
|
|
98
|
+
const added = current.filter((value) => !applied.includes(value));
|
|
99
|
+
if (stale.length === 0 && added.length === 0) return;
|
|
100
|
+
|
|
101
|
+
const kept = headers.getSetCookie();
|
|
102
|
+
for (const value of stale) {
|
|
103
|
+
const index = kept.indexOf(value);
|
|
104
|
+
if (index !== -1) kept.splice(index, 1);
|
|
105
|
+
}
|
|
106
|
+
headers.delete('Set-Cookie');
|
|
107
|
+
for (const value of [...kept, ...added]) headers.append('Set-Cookie', value);
|
|
108
|
+
}
|
|
109
|
+
|
|
73
110
|
/**
|
|
74
111
|
* Merge framework-managed response headers onto a terminal response without
|
|
75
112
|
* overwriting headers the terminal response already set itself.
|
|
@@ -111,22 +148,43 @@ export function cloneWithMutableHeaders(response: Response): Response {
|
|
|
111
148
|
});
|
|
112
149
|
}
|
|
113
150
|
|
|
151
|
+
// ─── Response Headers ──────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A fresh response header bag for a page the pipeline renders, seeded with
|
|
155
|
+
* the default dynamic-page cache policy.
|
|
156
|
+
*
|
|
157
|
+
* Without a Cache-Control header, CDNs (particularly Cloudflare) may attempt
|
|
158
|
+
* to buffer/process the response differently, causing intermittent
|
|
159
|
+
* multi-second delays. This matches Next.js's default behavior. Every bag a
|
|
160
|
+
* rendered page can be written onto starts here — a matched route, the
|
|
161
|
+
* no-match 404, and a deny page for a proxy-phase signal — so none of them
|
|
162
|
+
* leaves without a cache policy. Middleware and route handlers may replace it.
|
|
163
|
+
*/
|
|
164
|
+
export function dynamicResponseHeaders(): Headers {
|
|
165
|
+
return new Headers({
|
|
166
|
+
'Cache-Control': 'private, no-cache, no-store, max-age=0, must-revalidate',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
114
170
|
// ─── Redirect Builder ──────────────────────────────────────────────────────
|
|
115
171
|
|
|
116
172
|
/**
|
|
117
173
|
* Build a redirect Response from a RedirectSignal.
|
|
118
174
|
*
|
|
119
|
-
* For RSC payload
|
|
120
|
-
* so the client router can perform a soft SPA redirect. A
|
|
121
|
-
* turned into an opaque redirect by
|
|
122
|
-
* createFromFetch. See
|
|
175
|
+
* For a client navigation (an RSC payload request for a page), returns 204 +
|
|
176
|
+
* X-Timber-Redirect so the client router can perform a soft SPA redirect. A
|
|
177
|
+
* raw 302 would be turned into an opaque redirect by
|
|
178
|
+
* fetch({redirect:'manual'}), crashing createFromFetch. See
|
|
179
|
+
* design/19-client-navigation.md. The caller decides what counts as a
|
|
180
|
+
* navigation: a route.ts match answers with a real 3xx whatever it accepts.
|
|
123
181
|
*/
|
|
124
182
|
export function buildRedirectResponse(
|
|
125
183
|
signal: RedirectSignal,
|
|
126
|
-
|
|
127
|
-
|
|
184
|
+
headers: Headers,
|
|
185
|
+
navigation: boolean
|
|
128
186
|
): Response {
|
|
129
|
-
if (
|
|
187
|
+
if (navigation) {
|
|
130
188
|
headers.set('X-Timber-Redirect', signal.location);
|
|
131
189
|
return new Response(null, { status: 204, headers });
|
|
132
190
|
}
|
|
@@ -136,24 +194,211 @@ export function buildRedirectResponse(
|
|
|
136
194
|
|
|
137
195
|
// ─── Instrumentation ───────────────────────────────────────────────────────
|
|
138
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Where an error was raised: a pipeline phase, a server action, or a
|
|
199
|
+
* handler that answers outside the page pipeline — a metadata route or the
|
|
200
|
+
* auto-generated sitemap — which reports like a route.ts handler.
|
|
201
|
+
*/
|
|
202
|
+
export type ErrorSource = PhaseName | 'action' | 'route';
|
|
203
|
+
|
|
139
204
|
/**
|
|
140
205
|
* Fire the user's onRequestError hook with request context.
|
|
141
|
-
*
|
|
206
|
+
*
|
|
207
|
+
* The one place a pipeline phase becomes the `phase` / `routeType` the hook
|
|
208
|
+
* reports, so every caller agrees. A render-phase error on a route.ts match
|
|
209
|
+
* is phase `'route'`, because the handler is what ran. Middleware reports
|
|
210
|
+
* as `'handler'`, its public name.
|
|
142
211
|
*/
|
|
143
212
|
export async function fireOnRequestError(
|
|
144
213
|
error: unknown,
|
|
145
214
|
req: Request,
|
|
146
|
-
|
|
215
|
+
source: ErrorSource,
|
|
216
|
+
segments?: readonly ManifestSegmentNode[]
|
|
217
|
+
): Promise<void> {
|
|
218
|
+
// One rule for every caller (design/17 §"Which errors reach it"): outcomes
|
|
219
|
+
// and aborts are not failures, and neither is anything a request raises
|
|
220
|
+
// after its client left — a driver throwing "connection closed" is not a bug.
|
|
221
|
+
if (!isReportableError(error) || req.signal?.aborted) return;
|
|
222
|
+
if (isRepeatedLoadFailure(error)) return;
|
|
223
|
+
await callHook(error, req, source, segments);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Module evaluations whose failure was already reported. A module that fails
|
|
228
|
+
* to evaluate rethrows the same error on every later import, and
|
|
229
|
+
* `loadModule()` wraps each one in a new `ModuleLoadError` with that error
|
|
230
|
+
* as its `cause`. One broken module is one bug until it changes, however many
|
|
231
|
+
* loads trip over it in a request or across requests: the page's boundaries,
|
|
232
|
+
* then the fallback page, then the next request. A WeakSet, so a module that
|
|
233
|
+
* is re-evaluated (an HMR edit) gets a new cause and is reported again.
|
|
234
|
+
*/
|
|
235
|
+
const reportedLoadFailures = new WeakSet<object>();
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The same for a module that threw a primitive (`throw 'broken'`), which a
|
|
239
|
+
* WeakSet cannot hold. Keyed on the file and the value instead, so a module
|
|
240
|
+
* re-broken after an edit with the very same value is not reported again.
|
|
241
|
+
* It grows with the number of broken modules, not with requests.
|
|
242
|
+
*/
|
|
243
|
+
const reportedPrimitiveLoadFailures = new Set<string>();
|
|
244
|
+
|
|
245
|
+
function isRepeatedLoadFailure(error: unknown): boolean {
|
|
246
|
+
if (!(error instanceof ModuleLoadError)) return false;
|
|
247
|
+
const cause: unknown = error.cause;
|
|
248
|
+
if (typeof cause === 'object' && cause !== null) {
|
|
249
|
+
if (reportedLoadFailures.has(cause)) return true;
|
|
250
|
+
reportedLoadFailures.add(cause);
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const key = `${error.filePath}\0${typeof cause}\0${String(cause)}`;
|
|
254
|
+
if (reportedPrimitiveLoadFailures.has(key)) return true;
|
|
255
|
+
reportedPrimitiveLoadFailures.add(key);
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Call the hook with request context, with no filtering. Only
|
|
261
|
+
* `fireOnRequestError()` and background reporting call it: background work
|
|
262
|
+
* runs after the response, so its request having aborted says nothing about
|
|
263
|
+
* the failure.
|
|
264
|
+
*/
|
|
265
|
+
async function callHook(
|
|
266
|
+
error: unknown,
|
|
267
|
+
req: Request,
|
|
268
|
+
source: ErrorSource,
|
|
269
|
+
segments?: readonly ManifestSegmentNode[]
|
|
147
270
|
): Promise<void> {
|
|
271
|
+
if (!hasOnRequestError()) return;
|
|
148
272
|
const url = new URL(req.url);
|
|
149
273
|
const headersObj: Record<string, string> = {};
|
|
150
274
|
req.headers.forEach((v, k) => {
|
|
151
275
|
headersObj[k] = v;
|
|
152
276
|
});
|
|
277
|
+
const apiRoute = source === 'route' || (segments !== undefined && isApiRouteChain(segments));
|
|
153
278
|
|
|
154
279
|
await callOnRequestError(
|
|
155
280
|
error,
|
|
156
281
|
{ method: req.method, path: url.pathname, headers: headersObj },
|
|
157
|
-
{
|
|
282
|
+
{
|
|
283
|
+
phase: reportedPhase(source, apiRoute),
|
|
284
|
+
routePath: url.pathname,
|
|
285
|
+
routeType: source === 'action' ? 'action' : apiRoute ? 'route' : 'page',
|
|
286
|
+
traceId: getTraceId(),
|
|
287
|
+
}
|
|
158
288
|
);
|
|
159
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Whether an error is a failure `onRequestError` should hear about.
|
|
293
|
+
*
|
|
294
|
+
* Control-flow signals are outcomes the code chose — a `deny()` picks the
|
|
295
|
+
* page's status and data — and an abort is the client leaving. Callers answering
|
|
296
|
+
* a live request also skip everything once `req.signal` has aborted: a
|
|
297
|
+
* driver that throws "connection closed" after a disconnect is not a bug.
|
|
298
|
+
*/
|
|
299
|
+
export function isReportableError(error: unknown): boolean {
|
|
300
|
+
return !isControlFlowSignal(error) && !isAbortError(error);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Fire the hook without making the caller wait, for callers that cannot:
|
|
305
|
+
* Flight's `onError` is synchronous, and background work has no caller.
|
|
306
|
+
* The promise is registered with the request's `waitUntil` so platforms
|
|
307
|
+
* that stop work when the response ends (Workers, Lambda) let the hook
|
|
308
|
+
* finish. The hook's own failures are caught in `callOnRequestError()`.
|
|
309
|
+
*/
|
|
310
|
+
function fireInBackground(report: Promise<void>): void {
|
|
311
|
+
getWaitUntil()?.(
|
|
312
|
+
report.catch((hookError: unknown) => swallow(hookError, 'onRequestError could not be called'))
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Log and report a render error that was answered in place rather than
|
|
318
|
+
* handed to the pipeline: a slot that degraded to its fallback, a Flight
|
|
319
|
+
* error row, a fallback page that failed and fell back again. The pipeline
|
|
320
|
+
* never sees these, so this is their one report. Errors that are not
|
|
321
|
+
* reportable (`isReportableError()`), or that arrive after the request
|
|
322
|
+
* aborted, are only logged.
|
|
323
|
+
*/
|
|
324
|
+
export function reportRenderError(
|
|
325
|
+
error: unknown,
|
|
326
|
+
req: Request,
|
|
327
|
+
details: { errorId?: string; segments?: readonly ManifestSegmentNode[] } = {}
|
|
328
|
+
): void {
|
|
329
|
+
const { errorId, segments } = details;
|
|
330
|
+
logRenderError({ method: req.method, path: new URL(req.url).pathname, error, errorId });
|
|
331
|
+
fireInBackground(fireOnRequestError(error, req, 'render', segments));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Load a module the render can do without — a status page, an error page,
|
|
336
|
+
* `global-error.tsx`, a fallback page's layout — or report why it could not
|
|
337
|
+
* load and return `null`, so the next tier answers instead of the failure
|
|
338
|
+
* taking the page down. Every such load goes through here, whether it builds
|
|
339
|
+
* a boundary for a normal render or a fallback page, so a broken module is
|
|
340
|
+
* never skipped silently.
|
|
341
|
+
*/
|
|
342
|
+
export function loadFallbackModule(
|
|
343
|
+
file: ManifestLoader,
|
|
344
|
+
req: Request,
|
|
345
|
+
segments?: readonly ManifestSegmentNode[]
|
|
346
|
+
): Promise<Record<string, unknown> | null> {
|
|
347
|
+
return loadModule(file).catch((loadError: unknown) => {
|
|
348
|
+
reportRenderError(loadError, req, { segments });
|
|
349
|
+
return null;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Run a `cache.component` stale-while-revalidate re-render as a singleflight
|
|
355
|
+
* body, and log and report its failure once for the flight. The stale
|
|
356
|
+
* payload already answered, so nothing else sees the failure.
|
|
357
|
+
*
|
|
358
|
+
* - **Once per flight.** A `.catch` on the flight's promise would run once
|
|
359
|
+
* per request that joined it, so the report happens inside the body. A
|
|
360
|
+
* body still running when the singleflight times out is reported then,
|
|
361
|
+
* with the timeout error (the signal's reason), and its later rejection
|
|
362
|
+
* is dropped.
|
|
363
|
+
* - **Against the request whose stale read started it**, read from the
|
|
364
|
+
* request context the work runs in. With no request (the build-time
|
|
365
|
+
* capture store) it is only logged.
|
|
366
|
+
* - **As phase `'render'`, route type `'page`.** A `cache.component` is a
|
|
367
|
+
* server component, so the read that started this was a page render (a
|
|
368
|
+
* revalidated page tree included). The request's own abort does not
|
|
369
|
+
* suppress the report: the work runs after the response either way.
|
|
370
|
+
*/
|
|
371
|
+
export async function runBackgroundFlight(
|
|
372
|
+
signal: AbortSignal,
|
|
373
|
+
work: () => Promise<unknown>,
|
|
374
|
+
log: (error: unknown) => void
|
|
375
|
+
): Promise<void> {
|
|
376
|
+
const failed = (error: unknown): void => {
|
|
377
|
+
log(error);
|
|
378
|
+
const request = requestContextAls.getStore()?.request;
|
|
379
|
+
if (request && isReportableError(error)) fireInBackground(callHook(error, request, 'render'));
|
|
380
|
+
};
|
|
381
|
+
const onTimeout = (): void => failed(signal.reason);
|
|
382
|
+
signal.addEventListener('abort', onTimeout, { once: true });
|
|
383
|
+
try {
|
|
384
|
+
await work();
|
|
385
|
+
} catch (error) {
|
|
386
|
+
if (!signal.aborted) failed(error);
|
|
387
|
+
} finally {
|
|
388
|
+
signal.removeEventListener('abort', onTimeout);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function reportedPhase(
|
|
393
|
+
source: ErrorSource,
|
|
394
|
+
apiRoute: boolean
|
|
395
|
+
): InstrumentationErrorContext['phase'] {
|
|
396
|
+
switch (source) {
|
|
397
|
+
case 'middleware':
|
|
398
|
+
return 'handler';
|
|
399
|
+
case 'render':
|
|
400
|
+
return apiRoute ? 'route' : 'render';
|
|
401
|
+
default:
|
|
402
|
+
return source;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
import { classifyUrlSegment } from '../routing/segment-classify.ts';
|
|
13
13
|
import type { InterceptionRewrite } from '../routing/interception.ts';
|
|
14
|
+
import { computeSegmentTreePaths } from '../routing/segment-keys.ts';
|
|
15
|
+
import { isRscRequest } from '../shared/rsc-media-type.ts';
|
|
16
|
+
import { canonicalize } from './canonicalize.ts';
|
|
17
|
+
import { parseClientStateTree } from './state-tree-diff.ts';
|
|
18
|
+
import type { InterceptionContext, PipelineConfig, RouteMatch } from './pipeline.ts';
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Check if an intercepting route applies for this soft navigation.
|
|
@@ -150,3 +155,131 @@ export function pathnameMatchesPattern(pathname: string, pattern: string): boole
|
|
|
150
155
|
|
|
151
156
|
return pi === pathParts.length;
|
|
152
157
|
}
|
|
158
|
+
|
|
159
|
+
// ─── Pipeline Resolution ──────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Resolve which route renders a soft navigation that an intercepting route
|
|
163
|
+
* may claim. Returns the canonical match unchanged when no rewrite applies.
|
|
164
|
+
*
|
|
165
|
+
* Runs during route setup, so anything it throws is a `'setup'` outcome.
|
|
166
|
+
*/
|
|
167
|
+
export function resolveInterceptedRoute(
|
|
168
|
+
config: PipelineConfig,
|
|
169
|
+
req: Request,
|
|
170
|
+
canonicalPathname: string,
|
|
171
|
+
canonicalMatch: RouteMatch | null
|
|
172
|
+
): { match: RouteMatch | null; interception?: InterceptionContext } {
|
|
173
|
+
const stripTrailingSlash = config.stripTrailingSlash ?? true;
|
|
174
|
+
let match = canonicalMatch;
|
|
175
|
+
let interception: InterceptionContext | undefined;
|
|
176
|
+
|
|
177
|
+
// Stage 2a: Intercepting route resolution (modal pattern).
|
|
178
|
+
// Only honored on RSC client-navigation requests (Accept: text/x-component)
|
|
179
|
+
// to prevent spoofing from plain HTML navigations or raw HTTP clients.
|
|
180
|
+
// The header value must be a valid relative pathname — reject schemes,
|
|
181
|
+
// authority, and control characters. Canonicalize before matching.
|
|
182
|
+
if (isRscRequest(req) && config.interceptionRewrites?.length) {
|
|
183
|
+
const rawSourceUrl = req.headers.get('X-Timber-URL');
|
|
184
|
+
const validatedSourceUrl = rawSourceUrl
|
|
185
|
+
? validateInterceptionHeader(rawSourceUrl, stripTrailingSlash)
|
|
186
|
+
: null;
|
|
187
|
+
if (validatedSourceUrl) {
|
|
188
|
+
// The scope of an interception is the segment that owns the intercepting
|
|
189
|
+
// slot, so the guard asks whether the route the user is LEAVING renders
|
|
190
|
+
// that segment. Two independent answers to "what is on screen?", tried in
|
|
191
|
+
// order — deferred behind a thunk so neither runs unless a rewrite's
|
|
192
|
+
// target pattern matches.
|
|
193
|
+
//
|
|
194
|
+
// 1. What the client says it has mounted (X-Timber-State-Tree
|
|
195
|
+
// `treePaths`). Authoritative about tree shape and nothing else.
|
|
196
|
+
// 2. What re-matching X-Timber-URL implies. Correct while the address bar
|
|
197
|
+
// still holds the source page, which stops being true the moment a
|
|
198
|
+
// modal is open — the address is then the *intercepted* URL and
|
|
199
|
+
// re-matching it resolves through whichever group owns the canonical
|
|
200
|
+
// page (TIM-1282).
|
|
201
|
+
//
|
|
202
|
+
// Neither is trusted: both are client-supplied, and satisfying the scope
|
|
203
|
+
// only selects which build-time rewrite applies. The rendered pathname is
|
|
204
|
+
// always the rewrite's own `interceptingPrefix`, the server re-derives the
|
|
205
|
+
// rendered chain from it below, and every access.ts runs regardless.
|
|
206
|
+
//
|
|
207
|
+
// They are handed over as two sets in that order rather than one union:
|
|
208
|
+
// each describes a single rendered chain, and only within one chain does
|
|
209
|
+
// "the deepest scope" name the innermost owner (TIM-1318). Unioned, a
|
|
210
|
+
// deeper scope from the address-implied canonical branch — which after
|
|
211
|
+
// (2) may be a route group the client never mounted — would outrank the
|
|
212
|
+
// owner actually on screen.
|
|
213
|
+
const resolveSourceScopes = () => {
|
|
214
|
+
const mounted = new Set(parseClientStateTree(req)?.treePaths ?? []);
|
|
215
|
+
const sourceRoute = config.matchRoute(validatedSourceUrl);
|
|
216
|
+
const addressed = new Set(sourceRoute ? computeSegmentTreePaths(sourceRoute.segments) : []);
|
|
217
|
+
const scopes = [mounted, addressed].filter((scope) => scope.size > 0);
|
|
218
|
+
// Neither channel produced a tree — no scope can contain anything, and
|
|
219
|
+
// an origin that matches no route is not a legitimate soft navigation.
|
|
220
|
+
return scopes.length > 0 ? scopes : null;
|
|
221
|
+
};
|
|
222
|
+
const rewrite = findInterceptionMatch(
|
|
223
|
+
canonicalPathname,
|
|
224
|
+
resolveSourceScopes,
|
|
225
|
+
config.interceptionRewrites
|
|
226
|
+
);
|
|
227
|
+
if (rewrite?.mode === 'children') {
|
|
228
|
+
// Sibling layout swap (TIM-1280). Nothing is re-matched: the resolver
|
|
229
|
+
// addresses the owner directory by tree path and returns the chain
|
|
230
|
+
// `[…owner ancestors, interceptingNode, …descendants]`, which renders
|
|
231
|
+
// as an ordinary route. `interception` stays unset — there is no slot
|
|
232
|
+
// to intercept into, and setting it would additionally arm the modal
|
|
233
|
+
// path for every slot in the tree we are about to render.
|
|
234
|
+
//
|
|
235
|
+
// A null here means the scope named nothing in this manifest, or no
|
|
236
|
+
// intercepting child matched the target. Both fall through to the
|
|
237
|
+
// canonical route, which is what a hard navigation renders.
|
|
238
|
+
const interceptedMatch = config.matchInterceptedRoute?.(
|
|
239
|
+
canonicalPathname,
|
|
240
|
+
rewrite.interceptingScope
|
|
241
|
+
);
|
|
242
|
+
if (interceptedMatch) match = interceptedMatch;
|
|
243
|
+
} else if (rewrite) {
|
|
244
|
+
const sourceMatch = config.matchRoute(rewrite.interceptingPrefix);
|
|
245
|
+
// The scope test above was about the route the user is LEAVING. The
|
|
246
|
+
// route we are about to render is a different one —
|
|
247
|
+
// `interceptingPrefix` is URL-derived, and route groups are
|
|
248
|
+
// URL-invisible, so `/` can resolve into a sibling group that does not
|
|
249
|
+
// own the slot. Rendering that would reproduce the TIM-1278 failure on
|
|
250
|
+
// a valid tree: the rewrite hijacks the navigation and no slot exists
|
|
251
|
+
// to render the intercepting page. Fall through to the canonical
|
|
252
|
+
// target route instead.
|
|
253
|
+
const rendersTheSlot =
|
|
254
|
+
sourceMatch !== null &&
|
|
255
|
+
computeSegmentTreePaths(sourceMatch.segments).includes(rewrite.interceptingScope);
|
|
256
|
+
if (rendersTheSlot) {
|
|
257
|
+
match = sourceMatch;
|
|
258
|
+
interception = { targetPathname: canonicalPathname };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { match, interception };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Validate and canonicalize the X-Timber-URL header value.
|
|
269
|
+
*
|
|
270
|
+
* Returns the canonical pathname if valid, or null if rejected:
|
|
271
|
+
* - Must start with '/' (relative pathname, no scheme/authority)
|
|
272
|
+
* - Must not contain control characters
|
|
273
|
+
* - Must pass canonicalization (no encoded separators, null bytes, etc.)
|
|
274
|
+
*/
|
|
275
|
+
function validateInterceptionHeader(raw: string, stripTrailingSlash: boolean): string | null {
|
|
276
|
+
if (!raw.startsWith('/')) return null;
|
|
277
|
+
if (raw.startsWith('//')) return null;
|
|
278
|
+
for (let i = 0; i < raw.length; i++) {
|
|
279
|
+
const code = raw.charCodeAt(i);
|
|
280
|
+
if (code <= 0x1f || code === 0x7f) return null;
|
|
281
|
+
}
|
|
282
|
+
const result = canonicalize(raw, stripTrailingSlash);
|
|
283
|
+
if (!result.ok) return null;
|
|
284
|
+
return result.pathname;
|
|
285
|
+
}
|