@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
package/src/server/index.ts
CHANGED
|
@@ -7,20 +7,18 @@
|
|
|
7
7
|
export type { AccessContext } from './types';
|
|
8
8
|
export type { MiddlewareContext } from './types';
|
|
9
9
|
export type { RouteContext } from './types';
|
|
10
|
-
export type { Metadata, MetadataRoute } from './types';
|
|
10
|
+
export type { Metadata, MetadataRoute, MetadataHandler, MetadataResult } from './types';
|
|
11
11
|
|
|
12
|
-
// Request Context — ALS-backed
|
|
12
|
+
// Request Context — ALS-backed accessors (all sync)
|
|
13
|
+
// Prefer defineSearchParams().get() and defineSegmentParams().get() for typed access.
|
|
13
14
|
// Design doc: design/04-authorization.md §"AccessContext does not include cookies or headers"
|
|
14
|
-
|
|
15
|
-
export {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
getSegmentParams,
|
|
22
|
-
} from './request-context';
|
|
23
|
-
export type { ReadonlyHeaders, RequestCookies, CookieOptions } from './request-context';
|
|
15
|
+
export { getHeaders, getSegmentParams } from './request-context';
|
|
16
|
+
export type { ReadonlyHeaders } from './request-context';
|
|
17
|
+
|
|
18
|
+
// Cookie Context — ALS-backed getCookieJar() escape hatch
|
|
19
|
+
// Design doc: design/29-cookies.md
|
|
20
|
+
export { getCookieJar } from './cookie-context';
|
|
21
|
+
export type { RequestCookies, CookieOptions, SetCookieOptions } from './cookie-context';
|
|
24
22
|
|
|
25
23
|
// Runtime primitives
|
|
26
24
|
export {
|
|
@@ -52,8 +50,9 @@ export type {
|
|
|
52
50
|
ValidationErrors,
|
|
53
51
|
} from './action-client';
|
|
54
52
|
|
|
55
|
-
// FormData Preprocessing
|
|
56
|
-
|
|
53
|
+
// FormData Preprocessing — internal only, not part of public API.
|
|
54
|
+
// Schema libraries (Zod z.coerce.*, Valibot v.transform(), ArkType morphs)
|
|
55
|
+
// handle form coercion. Kept for action-client/action-handler internals.
|
|
57
56
|
|
|
58
57
|
// Form Flash (no-JS error round-trip)
|
|
59
58
|
export { getFormFlash } from './form-flash';
|
package/src/server/internal.ts
CHANGED
|
@@ -13,12 +13,18 @@
|
|
|
13
13
|
|
|
14
14
|
// ── Request Context internals ────────────────────────────────────────────
|
|
15
15
|
export {
|
|
16
|
+
getSearchParams,
|
|
17
|
+
getSegmentParams,
|
|
18
|
+
getHeader,
|
|
16
19
|
setSegmentParams,
|
|
17
20
|
runWithRequestContext,
|
|
18
21
|
setMutableCookieContext,
|
|
19
22
|
markResponseFlushed,
|
|
20
|
-
getSetCookieHeaders,
|
|
21
23
|
} from './request-context.js';
|
|
24
|
+
export { getCookie, getSetCookieHeaders } from './cookie-context.js';
|
|
25
|
+
|
|
26
|
+
// ── Form-data coercion (internal) ────────────────────────────────────────
|
|
27
|
+
export { coerce } from './form-data.js';
|
|
22
28
|
|
|
23
29
|
// ── Signal classes (instanceof targets for pipeline catch logic) ──────────
|
|
24
30
|
export { DenySignal, RedirectSignal, RenderError } from './primitives.js';
|
|
@@ -60,6 +66,7 @@ export type {
|
|
|
60
66
|
TreeBuilderConfig,
|
|
61
67
|
TreeBuildResult,
|
|
62
68
|
LoadedModule,
|
|
69
|
+
LoadedComponent,
|
|
63
70
|
ModuleLoader,
|
|
64
71
|
AccessGateProps,
|
|
65
72
|
SlotAccessGateProps,
|
|
@@ -157,8 +164,8 @@ export {
|
|
|
157
164
|
warnSlowSlotWithoutSuspense,
|
|
158
165
|
setViteServer,
|
|
159
166
|
WarningId,
|
|
160
|
-
} from '
|
|
161
|
-
export type { DevWarningConfig } from '
|
|
167
|
+
} from '../dev-tools/warnings.js';
|
|
168
|
+
export type { DevWarningConfig } from '../dev-tools/warnings.js';
|
|
162
169
|
|
|
163
170
|
// ── Route Handler ────────────────────────────────────────────────────────
|
|
164
171
|
export { handleRouteRequest, resolveAllowedMethods } from './route-handler.js';
|
package/src/server/logger.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { getTraceStore } from './tracing.js';
|
|
12
12
|
import { createDefaultLogger } from './default-logger.js';
|
|
13
|
+
import { isDevMode } from './debug.js';
|
|
13
14
|
|
|
14
15
|
// ─── Logger Interface ─────────────────────────────────────────────────────
|
|
15
16
|
|
|
@@ -156,3 +157,25 @@ export function logSwrRefetchFailed(data: { cacheKey: string; error: unknown }):
|
|
|
156
157
|
export function logCacheMiss(data: { cacheKey: string }): void {
|
|
157
158
|
_logger.debug('timber.cache MISS', withTraceContext(data));
|
|
158
159
|
}
|
|
160
|
+
|
|
161
|
+
// ─── Swallow Helper ───────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Log an intentionally swallowed error. Provides observability into catch
|
|
165
|
+
* blocks that are deliberately empty — the error is consumed, never rethrown.
|
|
166
|
+
*
|
|
167
|
+
* Default level: `warn` in dev (so the overlay surfaces patterns), `debug`
|
|
168
|
+
* in production (low noise unless TIMBER_DEBUG is set). Pass `opts.level`
|
|
169
|
+
* to override.
|
|
170
|
+
*
|
|
171
|
+
* **Infallible** — swallow() itself never throws, even if the logger is
|
|
172
|
+
* broken. A thrown swallow would turn a benign catch into a crash.
|
|
173
|
+
*/
|
|
174
|
+
export function swallow(err: unknown, reason: string, opts?: { level?: 'debug' | 'warn' }): void {
|
|
175
|
+
try {
|
|
176
|
+
const level = opts?.level ?? (isDevMode() ? 'warn' : 'debug');
|
|
177
|
+
_logger[level](`swallowed: ${reason}`, withTraceContext({ error: err }));
|
|
178
|
+
} catch {
|
|
179
|
+
// swallow() must never throw.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -57,3 +57,47 @@ export async function runMiddlewareChain(
|
|
|
57
57
|
}
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
// ─── Per-Request Middleware Bypass ─────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Per-request marker for synthetic re-render requests that should NOT
|
|
65
|
+
* re-execute `middleware.ts`. The action-dispatch wrapper runs middleware
|
|
66
|
+
* once on the inbound action POST; when validation fails on the no-JS
|
|
67
|
+
* path, it builds a synthetic GET that flows through the normal pipeline
|
|
68
|
+
* to render the page with `getFormFlash()` data. Without this marker, the
|
|
69
|
+
* pipeline would run middleware a second time on that synthetic GET.
|
|
70
|
+
*
|
|
71
|
+
* The set is keyed by the synthetic Request object itself, so the entry
|
|
72
|
+
* lives exactly as long as the request and is garbage-collected with it.
|
|
73
|
+
* Cannot be set or detected by user code — there is no header, no URL
|
|
74
|
+
* parameter, nothing on the wire that an attacker could spoof.
|
|
75
|
+
*
|
|
76
|
+
* See TIM-871.
|
|
77
|
+
*
|
|
78
|
+
* @internal — framework use only.
|
|
79
|
+
*/
|
|
80
|
+
const middlewareBypassRequests = new WeakSet<Request>();
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Mark a request so the pipeline skips its middleware phase.
|
|
84
|
+
*
|
|
85
|
+
* Used by `wrap-action-dispatch.ts` for the no-JS form-rerender path.
|
|
86
|
+
*
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
export function markRequestBypassMiddleware(req: Request): void {
|
|
90
|
+
middlewareBypassRequests.add(req);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check whether a request was marked to bypass middleware.
|
|
95
|
+
*
|
|
96
|
+
* Called by `handleRequest` in pipeline-phases.ts before invoking the
|
|
97
|
+
* middleware phase. Returns false for any request not explicitly marked.
|
|
98
|
+
*
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
export function shouldBypassMiddleware(req: Request): boolean {
|
|
102
|
+
return middlewareBypassRequests.has(req);
|
|
103
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node.js native stream transforms for SSR HTML post-processing.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* streams
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Node.js `Transform` wrappers around the pure helpers in
|
|
5
|
+
* `html-injector-core.ts`. Used on Node.js / Bun where C++-backed
|
|
6
|
+
* native streams are significantly faster than the JS-implemented
|
|
7
|
+
* Web Streams. The Web `TransformStream` equivalents live in
|
|
8
|
+
* `html-injectors.ts` and wrap the same core — keep the two files
|
|
9
|
+
* byte-for-byte equivalent in behavior. If you fix a streaming bug
|
|
10
|
+
* here, it almost certainly belongs in the core (`html-injector-core.ts`),
|
|
11
|
+
* not in this wrapper.
|
|
11
12
|
*
|
|
12
13
|
* Architecture:
|
|
13
14
|
* renderToPipeableStream → pipe(errorHandler) → pipe(headInjector)
|
|
@@ -20,11 +21,21 @@
|
|
|
20
21
|
import { Transform } from 'node:stream';
|
|
21
22
|
import { createGzip, constants } from 'node:zlib';
|
|
22
23
|
|
|
24
|
+
import {
|
|
25
|
+
BufferAggregator,
|
|
26
|
+
FlightInjectorCore,
|
|
27
|
+
SUFFIX_BYTES,
|
|
28
|
+
createInjectorState,
|
|
29
|
+
createSuffixState,
|
|
30
|
+
injectorFlush,
|
|
31
|
+
processInjectorChunk,
|
|
32
|
+
processSuffixChunk,
|
|
33
|
+
suffixFlush,
|
|
34
|
+
} from './html-injector-core.js';
|
|
35
|
+
import { logStreamingError } from './logger.js';
|
|
36
|
+
|
|
23
37
|
// ─── Buffered Transform ──────────────────────────────────────────────────────
|
|
24
38
|
|
|
25
|
-
/**
|
|
26
|
-
* Options for the Node.js buffered transform.
|
|
27
|
-
*/
|
|
28
39
|
export interface NodeBufferedTransformOptions {
|
|
29
40
|
/**
|
|
30
41
|
* Flush synchronously once the buffer reaches this many bytes.
|
|
@@ -35,51 +46,44 @@ export interface NodeBufferedTransformOptions {
|
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
/**
|
|
38
|
-
* Node.js Transform that buffers incoming chunks and coalesces them
|
|
49
|
+
* Node.js `Transform` that buffers incoming chunks and coalesces them
|
|
39
50
|
* within a single event loop tick.
|
|
40
51
|
*
|
|
41
|
-
* Equivalent to createBufferedTransformStream
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* could see chunk boundaries in the middle of HTML tags or attributes.
|
|
45
|
-
*
|
|
46
|
-
* This transform collects all chunks that arrive in the same tick and
|
|
47
|
-
* emits them as a single concatenated Buffer on the next `setImmediate`.
|
|
52
|
+
* Equivalent to `createBufferedTransformStream` in `html-injectors.ts` —
|
|
53
|
+
* the actual buffer math lives in `BufferAggregator`. This wrapper only
|
|
54
|
+
* owns the `setImmediate` scheduling and the push to the Node `Transform`.
|
|
48
55
|
*
|
|
49
|
-
* **Not a polling loop.**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* Inspired by Next.js `createBufferedTransformStream`.
|
|
56
|
+
* **Not a polling loop.** Single-shot `setImmediate` per flush cycle —
|
|
57
|
+
* no recursive scheduling. See design/02 §"No Polling".
|
|
53
58
|
*/
|
|
54
59
|
export function createNodeBufferedTransform(options: NodeBufferedTransformOptions = {}): Transform {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
let bufferedChunks: Buffer[] = [];
|
|
58
|
-
let bufferByteLength = 0;
|
|
60
|
+
const buffer = new BufferAggregator(options.maxBufferByteLength ?? Infinity);
|
|
59
61
|
let pendingImmediate: ReturnType<typeof setImmediate> | null = null;
|
|
60
62
|
|
|
63
|
+
const flushBuffer = () => {
|
|
64
|
+
const merged = buffer.drain();
|
|
65
|
+
if (merged) transform.push(merged);
|
|
66
|
+
};
|
|
67
|
+
|
|
61
68
|
const transform = new Transform({
|
|
62
69
|
transform(chunk: Buffer, _encoding, callback) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (bufferByteLength >= maxBufferByteLength) {
|
|
67
|
-
// Synchronous flush — buffer is too large to hold
|
|
70
|
+
const overCap = buffer.append(chunk);
|
|
71
|
+
if (overCap) {
|
|
72
|
+
// Buffer too large to hold — flush synchronously now.
|
|
68
73
|
flushBuffer();
|
|
69
74
|
} else if (!pendingImmediate) {
|
|
70
75
|
// Schedule a deferred flush for end of this tick.
|
|
71
|
-
// Single-shot setImmediate — NOT a recursive loop.
|
|
72
|
-
//
|
|
76
|
+
// Single-shot setImmediate — NOT a recursive loop. See design/02
|
|
77
|
+
// §"No Polling".
|
|
73
78
|
pendingImmediate = setImmediate(() => {
|
|
74
79
|
pendingImmediate = null;
|
|
75
80
|
flushBuffer();
|
|
76
81
|
});
|
|
77
82
|
}
|
|
78
|
-
|
|
79
83
|
callback();
|
|
80
84
|
},
|
|
81
85
|
flush(callback) {
|
|
82
|
-
// Cancel any pending deferred flush and flush synchronously
|
|
86
|
+
// Cancel any pending deferred flush and flush synchronously.
|
|
83
87
|
if (pendingImmediate) {
|
|
84
88
|
clearImmediate(pendingImmediate);
|
|
85
89
|
pendingImmediate = null;
|
|
@@ -89,94 +93,52 @@ export function createNodeBufferedTransform(options: NodeBufferedTransformOption
|
|
|
89
93
|
},
|
|
90
94
|
});
|
|
91
95
|
|
|
92
|
-
function flushBuffer() {
|
|
93
|
-
if (bufferedChunks.length === 0) return;
|
|
94
|
-
|
|
95
|
-
const merged = Buffer.concat(bufferedChunks, bufferByteLength);
|
|
96
|
-
bufferedChunks = [];
|
|
97
|
-
bufferByteLength = 0;
|
|
98
|
-
transform.push(merged);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
96
|
return transform;
|
|
102
97
|
}
|
|
103
98
|
|
|
104
|
-
// ─── Injection Transforms ────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
import { createMachine } from '../utils/state-machine.js';
|
|
107
|
-
import { flightChunkScript } from './flight-scripts.js';
|
|
108
|
-
import {
|
|
109
|
-
flightInjectionTransitions,
|
|
110
|
-
isHtmlDone,
|
|
111
|
-
isPullDone,
|
|
112
|
-
type FlightInjectionState,
|
|
113
|
-
type FlightInjectionEvent,
|
|
114
|
-
} from './flight-injection-state.js';
|
|
115
|
-
import { withTimeout, RenderTimeoutError } from './render-timeout.js';
|
|
116
|
-
import { logStreamingError } from './logger.js';
|
|
117
|
-
|
|
118
99
|
// ─── Move Suffix Transform ───────────────────────────────────────────────────
|
|
119
100
|
|
|
120
|
-
const SUFFIX = '</body></html>';
|
|
121
|
-
const SUFFIX_BUF = Buffer.from(SUFFIX, 'utf-8');
|
|
122
|
-
|
|
123
101
|
/**
|
|
124
|
-
* Node.js Transform that moves `</body></html>` to the end of the stream.
|
|
102
|
+
* Node.js `Transform` that moves `</body></html>` to the end of the stream.
|
|
125
103
|
*
|
|
126
|
-
* Equivalent to createMoveSuffixStream
|
|
127
|
-
*
|
|
128
|
-
*
|
|
104
|
+
* Equivalent to `createMoveSuffixStream` in `html-injectors.ts`. Strips
|
|
105
|
+
* the suffix on first sight and re-emits it from `flush()`. If the
|
|
106
|
+
* suffix never appeared in the input, it is appended anyway so output
|
|
107
|
+
* is well-formed.
|
|
129
108
|
*/
|
|
130
109
|
export function createNodeMoveSuffixTransform(): Transform {
|
|
131
|
-
|
|
110
|
+
const state = createSuffixState();
|
|
132
111
|
|
|
133
112
|
return new Transform({
|
|
134
113
|
transform(chunk: Buffer, _encoding, callback) {
|
|
135
|
-
|
|
114
|
+
const result = processSuffixChunk(state, chunk);
|
|
115
|
+
if (result.kind === 'passthrough' || result.kind === 'noSuffix') {
|
|
136
116
|
this.push(chunk);
|
|
137
117
|
callback();
|
|
138
118
|
return;
|
|
139
119
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const idx = text.indexOf(SUFFIX);
|
|
143
|
-
if (idx === -1) {
|
|
144
|
-
this.push(chunk);
|
|
145
|
-
callback();
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
foundSuffix = true;
|
|
150
|
-
|
|
151
|
-
// If the entire chunk is exactly the suffix, skip it
|
|
152
|
-
if (chunk.byteLength === SUFFIX_BUF.byteLength) {
|
|
153
|
-
callback();
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Emit content before the suffix
|
|
158
|
-
const before = text.slice(0, idx);
|
|
159
|
-
const after = text.slice(idx + SUFFIX.length);
|
|
160
|
-
if (before) this.push(Buffer.from(before, 'utf-8'));
|
|
161
|
-
if (after) this.push(Buffer.from(after, 'utf-8'));
|
|
120
|
+
if (result.before) this.push(Buffer.from(result.before));
|
|
121
|
+
if (result.after) this.push(Buffer.from(result.after));
|
|
162
122
|
callback();
|
|
163
123
|
},
|
|
164
124
|
flush(callback) {
|
|
165
|
-
|
|
166
|
-
this.push(SUFFIX_BUF);
|
|
125
|
+
this.push(Buffer.from(suffixFlush(state)));
|
|
167
126
|
callback();
|
|
168
127
|
},
|
|
169
128
|
});
|
|
170
129
|
}
|
|
171
130
|
|
|
131
|
+
// Re-export for tests / consumers that need the suffix bytes constant.
|
|
132
|
+
export { SUFFIX_BYTES };
|
|
133
|
+
|
|
172
134
|
// ─── Head Injection ──────────────────────────────────────────────────────────
|
|
173
135
|
|
|
174
136
|
/**
|
|
175
|
-
* Node.js Transform that injects HTML content before
|
|
137
|
+
* Node.js `Transform` that injects HTML content before `</head>`.
|
|
176
138
|
*
|
|
177
|
-
* Equivalent to injectHead
|
|
139
|
+
* Equivalent to `injectHead` in `html-injectors.ts`. Streams chunks
|
|
178
140
|
* through immediately, keeping only a small trailing buffer to handle
|
|
179
|
-
*
|
|
141
|
+
* `</head>` split across chunk boundaries.
|
|
180
142
|
*/
|
|
181
143
|
export function createNodeHeadInjector(headHtml: string): Transform {
|
|
182
144
|
if (!headHtml) {
|
|
@@ -187,41 +149,21 @@ export function createNodeHeadInjector(headHtml: string): Transform {
|
|
|
187
149
|
});
|
|
188
150
|
}
|
|
189
151
|
|
|
190
|
-
const
|
|
191
|
-
const tailLen = target.length - 1;
|
|
192
|
-
let injected = false;
|
|
193
|
-
let tail = '';
|
|
152
|
+
const state = createInjectorState({ content: headHtml, targetTag: '</head>' });
|
|
194
153
|
|
|
195
154
|
return new Transform({
|
|
196
155
|
transform(chunk: Buffer, _encoding, callback) {
|
|
197
|
-
|
|
156
|
+
const result = processInjectorChunk(state, chunk);
|
|
157
|
+
if (result.kind === 'passthrough') {
|
|
198
158
|
callback(null, chunk);
|
|
199
159
|
return;
|
|
200
160
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const tagIndex = text.indexOf(target);
|
|
204
|
-
|
|
205
|
-
if (tagIndex !== -1) {
|
|
206
|
-
const before = text.slice(0, tagIndex);
|
|
207
|
-
const after = text.slice(tagIndex);
|
|
208
|
-
this.push(Buffer.from(before + headHtml + after, 'utf-8'));
|
|
209
|
-
injected = true;
|
|
210
|
-
tail = '';
|
|
211
|
-
callback();
|
|
212
|
-
} else {
|
|
213
|
-
const safeEnd = Math.max(0, text.length - tailLen);
|
|
214
|
-
if (safeEnd > 0) {
|
|
215
|
-
this.push(Buffer.from(text.slice(0, safeEnd), 'utf-8'));
|
|
216
|
-
}
|
|
217
|
-
tail = text.slice(safeEnd);
|
|
218
|
-
callback();
|
|
219
|
-
}
|
|
161
|
+
if (result.bytes) this.push(Buffer.from(result.bytes));
|
|
162
|
+
callback();
|
|
220
163
|
},
|
|
221
164
|
flush(callback) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
165
|
+
const leftover = injectorFlush(state);
|
|
166
|
+
if (leftover) this.push(Buffer.from(leftover));
|
|
225
167
|
callback();
|
|
226
168
|
},
|
|
227
169
|
});
|
|
@@ -229,35 +171,37 @@ export function createNodeHeadInjector(headHtml: string): Transform {
|
|
|
229
171
|
|
|
230
172
|
// ─── RSC Flight Injection ────────────────────────────────────────────────────
|
|
231
173
|
|
|
232
|
-
/**
|
|
233
|
-
* Node.js Transform that merges RSC script tags into the HTML stream.
|
|
234
|
-
*
|
|
235
|
-
* Reads RSC chunks from the provided ReadableStream and injects them
|
|
236
|
-
* as `<script>` tags between HTML chunks. Scripts are buffered in
|
|
237
|
-
* pending[] and only drained from transform() (after a complete HTML
|
|
238
|
-
* chunk) or flush() — never pushed directly from the pull loop.
|
|
239
|
-
*
|
|
240
|
-
* Suffix stripping (</body></html>) is handled upstream by
|
|
241
|
-
* createNodeMoveSuffixTransform. This transform only interleaves
|
|
242
|
-
* RSC scripts at safe chunk boundaries.
|
|
243
|
-
*
|
|
244
|
-
* The RSC stream is a Web ReadableStream (from the tee'd RSC Flight
|
|
245
|
-
* stream). We read from it using the Web API — this is the one bridge
|
|
246
|
-
* point between Web Streams and Node.js streams in the pipeline.
|
|
247
|
-
*/
|
|
248
174
|
/**
|
|
249
175
|
* Options for the Node.js flight injector.
|
|
250
176
|
*/
|
|
251
177
|
export interface NodeFlightInjectorOptions {
|
|
252
178
|
/**
|
|
253
|
-
* Timeout in milliseconds for individual RSC stream reads.
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
179
|
+
* Timeout in milliseconds for individual RSC stream reads. If a single
|
|
180
|
+
* `rscReader.read()` call does not resolve within this duration, the
|
|
181
|
+
* read is aborted and the stream errors with a `RenderTimeoutError`.
|
|
182
|
+
* Default: 30000 (30s).
|
|
257
183
|
*/
|
|
258
184
|
renderTimeoutMs?: number;
|
|
259
185
|
}
|
|
260
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Node.js `Transform` that merges RSC script tags into the HTML stream.
|
|
189
|
+
*
|
|
190
|
+
* Equivalent to `createFlightInjectionTransform` in `html-injectors.ts`.
|
|
191
|
+
* The state machine, pending queue, and pull loop all live in
|
|
192
|
+
* `FlightInjectorCore` — this wrapper only shuffles bytes between the
|
|
193
|
+
* core and the Node `Transform.push()` API.
|
|
194
|
+
*
|
|
195
|
+
* Suffix stripping (`</body></html>`) is handled upstream by
|
|
196
|
+
* `createNodeMoveSuffixTransform`. RSC scripts are buffered in
|
|
197
|
+
* `core.pending[]` and only drained from `transform()` (after a complete
|
|
198
|
+
* HTML chunk) or `flush()` — never mid-tag. See design/02
|
|
199
|
+
* §"Scripts at Chunk Boundaries Only".
|
|
200
|
+
*
|
|
201
|
+
* The RSC stream is a Web `ReadableStream` (from the tee'd RSC Flight
|
|
202
|
+
* stream). The core reads from it using the Web API — this is the one
|
|
203
|
+
* bridge point between Web Streams and Node.js streams in the pipeline.
|
|
204
|
+
*/
|
|
261
205
|
export function createNodeFlightInjector(
|
|
262
206
|
rscStream: ReadableStream<Uint8Array> | undefined,
|
|
263
207
|
options?: NodeFlightInjectorOptions
|
|
@@ -270,136 +214,53 @@ export function createNodeFlightInjector(
|
|
|
270
214
|
});
|
|
271
215
|
}
|
|
272
216
|
|
|
273
|
-
const
|
|
274
|
-
const rscReader = rscStream.getReader();
|
|
275
|
-
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
217
|
+
const core = new FlightInjectorCore(rscStream, options?.renderTimeoutMs);
|
|
276
218
|
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// Stored promise from pullLoop — awaited in flush() via .then()
|
|
283
|
-
// instead of polling. See design/02 §"No Polling".
|
|
284
|
-
let pullPromise: Promise<void> | null = null;
|
|
285
|
-
|
|
286
|
-
// RSC script chunks waiting to be drained at a safe boundary.
|
|
287
|
-
// pullLoop buffers here; transform() and flush() drain.
|
|
288
|
-
// RSC script chunks waiting to be drained at a safe boundary.
|
|
289
|
-
// pullLoop buffers here; transform(), flush(), and pullLoop's
|
|
290
|
-
// post-yield drain all consume from this buffer.
|
|
291
|
-
const pending: Buffer[] = [];
|
|
292
|
-
async function pullLoop(): Promise<void> {
|
|
293
|
-
// Yield once so the first transform() call can process the shell
|
|
294
|
-
// HTML chunk before we start reading RSC data.
|
|
295
|
-
await new Promise<void>((r) => setImmediate(r));
|
|
296
|
-
try {
|
|
297
|
-
for (;;) {
|
|
298
|
-
// Guard each RSC read with a timeout so a permanently hung
|
|
299
|
-
// RSC stream eventually aborts instead of blocking forever.
|
|
300
|
-
// See design/02-rendering-pipeline.md §"Streaming Constraints".
|
|
301
|
-
const readPromise = rscReader.read();
|
|
302
|
-
const { done, value } =
|
|
303
|
-
timeoutMs > 0
|
|
304
|
-
? await withTimeout(readPromise, timeoutMs, 'RSC stream read timed out')
|
|
305
|
-
: await readPromise;
|
|
306
|
-
if (done) {
|
|
307
|
-
machine.send({ type: 'PULL_DONE' });
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
const decoded = decoder.decode(value, { stream: true });
|
|
311
|
-
const scriptBuf = Buffer.from(flightChunkScript(decoded), 'utf-8');
|
|
312
|
-
// Buffer the script — drained by the next transform() call,
|
|
313
|
-
// flush(), or by the scheduled drain below.
|
|
314
|
-
pending.push(scriptBuf);
|
|
315
|
-
// Yield between reads so HTML chunks get priority in the event
|
|
316
|
-
// loop — but only while HTML is still streaming. Once flush()
|
|
317
|
-
// fires, read without yielding to drain remaining RSC data.
|
|
318
|
-
if (!isHtmlDone(machine.state)) {
|
|
319
|
-
await new Promise<void>((r) => setImmediate(r));
|
|
320
|
-
// After yielding, if no transform() call drained the buffer
|
|
321
|
-
// (i.e., no new HTML chunk arrived), drain now. This ensures
|
|
322
|
-
// RSC flight data reaches the client at shell-flush time —
|
|
323
|
-
// without this, hydration blocks on createFromReadableStream
|
|
324
|
-
// waiting for data that's stuck in pending[].
|
|
325
|
-
// This is safe: we're between event loop ticks, so no
|
|
326
|
-
// transform() call is mid-execution (no mid-tag risk).
|
|
327
|
-
if (pending.length > 0) {
|
|
328
|
-
drainPending();
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
} catch (err) {
|
|
333
|
-
// On timeout, cancel the RSC reader to release resources.
|
|
334
|
-
if (err instanceof RenderTimeoutError) {
|
|
335
|
-
rscReader.cancel(err).catch(() => {});
|
|
336
|
-
}
|
|
337
|
-
machine.send({ type: 'PULL_ERROR', error: err });
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/** Drain all buffered RSC script chunks to the transform output. */
|
|
342
|
-
function drainPending(): void {
|
|
343
|
-
while (pending.length > 0) {
|
|
344
|
-
transform.push(pending.shift()!);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// No bootstrap script here — the init script is in <head> via
|
|
349
|
-
// flightInitScript() (see flight-scripts.ts). This ensures __timber_f
|
|
350
|
-
// exists before any chunk scripts execute.
|
|
219
|
+
const drain = (): void => {
|
|
220
|
+
while (core.pending.length > 0) transform.push(Buffer.from(core.pending.shift()!));
|
|
221
|
+
};
|
|
351
222
|
|
|
352
223
|
const transform = new Transform({
|
|
353
224
|
transform(chunk: Buffer, _encoding, callback) {
|
|
354
|
-
const
|
|
355
|
-
if (
|
|
356
|
-
machine.send({ type: 'FIRST_CHUNK' });
|
|
357
|
-
}
|
|
225
|
+
const wasInit = core.isInit;
|
|
226
|
+
if (wasInit) core.notifyFirstChunk();
|
|
358
227
|
|
|
359
228
|
// Emit the HTML chunk, then drain any buffered RSC scripts.
|
|
360
229
|
// Scripts always come AFTER a complete HTML chunk — never mid-tag.
|
|
361
|
-
// The buffered transform upstream (TIM-528) ensures
|
|
362
|
-
//
|
|
363
|
-
// by createNodeMoveSuffixTransform (TIM-530).
|
|
230
|
+
// The buffered transform upstream (TIM-528) ensures coherent chunks.
|
|
231
|
+
// Suffix stripping is upstream via createNodeMoveSuffixTransform (TIM-530).
|
|
364
232
|
transform.push(chunk);
|
|
365
|
-
|
|
233
|
+
drain();
|
|
366
234
|
|
|
367
235
|
// Start the pull loop on the first HTML chunk.
|
|
368
|
-
if (
|
|
369
|
-
pullPromise = pullLoop();
|
|
370
|
-
}
|
|
236
|
+
if (wasInit) core.ensurePullLoop(drain);
|
|
371
237
|
callback();
|
|
372
238
|
},
|
|
373
239
|
flush(callback) {
|
|
374
|
-
// All HTML chunks have been emitted.
|
|
375
|
-
|
|
376
|
-
// isHtmlDone() now returns true.
|
|
377
|
-
machine.send({ type: 'HTML_DONE' });
|
|
240
|
+
// All HTML chunks have been emitted. Pull loop stops yielding.
|
|
241
|
+
core.notifyHtmlDone();
|
|
378
242
|
|
|
379
243
|
const finish = () => {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (
|
|
383
|
-
const err = machine.state.error;
|
|
244
|
+
drain();
|
|
245
|
+
const err = core.terminalError;
|
|
246
|
+
if (err !== null) {
|
|
384
247
|
transform.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
385
248
|
return;
|
|
386
249
|
}
|
|
387
250
|
callback();
|
|
388
251
|
};
|
|
389
252
|
|
|
390
|
-
if (isPullDone
|
|
253
|
+
if (core.isPullDone) {
|
|
391
254
|
finish();
|
|
392
255
|
return;
|
|
393
256
|
}
|
|
394
|
-
// Wait
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
machine.send({ type: 'PULL_ERROR', error: err });
|
|
402
|
-
finish();
|
|
257
|
+
// Wait on the pull-loop promise instead of polling. Zero CPU cost
|
|
258
|
+
// while waiting. See design/02 §"No Polling".
|
|
259
|
+
core.ensurePullLoop(drain).then(finish, (err) => {
|
|
260
|
+
// ensurePullLoop's underlying loop catches its own errors and
|
|
261
|
+
// sends PULL_ERROR — this catch is defence-in-depth for any
|
|
262
|
+
// synchronous throws inside the .then() chain itself.
|
|
263
|
+
transform.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
403
264
|
});
|
|
404
265
|
},
|
|
405
266
|
});
|
|
@@ -459,7 +320,6 @@ const COMPRESSIBLE_TYPES = new Set([
|
|
|
459
320
|
'application/xml',
|
|
460
321
|
'application/xhtml+xml',
|
|
461
322
|
'application/rss+xml',
|
|
462
|
-
'application/atom+xml',
|
|
463
323
|
'image/svg+xml',
|
|
464
324
|
]);
|
|
465
325
|
|