@timber-js/app 0.2.0-alpha.97 → 0.2.0-alpha.99
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/actions-CQ8Z8VGL.js +1061 -0
- package/dist/_chunks/actions-CQ8Z8VGL.js.map +1 -0
- package/dist/_chunks/build-output-helper-DXnW0qjz.js +61 -0
- package/dist/_chunks/build-output-helper-DXnW0qjz.js.map +1 -0
- package/dist/_chunks/{define-Itxvcd7F.js → define-B-Q_UMOD.js} +19 -23
- package/dist/_chunks/define-B-Q_UMOD.js.map +1 -0
- package/dist/_chunks/{define-C77ScO0m.js → define-CfBPoJb0.js} +24 -7
- package/dist/_chunks/define-CfBPoJb0.js.map +1 -0
- package/dist/_chunks/define-cookie-BjpIt4UC.js +194 -0
- package/dist/_chunks/define-cookie-BjpIt4UC.js.map +1 -0
- package/dist/_chunks/{format-CYBGxKtc.js → format-Bcn-Iv1x.js} +1 -1
- package/dist/_chunks/{format-CYBGxKtc.js.map → format-Bcn-Iv1x.js.map} +1 -1
- package/dist/_chunks/handler-store-B-lqaGyh.js +54 -0
- package/dist/_chunks/handler-store-B-lqaGyh.js.map +1 -0
- package/dist/_chunks/logger-0m8MsKdc.js +291 -0
- package/dist/_chunks/logger-0m8MsKdc.js.map +1 -0
- package/dist/_chunks/merge-search-params-BphMdht_.js +122 -0
- package/dist/_chunks/merge-search-params-BphMdht_.js.map +1 -0
- package/dist/_chunks/{metadata-routes-DS3eKNmf.js → metadata-routes-BU684ls2.js} +1 -1
- package/dist/_chunks/{metadata-routes-DS3eKNmf.js.map → metadata-routes-BU684ls2.js.map} +1 -1
- package/dist/_chunks/navigation-root-BCYczjml.js +96 -0
- package/dist/_chunks/navigation-root-BCYczjml.js.map +1 -0
- package/dist/_chunks/registry-I2ss-lvy.js +20 -0
- package/dist/_chunks/registry-I2ss-lvy.js.map +1 -0
- package/dist/_chunks/router-ref-h3-UaCQv.js +28 -0
- package/dist/_chunks/router-ref-h3-UaCQv.js.map +1 -0
- package/dist/_chunks/{schema-bridge-C3xl_vfb.js → schema-bridge-Cxu4l-7p.js} +1 -1
- package/dist/_chunks/{schema-bridge-C3xl_vfb.js.map → schema-bridge-Cxu4l-7p.js.map} +1 -1
- package/dist/_chunks/segment-classify-BjfuctV2.js +137 -0
- package/dist/_chunks/segment-classify-BjfuctV2.js.map +1 -0
- package/dist/_chunks/{segment-context-fHFLF1PE.js → segment-context-Dx_OizxD.js} +1 -1
- package/dist/_chunks/{segment-context-fHFLF1PE.js.map → segment-context-Dx_OizxD.js.map} +1 -1
- package/dist/_chunks/{router-ref-C8OCm7g7.js → ssr-data-B4CdH7rE.js} +2 -26
- package/dist/_chunks/ssr-data-B4CdH7rE.js.map +1 -0
- package/dist/_chunks/{stale-reload-BX5gL1r-.js → stale-reload-Bab885FO.js} +1 -1
- package/dist/_chunks/{stale-reload-BX5gL1r-.js.map → stale-reload-Bab885FO.js.map} +1 -1
- package/dist/_chunks/tracing-C8V-YGsP.js +329 -0
- package/dist/_chunks/tracing-C8V-YGsP.js.map +1 -0
- package/dist/_chunks/{use-query-states-BiV5GJgm.js → use-query-states-B2XTqxDR.js} +3 -19
- package/dist/_chunks/use-query-states-B2XTqxDR.js.map +1 -0
- package/dist/_chunks/{use-params-IOPu7E8t.js → use-segment-params-BkpKAQ7D.js} +9 -95
- package/dist/_chunks/use-segment-params-BkpKAQ7D.js.map +1 -0
- package/dist/_chunks/{interception-BbqMCVXa.js → walkers-Tg0Alwcg.js} +66 -87
- package/dist/_chunks/walkers-Tg0Alwcg.js.map +1 -0
- package/dist/_chunks/{dev-warnings-DpGRGoDi.js → warnings-Cg47l5sk.js} +3 -3
- package/dist/_chunks/warnings-Cg47l5sk.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +28 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +8 -28
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +63 -31
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +16 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/cache/index.js +9 -2
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.js +2 -1
- package/dist/client/error-boundary.js.map +1 -1
- package/dist/client/form.d.ts +10 -24
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +41 -91
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +2 -1
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +81 -7
- package/dist/client/internal.js.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/state.d.ts +1 -1
- package/dist/client/use-cookie.d.ts +8 -0
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +1 -1
- package/dist/client/use-segment-params.d.ts.map +1 -0
- package/dist/codec.d.ts +1 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +2 -2
- package/dist/config-types.d.ts +28 -0
- package/dist/config-types.d.ts.map +1 -1
- package/dist/cookies/define-cookie.d.ts +87 -35
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.d.ts +2 -1
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +48 -2
- package/dist/cookies/index.js.map +1 -0
- package/dist/cookies/json-cookie.d.ts +64 -0
- package/dist/cookies/json-cookie.d.ts.map +1 -0
- package/dist/cookies/validation.d.ts +46 -0
- package/dist/cookies/validation.d.ts.map +1 -0
- package/dist/{plugins/dev-404-page.d.ts → dev-tools/404-page.d.ts} +9 -19
- package/dist/dev-tools/404-page.d.ts.map +1 -0
- package/dist/{plugins/dev-browser-logs.d.ts → dev-tools/browser-logs.d.ts} +1 -1
- package/dist/dev-tools/browser-logs.d.ts.map +1 -0
- package/dist/{plugins/dev-error-page.d.ts → dev-tools/error-page.d.ts} +2 -2
- package/dist/dev-tools/error-page.d.ts.map +1 -0
- package/dist/{server/dev-holding-server.d.ts → dev-tools/holding-server.d.ts} +5 -3
- package/dist/dev-tools/holding-server.d.ts.map +1 -0
- package/dist/dev-tools/index.d.ts +31 -0
- package/dist/dev-tools/index.d.ts.map +1 -0
- package/dist/{server/dev-span-processor.d.ts → dev-tools/instrumentation.d.ts} +26 -6
- package/dist/dev-tools/instrumentation.d.ts.map +1 -0
- package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
- package/dist/dev-tools/logger.d.ts.map +1 -0
- package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +1 -1
- package/dist/dev-tools/logs.d.ts.map +1 -0
- package/dist/{plugins/dev-error-overlay.d.ts → dev-tools/overlay.d.ts} +3 -12
- package/dist/dev-tools/overlay.d.ts.map +1 -0
- package/dist/dev-tools/stack-classifier.d.ts +34 -0
- package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
- package/dist/{plugins/dev-terminal-error.d.ts → dev-tools/terminal.d.ts} +2 -2
- package/dist/dev-tools/terminal.d.ts.map +1 -0
- package/dist/{server/dev-warnings.d.ts → dev-tools/warnings.d.ts} +1 -1
- package/dist/dev-tools/warnings.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +285 -133
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +1 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +6 -4
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/index.d.ts +5 -3
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +3 -3
- package/dist/routing/scanner.d.ts +1 -10
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +37 -8
- package/dist/routing/segment-classify.d.ts.map +1 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/routing/types.d.ts +63 -23
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/routing/walkers.d.ts +51 -0
- package/dist/routing/walkers.d.ts.map +1 -0
- package/dist/search-params/define.d.ts +25 -7
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +5 -3
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/wrappers.d.ts +2 -2
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/segment-params/define.d.ts +23 -6
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +1 -1
- package/dist/server/access-gate.d.ts +4 -3
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts +15 -6
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +5 -5
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/asset-headers.d.ts +1 -15
- package/dist/server/asset-headers.d.ts.map +1 -1
- package/dist/server/cookie-context.d.ts +170 -0
- package/dist/server/cookie-context.d.ts.map +1 -0
- package/dist/server/cookie-parsing.d.ts +51 -0
- package/dist/server/cookie-parsing.d.ts.map +1 -0
- package/dist/server/deny-boundary.d.ts +90 -0
- package/dist/server/deny-boundary.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/early-hints-sender.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts +212 -0
- package/dist/server/html-injector-core.d.ts.map +1 -0
- package/dist/server/html-injectors.d.ts +59 -59
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -149
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +6 -4
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +852 -852
- package/dist/server/internal.js.map +1 -1
- package/dist/server/logger.d.ts +14 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware-runner.d.ts +17 -0
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +46 -49
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts +26 -0
- package/dist/server/param-coercion.d.ts.map +1 -0
- package/dist/server/pipeline-helpers.d.ts +95 -0
- package/dist/server/pipeline-helpers.d.ts.map +1 -0
- package/dist/server/pipeline-outcome.d.ts +49 -0
- package/dist/server/pipeline-outcome.d.ts.map +1 -0
- package/dist/server/pipeline-phases.d.ts +52 -0
- package/dist/server/pipeline-phases.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +51 -32
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/port-resolution.d.ts +117 -0
- package/dist/server/port-resolution.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +22 -159
- 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-matcher.d.ts +20 -47
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
- package/dist/server/rsc-entry/helpers.d.ts +1 -1
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +50 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +119 -0
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -0
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/status-code-resolver.d.ts +16 -11
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +1 -1
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +45 -16
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +48 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/escape-html.d.ts +14 -0
- package/dist/server/utils/escape-html.d.ts.map +1 -0
- package/dist/shims/headers.d.ts +2 -2
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/navigation-client.d.ts +3 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +9 -4
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/utils/directive-parser.d.ts +0 -45
- package/dist/utils/directive-parser.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/build-output-helper.ts +77 -0
- package/src/adapters/cloudflare.ts +10 -50
- package/src/adapters/nitro.ts +66 -50
- package/src/adapters/shared.ts +40 -0
- package/src/cache/timber-cache.ts +3 -2
- package/src/client/form.tsx +17 -25
- package/src/client/index.ts +16 -9
- package/src/client/internal.ts +3 -2
- package/src/client/router.ts +1 -1
- package/src/client/rsc-fetch.ts +15 -0
- package/src/client/state.ts +2 -2
- package/src/client/use-cookie.ts +29 -0
- package/src/codec.ts +3 -7
- package/src/config-types.ts +28 -0
- package/src/cookies/define-cookie.ts +271 -78
- package/src/cookies/index.ts +11 -8
- package/src/cookies/json-cookie.ts +105 -0
- package/src/cookies/validation.ts +134 -0
- package/src/{plugins/dev-404-page.ts → dev-tools/404-page.ts} +17 -48
- package/src/{plugins/dev-error-page.ts → dev-tools/error-page.ts} +5 -32
- package/src/{server/dev-holding-server.ts → dev-tools/holding-server.ts} +4 -2
- package/src/dev-tools/index.ts +90 -0
- package/src/dev-tools/instrumentation.ts +176 -0
- package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +2 -2
- package/src/{plugins/dev-error-overlay.ts → dev-tools/overlay.ts} +5 -23
- package/src/dev-tools/stack-classifier.ts +75 -0
- package/src/{plugins/dev-terminal-error.ts → dev-tools/terminal.ts} +4 -38
- package/src/{server/dev-warnings.ts → dev-tools/warnings.ts} +1 -1
- package/src/index.ts +95 -34
- package/src/plugin-context.ts +1 -1
- package/src/plugins/adapter-build.ts +3 -1
- package/src/plugins/build-report.ts +13 -22
- package/src/plugins/dev-server.ts +3 -3
- package/src/plugins/routing.ts +14 -12
- package/src/plugins/shims.ts +1 -1
- package/src/plugins/static-build.ts +1 -1
- package/src/routing/codegen.ts +1 -1
- package/src/routing/convention-lint.ts +9 -8
- package/src/routing/index.ts +5 -3
- package/src/routing/interception.ts +1 -1
- package/src/routing/scanner.ts +22 -95
- package/src/routing/segment-classify.ts +107 -8
- package/src/routing/status-file-lint.ts +7 -5
- package/src/routing/types.ts +63 -23
- package/src/routing/walkers.ts +90 -0
- package/src/search-params/define.ts +71 -15
- package/src/search-params/wrappers.ts +9 -2
- package/src/segment-params/define.ts +66 -13
- package/src/server/access-gate.tsx +9 -8
- package/src/server/action-handler.ts +34 -38
- package/src/server/als-registry.ts +5 -5
- package/src/server/asset-headers.ts +8 -34
- package/src/server/cookie-context.ts +468 -0
- package/src/server/cookie-parsing.ts +135 -0
- package/src/server/{deny-page-resolver.ts → deny-boundary.ts} +78 -14
- package/src/server/deny-renderer.ts +7 -12
- package/src/server/early-hints-sender.ts +3 -2
- package/src/server/fallback-error.ts +2 -2
- package/src/server/html-injector-core.ts +403 -0
- package/src/server/html-injectors.ts +158 -297
- package/src/server/index.ts +13 -14
- package/src/server/internal.ts +10 -3
- package/src/server/logger.ts +23 -0
- package/src/server/middleware-runner.ts +44 -0
- package/src/server/node-stream-transforms.ts +108 -248
- package/src/server/param-coercion.ts +76 -0
- package/src/server/pipeline-helpers.ts +204 -0
- package/src/server/pipeline-outcome.ts +167 -0
- package/src/server/pipeline-phases.ts +409 -0
- package/src/server/pipeline.ts +70 -540
- package/src/server/port-resolution.ts +215 -0
- package/src/server/request-context.ts +46 -451
- package/src/server/route-element-builder.ts +8 -4
- package/src/server/route-matcher.ts +28 -60
- package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
- package/src/server/rsc-entry/api-handler.ts +2 -2
- package/src/server/rsc-entry/error-renderer.ts +2 -2
- package/src/server/rsc-entry/helpers.ts +2 -7
- package/src/server/rsc-entry/index.ts +81 -366
- package/src/server/rsc-entry/render-route.ts +304 -0
- package/src/server/rsc-entry/rsc-payload.ts +1 -1
- package/src/server/rsc-entry/ssr-renderer.ts +2 -2
- package/src/server/rsc-entry/wrap-action-dispatch.ts +449 -0
- package/src/server/sitemap-generator.ts +1 -1
- package/src/server/slot-resolver.ts +1 -1
- package/src/server/ssr-entry.ts +1 -1
- package/src/server/state-tree-diff.ts +4 -1
- package/src/server/status-code-resolver.ts +112 -128
- package/src/server/tracing.ts +3 -3
- package/src/server/tree-builder.ts +134 -56
- package/src/server/types.ts +52 -0
- package/src/server/utils/escape-html.ts +20 -0
- package/src/shims/headers.ts +3 -3
- package/src/shims/navigation-client.ts +4 -3
- package/src/shims/navigation.ts +9 -7
- package/src/utils/directive-parser.ts +0 -392
- package/dist/_chunks/actions-DLnUaR65.js +0 -421
- package/dist/_chunks/actions-DLnUaR65.js.map +0 -1
- package/dist/_chunks/als-registry-HS0LGUl2.js +0 -41
- package/dist/_chunks/als-registry-HS0LGUl2.js.map +0 -1
- package/dist/_chunks/debug-ECi_61pb.js +0 -108
- package/dist/_chunks/debug-ECi_61pb.js.map +0 -1
- package/dist/_chunks/define-C77ScO0m.js.map +0 -1
- package/dist/_chunks/define-Itxvcd7F.js.map +0 -1
- package/dist/_chunks/define-cookie-BowvzoP0.js +0 -94
- package/dist/_chunks/define-cookie-BowvzoP0.js.map +0 -1
- package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +0 -1
- package/dist/_chunks/interception-BbqMCVXa.js.map +0 -1
- package/dist/_chunks/merge-search-params-Cm_KIWDX.js +0 -41
- package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +0 -1
- package/dist/_chunks/request-context-CK5tZqIP.js +0 -478
- package/dist/_chunks/request-context-CK5tZqIP.js.map +0 -1
- package/dist/_chunks/router-ref-C8OCm7g7.js.map +0 -1
- package/dist/_chunks/segment-classify-BDNn6EzD.js +0 -65
- package/dist/_chunks/segment-classify-BDNn6EzD.js.map +0 -1
- package/dist/_chunks/tracing-CCYbKn5n.js +0 -238
- package/dist/_chunks/tracing-CCYbKn5n.js.map +0 -1
- package/dist/_chunks/use-params-IOPu7E8t.js.map +0 -1
- package/dist/_chunks/use-query-states-BiV5GJgm.js.map +0 -1
- package/dist/client/use-params.d.ts.map +0 -1
- package/dist/plugins/dev-404-page.d.ts.map +0 -1
- package/dist/plugins/dev-browser-logs.d.ts.map +0 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
- package/dist/plugins/dev-error-page.d.ts.map +0 -1
- package/dist/plugins/dev-logs.d.ts.map +0 -1
- package/dist/plugins/dev-terminal-error.d.ts.map +0 -1
- package/dist/server/deny-page-resolver.d.ts +0 -52
- package/dist/server/deny-page-resolver.d.ts.map +0 -1
- package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
- package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
- package/dist/server/dev-holding-server.d.ts.map +0 -1
- package/dist/server/dev-logger.d.ts.map +0 -1
- package/dist/server/dev-span-processor.d.ts.map +0 -1
- package/dist/server/dev-warnings.d.ts.map +0 -1
- package/dist/server/manifest-status-resolver.d.ts +0 -58
- package/dist/server/manifest-status-resolver.d.ts.map +0 -1
- package/dist/server/page-deny-boundary.d.ts +0 -31
- package/dist/server/page-deny-boundary.d.ts.map +0 -1
- package/src/server/dev-fetch-instrumentation.ts +0 -96
- package/src/server/dev-span-processor.ts +0 -78
- package/src/server/manifest-status-resolver.ts +0 -215
- package/src/server/page-deny-boundary.tsx +0 -56
- /package/src/client/{use-params.ts → use-segment-params.ts} +0 -0
- /package/src/{plugins/dev-browser-logs.ts → dev-tools/browser-logs.ts} +0 -0
- /package/src/{server/dev-logger.ts → dev-tools/logger.ts} +0 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action-dispatch wrapper around the route pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from rsc-entry/index.ts so the wiring can be unit-tested in
|
|
5
|
+
* isolation from Vite's virtual modules. The wrapper is responsible for
|
|
6
|
+
* four things, in order:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Pipeline-boundary CSRF validation** — runs on EVERY unsafe-method
|
|
9
|
+
* request, before any dispatch decision. This is the only line of
|
|
10
|
+
* defense for `route.ts` API handlers, which never see the action
|
|
11
|
+
* handler. See LOCAL-773.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Middleware-on-actions execution** — when the request is a server
|
|
14
|
+
* action POST and `actions.runMiddleware !== false`, the matched
|
|
15
|
+
* route's `middleware.ts` chain runs BEFORE the action body. This
|
|
16
|
+
* closes the Next.js CVE-2025-29927 class of bug, where developers
|
|
17
|
+
* reasonably believe `middleware.ts` runs on every request and find
|
|
18
|
+
* out (the hard way) that actions silently bypass it. Middleware can
|
|
19
|
+
* short-circuit with a `Response`, `redirect()`, or `deny()`; can
|
|
20
|
+
* mutate cookies; and can inject request headers visible to the
|
|
21
|
+
* action body via `getHeaders()`. See TIM-871.
|
|
22
|
+
*
|
|
23
|
+
* 3. **Server action interception** — POST requests carrying an
|
|
24
|
+
* `x-rsc-action` header or React's `$ACTION_REF` form fields are
|
|
25
|
+
* handed to `handleActionRequest`, which executes the action and
|
|
26
|
+
* returns either an RSC response or a no-JS rerender signal.
|
|
27
|
+
*
|
|
28
|
+
* 4. **No-JS validation rerender** — when an action returns flash data
|
|
29
|
+
* instead of a redirect, the wrapper re-runs the page render via the
|
|
30
|
+
* pipeline with the post-action cookie state and `runWithFormFlash`
|
|
31
|
+
* so server components can read the flash. The synthetic GET is
|
|
32
|
+
* marked via `markRequestBypassMiddleware` so the pipeline does not
|
|
33
|
+
* double-execute middleware on it. See TIM-836 / TIM-837 / TIM-871.
|
|
34
|
+
*
|
|
35
|
+
* Anything else falls through to `pipeline(req)` for normal route handling.
|
|
36
|
+
*
|
|
37
|
+
* The wrapper takes its dependencies as parameters (no module-level
|
|
38
|
+
* imports of virtual modules) so tests can construct it with stub
|
|
39
|
+
* pipelines, stub revalidate renderers, and stub route matchers.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import type { FormRerender } from '../action-handler.js';
|
|
43
|
+
import { handleActionRequest, isActionRequest } from '../action-handler.js';
|
|
44
|
+
import type { BodyLimitsConfig } from '../body-limits.js';
|
|
45
|
+
import { validateCsrf, type CsrfConfig } from '../csrf.js';
|
|
46
|
+
import { runWithFormFlash } from '../form-flash.js';
|
|
47
|
+
import { seedRequestCookies } from '../cookie-context.js';
|
|
48
|
+
import { markRequestBypassMiddleware } from '../middleware-runner.js';
|
|
49
|
+
import { DenySignal } from '../primitives.js';
|
|
50
|
+
import { canonicalize } from '../canonicalize.js';
|
|
51
|
+
import {
|
|
52
|
+
buildRedirectResponse,
|
|
53
|
+
cloneWithMutableHeaders,
|
|
54
|
+
fireOnRequestError,
|
|
55
|
+
} from '../pipeline-helpers.js';
|
|
56
|
+
import { logRenderError } from '../logger.js';
|
|
57
|
+
import type { RouteMatch, RouteMatcher } from '../pipeline.js';
|
|
58
|
+
import type { SensitiveFieldsOption } from '../sensitive-fields.js';
|
|
59
|
+
import type { RevalidateRenderer } from '../actions.js';
|
|
60
|
+
import { runMiddlewareForAction, type CoerceSegmentParamsFn } from './action-middleware-runner.js';
|
|
61
|
+
|
|
62
|
+
// ─── Types ────────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build a `RevalidateRenderer` for a specific request.
|
|
66
|
+
*
|
|
67
|
+
* The renderer needs to forward cookies/headers from the originating
|
|
68
|
+
* request when fetching the revalidated route. We pass the request in
|
|
69
|
+
* via a builder so the wrapper doesn't need to know about route matching,
|
|
70
|
+
* segment param coercion, or element building — those concerns stay in
|
|
71
|
+
* `rsc-entry/index.ts`.
|
|
72
|
+
*/
|
|
73
|
+
export type RevalidateRendererFactory = (req: Request) => RevalidateRenderer;
|
|
74
|
+
|
|
75
|
+
/** Optional renderer for fallback deny pages — mirrors `PipelineConfig.renderDenyFallback`. */
|
|
76
|
+
export type RenderDenyFallbackFn = (
|
|
77
|
+
deny: DenySignal,
|
|
78
|
+
req: Request,
|
|
79
|
+
responseHeaders: Headers,
|
|
80
|
+
match?: RouteMatch
|
|
81
|
+
) => Response | Promise<Response>;
|
|
82
|
+
|
|
83
|
+
/** Dependencies for the action-dispatch wrapper. */
|
|
84
|
+
export interface ActionDispatchDeps {
|
|
85
|
+
/** CSRF configuration (Origin allow-list, on/off switch). */
|
|
86
|
+
csrfConfig: CsrfConfig;
|
|
87
|
+
/** Body size limits forwarded to `handleActionRequest`. */
|
|
88
|
+
bodyLimits?: BodyLimitsConfig['limits'];
|
|
89
|
+
/** Sensitive-field deny-list forwarded to `handleActionRequest`. */
|
|
90
|
+
sensitiveFields?: SensitiveFieldsOption;
|
|
91
|
+
/** Per-request factory that builds a `RevalidateRenderer`. */
|
|
92
|
+
buildRevalidateRenderer: RevalidateRendererFactory;
|
|
93
|
+
/**
|
|
94
|
+
* Route matcher — when present, enables middleware-on-actions execution.
|
|
95
|
+
* Mirrors `PipelineConfig.matchRoute`. Tests that don't exercise the
|
|
96
|
+
* middleware path may omit this; the wrapper falls back to the legacy
|
|
97
|
+
* "actions skip middleware" behavior.
|
|
98
|
+
*/
|
|
99
|
+
matchRoute?: RouteMatcher;
|
|
100
|
+
/**
|
|
101
|
+
* Segment-param coercer — runs the matched route's `params.ts` codecs
|
|
102
|
+
* so the middleware context sees typed `segmentParams`. Required when
|
|
103
|
+
* `matchRoute` is provided.
|
|
104
|
+
*/
|
|
105
|
+
coerceSegmentParams?: CoerceSegmentParamsFn;
|
|
106
|
+
/**
|
|
107
|
+
* Renderer for fallback deny pages — used when middleware throws a
|
|
108
|
+
* `DenySignal` and the framework wants to render `403.tsx` / `404.tsx`
|
|
109
|
+
* instead of returning a bare empty Response. Optional — when omitted
|
|
110
|
+
* the wrapper falls back to a bare status response.
|
|
111
|
+
*/
|
|
112
|
+
renderDenyFallback?: RenderDenyFallbackFn;
|
|
113
|
+
/**
|
|
114
|
+
* Whether to run `middleware.ts` on server action requests. Defaults
|
|
115
|
+
* to `true` (the safe default). Controlled by
|
|
116
|
+
* `actions.runMiddleware` in `timber.config.ts`. See TIM-871.
|
|
117
|
+
*/
|
|
118
|
+
runMiddleware?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Whether to strip trailing slashes during pathname canonicalization
|
|
121
|
+
* for route matching. Mirrors `PipelineConfig.stripTrailingSlash`;
|
|
122
|
+
* defaults to `true` when omitted, matching the page pipeline. The
|
|
123
|
+
* wrapper MUST canonicalize before matching so non-canonical action
|
|
124
|
+
* POSTs (`/admin/`, `/admin//`, encoded separators) cannot bypass the
|
|
125
|
+
* middleware gate by missing the route match and falling through to
|
|
126
|
+
* the legacy path. See TIM-871 / codex review.
|
|
127
|
+
*/
|
|
128
|
+
stripTrailingSlash?: boolean;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─── Implementation ───────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Wrap a pipeline function with CSRF validation and server-action dispatch.
|
|
135
|
+
*
|
|
136
|
+
* The returned handler is the framework's outermost request entry point.
|
|
137
|
+
* Its responsibilities are documented in the file header.
|
|
138
|
+
*
|
|
139
|
+
* The duplicate `validateCsrf` call inside `handleActionRequest` is left in
|
|
140
|
+
* place as defense-in-depth (no-op on the happy path) so the action handler
|
|
141
|
+
* remains safe to call from any future entry point that bypasses this
|
|
142
|
+
* wrapper. See LOCAL-773.
|
|
143
|
+
*/
|
|
144
|
+
export function wrapPipelineWithActionDispatch(
|
|
145
|
+
pipeline: (req: Request) => Promise<Response>,
|
|
146
|
+
deps: ActionDispatchDeps
|
|
147
|
+
): (req: Request) => Promise<Response> {
|
|
148
|
+
const middlewareEnabled = deps.runMiddleware !== false;
|
|
149
|
+
const stripTrailingSlash = deps.stripTrailingSlash ?? true;
|
|
150
|
+
|
|
151
|
+
return async (req: Request): Promise<Response> => {
|
|
152
|
+
// ─── 1. Pipeline-boundary CSRF validation (LOCAL-773) ─────────────
|
|
153
|
+
//
|
|
154
|
+
// Runs on EVERY unsafe-method request, before any dispatch decision.
|
|
155
|
+
// Without this, `route.ts` PUT/PATCH/DELETE handlers and POSTs with
|
|
156
|
+
// `Content-Type: application/json` or `text/plain` would reach the
|
|
157
|
+
// route handler with no Origin check at all — `isActionRequest` only
|
|
158
|
+
// matches POST + form/multipart/x-rsc-action.
|
|
159
|
+
//
|
|
160
|
+
// `text/plain` POST is a CORS-simple request, so a cross-site
|
|
161
|
+
// `<form enctype="text/plain">` submission carries `SameSite=Lax`
|
|
162
|
+
// cookies (the framework's own default).
|
|
163
|
+
const csrfResult = validateCsrf(req, deps.csrfConfig);
|
|
164
|
+
if (!csrfResult.ok) {
|
|
165
|
+
return new Response(null, { status: csrfResult.status });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── 2. Server action interception ────────────────────────────────
|
|
169
|
+
if (isActionRequest(req)) {
|
|
170
|
+
// ─── 2a. Route-type check + canonicalize (TIM-870) ──────────────
|
|
171
|
+
//
|
|
172
|
+
// Canonicalize and match the route BEFORE any body parsing. If the
|
|
173
|
+
// matched route is an API route (route.ts), skip action detection
|
|
174
|
+
// entirely and dispatch straight to the pipeline. Server actions
|
|
175
|
+
// only live on page routes; POSTs to route.ts are API requests
|
|
176
|
+
// whose body must reach the handler untouched — not pre-parsed
|
|
177
|
+
// looking for $ACTION_REF fields.
|
|
178
|
+
//
|
|
179
|
+
// This also avoids allocating a full formData() parse over large
|
|
180
|
+
// multipart uploads (e.g. 50 MB file uploads to /api/upload) that
|
|
181
|
+
// would otherwise be buffered and discarded by handleFormAction.
|
|
182
|
+
//
|
|
183
|
+
// The same canonicalized match is reused for the middleware-on-
|
|
184
|
+
// actions path below, avoiding a redundant match.
|
|
185
|
+
let match: RouteMatch | null = null;
|
|
186
|
+
|
|
187
|
+
if (deps.matchRoute) {
|
|
188
|
+
// Canonicalize the pathname BEFORE matching, with the exact same
|
|
189
|
+
// rules the page pipeline uses (`canonicalize()` in
|
|
190
|
+
// `pipeline-phases.ts` stage 1). Without this, an attacker could
|
|
191
|
+
// POST to a non-canonical variant of an authenticated route —
|
|
192
|
+
// `/admin/` with a trailing slash, `/admin//` with a doubled
|
|
193
|
+
// slash, `/adm%69n` with percent-escapes, `/admin%2fnested` with
|
|
194
|
+
// an encoded separator — fail the match here, fall through to
|
|
195
|
+
// the legacy "no middleware" path, and still reach the action
|
|
196
|
+
// handler. The canonicalization step closes that bypass and
|
|
197
|
+
// guarantees the wrapper matches EXACTLY the same route the page
|
|
198
|
+
// pipeline would match. A canonicalize failure (encoded
|
|
199
|
+
// separator, null byte, malformed escape, `..` escaping root)
|
|
200
|
+
// returns the canonicalizer's status directly — the request is
|
|
201
|
+
// malformed, never dispatched. See TIM-871 (codex review).
|
|
202
|
+
const url = new URL(req.url);
|
|
203
|
+
const canonical = canonicalize(url.pathname, stripTrailingSlash);
|
|
204
|
+
if (!canonical.ok) {
|
|
205
|
+
return new Response(null, { status: canonical.status });
|
|
206
|
+
}
|
|
207
|
+
match = deps.matchRoute(canonical.pathname);
|
|
208
|
+
|
|
209
|
+
// Skip action detection for route.ts API handlers (TIM-870).
|
|
210
|
+
// Server actions only target page routes. A POST to a route.ts
|
|
211
|
+
// path is an API request — the full body should reach the route
|
|
212
|
+
// handler without being pre-parsed by handleFormAction.
|
|
213
|
+
if (match) {
|
|
214
|
+
const leaf = match.segments[match.segments.length - 1];
|
|
215
|
+
if (leaf?.route) {
|
|
216
|
+
return pipeline(req);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── 2b. Middleware-on-actions (TIM-871) ────────────────────────
|
|
222
|
+
//
|
|
223
|
+
// When middleware execution is enabled and the request matches a
|
|
224
|
+
// known route, run the matched chain BEFORE the action handler.
|
|
225
|
+
// The middleware run happens inside its own `runWithRequestContext`
|
|
226
|
+
// scope so middleware can read/write cookies and inject request
|
|
227
|
+
// headers via the standard ALS APIs. Mutations are captured at the
|
|
228
|
+
// end of the scope and threaded into the action handler's own ALS
|
|
229
|
+
// scope via `seedRequestCookies` + a request rebuilt with overlay
|
|
230
|
+
// headers merged in.
|
|
231
|
+
let downstreamReq = req;
|
|
232
|
+
let middlewareSetCookieHeaders: string[] = [];
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
middlewareEnabled &&
|
|
236
|
+
deps.coerceSegmentParams &&
|
|
237
|
+
match &&
|
|
238
|
+
match.middlewareChain.length > 0
|
|
239
|
+
) {
|
|
240
|
+
const outcome = await runMiddlewareForAction(req, match, deps.coerceSegmentParams);
|
|
241
|
+
|
|
242
|
+
// ─── Translate middleware outcome ─────────────────────────
|
|
243
|
+
if (outcome.kind === 'param-coercion-error') {
|
|
244
|
+
// Bad segment params → 404. Matches the page pipeline.
|
|
245
|
+
return new Response(null, { status: 404 });
|
|
246
|
+
}
|
|
247
|
+
if (outcome.kind === 'error') {
|
|
248
|
+
// Unhandled middleware error → 500.
|
|
249
|
+
return new Response(null, { status: 500 });
|
|
250
|
+
}
|
|
251
|
+
if (outcome.kind === 'short-circuit') {
|
|
252
|
+
// Middleware returned a Response. Apply its Set-Cookie
|
|
253
|
+
// snapshot before returning. Clone unconditionally so the
|
|
254
|
+
// header bag is mutable.
|
|
255
|
+
const finalResponse = cloneWithMutableHeaders(outcome.response);
|
|
256
|
+
for (const value of outcome.setCookieHeaders) {
|
|
257
|
+
finalResponse.headers.append('Set-Cookie', value);
|
|
258
|
+
}
|
|
259
|
+
return finalResponse;
|
|
260
|
+
}
|
|
261
|
+
if (outcome.kind === 'redirect') {
|
|
262
|
+
// RedirectSignal from middleware → standard redirect Response.
|
|
263
|
+
// Use `buildRedirectResponse` so the with-JS path produces a
|
|
264
|
+
// 204 + X-Timber-Redirect (SPA navigation) and the no-JS path
|
|
265
|
+
// produces a real HTTP 30x — exactly the same translation
|
|
266
|
+
// the page pipeline applies in `outcomeToResponse`.
|
|
267
|
+
const headers = new Headers();
|
|
268
|
+
for (const value of outcome.setCookieHeaders) {
|
|
269
|
+
headers.append('Set-Cookie', value);
|
|
270
|
+
}
|
|
271
|
+
return buildRedirectResponse(outcome.signal, req, headers);
|
|
272
|
+
}
|
|
273
|
+
if (outcome.kind === 'deny') {
|
|
274
|
+
// DenySignal from middleware → render the matched route's
|
|
275
|
+
// colocated deny page if available, otherwise a bare empty
|
|
276
|
+
// status response. Mirrors the page pipeline's deny handling.
|
|
277
|
+
const headers = new Headers();
|
|
278
|
+
for (const value of outcome.setCookieHeaders) {
|
|
279
|
+
headers.append('Set-Cookie', value);
|
|
280
|
+
}
|
|
281
|
+
if (deps.renderDenyFallback) {
|
|
282
|
+
try {
|
|
283
|
+
return cloneWithMutableHeaders(
|
|
284
|
+
await deps.renderDenyFallback(outcome.signal, req, headers, outcome.match)
|
|
285
|
+
);
|
|
286
|
+
} catch (denyRenderError) {
|
|
287
|
+
// Deny page rendering failed — log before falling through to bare
|
|
288
|
+
// response. Without this, a crashing deny page on the action path
|
|
289
|
+
// produces a blank response with zero server-side signal. See TIM-876.
|
|
290
|
+
const url = new URL(req.url);
|
|
291
|
+
logRenderError({ method: req.method, path: url.pathname, error: denyRenderError });
|
|
292
|
+
await fireOnRequestError(denyRenderError, req, 'render');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return new Response(null, { status: outcome.signal.status, headers });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Continue: middleware passed ──────────────────────────
|
|
299
|
+
//
|
|
300
|
+
// Build a downstream Request that the action handler will see:
|
|
301
|
+
// - Original headers + middleware's request-header overlay
|
|
302
|
+
// merged on top, so `getHeaders()` inside the action body
|
|
303
|
+
// observes the injected values.
|
|
304
|
+
// - Cookie header dropped; the post-middleware RYW cookie
|
|
305
|
+
// state is threaded directly into the action handler's
|
|
306
|
+
// request context via `seedRequestCookies`. This avoids
|
|
307
|
+
// a Cookie-header round-trip that would re-parse via
|
|
308
|
+
// `parseCookieHeader` — the same H-3 smuggling primitive
|
|
309
|
+
// mitigated by TIM-868. The action's own cookie reads
|
|
310
|
+
// therefore observe both the original cookies and any
|
|
311
|
+
// middleware mutations, with no cleartext encoding step
|
|
312
|
+
// in between.
|
|
313
|
+
// - Body forwarded as-is so the action handler can decode it.
|
|
314
|
+
// - Method preserved (POST).
|
|
315
|
+
//
|
|
316
|
+
// We hold the middleware Set-Cookie snapshot to prepend to the
|
|
317
|
+
// final response below — middleware writes precede action
|
|
318
|
+
// writes in the response order so the browser's last-wins
|
|
319
|
+
// resolution lets the action override middleware on conflict.
|
|
320
|
+
const mergedHeaders = new Headers(req.headers);
|
|
321
|
+
outcome.overlay.forEach((value, key) => {
|
|
322
|
+
mergedHeaders.set(key, value);
|
|
323
|
+
});
|
|
324
|
+
mergedHeaders.delete('cookie');
|
|
325
|
+
downstreamReq = new Request(req.url, {
|
|
326
|
+
method: req.method,
|
|
327
|
+
headers: mergedHeaders,
|
|
328
|
+
body: req.body,
|
|
329
|
+
// Required by undici when constructing a Request with a
|
|
330
|
+
// streaming body — `req.body` is a ReadableStream and the
|
|
331
|
+
// fetch spec needs an explicit half-duplex declaration.
|
|
332
|
+
// @ts-expect-error — `duplex` is not in the standard Request init type yet.
|
|
333
|
+
duplex: 'half',
|
|
334
|
+
});
|
|
335
|
+
seedRequestCookies(downstreamReq, outcome.cookies);
|
|
336
|
+
middlewareSetCookieHeaders = outcome.setCookieHeaders;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ─── 2c. Action handler dispatch ────────────────────────────────
|
|
340
|
+
//
|
|
341
|
+
// The revalidate renderer is built from the ORIGINAL request, not
|
|
342
|
+
// `downstreamReq`. The downstream request had its `cookie` header
|
|
343
|
+
// stripped (the post-middleware RYW cookie state is threaded
|
|
344
|
+
// through ALS via `seedRequestCookies` instead, to preserve the
|
|
345
|
+
// H-3 smuggling invariant from TIM-868). But the revalidate
|
|
346
|
+
// renderer in `rsc-entry/index.ts` forwards `req.headers` onto the
|
|
347
|
+
// synthetic revalidation Request it builds for the target path —
|
|
348
|
+
// if we passed `downstreamReq` here, that synthetic request would
|
|
349
|
+
// carry no cookies, and an action that calls `revalidatePath()`
|
|
350
|
+
// would re-render the target route with no session / tenant / auth
|
|
351
|
+
// context. Using `req` (the unmodified inbound request) preserves
|
|
352
|
+
// the original cookie header for the revalidation side channel
|
|
353
|
+
// while `seedRequestCookies` handles the middleware RYW state for
|
|
354
|
+
// the action body itself. See TIM-871 (codex review).
|
|
355
|
+
const actionResponse = await handleActionRequest(downstreamReq, {
|
|
356
|
+
csrf: deps.csrfConfig,
|
|
357
|
+
bodyLimits: { limits: deps.bodyLimits },
|
|
358
|
+
sensitiveFields: deps.sensitiveFields,
|
|
359
|
+
revalidateRenderer: deps.buildRevalidateRenderer(req),
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
if (actionResponse) {
|
|
363
|
+
// ─── 3. No-JS validation rerender ─────────────────────────────
|
|
364
|
+
if ('rerender' in actionResponse) {
|
|
365
|
+
const formRerender = actionResponse as FormRerender;
|
|
366
|
+
// Build a synthetic GET request for the rerender pipeline:
|
|
367
|
+
// - Same URL (so route matching lands on the same page)
|
|
368
|
+
// - Cookie header DROPPED entirely. The post-action RYW state
|
|
369
|
+
// is threaded into the rerender request context as a parsed
|
|
370
|
+
// `Map<string, string>` via `seedRequestCookies` below, so
|
|
371
|
+
// `parseCookieHeader` is never called for this request.
|
|
372
|
+
// This eliminates the value-smuggling primitive that the
|
|
373
|
+
// previous string round-trip exposed: a `;`-laden cookie
|
|
374
|
+
// value would otherwise split into sibling cookies during
|
|
375
|
+
// re-parse and let an attacker inject `role=admin` /
|
|
376
|
+
// forged session cookies into the rerender response. See
|
|
377
|
+
// ONGOING_SECURITY.md H-3 (TIM-868) and TIM-837.
|
|
378
|
+
// - Method GET because the rerender is conceptually a page
|
|
379
|
+
// render, not a re-POST. The pipeline doesn't branch on
|
|
380
|
+
// method for page rendering, and constructing a POST without
|
|
381
|
+
// a body is awkward across Request implementations.
|
|
382
|
+
// - Marked via `markRequestBypassMiddleware` so the pipeline
|
|
383
|
+
// skips its middleware phase on this synthetic request.
|
|
384
|
+
// Middleware already ran once on the inbound POST (above);
|
|
385
|
+
// letting the pipeline run it again would double-execute
|
|
386
|
+
// auth, rate limiting, and request-header injection. See
|
|
387
|
+
// TIM-871.
|
|
388
|
+
const rerenderHeaders = new Headers(req.headers);
|
|
389
|
+
rerenderHeaders.delete('cookie');
|
|
390
|
+
const rerenderReq = new Request(req.url, {
|
|
391
|
+
method: 'GET',
|
|
392
|
+
headers: rerenderHeaders,
|
|
393
|
+
});
|
|
394
|
+
// Seed BEFORE pipeline() runs — runWithRequestContext consumes
|
|
395
|
+
// the seed when it constructs the per-request store.
|
|
396
|
+
seedRequestCookies(rerenderReq, formRerender.cookies);
|
|
397
|
+
markRequestBypassMiddleware(rerenderReq);
|
|
398
|
+
const response = await runWithFormFlash(formRerender.rerender, () =>
|
|
399
|
+
pipeline(rerenderReq)
|
|
400
|
+
);
|
|
401
|
+
// Apply Set-Cookie headers snapshotted from the action's ALS scope.
|
|
402
|
+
// The pipeline above runs in its own request context with a fresh
|
|
403
|
+
// cookie jar, so cookies set inside the action would otherwise be
|
|
404
|
+
// silently dropped on the no-JS rerender path. See TIM-836
|
|
405
|
+
// (LOCAL-740). Middleware-set cookies are prepended first so
|
|
406
|
+
// browser last-wins still lets action writes override.
|
|
407
|
+
for (const value of middlewareSetCookieHeaders) {
|
|
408
|
+
response.headers.append('Set-Cookie', value);
|
|
409
|
+
}
|
|
410
|
+
for (const value of formRerender.setCookieHeaders) {
|
|
411
|
+
response.headers.append('Set-Cookie', value);
|
|
412
|
+
}
|
|
413
|
+
return response;
|
|
414
|
+
}
|
|
415
|
+
// Apply middleware Set-Cookie snapshot to the action's RSC
|
|
416
|
+
// response. Action's own cookies were already appended inside
|
|
417
|
+
// `handleActionRequest` via `getSetCookieHeaders()` before the
|
|
418
|
+
// action ALS scope exited; we prepend middleware writes so
|
|
419
|
+
// browser last-wins lets action writes take precedence on
|
|
420
|
+
// conflicting names.
|
|
421
|
+
if (middlewareSetCookieHeaders.length > 0) {
|
|
422
|
+
// Middleware writes go FIRST in the response order, so we
|
|
423
|
+
// build a fresh Headers and rebuild the Response. Cloning the
|
|
424
|
+
// Response keeps the body stream intact.
|
|
425
|
+
const mergedHeaders = new Headers();
|
|
426
|
+
for (const value of middlewareSetCookieHeaders) {
|
|
427
|
+
mergedHeaders.append('Set-Cookie', value);
|
|
428
|
+
}
|
|
429
|
+
actionResponse.headers.forEach((value, key) => {
|
|
430
|
+
if (key.toLowerCase() === 'set-cookie') {
|
|
431
|
+
mergedHeaders.append('Set-Cookie', value);
|
|
432
|
+
} else {
|
|
433
|
+
mergedHeaders.set(key, value);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
return new Response(actionResponse.body, {
|
|
437
|
+
status: actionResponse.status,
|
|
438
|
+
statusText: actionResponse.statusText,
|
|
439
|
+
headers: mergedHeaders,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
return actionResponse;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ─── 4. Normal route dispatch ─────────────────────────────────────
|
|
447
|
+
return pipeline(req);
|
|
448
|
+
};
|
|
449
|
+
}
|
|
@@ -352,7 +352,7 @@ interface SlotMatchResult {
|
|
|
352
352
|
* to find the deepest matching page.
|
|
353
353
|
*/
|
|
354
354
|
function findSlotMatch(slotNode: ManifestSegmentNode, match: RouteMatch): SlotMatchResult | null {
|
|
355
|
-
const segments = match.segments
|
|
355
|
+
const segments = match.segments;
|
|
356
356
|
|
|
357
357
|
// Find the parent segment that owns this slot by comparing urlPaths.
|
|
358
358
|
// The slot's urlPath matches its parent's urlPath (slots don't add URL depth).
|
package/src/server/ssr-entry.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { SsrStreamError } from './primitives.js';
|
|
|
37
37
|
import { createBufferedTransformStream, injectHead, injectRscPayload } from './html-injectors.js';
|
|
38
38
|
import { wrapSsrElement } from './ssr-wrappers.js';
|
|
39
39
|
import { withSpan } from './tracing.js';
|
|
40
|
-
import { setCurrentParams } from '../client/use-params.js';
|
|
40
|
+
import { setCurrentParams } from '../client/use-segment-params.js';
|
|
41
41
|
import { registerSsrDataProvider, type SsrData } from '../client/ssr-data.js';
|
|
42
42
|
|
|
43
43
|
// Pre-import Node.js stream modules at module load time — not per-request.
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* See design/13-security.md §"State tree manipulation"
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { swallow } from './logger.js';
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* Parse the X-Timber-State-Tree header from a request.
|
|
19
21
|
*
|
|
@@ -33,7 +35,8 @@ export function parseClientStateTree(req: Request): Set<string> | null {
|
|
|
33
35
|
return null;
|
|
34
36
|
}
|
|
35
37
|
return new Set(parsed.segments as string[]);
|
|
36
|
-
} catch {
|
|
38
|
+
} catch (err) {
|
|
39
|
+
swallow(err, 'malformed X-Timber-State-Tree header');
|
|
37
40
|
return null;
|
|
38
41
|
}
|
|
39
42
|
}
|