@timber-js/app 0.2.0-alpha.7 → 0.2.0-alpha.71
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/LICENSE +8 -0
- package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-BJARkOcu.js} +1 -1
- package/dist/_chunks/als-registry-BJARkOcu.js.map +1 -0
- package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
- package/dist/_chunks/{debug-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-CGuYoRHU.js +199 -0
- package/dist/_chunks/define-CGuYoRHU.js.map +1 -0
- package/dist/_chunks/define-Dz1bqwaS.js +106 -0
- package/dist/_chunks/define-Dz1bqwaS.js.map +1 -0
- package/dist/_chunks/define-cookie-B5mewxwM.js +93 -0
- package/dist/_chunks/define-cookie-B5mewxwM.js.map +1 -0
- package/dist/_chunks/error-boundary-D9hzsveV.js +216 -0
- package/dist/_chunks/error-boundary-D9hzsveV.js.map +1 -0
- package/dist/_chunks/{format-DviM89f0.js → format-Rn922VH2.js} +3 -20
- package/dist/_chunks/format-Rn922VH2.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → handler-store-BVePM7hp.js} +68 -3
- package/dist/_chunks/handler-store-BVePM7hp.js.map +1 -0
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-CEdHHviP.js} +171 -97
- package/dist/_chunks/interception-CEdHHviP.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
- package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-CywiO4jV.js} +181 -69
- package/dist/_chunks/request-context-CywiO4jV.js.map +1 -0
- package/dist/_chunks/schema-bridge-C4SwjCQD.js +86 -0
- package/dist/_chunks/schema-bridge-C4SwjCQD.js.map +1 -0
- package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
- package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
- package/dist/_chunks/segment-context-hzuJ048X.js +72 -0
- package/dist/_chunks/segment-context-hzuJ048X.js.map +1 -0
- package/dist/_chunks/stale-reload-BLUC_Pl_.js +64 -0
- package/dist/_chunks/stale-reload-BLUC_Pl_.js.map +1 -0
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DAhgj8Gx.js} +1 -1
- package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -0
- package/dist/_chunks/wrappers-LZbghvn0.js +63 -0
- package/dist/_chunks/wrappers-LZbghvn0.js.map +1 -0
- package/dist/adapters/cloudflare-dev.d.ts +109 -0
- package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
- package/dist/adapters/cloudflare-dev.js +73 -0
- package/dist/adapters/cloudflare-dev.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +148 -12
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +135 -11
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +17 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +56 -13
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/cache-api.d.ts +24 -0
- package/dist/cache/cache-api.d.ts.map +1 -0
- package/dist/cache/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/handler-store.d.ts +31 -0
- package/dist/cache/handler-store.d.ts.map +1 -0
- package/dist/cache/index.d.ts +7 -5
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +111 -73
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/singleflight.d.ts +18 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +12 -5
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -125
- package/dist/client/error-reconstituter.d.ts +54 -0
- package/dist/client/error-reconstituter.d.ts.map +1 -0
- package/dist/client/form.d.ts +2 -2
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/history.d.ts +19 -4
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +6 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +537 -166
- package/dist/client/index.js.map +1 -1
- package/dist/client/link-pending-store.d.ts +78 -0
- package/dist/client/link-pending-store.d.ts.map +1 -0
- package/dist/client/link.d.ts +90 -32
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/nav-link-store.d.ts +36 -0
- package/dist/client/nav-link-store.d.ts.map +1 -0
- package/dist/client/navigation-api-types.d.ts +90 -0
- package/dist/client/navigation-api-types.d.ts.map +1 -0
- package/dist/client/navigation-api.d.ts +115 -0
- package/dist/client/navigation-api.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +13 -2
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
- package/dist/client/navigation-root.d.ts.map +1 -0
- package/dist/client/nuqs-adapter.d.ts.map +1 -1
- package/dist/client/router.d.ts +70 -4
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +38 -3
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-context.d.ts +1 -1
- package/dist/client/segment-context.d.ts.map +1 -1
- package/dist/client/segment-merger.d.ts.map +1 -1
- package/dist/client/segment-outlet.d.ts +63 -0
- package/dist/client/segment-outlet.d.ts.map +1 -0
- package/dist/client/ssr-data.d.ts +13 -4
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/stale-reload.d.ts +15 -0
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +3 -3
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +6 -4
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/codec.d.ts +23 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +2 -0
- package/dist/cookies/define-cookie.d.ts +35 -14
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.d.ts +2 -0
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +3 -84
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/index.d.ts +154 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12092 -11916
- package/dist/index.js.map +1 -1
- package/dist/plugins/adapter-build.d.ts +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +2 -2
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +3 -3
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/client-chunks.d.ts +32 -0
- package/dist/plugins/client-chunks.d.ts.map +1 -0
- package/dist/plugins/content.d.ts +1 -1
- package/dist/plugins/content.d.ts.map +1 -1
- package/dist/plugins/dev-browser-logs.d.ts +84 -0
- package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
- package/dist/plugins/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/dev-logs.d.ts +1 -1
- package/dist/plugins/dev-logs.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +19 -5
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +1 -1
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts +6 -5
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts +2 -2
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/index.d.ts +2 -0
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +3 -2
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +46 -0
- package/dist/routing/segment-classify.d.ts.map +1 -0
- package/dist/routing/status-file-lint.d.ts +2 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/routing/types.d.ts +16 -4
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/rsc-runtime/ssr.d.ts +12 -0
- package/dist/rsc-runtime/ssr.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +76 -0
- package/dist/schema-bridge.d.ts.map +1 -0
- package/dist/search-params/define.d.ts +139 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -6
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +4 -474
- package/dist/search-params/registry.d.ts +1 -1
- package/dist/search-params/wrappers.d.ts +53 -0
- package/dist/search-params/wrappers.d.ts.map +1 -0
- package/dist/segment-params/define.d.ts +78 -0
- package/dist/segment-params/define.d.ts.map +1 -0
- package/dist/segment-params/index.d.ts +7 -0
- package/dist/segment-params/index.d.ts.map +1 -0
- package/dist/segment-params/index.js +4 -0
- package/dist/server/access-gate.d.ts +4 -0
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-client.d.ts +12 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/action-encryption.d.ts +76 -0
- package/dist/server/action-encryption.d.ts.map +1 -0
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +3 -6
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +32 -4
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +2 -2
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/debug.d.ts +1 -1
- package/dist/server/default-logger.d.ts +22 -0
- package/dist/server/default-logger.d.ts.map +1 -0
- package/dist/server/deny-page-resolver.d.ts +52 -0
- package/dist/server/deny-page-resolver.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/dev-warnings.d.ts +0 -14
- package/dist/server/dev-warnings.d.ts.map +1 -1
- package/dist/server/early-hints.d.ts +13 -5
- package/dist/server/early-hints.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +7 -1
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +4 -3
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/flight-injection-state.d.ts +66 -0
- package/dist/server/flight-injection-state.d.ts.map +1 -0
- package/dist/server/flight-scripts.d.ts +42 -0
- package/dist/server/flight-scripts.d.ts.map +1 -0
- package/dist/server/flush.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +29 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +51 -11
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2176 -1663
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +25 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware-runner.d.ts +19 -4
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +113 -0
- package/dist/server/node-stream-transforms.d.ts.map +1 -0
- package/dist/server/page-deny-boundary.d.ts +31 -0
- package/dist/server/page-deny-boundary.d.ts.map +1 -0
- package/dist/server/pipeline-interception.d.ts +1 -1
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-metadata.d.ts +6 -0
- package/dist/server/pipeline-metadata.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +32 -10
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +30 -3
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts +51 -0
- package/dist/server/render-timeout.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +76 -37
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +27 -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 +9 -2
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/api-handler.d.ts +2 -2
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +48 -5
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +8 -3
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts +46 -0
- package/dist/server/safe-load.d.ts.map +1 -0
- package/dist/server/sitemap-generator.d.ts +129 -0
- package/dist/server/sitemap-generator.d.ts.map +1 -0
- package/dist/server/sitemap-handler.d.ts +22 -0
- package/dist/server/sitemap-handler.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +22 -0
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +39 -21
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +50 -0
- package/dist/server/ssr-wrappers.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +1 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts +36 -0
- package/dist/server/stream-utils.d.ts.map +1 -0
- package/dist/server/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +22 -19
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -4
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/version-skew.d.ts +61 -0
- package/dist/server/version-skew.d.ts.map +1 -0
- package/dist/server/waituntil-bridge.d.ts.map +1 -1
- package/dist/shared/merge-search-params.d.ts +22 -0
- package/dist/shared/merge-search-params.d.ts.map +1 -0
- package/dist/shims/font-google.d.ts +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/dist/shims/font-google.js +42 -0
- package/dist/shims/font-google.js.map +1 -0
- package/dist/shims/font-local.d.ts +26 -0
- package/dist/shims/font-local.d.ts.map +1 -0
- package/dist/shims/font-local.js +20 -0
- package/dist/shims/font-local.js.map +1 -0
- package/dist/shims/navigation-client.d.ts +1 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/utils/directive-parser.d.ts +5 -2
- package/dist/utils/directive-parser.d.ts.map +1 -1
- package/dist/utils/state-machine.d.ts +80 -0
- package/dist/utils/state-machine.d.ts.map +1 -0
- package/package.json +37 -17
- package/src/adapters/cloudflare-dev.ts +177 -0
- package/src/adapters/cloudflare.ts +342 -28
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/adapters/wrangler.d.ts +7 -0
- package/src/cache/cache-api.ts +38 -0
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/handler-store.ts +68 -0
- package/src/cache/index.ts +9 -5
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +40 -29
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +314 -142
- package/src/client/error-boundary.tsx +48 -16
- package/src/client/error-reconstituter.tsx +65 -0
- package/src/client/form.tsx +2 -2
- package/src/client/history.ts +26 -4
- package/src/client/index.ts +13 -4
- package/src/client/link-pending-store.ts +136 -0
- package/src/client/link.tsx +346 -105
- package/src/client/nav-link-store.ts +47 -0
- package/src/client/navigation-api-types.ts +112 -0
- package/src/client/navigation-api.ts +332 -0
- package/src/client/navigation-context.ts +27 -6
- package/src/client/navigation-root.tsx +346 -0
- package/src/client/nuqs-adapter.tsx +16 -3
- package/src/client/router.ts +302 -77
- package/src/client/rsc-fetch.ts +93 -5
- package/src/client/segment-cache.ts +1 -1
- package/src/client/segment-context.ts +6 -1
- package/src/client/segment-merger.ts +2 -8
- package/src/client/segment-outlet.tsx +86 -0
- package/src/client/ssr-data.ts +13 -5
- package/src/client/stale-reload.ts +73 -6
- package/src/client/top-loader.tsx +22 -13
- package/src/client/use-navigation-pending.ts +1 -1
- package/src/client/use-params.ts +7 -5
- package/src/client/use-query-states.ts +2 -2
- package/src/codec.ts +34 -0
- package/src/cookies/define-cookie.ts +72 -21
- package/src/cookies/index.ts +7 -0
- package/src/fonts/css.ts +2 -1
- package/src/index.ts +328 -92
- package/src/plugins/adapter-build.ts +8 -2
- package/src/plugins/build-manifest.ts +13 -2
- package/src/plugins/build-report.ts +3 -3
- package/src/plugins/client-chunks.ts +65 -0
- package/src/plugins/content.ts +1 -1
- package/src/plugins/dev-browser-logs.ts +288 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-logs.ts +1 -1
- package/src/plugins/dev-server.ts +55 -9
- package/src/plugins/entries.ts +70 -9
- package/src/plugins/fonts.ts +167 -61
- package/src/plugins/mdx.ts +1 -1
- package/src/plugins/routing.ts +57 -17
- package/src/plugins/server-action-exports.ts +1 -1
- package/src/plugins/server-bundle.ts +32 -1
- package/src/plugins/shims.ts +76 -33
- package/src/plugins/static-build.ts +10 -6
- package/src/routing/codegen.ts +165 -105
- package/src/routing/index.ts +2 -0
- package/src/routing/scanner.ts +93 -23
- package/src/routing/segment-classify.ts +89 -0
- package/src/routing/status-file-lint.ts +3 -2
- package/src/routing/types.ts +17 -4
- package/src/rsc-runtime/rsc.ts +2 -0
- package/src/rsc-runtime/ssr.ts +50 -0
- package/src/rsc-runtime/vendor-types.d.ts +7 -0
- package/src/{search-params/codecs.ts → schema-bridge.ts} +57 -20
- package/src/search-params/define.ts +482 -0
- package/src/search-params/index.ts +13 -19
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/segment-params/define.ts +279 -0
- package/src/segment-params/index.ts +28 -0
- package/src/server/access-gate.tsx +70 -29
- package/src/server/action-client.ts +28 -3
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +20 -3
- package/src/server/actions.ts +10 -9
- package/src/server/als-registry.ts +32 -4
- package/src/server/build-manifest.ts +10 -4
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +1 -1
- package/src/server/default-logger.ts +99 -0
- package/src/server/deny-page-resolver.ts +154 -0
- package/src/server/deny-renderer.ts +24 -38
- package/src/server/dev-warnings.ts +2 -28
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +74 -22
- package/src/server/fallback-error.ts +31 -15
- package/src/server/flight-injection-state.ts +113 -0
- package/src/server/flight-scripts.ts +62 -0
- package/src/server/flush.ts +2 -1
- package/src/server/form-data.ts +76 -0
- package/src/server/html-injectors.ts +277 -117
- package/src/server/index.ts +9 -5
- package/src/server/logger.ts +44 -36
- package/src/server/middleware-runner.ts +31 -4
- package/src/server/node-stream-transforms.ts +509 -0
- package/src/server/page-deny-boundary.tsx +56 -0
- package/src/server/pipeline-interception.ts +17 -16
- package/src/server/pipeline-metadata.ts +13 -0
- package/src/server/pipeline.ts +195 -51
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +240 -117
- package/src/server/route-element-builder.ts +284 -197
- package/src/server/route-handler.ts +24 -4
- package/src/server/route-matcher.ts +24 -20
- package/src/server/rsc-entry/api-handler.ts +15 -16
- package/src/server/rsc-entry/error-renderer.ts +300 -89
- package/src/server/rsc-entry/helpers.ts +134 -5
- package/src/server/rsc-entry/index.ts +202 -113
- package/src/server/rsc-entry/rsc-payload.ts +100 -21
- package/src/server/rsc-entry/rsc-stream.ts +74 -18
- package/src/server/rsc-entry/ssr-bridge.ts +14 -5
- package/src/server/rsc-entry/ssr-renderer.ts +173 -40
- package/src/server/safe-load.ts +60 -0
- package/src/server/sitemap-generator.ts +338 -0
- package/src/server/sitemap-handler.ts +126 -0
- package/src/server/slot-resolver.ts +243 -228
- package/src/server/ssr-entry.ts +211 -32
- package/src/server/ssr-render.ts +289 -67
- package/src/server/ssr-wrappers.tsx +139 -0
- package/src/server/status-code-resolver.ts +1 -1
- package/src/server/stream-utils.ts +213 -0
- package/src/server/tracing.ts +37 -3
- package/src/server/tree-builder.ts +92 -58
- package/src/server/types.ts +3 -6
- package/src/server/version-skew.ts +104 -0
- package/src/server/waituntil-bridge.ts +4 -1
- package/src/shared/merge-search-params.ts +55 -0
- package/src/shims/font-google.ts +1 -1
- package/src/shims/font-local.ts +34 -0
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +2 -1
- package/src/utils/directive-parser.ts +5 -2
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
- package/dist/_chunks/format-DviM89f0.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
- package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
- package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
- package/dist/_chunks/tracing-Cwn7697K.js.map +0 -1
- package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
- package/dist/cache/register-cached-function.d.ts +0 -17
- package/dist/cache/register-cached-function.d.ts.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/client/link-status-provider.d.ts +0 -11
- package/dist/client/link-status-provider.d.ts.map +0 -1
- package/dist/client/transition-root.d.ts.map +0 -1
- package/dist/cookies/index.js.map +0 -1
- package/dist/plugins/cache-transform.d.ts +0 -36
- package/dist/plugins/cache-transform.d.ts.map +0 -1
- package/dist/plugins/dynamic-transform.d.ts +0 -72
- package/dist/plugins/dynamic-transform.d.ts.map +0 -1
- package/dist/search-params/analyze.d.ts +0 -54
- package/dist/search-params/analyze.d.ts.map +0 -1
- package/dist/search-params/builtin-codecs.d.ts +0 -105
- package/dist/search-params/builtin-codecs.d.ts.map +0 -1
- package/dist/search-params/codecs.d.ts +0 -53
- package/dist/search-params/codecs.d.ts.map +0 -1
- package/dist/search-params/create.d.ts +0 -106
- package/dist/search-params/create.d.ts.map +0 -1
- package/dist/search-params/index.js.map +0 -1
- package/dist/server/prerender.d.ts +0 -77
- package/dist/server/prerender.d.ts.map +0 -1
- package/dist/server/response-cache.d.ts +0 -53
- package/dist/server/response-cache.d.ts.map +0 -1
- package/src/cache/register-cached-function.ts +0 -99
- package/src/client/link-status-provider.tsx +0 -30
- package/src/client/transition-root.tsx +0 -160
- package/src/plugins/cache-transform.ts +0 -199
- package/src/plugins/dynamic-transform.ts +0 -161
- package/src/search-params/analyze.ts +0 -192
- package/src/search-params/builtin-codecs.ts +0 -228
- package/src/search-params/create.ts +0 -321
- package/src/server/prerender.ts +0 -139
- package/src/server/response-cache.ts +0 -277
package/src/server/pipeline.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { canonicalize } from './canonicalize.js';
|
|
15
15
|
import { runProxy, type ProxyExport } from './proxy.js';
|
|
16
|
-
import {
|
|
16
|
+
import { runMiddlewareChain, type MiddlewareFn } from './middleware-runner.js';
|
|
17
17
|
import { runWithTimingCollector, withTiming, getServerTimingHeader } from './server-timing.js';
|
|
18
18
|
import {
|
|
19
19
|
runWithRequestContext,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
setMutableCookieContext,
|
|
22
22
|
getSetCookieHeaders,
|
|
23
23
|
markResponseFlushed,
|
|
24
|
+
setSegmentParams,
|
|
24
25
|
} from './request-context.js';
|
|
25
26
|
import {
|
|
26
27
|
generateTraceId,
|
|
@@ -42,10 +43,13 @@ import {
|
|
|
42
43
|
} from './logger.js';
|
|
43
44
|
import { callOnRequestError } from './instrumentation.js';
|
|
44
45
|
import { RedirectSignal, DenySignal } from './primitives.js';
|
|
46
|
+
import { ParamCoercionError } from './route-element-builder.js';
|
|
47
|
+
import { checkVersionSkew, applyReloadHeaders } from './version-skew.js';
|
|
45
48
|
import { serveStaticMetadataFile, serializeSitemap } from './pipeline-metadata.js';
|
|
49
|
+
import { loadModule } from './safe-load.js';
|
|
46
50
|
import { findInterceptionMatch } from './pipeline-interception.js';
|
|
47
51
|
import type { MiddlewareContext } from './types.js';
|
|
48
|
-
import type { SegmentNode } from '
|
|
52
|
+
import type { SegmentNode } from '../routing/types.js';
|
|
49
53
|
|
|
50
54
|
// ─── Route Match Result ────────────────────────────────────────────────────
|
|
51
55
|
|
|
@@ -53,10 +57,10 @@ import type { SegmentNode } from '#/routing/types.js';
|
|
|
53
57
|
export interface RouteMatch {
|
|
54
58
|
/** The matched segment chain from root to leaf. */
|
|
55
59
|
segments: SegmentNode[];
|
|
56
|
-
/** Extracted
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
+
/** Extracted segment params (catch-all segments produce string[]). */
|
|
61
|
+
segmentParams: Record<string, string | string[]>;
|
|
62
|
+
/** Middleware chain from the segment tree, ordered root-to-leaf. */
|
|
63
|
+
middlewareChain: MiddlewareFn[];
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
/** Function that matches a canonical pathname to a route. */
|
|
@@ -115,14 +119,25 @@ export interface PipelineConfig {
|
|
|
115
119
|
* Generated at build time from intercepting route directories.
|
|
116
120
|
* See design/07-routing.md §"Intercepting Routes"
|
|
117
121
|
*/
|
|
118
|
-
interceptionRewrites?: import('
|
|
122
|
+
interceptionRewrites?: import('../routing/interception.js').InterceptionRewrite[];
|
|
119
123
|
/**
|
|
120
|
-
*
|
|
121
|
-
* Only enable in dev mode — exposes internal timing data.
|
|
124
|
+
* Control Server-Timing header output.
|
|
122
125
|
*
|
|
123
|
-
*
|
|
126
|
+
* - `'detailed'` — per-phase breakdown (proxy, middleware, render).
|
|
127
|
+
* - `'total'` — single `total;dur=N` entry (production-safe).
|
|
128
|
+
* - `false` — no Server-Timing header at all.
|
|
129
|
+
*
|
|
130
|
+
* Default: `'total'`.
|
|
131
|
+
*/
|
|
132
|
+
serverTiming?: 'detailed' | 'total' | false;
|
|
133
|
+
/**
|
|
134
|
+
* Auto-generated sitemap handler. When provided, the pipeline intercepts
|
|
135
|
+
* `/sitemap.xml` and `/sitemap/N.xml` requests and delegates to this
|
|
136
|
+
* function. Returns a Response or null (pass-through to regular routing).
|
|
137
|
+
*
|
|
138
|
+
* See design/16-metadata.md §"Auto-generated Sitemap"
|
|
124
139
|
*/
|
|
125
|
-
|
|
140
|
+
autoSitemapHandler?: (pathname: string) => Promise<Response | null>;
|
|
126
141
|
/**
|
|
127
142
|
* Dev pipeline error callback — called when a pipeline phase (proxy,
|
|
128
143
|
* middleware, render) catches an unhandled error. Used to wire the error
|
|
@@ -149,6 +164,50 @@ export interface PipelineConfig {
|
|
|
149
164
|
) => Response | Promise<Response>;
|
|
150
165
|
}
|
|
151
166
|
|
|
167
|
+
// ─── Param Coercion ────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Run segment param coercion on the matched route's segments.
|
|
171
|
+
*
|
|
172
|
+
* Loads params.ts modules from segments that have them, extracts the
|
|
173
|
+
* segmentParams definition, and coerces raw string params through codecs.
|
|
174
|
+
* Throws ParamCoercionError if any codec fails (→ 404).
|
|
175
|
+
*
|
|
176
|
+
* This runs BEFORE middleware, so ctx.segmentParams is already typed.
|
|
177
|
+
* See design/07-routing.md §"Where Coercion Runs"
|
|
178
|
+
*/
|
|
179
|
+
export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
|
|
180
|
+
const segments = match.segments as unknown as import('./route-matcher.js').ManifestSegmentNode[];
|
|
181
|
+
|
|
182
|
+
for (const segment of segments) {
|
|
183
|
+
// Only process segments that have a params.ts convention file
|
|
184
|
+
if (!segment.params) continue;
|
|
185
|
+
|
|
186
|
+
let mod: Record<string, unknown>;
|
|
187
|
+
try {
|
|
188
|
+
mod = await loadModule(segment.params);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
throw new ParamCoercionError(
|
|
191
|
+
`Failed to load params module for segment "${segment.segmentName}": ${err instanceof Error ? err.message : String(err)}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const segmentParamsDef = mod.segmentParams as
|
|
196
|
+
| { parse(raw: Record<string, string | string[]>): Record<string, unknown> }
|
|
197
|
+
| undefined;
|
|
198
|
+
|
|
199
|
+
if (!segmentParamsDef || typeof segmentParamsDef.parse !== 'function') continue;
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const coerced = segmentParamsDef.parse(match.segmentParams);
|
|
203
|
+
// Merge coerced values back into match.segmentParams
|
|
204
|
+
Object.assign(match.segmentParams, coerced);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
throw new ParamCoercionError(err instanceof Error ? err.message : String(err));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
152
211
|
// ─── Pipeline ──────────────────────────────────────────────────────────────
|
|
153
212
|
|
|
154
213
|
/**
|
|
@@ -165,7 +224,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
165
224
|
earlyHints,
|
|
166
225
|
stripTrailingSlash = true,
|
|
167
226
|
slowRequestMs = 3000,
|
|
168
|
-
|
|
227
|
+
serverTiming = 'total',
|
|
169
228
|
onPipelineError,
|
|
170
229
|
} = config;
|
|
171
230
|
|
|
@@ -216,25 +275,25 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
216
275
|
// DevSpanProcessor reads this for tree/summary output.
|
|
217
276
|
await setSpanAttribute('http.response.status_code', result.status);
|
|
218
277
|
|
|
219
|
-
// Append Server-Timing header.
|
|
220
|
-
// In dev mode: detailed per-phase breakdown (proxy, middleware, render).
|
|
221
|
-
// In production: single total duration — safe to expose, no phase names.
|
|
278
|
+
// Append Server-Timing header based on configured mode.
|
|
222
279
|
// Response.redirect() creates immutable headers, so we must
|
|
223
280
|
// ensure mutability before writing Server-Timing.
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
281
|
+
if (serverTiming === 'detailed') {
|
|
282
|
+
// Detailed: per-phase breakdown (proxy, middleware, render).
|
|
283
|
+
const timingHeader = getServerTimingHeader();
|
|
284
|
+
if (timingHeader) {
|
|
227
285
|
result = ensureMutableResponse(result);
|
|
228
|
-
result.headers.set('Server-Timing',
|
|
286
|
+
result.headers.set('Server-Timing', timingHeader);
|
|
229
287
|
}
|
|
230
|
-
} else {
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
288
|
+
} else if (serverTiming === 'total') {
|
|
289
|
+
// Total only: single `total;dur=N` — no phase names.
|
|
290
|
+
// Prevents information disclosure while giving browser
|
|
291
|
+
// DevTools useful timing data.
|
|
234
292
|
const totalMs = Math.round(performance.now() - startTime);
|
|
235
293
|
result = ensureMutableResponse(result);
|
|
236
294
|
result.headers.set('Server-Timing', `total;dur=${totalMs}`);
|
|
237
295
|
}
|
|
296
|
+
// serverTiming === false: no header at all
|
|
238
297
|
|
|
239
298
|
return result;
|
|
240
299
|
}
|
|
@@ -254,7 +313,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
254
313
|
return response;
|
|
255
314
|
};
|
|
256
315
|
|
|
257
|
-
return
|
|
316
|
+
return serverTiming === 'detailed' ? runWithTimingCollector(runRequest) : runRequest();
|
|
258
317
|
});
|
|
259
318
|
});
|
|
260
319
|
};
|
|
@@ -272,7 +331,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
272
331
|
}
|
|
273
332
|
const proxyFn = () => runProxy(proxyExport, req, () => handleRequest(req, method, path));
|
|
274
333
|
return await withSpan('timber.proxy', {}, () =>
|
|
275
|
-
|
|
334
|
+
serverTiming === 'detailed' ? withTiming('proxy', 'proxy.ts', proxyFn) : proxyFn()
|
|
276
335
|
);
|
|
277
336
|
} catch (error) {
|
|
278
337
|
// Uncaught proxy.ts error → bare HTTP 500
|
|
@@ -283,6 +342,24 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
283
342
|
}
|
|
284
343
|
}
|
|
285
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Build a redirect Response from a RedirectSignal.
|
|
347
|
+
*
|
|
348
|
+
* For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect
|
|
349
|
+
* so the client router can perform a soft SPA redirect. A raw 302 would be
|
|
350
|
+
* turned into an opaque redirect by fetch({redirect:'manual'}), crashing
|
|
351
|
+
* createFromFetch. See design/19-client-navigation.md.
|
|
352
|
+
*/
|
|
353
|
+
function buildRedirectResponse(signal: RedirectSignal, req: Request, headers: Headers): Response {
|
|
354
|
+
const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');
|
|
355
|
+
if (isRsc) {
|
|
356
|
+
headers.set('X-Timber-Redirect', signal.location);
|
|
357
|
+
return new Response(null, { status: 204, headers });
|
|
358
|
+
}
|
|
359
|
+
headers.set('Location', signal.location);
|
|
360
|
+
return new Response(null, { status: signal.status, headers });
|
|
361
|
+
}
|
|
362
|
+
|
|
286
363
|
async function handleRequest(req: Request, method: string, path: string): Promise<Response> {
|
|
287
364
|
// Stage 1: URL canonicalization
|
|
288
365
|
const url = new URL(req.url);
|
|
@@ -306,7 +383,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
306
383
|
return await serveStaticMetadataFile(metaMatch);
|
|
307
384
|
}
|
|
308
385
|
|
|
309
|
-
const mod =
|
|
386
|
+
const mod = await loadModule<{ default?: Function }>(metaMatch.file);
|
|
310
387
|
if (typeof mod.default !== 'function') {
|
|
311
388
|
return new Response('Metadata route must export a default function', { status: 500 });
|
|
312
389
|
}
|
|
@@ -339,6 +416,36 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
339
416
|
}
|
|
340
417
|
}
|
|
341
418
|
|
|
419
|
+
// Stage 1b.2: Auto-generated sitemap — serves /sitemap.xml and /sitemap/N.xml
|
|
420
|
+
// when sitemap generation is enabled and no user-authored sitemap exists.
|
|
421
|
+
// Runs after metadata route matching so user sitemaps always take precedence.
|
|
422
|
+
// See design/16-metadata.md §"Auto-generated Sitemap"
|
|
423
|
+
if (config.autoSitemapHandler) {
|
|
424
|
+
try {
|
|
425
|
+
const sitemapResponse = await config.autoSitemapHandler(canonicalPathname);
|
|
426
|
+
if (sitemapResponse) return sitemapResponse;
|
|
427
|
+
} catch (error) {
|
|
428
|
+
logRenderError({ method, path, error });
|
|
429
|
+
if (onPipelineError && error instanceof Error) onPipelineError(error, 'auto-sitemap');
|
|
430
|
+
return new Response(null, { status: 500 });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Stage 1c: Version skew detection (TIM-446).
|
|
435
|
+
// For RSC payload requests (client navigation), check if the client's
|
|
436
|
+
// deployment ID matches the current build. On mismatch, signal the
|
|
437
|
+
// client to do a full page reload instead of returning an RSC payload
|
|
438
|
+
// that references mismatched module IDs.
|
|
439
|
+
const isRscRequest = (req.headers.get('Accept') ?? '').includes('text/x-component');
|
|
440
|
+
if (isRscRequest) {
|
|
441
|
+
const skewCheck = checkVersionSkew(req);
|
|
442
|
+
if (!skewCheck.ok) {
|
|
443
|
+
const reloadHeaders = new Headers();
|
|
444
|
+
applyReloadHeaders(reloadHeaders);
|
|
445
|
+
return new Response(null, { status: 204, headers: reloadHeaders });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
342
449
|
// Stage 2: Route matching
|
|
343
450
|
let match = matchRoute(canonicalPathname);
|
|
344
451
|
let interception: InterceptionContext | undefined;
|
|
@@ -397,18 +504,57 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
397
504
|
}
|
|
398
505
|
}
|
|
399
506
|
|
|
400
|
-
// Stage
|
|
401
|
-
|
|
507
|
+
// Stage 2c: Param coercion (before middleware)
|
|
508
|
+
// Load params.ts modules from matched segments and coerce raw string
|
|
509
|
+
// params through defineSegmentParams codecs. Coercion failure → 404
|
|
510
|
+
// (middleware never runs). See design/07-routing.md §"Where Coercion Runs"
|
|
511
|
+
try {
|
|
512
|
+
await coerceSegmentParams(match);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
if (error instanceof ParamCoercionError) {
|
|
515
|
+
// For API routes (route.ts), return a bare 404 — not an HTML page.
|
|
516
|
+
// API consumers expect JSON/empty responses, not rendered HTML.
|
|
517
|
+
const leafSegment = match.segments[match.segments.length - 1];
|
|
518
|
+
if (
|
|
519
|
+
(leafSegment as { route?: unknown }).route &&
|
|
520
|
+
!(leafSegment as { page?: unknown }).page
|
|
521
|
+
) {
|
|
522
|
+
return new Response(null, { status: 404 });
|
|
523
|
+
}
|
|
524
|
+
// Route through the app's 404 page (404.tsx in root layout) instead of
|
|
525
|
+
// returning a bare empty 404 Response. Falls back to bare 404 only if
|
|
526
|
+
// no renderNoMatch renderer is configured.
|
|
527
|
+
if (config.renderNoMatch) {
|
|
528
|
+
return config.renderNoMatch(req, responseHeaders);
|
|
529
|
+
}
|
|
530
|
+
return new Response(null, { status: 404 });
|
|
531
|
+
}
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Store coerced segment params in ALS so components can access them
|
|
536
|
+
// via rawSegmentParams() instead of receiving them as a prop.
|
|
537
|
+
// See design/07-routing.md §"params.ts — Convention File for Typed Params"
|
|
538
|
+
setSegmentParams(match.segmentParams);
|
|
539
|
+
|
|
540
|
+
// Stage 3: Middleware chain (root-to-leaf, short-circuits on first Response)
|
|
541
|
+
if (match.middlewareChain.length > 0) {
|
|
402
542
|
const ctx: MiddlewareContext = {
|
|
403
543
|
req,
|
|
404
544
|
requestHeaders: requestHeaderOverlay,
|
|
405
545
|
headers: responseHeaders,
|
|
406
|
-
|
|
407
|
-
searchParams: new URL(req.url).searchParams,
|
|
546
|
+
segmentParams: match.segmentParams,
|
|
408
547
|
earlyHints: (hints) => {
|
|
409
548
|
for (const hint of hints) {
|
|
410
|
-
|
|
411
|
-
|
|
549
|
+
// Match Cloudflare's cached Early Hints attribute order: `as` before `rel`.
|
|
550
|
+
// Cloudflare caches Link headers and re-emits them on subsequent 200s.
|
|
551
|
+
// If our order differs, the browser sees duplicate preloads and warns.
|
|
552
|
+
let value: string;
|
|
553
|
+
if (hint.as !== undefined) {
|
|
554
|
+
value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;
|
|
555
|
+
} else {
|
|
556
|
+
value = `<${hint.href}>; rel=${hint.rel}`;
|
|
557
|
+
}
|
|
412
558
|
if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;
|
|
413
559
|
if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;
|
|
414
560
|
responseHeaders.append('Link', value);
|
|
@@ -419,9 +565,9 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
419
565
|
try {
|
|
420
566
|
// Enable cookie mutation during middleware (design/29-cookies.md §"Context Tracking")
|
|
421
567
|
setMutableCookieContext(true);
|
|
422
|
-
const
|
|
568
|
+
const chainFn = () => runMiddlewareChain(match.middlewareChain, ctx);
|
|
423
569
|
const middlewareResponse = await withSpan('timber.middleware', {}, () =>
|
|
424
|
-
|
|
570
|
+
serverTiming === 'detailed' ? withTiming('mw', 'middleware.ts', chainFn) : chainFn()
|
|
425
571
|
);
|
|
426
572
|
setMutableCookieContext(false);
|
|
427
573
|
if (middlewareResponse) {
|
|
@@ -430,28 +576,25 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
430
576
|
// mutability before appending Set-Cookie entries.
|
|
431
577
|
const finalResponse = ensureMutableResponse(middlewareResponse);
|
|
432
578
|
applyCookieJar(finalResponse.headers);
|
|
579
|
+
// Merge parent-set responseHeaders onto the short-circuit response.
|
|
580
|
+
// Child-set headers take precedence — only add headers not already present.
|
|
581
|
+
for (const [key, value] of responseHeaders.entries()) {
|
|
582
|
+
if (!finalResponse.headers.has(key)) {
|
|
583
|
+
finalResponse.headers.set(key, value);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
433
586
|
logMiddlewareShortCircuit({ method, path, status: finalResponse.status });
|
|
434
587
|
return finalResponse;
|
|
435
588
|
}
|
|
436
|
-
// Middleware
|
|
589
|
+
// Middleware chain completed without short-circuiting — apply any
|
|
437
590
|
// injected request headers so headers() returns them downstream.
|
|
438
591
|
applyRequestHeaderOverlay(requestHeaderOverlay);
|
|
439
592
|
} catch (error) {
|
|
440
593
|
setMutableCookieContext(false);
|
|
441
|
-
// RedirectSignal from middleware → HTTP redirect (not an error)
|
|
442
|
-
// For RSC payload requests (client navigation), return 204 + X-Timber-Redirect
|
|
443
|
-
// so the client router can perform a soft SPA redirect. A raw 302 would be
|
|
444
|
-
// turned into an opaque redirect by fetch({redirect:'manual'}), crashing
|
|
445
|
-
// createFromFetch. See design/19-client-navigation.md.
|
|
594
|
+
// RedirectSignal from middleware → HTTP redirect (not an error)
|
|
446
595
|
if (error instanceof RedirectSignal) {
|
|
447
596
|
applyCookieJar(responseHeaders);
|
|
448
|
-
|
|
449
|
-
if (isRsc) {
|
|
450
|
-
responseHeaders.set('X-Timber-Redirect', error.location);
|
|
451
|
-
return new Response(null, { status: 204, headers: responseHeaders });
|
|
452
|
-
}
|
|
453
|
-
responseHeaders.set('Location', error.location);
|
|
454
|
-
return new Response(null, { status: error.status, headers: responseHeaders });
|
|
597
|
+
return buildRedirectResponse(error, req, responseHeaders);
|
|
455
598
|
}
|
|
456
599
|
// DenySignal from middleware → HTTP deny status
|
|
457
600
|
if (error instanceof DenySignal) {
|
|
@@ -476,7 +619,9 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
476
619
|
const renderFn = () =>
|
|
477
620
|
render(req, match, responseHeaders, requestHeaderOverlay, interception);
|
|
478
621
|
const response = await withSpan('timber.render', { 'http.route': canonicalPathname }, () =>
|
|
479
|
-
|
|
622
|
+
serverTiming === 'detailed'
|
|
623
|
+
? withTiming('render', 'RSC + SSR render', renderFn)
|
|
624
|
+
: renderFn()
|
|
480
625
|
);
|
|
481
626
|
markResponseFlushed();
|
|
482
627
|
return response;
|
|
@@ -486,10 +631,9 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
486
631
|
if (error instanceof DenySignal) {
|
|
487
632
|
return new Response(null, { status: error.status });
|
|
488
633
|
}
|
|
489
|
-
// RedirectSignal leaked from render — honour the redirect
|
|
634
|
+
// RedirectSignal leaked from render — honour the redirect
|
|
490
635
|
if (error instanceof RedirectSignal) {
|
|
491
|
-
|
|
492
|
-
return new Response(null, { status: error.status, headers: responseHeaders });
|
|
636
|
+
return buildRedirectResponse(error, req, responseHeaders);
|
|
493
637
|
}
|
|
494
638
|
logRenderError({ method, path, error });
|
|
495
639
|
await fireOnRequestError(error, req, 'render');
|
package/src/server/primitives.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import type { JsonSerializable } from './types.js';
|
|
7
7
|
import { getWaitUntil as _getWaitUntil } from './waituntil-bridge.js';
|
|
8
8
|
import { isDebug } from './debug.js';
|
|
9
|
+
import { getRequestSearchString } from './request-context.js';
|
|
10
|
+
import { mergePreservedSearchParams } from '../shared/merge-search-params.js';
|
|
9
11
|
|
|
10
12
|
// ─── Dev-mode validation ────────────────────────────────────────────────────
|
|
11
13
|
|
|
@@ -209,14 +211,46 @@ export class RedirectSignal extends Error {
|
|
|
209
211
|
/** Pattern matching absolute URLs: http(s):// or protocol-relative // */
|
|
210
212
|
const ABSOLUTE_URL_RE = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/)/;
|
|
211
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Options for redirect() — alternative to passing a bare status code.
|
|
216
|
+
*/
|
|
217
|
+
export interface RedirectOptions {
|
|
218
|
+
/** HTTP redirect status code (3xx). Defaults to 302. */
|
|
219
|
+
status?: number;
|
|
220
|
+
/**
|
|
221
|
+
* Preserve search params from the current request URL on the redirect target.
|
|
222
|
+
*
|
|
223
|
+
* - `true` — preserve ALL current search params (target params take precedence)
|
|
224
|
+
* - `string[]` — preserve only the named params (e.g. `['private', 'token']`)
|
|
225
|
+
*
|
|
226
|
+
* Target path's own query params always take precedence over preserved ones.
|
|
227
|
+
*/
|
|
228
|
+
preserveSearchParams?: true | string[];
|
|
229
|
+
}
|
|
230
|
+
|
|
212
231
|
/**
|
|
213
232
|
* Redirect to a relative path. Rejects absolute and protocol-relative URLs.
|
|
214
233
|
* Use `redirectExternal()` for external redirects with an allow-list.
|
|
215
234
|
*
|
|
216
235
|
* @param path - Relative path (e.g. '/login', 'settings', '/login?returnTo=/dash')
|
|
217
|
-
* @param
|
|
236
|
+
* @param statusOrOptions - HTTP status code (3xx, default 302) or options object.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* // Simple redirect
|
|
240
|
+
* redirect('/login');
|
|
241
|
+
*
|
|
242
|
+
* // With status code
|
|
243
|
+
* redirect('/login', 301);
|
|
244
|
+
*
|
|
245
|
+
* // With preserved search params
|
|
246
|
+
* redirect(`/docs/${version}/${slug}`, { preserveSearchParams: ['foo'] });
|
|
218
247
|
*/
|
|
219
|
-
export function redirect(path: string,
|
|
248
|
+
export function redirect(path: string, statusOrOptions?: number | RedirectOptions): never {
|
|
249
|
+
const status =
|
|
250
|
+
typeof statusOrOptions === 'number' ? statusOrOptions : (statusOrOptions?.status ?? 302);
|
|
251
|
+
const preserveSearchParams =
|
|
252
|
+
typeof statusOrOptions === 'object' ? statusOrOptions.preserveSearchParams : undefined;
|
|
253
|
+
|
|
220
254
|
if (status < 300 || status > 399) {
|
|
221
255
|
throw new Error(`redirect() requires a 3xx status code, got ${status}.`);
|
|
222
256
|
}
|
|
@@ -226,7 +260,14 @@ export function redirect(path: string, status: number = 302): never {
|
|
|
226
260
|
'Use redirectExternal(url, allowList) for external redirects.'
|
|
227
261
|
);
|
|
228
262
|
}
|
|
229
|
-
|
|
263
|
+
|
|
264
|
+
let resolvedPath = path;
|
|
265
|
+
if (preserveSearchParams) {
|
|
266
|
+
const currentSearch = getRequestSearchString();
|
|
267
|
+
resolvedPath = mergePreservedSearchParams(path, currentSearch, preserveSearchParams);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
throw new RedirectSignal(resolvedPath, status);
|
|
230
271
|
}
|
|
231
272
|
|
|
232
273
|
/**
|
|
@@ -236,9 +277,10 @@ export function redirect(path: string, status: number = 302): never {
|
|
|
236
277
|
* will replay POST requests to the new location. This matches Next.js behavior.
|
|
237
278
|
*
|
|
238
279
|
* @param path - Relative path (e.g. '/new-page', '/dashboard')
|
|
280
|
+
* @param options - Optional redirect options (e.g. preserveSearchParams).
|
|
239
281
|
*/
|
|
240
|
-
export function permanentRedirect(path: string): never {
|
|
241
|
-
redirect(path, 308);
|
|
282
|
+
export function permanentRedirect(path: string, options?: Omit<RedirectOptions, 'status'>): never {
|
|
283
|
+
redirect(path, { status: 308, ...options });
|
|
242
284
|
}
|
|
243
285
|
|
|
244
286
|
/**
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render timeout utilities for SSR streaming pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Provides a RenderTimeoutError class and a helper to create
|
|
5
|
+
* timeout-guarded AbortSignals. Used to defend against hung RSC
|
|
6
|
+
* streams and infinite SSR renders.
|
|
7
|
+
*
|
|
8
|
+
* Design doc: 02-rendering-pipeline.md §"Streaming Constraints"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when an SSR render or RSC stream read exceeds the
|
|
13
|
+
* configured timeout. Callers can check `instanceof RenderTimeoutError`
|
|
14
|
+
* to distinguish timeout from other errors and return a 504 or close
|
|
15
|
+
* the connection cleanly.
|
|
16
|
+
*/
|
|
17
|
+
export class RenderTimeoutError extends Error {
|
|
18
|
+
readonly timeoutMs: number;
|
|
19
|
+
|
|
20
|
+
constructor(timeoutMs: number, context?: string) {
|
|
21
|
+
const message = context
|
|
22
|
+
? `Render timeout after ${timeoutMs}ms: ${context}`
|
|
23
|
+
: `Render timeout after ${timeoutMs}ms`;
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'RenderTimeoutError';
|
|
26
|
+
this.timeoutMs = timeoutMs;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Result of createRenderTimeout — an AbortSignal that fires after
|
|
32
|
+
* the given duration, plus a cancel function to clear the timer
|
|
33
|
+
* when the render completes normally.
|
|
34
|
+
*/
|
|
35
|
+
export interface RenderTimeout {
|
|
36
|
+
/** AbortSignal that aborts after timeoutMs. */
|
|
37
|
+
signal: AbortSignal;
|
|
38
|
+
/** Cancel the timeout timer. Call this when the render completes. */
|
|
39
|
+
cancel: () => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create a render timeout that aborts after the given duration.
|
|
44
|
+
*
|
|
45
|
+
* Returns an AbortSignal and a cancel function. The signal fires
|
|
46
|
+
* with a RenderTimeoutError as the abort reason after `timeoutMs`.
|
|
47
|
+
* Call `cancel()` when the render completes to prevent the timeout
|
|
48
|
+
* from firing.
|
|
49
|
+
*
|
|
50
|
+
* If an existing `parentSignal` is provided, the returned signal
|
|
51
|
+
* aborts when either the parent signal or the timeout fires —
|
|
52
|
+
* whichever comes first.
|
|
53
|
+
*/
|
|
54
|
+
export function createRenderTimeout(timeoutMs: number, parentSignal?: AbortSignal): RenderTimeout {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const reason = new RenderTimeoutError(timeoutMs, 'RSC stream read timed out');
|
|
57
|
+
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
controller.abort(reason);
|
|
60
|
+
}, timeoutMs);
|
|
61
|
+
|
|
62
|
+
// If there's a parent signal (e.g. request abort), chain it
|
|
63
|
+
if (parentSignal) {
|
|
64
|
+
if (parentSignal.aborted) {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
controller.abort(parentSignal.reason);
|
|
67
|
+
} else {
|
|
68
|
+
parentSignal.addEventListener(
|
|
69
|
+
'abort',
|
|
70
|
+
() => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
controller.abort(parentSignal.reason);
|
|
73
|
+
},
|
|
74
|
+
{ once: true }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
signal: controller.signal,
|
|
81
|
+
cancel: () => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Race a promise against a timeout. Rejects with RenderTimeoutError
|
|
89
|
+
* if the promise does not resolve within `timeoutMs`.
|
|
90
|
+
*
|
|
91
|
+
* Used to guard individual `rscReader.read()` calls inside pullLoop.
|
|
92
|
+
*/
|
|
93
|
+
export function withTimeout<T>(
|
|
94
|
+
promise: Promise<T>,
|
|
95
|
+
timeoutMs: number,
|
|
96
|
+
context?: string
|
|
97
|
+
): Promise<T> {
|
|
98
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
99
|
+
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
100
|
+
timer = setTimeout(() => {
|
|
101
|
+
reject(new RenderTimeoutError(timeoutMs, context));
|
|
102
|
+
}, timeoutMs);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
106
|
+
clearTimeout(timer!);
|
|
107
|
+
});
|
|
108
|
+
}
|