@timber-js/app 0.2.0-alpha.4 → 0.2.0-alpha.40
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-Ba7URUIn.js} +1 -1
- package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
- package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
- package/dist/_chunks/debug-ECi_61pb.js +108 -0
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-cookie-BmKbSyp0.js +93 -0
- package/dist/_chunks/define-cookie-BmKbSyp0.js.map +1 -0
- package/dist/_chunks/error-boundary-BAN3751q.js +211 -0
- package/dist/_chunks/error-boundary-BAN3751q.js.map +1 -0
- package/dist/_chunks/{format-CwdaB0_2.js → format-cX7wzEp2.js} +2 -2
- package/dist/_chunks/{format-CwdaB0_2.js.map → format-cX7wzEp2.js.map} +1 -1
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-D2djYaIm.js} +112 -77
- package/dist/_chunks/interception-D2djYaIm.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
- package/dist/_chunks/{request-context-CZJi4CuK.js → request-context-BxYIJM24.js} +93 -69
- package/dist/_chunks/request-context-BxYIJM24.js.map +1 -0
- package/dist/_chunks/segment-context-C6byCyZU.js +69 -0
- package/dist/_chunks/segment-context-C6byCyZU.js.map +1 -0
- package/dist/_chunks/stale-reload-C0ValzG7.js +47 -0
- package/dist/_chunks/stale-reload-C0ValzG7.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → tracing-CuXiCP5p.js} +17 -3
- package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CuXiCP5p.js.map} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BvW0TKDn.js} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-BvW0TKDn.js.map} +1 -1
- package/dist/_chunks/wrappers-C6J0nNji.js +331 -0
- package/dist/_chunks/wrappers-C6J0nNji.js.map +1 -0
- 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/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/index.d.ts +5 -2
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +88 -18
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/register-cached-function.d.ts.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.map +1 -1
- package/dist/client/error-boundary.d.ts +10 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -125
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +213 -93
- package/dist/client/index.js.map +1 -1
- package/dist/client/link.d.ts +22 -8
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +2 -2
- package/dist/client/router.d.ts +25 -3
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +23 -2
- 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/stale-reload.d.ts +15 -0
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +1 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/transition-root.d.ts +1 -1
- package/dist/client/transition-root.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +2 -2
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/codec.d.ts +21 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/cookies/define-cookie.d.ts +33 -12
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -83
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/fonts/local.d.ts +4 -2
- package/dist/fonts/local.d.ts.map +1 -1
- package/dist/index.d.ts +112 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +635 -233
- package/dist/index.js.map +1 -1
- package/dist/params/define.d.ts +76 -0
- package/dist/params/define.d.ts.map +1 -0
- package/dist/params/index.d.ts +8 -0
- package/dist/params/index.d.ts.map +1 -0
- package/dist/params/index.js +104 -0
- package/dist/params/index.js.map +1 -0
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.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/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +7 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +9 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +6 -0
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +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.js +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- 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 +6 -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/search-params/codecs.d.ts +1 -1
- package/dist/search-params/define.d.ts +153 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -5
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +3 -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/server/access-gate.d.ts +4 -0
- package/dist/server/access-gate.d.ts.map +1 -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/als-registry.d.ts +18 -4
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +2 -2
- package/dist/server/debug.d.ts +46 -15
- package/dist/server/debug.d.ts.map +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-renderer.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 +4 -0
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/flight-injection-state.d.ts +78 -0
- package/dist/server/flight-injection-state.d.ts.map +1 -0
- package/dist/server/flight-scripts.d.ts +39 -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 +5 -11
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1975 -1649
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +24 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +77 -0
- package/dist/server/node-stream-transforms.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +7 -4
- 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 +65 -38
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +7 -0
- 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 +2 -2
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +46 -3
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +6 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +9 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- 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/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +19 -12
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -3
- 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/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/state-machine.d.ts +80 -0
- package/dist/utils/state-machine.d.ts.map +1 -0
- package/package.json +17 -14
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/index.ts +5 -2
- package/src/cache/register-cached-function.ts +7 -3
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +34 -26
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +94 -90
- package/src/client/error-boundary.tsx +18 -1
- package/src/client/index.ts +10 -1
- package/src/client/link.tsx +78 -19
- package/src/client/navigation-context.ts +2 -2
- package/src/client/router.ts +105 -60
- package/src/client/rsc-fetch.ts +63 -2
- 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/stale-reload.ts +32 -6
- package/src/client/top-loader.tsx +10 -9
- package/src/client/transition-root.tsx +7 -1
- package/src/client/use-params.ts +3 -3
- package/src/client/use-query-states.ts +1 -1
- package/src/codec.ts +21 -0
- package/src/cookies/define-cookie.ts +69 -18
- package/src/fonts/css.ts +2 -1
- package/src/fonts/local.ts +7 -3
- package/src/index.ts +280 -85
- package/src/params/define.ts +260 -0
- package/src/params/index.ts +28 -0
- package/src/plugins/adapter-build.ts +6 -0
- package/src/plugins/build-manifest.ts +11 -0
- package/src/plugins/client-chunks.ts +65 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-server.ts +38 -4
- package/src/plugins/entries.ts +12 -11
- package/src/plugins/fonts.ts +171 -19
- package/src/plugins/mdx.ts +9 -5
- package/src/plugins/routing.ts +40 -14
- package/src/plugins/server-bundle.ts +32 -1
- package/src/plugins/shims.ts +1 -1
- package/src/plugins/static-build.ts +8 -4
- package/src/routing/codegen.ts +109 -88
- package/src/routing/scanner.ts +55 -6
- package/src/routing/status-file-lint.ts +2 -1
- package/src/routing/types.ts +7 -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 +1 -1
- package/src/search-params/define.ts +504 -0
- package/src/search-params/index.ts +12 -18
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/server/access-gate.tsx +40 -9
- package/src/server/action-client.ts +14 -5
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +19 -2
- package/src/server/als-registry.ts +18 -4
- package/src/server/build-manifest.ts +4 -4
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +55 -17
- package/src/server/default-logger.ts +98 -0
- package/src/server/deny-renderer.ts +2 -1
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +57 -14
- package/src/server/flight-injection-state.ts +152 -0
- package/src/server/flight-scripts.ts +59 -0
- package/src/server/flush.ts +2 -1
- package/src/server/form-data.ts +76 -0
- package/src/server/html-injectors.ts +103 -66
- package/src/server/index.ts +9 -4
- package/src/server/logger.ts +38 -35
- package/src/server/node-stream-transforms.ts +381 -0
- package/src/server/pipeline.ts +131 -39
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +112 -119
- package/src/server/route-element-builder.ts +106 -114
- package/src/server/route-handler.ts +2 -1
- package/src/server/route-matcher.ts +2 -2
- package/src/server/rsc-entry/error-renderer.ts +5 -3
- package/src/server/rsc-entry/helpers.ts +122 -3
- package/src/server/rsc-entry/index.ts +125 -49
- package/src/server/rsc-entry/rsc-payload.ts +52 -12
- package/src/server/rsc-entry/rsc-stream.ts +33 -8
- package/src/server/rsc-entry/ssr-renderer.ts +40 -13
- package/src/server/slot-resolver.ts +199 -210
- package/src/server/ssr-entry.ts +169 -17
- package/src/server/ssr-render.ts +266 -67
- package/src/server/tracing.ts +23 -0
- package/src/server/tree-builder.ts +91 -57
- package/src/server/types.ts +1 -3
- package/src/server/version-skew.ts +104 -0
- package/src/server/waituntil-bridge.ts +4 -1
- package/src/shared/merge-search-params.ts +48 -0
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +1 -1
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-B4WUeqJ-.js +0 -75
- package/dist/_chunks/debug-B4WUeqJ-.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/request-context-CZJi4CuK.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/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/cookies/index.js.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/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/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
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
* Design docs: 02-rendering-pipeline.md, 18-build-system.md §"Entry Files"
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { createMachine } from '../utils/state-machine.js';
|
|
11
|
+
import { flightChunkScript } from './flight-scripts.js';
|
|
12
|
+
import {
|
|
13
|
+
flightInjectionTransitions,
|
|
14
|
+
isSuffixStripped,
|
|
15
|
+
isHtmlDone,
|
|
16
|
+
isPullDone,
|
|
17
|
+
type FlightInjectionState,
|
|
18
|
+
type FlightInjectionEvent,
|
|
19
|
+
} from './flight-injection-state.js';
|
|
20
|
+
import { withTimeout, RenderTimeoutError } from './render-timeout.js';
|
|
21
|
+
|
|
10
22
|
/**
|
|
11
23
|
* Inject HTML content before a closing tag in the stream.
|
|
12
24
|
*
|
|
@@ -95,22 +107,6 @@ export function injectScripts(
|
|
|
95
107
|
return createInjector(stream, scriptsHtml, '</body>');
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
/**
|
|
99
|
-
* Escape a string for safe embedding inside a `<script>` tag within
|
|
100
|
-
* a JSON-encoded value.
|
|
101
|
-
*
|
|
102
|
-
* Only needs to prevent `</script>` from closing the tag early and
|
|
103
|
-
* handle U+2028/U+2029 (line/paragraph separators valid in JSON but
|
|
104
|
-
* historically problematic in JS). Since we use JSON.stringify for the
|
|
105
|
-
* outer encoding, we only escape `<` and the line separators.
|
|
106
|
-
*/
|
|
107
|
-
function htmlEscapeJsonString(str: string): string {
|
|
108
|
-
return str
|
|
109
|
-
.replace(/</g, '\\u003c')
|
|
110
|
-
.replace(/\u2028/g, '\\u2028')
|
|
111
|
-
.replace(/\u2029/g, '\\u2029');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
110
|
/**
|
|
115
111
|
* Transform an RSC Flight stream into a stream of inline `<script>` tags.
|
|
116
112
|
*
|
|
@@ -118,42 +114,42 @@ function htmlEscapeJsonString(str: string): string {
|
|
|
118
114
|
* transform) drives reads from the RSC stream on demand. No background
|
|
119
115
|
* reader, no shared mutable arrays, no race conditions.
|
|
120
116
|
*
|
|
121
|
-
* Each RSC chunk becomes
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* The first chunk emitted is the bootstrap signal [0] which the client
|
|
125
|
-
* uses to initialize its buffer.
|
|
126
|
-
*
|
|
127
|
-
* Uses JSON-encoded typed tuples matching the pattern from Next.js:
|
|
128
|
-
* [0] — bootstrap signal
|
|
129
|
-
* [1, data] — RSC Flight data chunk (UTF-8 string)
|
|
117
|
+
* Each RSC chunk becomes a `<script>self.__timber_f.push([1,"data"])</script>`.
|
|
118
|
+
* The init script (which creates __timber_f) is in `<head>` via
|
|
119
|
+
* flightInitScript() — see flight-scripts.ts.
|
|
130
120
|
*/
|
|
131
121
|
export function createInlinedRscStream(
|
|
132
|
-
rscStream: ReadableStream<Uint8Array
|
|
122
|
+
rscStream: ReadableStream<Uint8Array>,
|
|
123
|
+
renderTimeoutMs?: number
|
|
133
124
|
): ReadableStream<Uint8Array> {
|
|
134
125
|
const encoder = new TextEncoder();
|
|
135
126
|
const rscReader = rscStream.getReader();
|
|
136
127
|
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
128
|
+
const timeoutMs = renderTimeoutMs ?? 30_000;
|
|
137
129
|
|
|
138
130
|
return new ReadableStream<Uint8Array>({
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
controller.enqueue(encoder.encode(bootstrap));
|
|
143
|
-
},
|
|
131
|
+
// No bootstrap signal here — the init script is in <head> via
|
|
132
|
+
// flightInitScript() (see flight-scripts.ts). This ensures the
|
|
133
|
+
// __timber_f array exists before any chunk scripts execute.
|
|
144
134
|
async pull(controller) {
|
|
145
135
|
try {
|
|
146
|
-
const
|
|
136
|
+
const readPromise = rscReader.read();
|
|
137
|
+
const { done, value } =
|
|
138
|
+
timeoutMs > 0
|
|
139
|
+
? await withTimeout(readPromise, timeoutMs, 'RSC stream read timed out')
|
|
140
|
+
: await readPromise;
|
|
147
141
|
if (done) {
|
|
148
142
|
controller.close();
|
|
149
143
|
return;
|
|
150
144
|
}
|
|
151
145
|
if (value) {
|
|
152
146
|
const decoded = decoder.decode(value, { stream: true });
|
|
153
|
-
|
|
154
|
-
controller.enqueue(encoder.encode(`<script>self.__timber_f.push(${escaped})</script>`));
|
|
147
|
+
controller.enqueue(encoder.encode(flightChunkScript(decoded)));
|
|
155
148
|
}
|
|
156
149
|
} catch (error) {
|
|
150
|
+
if (error instanceof RenderTimeoutError) {
|
|
151
|
+
rscReader.cancel(error).catch(() => {});
|
|
152
|
+
}
|
|
157
153
|
controller.error(error);
|
|
158
154
|
}
|
|
159
155
|
},
|
|
@@ -179,10 +175,16 @@ export function createInlinedRscStream(
|
|
|
179
175
|
* scanning or depth tracking needed — the suffix removal is the
|
|
180
176
|
* structural guarantee.
|
|
181
177
|
*
|
|
178
|
+
* State machine phases:
|
|
179
|
+
* init → streaming → body-level → flushing → done
|
|
180
|
+
* └──────────────→ flushing → done
|
|
181
|
+
* (any) → error
|
|
182
|
+
*
|
|
182
183
|
* Inspired by Next.js createFlightDataInjectionTransformStream.
|
|
183
184
|
*/
|
|
184
185
|
function createFlightInjectionTransform(
|
|
185
|
-
rscScriptStream: ReadableStream<Uint8Array
|
|
186
|
+
rscScriptStream: ReadableStream<Uint8Array>,
|
|
187
|
+
renderTimeoutMs?: number
|
|
186
188
|
): TransformStream<Uint8Array, Uint8Array> {
|
|
187
189
|
const encoder = new TextEncoder();
|
|
188
190
|
const decoder = new TextDecoder();
|
|
@@ -190,39 +192,59 @@ function createFlightInjectionTransform(
|
|
|
190
192
|
const suffixBytes = encoder.encode(suffix);
|
|
191
193
|
|
|
192
194
|
const rscReader = rscScriptStream.getReader();
|
|
195
|
+
const timeoutMs = renderTimeoutMs ?? 30_000;
|
|
196
|
+
|
|
197
|
+
const machine = createMachine<FlightInjectionState, FlightInjectionEvent>({
|
|
198
|
+
initial: { phase: 'init' },
|
|
199
|
+
transitions: flightInjectionTransitions,
|
|
200
|
+
});
|
|
201
|
+
|
|
193
202
|
let pullPromise: Promise<void> | null = null;
|
|
194
|
-
let donePulling = false;
|
|
195
|
-
let pullError: unknown = null;
|
|
196
|
-
// Once the suffix is stripped, all content is body-level and
|
|
197
|
-
// scripts can safely be drained after any HTML chunk.
|
|
198
|
-
let foundSuffix = false;
|
|
199
203
|
|
|
200
204
|
// RSC script chunks waiting to be injected at the body level.
|
|
201
205
|
const pending: Uint8Array[] = [];
|
|
202
206
|
|
|
203
207
|
async function pullLoop(): Promise<void> {
|
|
204
|
-
//
|
|
205
|
-
// transform()
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
+
// Yield once so the first HTML shell chunk flows through
|
|
209
|
+
// transform() before we start reading RSC data. Uses
|
|
210
|
+
// setImmediate (check phase — end of current event loop
|
|
211
|
+
// iteration) instead of setTimeout(0) (timer phase — next
|
|
212
|
+
// iteration). Under concurrency, setTimeout(0) yields to
|
|
213
|
+
// ALL pending timer callbacks from other requests, adding
|
|
214
|
+
// 1-4ms per yield. setImmediate fires before timers.
|
|
215
|
+
// Available on both Node.js and Cloudflare Workers.
|
|
216
|
+
await new Promise<void>((r) => setImmediate(r));
|
|
208
217
|
|
|
209
218
|
try {
|
|
210
219
|
for (;;) {
|
|
211
|
-
|
|
220
|
+
// Guard each RSC read with a timeout so a permanently hung
|
|
221
|
+
// RSC stream eventually aborts. When timeoutMs <= 0, the
|
|
222
|
+
// guard is disabled. See design/02-rendering-pipeline.md
|
|
223
|
+
// §"Streaming Constraints".
|
|
224
|
+
const readPromise = rscReader.read();
|
|
225
|
+
const { done, value } =
|
|
226
|
+
timeoutMs > 0
|
|
227
|
+
? await withTimeout(readPromise, timeoutMs, 'RSC stream read timed out')
|
|
228
|
+
: await readPromise;
|
|
212
229
|
if (done) {
|
|
213
|
-
|
|
230
|
+
machine.send({ type: 'PULL_DONE' });
|
|
214
231
|
return;
|
|
215
232
|
}
|
|
216
233
|
pending.push(value);
|
|
217
234
|
// Yield between reads so HTML chunks get a chance to flow
|
|
218
|
-
// through transform() first
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
235
|
+
// through transform() first — but only while HTML is still
|
|
236
|
+
// streaming. Once flush() fires (all HTML emitted), drain
|
|
237
|
+
// remaining RSC chunks without yielding.
|
|
238
|
+
if (!isHtmlDone(machine.state)) {
|
|
239
|
+
await new Promise<void>((r) => setImmediate(r));
|
|
240
|
+
}
|
|
222
241
|
}
|
|
223
242
|
} catch (err) {
|
|
224
|
-
|
|
225
|
-
|
|
243
|
+
// On timeout, cancel the RSC reader to release resources.
|
|
244
|
+
if (err instanceof RenderTimeoutError) {
|
|
245
|
+
rscReader.cancel(err).catch(() => {});
|
|
246
|
+
}
|
|
247
|
+
machine.send({ type: 'PULL_ERROR', error: err });
|
|
226
248
|
}
|
|
227
249
|
}
|
|
228
250
|
|
|
@@ -231,21 +253,24 @@ function createFlightInjectionTransform(
|
|
|
231
253
|
while (pending.length > 0) {
|
|
232
254
|
controller.enqueue(pending.shift()!);
|
|
233
255
|
}
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
pullError = null;
|
|
237
|
-
controller.error(err);
|
|
256
|
+
if (machine.state.phase === 'error') {
|
|
257
|
+
controller.error(machine.state.error);
|
|
238
258
|
}
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
return new TransformStream<Uint8Array, Uint8Array>({
|
|
242
262
|
transform(chunk, controller) {
|
|
243
|
-
//
|
|
244
|
-
|
|
263
|
+
// Pull-based start: don't begin reading RSC until the first
|
|
264
|
+
// HTML chunk flows through. This matches Next.js's approach
|
|
265
|
+
// and ensures the shell HTML is enqueued before any RSC
|
|
266
|
+
// script tags. Without this, the pull loop starts eagerly
|
|
267
|
+
// and may read RSC data before the browser has any HTML.
|
|
268
|
+
if (machine.state.phase === 'init') {
|
|
269
|
+
machine.send({ type: 'FIRST_CHUNK' });
|
|
245
270
|
pullPromise = pullLoop();
|
|
246
271
|
}
|
|
247
272
|
|
|
248
|
-
if (
|
|
273
|
+
if (isSuffixStripped(machine.state)) {
|
|
249
274
|
// Post-suffix: everything is body-level (Suspense chunks).
|
|
250
275
|
// Emit HTML, then drain any buffered scripts.
|
|
251
276
|
controller.enqueue(chunk);
|
|
@@ -257,7 +282,7 @@ function createFlightInjectionTransform(
|
|
|
257
282
|
const text = decoder.decode(chunk, { stream: true });
|
|
258
283
|
const idx = text.indexOf(suffix);
|
|
259
284
|
if (idx !== -1) {
|
|
260
|
-
|
|
285
|
+
machine.send({ type: 'SUFFIX_FOUND' });
|
|
261
286
|
// Emit everything before the suffix (still inside <body>'s
|
|
262
287
|
// child elements — don't inject scripts here).
|
|
263
288
|
const before = text.slice(0, idx);
|
|
@@ -274,16 +299,27 @@ function createFlightInjectionTransform(
|
|
|
274
299
|
}
|
|
275
300
|
},
|
|
276
301
|
flush(controller) {
|
|
277
|
-
// HTML
|
|
302
|
+
// All HTML chunks have been emitted. Transition to flushing —
|
|
303
|
+
// the pull loop will stop yielding between RSC reads since
|
|
304
|
+
// isHtmlDone() now returns true. This eliminates ~36 macrotask
|
|
305
|
+
// yields per request (18 chunks × 2 yields each) that were the
|
|
306
|
+
// primary source of SSR overhead vs Next.js.
|
|
307
|
+
machine.send({ type: 'HTML_DONE' });
|
|
308
|
+
|
|
309
|
+
// Drain remaining RSC chunks at body level
|
|
278
310
|
const finish = () => {
|
|
279
311
|
drainPending(controller);
|
|
280
312
|
// Re-emit the suffix at the very end so HTML is well-formed
|
|
281
|
-
if (
|
|
313
|
+
if (machine.state.phase === 'done' && machine.state.hadSuffix) {
|
|
314
|
+
controller.enqueue(suffixBytes);
|
|
315
|
+
} else if (machine.state.phase === 'flushing' && machine.state.hadSuffix) {
|
|
316
|
+
// Pull was already done before flush — drainPending didn't
|
|
317
|
+
// transition, but we still need the suffix
|
|
282
318
|
controller.enqueue(suffixBytes);
|
|
283
319
|
}
|
|
284
320
|
};
|
|
285
321
|
|
|
286
|
-
if (
|
|
322
|
+
if (isPullDone(machine.state)) {
|
|
287
323
|
finish();
|
|
288
324
|
return;
|
|
289
325
|
}
|
|
@@ -314,16 +350,17 @@ function createFlightInjectionTransform(
|
|
|
314
350
|
*/
|
|
315
351
|
export function injectRscPayload(
|
|
316
352
|
htmlStream: ReadableStream<Uint8Array>,
|
|
317
|
-
rscStream: ReadableStream<Uint8Array> | undefined
|
|
353
|
+
rscStream: ReadableStream<Uint8Array> | undefined,
|
|
354
|
+
renderTimeoutMs?: number
|
|
318
355
|
): ReadableStream<Uint8Array> {
|
|
319
356
|
if (!rscStream) return htmlStream;
|
|
320
357
|
|
|
321
358
|
// Transform RSC binary stream → stream of <script> tags
|
|
322
|
-
const rscScriptStream = createInlinedRscStream(rscStream);
|
|
359
|
+
const rscScriptStream = createInlinedRscStream(rscStream, renderTimeoutMs);
|
|
323
360
|
|
|
324
361
|
// Single transform: strip </body></html>, inject RSC scripts at
|
|
325
362
|
// body level, re-emit suffix at the very end.
|
|
326
|
-
return htmlStream.pipeThrough(createFlightInjectionTransform(rscScriptStream));
|
|
363
|
+
return htmlStream.pipeThrough(createFlightInjectionTransform(rscScriptStream, renderTimeoutMs));
|
|
327
364
|
}
|
|
328
365
|
|
|
329
366
|
/**
|
package/src/server/index.ts
CHANGED
|
@@ -6,19 +6,19 @@ export type { MiddlewareContext } from './types';
|
|
|
6
6
|
export type { RouteContext } from './types';
|
|
7
7
|
export type { Metadata, MetadataRoute } from './types';
|
|
8
8
|
|
|
9
|
-
// Request Context — ALS-backed headers(), cookies(), and
|
|
9
|
+
// Request Context — ALS-backed headers(), cookies(), and rawSearchParams()
|
|
10
10
|
// Design doc: design/04-authorization.md §"AccessContext does not include cookies or headers"
|
|
11
11
|
// Design doc: design/23-search-params.md §"Server Integration"
|
|
12
12
|
export {
|
|
13
13
|
headers,
|
|
14
14
|
cookies,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
rawSearchParams,
|
|
16
|
+
rawSegmentParams,
|
|
17
|
+
setSegmentParams,
|
|
17
18
|
runWithRequestContext,
|
|
18
19
|
setMutableCookieContext,
|
|
19
20
|
markResponseFlushed,
|
|
20
21
|
getSetCookieHeaders,
|
|
21
|
-
setCookieSecrets,
|
|
22
22
|
} from './request-context';
|
|
23
23
|
export type { ReadonlyHeaders, RequestCookies, CookieOptions } from './request-context';
|
|
24
24
|
|
|
@@ -34,6 +34,7 @@ export {
|
|
|
34
34
|
waitUntil,
|
|
35
35
|
DenySignal,
|
|
36
36
|
RedirectSignal,
|
|
37
|
+
type RedirectOptions,
|
|
37
38
|
} from './primitives';
|
|
38
39
|
export type { RenderErrorDigest, WaitUntilAdapter } from './primitives';
|
|
39
40
|
export type { JsonSerializable } from './types';
|
|
@@ -221,3 +222,7 @@ export type { DevWarningConfig } from './dev-warnings';
|
|
|
221
222
|
// Design doc: design/07-routing.md §"route.ts — API Endpoints"
|
|
222
223
|
export { handleRouteRequest, resolveAllowedMethods } from './route-handler';
|
|
223
224
|
export type { RouteModule, RouteHandler, HttpMethod } from './route-handler';
|
|
225
|
+
|
|
226
|
+
// Render timeout — design doc: 02-rendering-pipeline.md §"Streaming Constraints"
|
|
227
|
+
export { RenderTimeoutError } from './render-timeout';
|
|
228
|
+
export type { RenderTimeout } from './render-timeout';
|
package/src/server/logger.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logger — structured logging with environment-aware formatting.
|
|
3
3
|
*
|
|
4
|
-
* timber.js
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* timber.js ships a DefaultLogger that writes human-readable lines to stderr
|
|
5
|
+
* in production. Users can export a custom logger from instrumentation.ts to
|
|
6
|
+
* replace it with pino, winston, or any TimberLogger-compatible object.
|
|
7
7
|
*
|
|
8
8
|
* See design/17-logging.md §"Production Logging"
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { getTraceStore } from './tracing.js';
|
|
12
|
-
import {
|
|
13
|
-
import { isDebug } from './debug.js';
|
|
12
|
+
import { createDefaultLogger } from './default-logger.js';
|
|
14
13
|
|
|
15
14
|
// ─── Logger Interface ─────────────────────────────────────────────────────
|
|
16
15
|
|
|
@@ -24,21 +23,24 @@ export interface TimberLogger {
|
|
|
24
23
|
|
|
25
24
|
// ─── Logger Registry ──────────────────────────────────────────────────────
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
// Initialize with DefaultLogger so production errors are never silent.
|
|
27
|
+
// Replaced when setLogger() is called from instrumentation.ts.
|
|
28
|
+
let _logger: TimberLogger = createDefaultLogger();
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Set the user-provided logger. Called by the instrumentation loader
|
|
31
|
-
* when it finds a `logger` export in instrumentation.ts.
|
|
32
|
+
* when it finds a `logger` export in instrumentation.ts. Replaces
|
|
33
|
+
* the DefaultLogger entirely.
|
|
32
34
|
*/
|
|
33
35
|
export function setLogger(logger: TimberLogger): void {
|
|
34
36
|
_logger = logger;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
* Get the current logger
|
|
39
|
-
*
|
|
40
|
+
* Get the current logger. Always non-null — returns DefaultLogger when
|
|
41
|
+
* no custom logger is configured.
|
|
40
42
|
*/
|
|
41
|
-
export function getLogger(): TimberLogger
|
|
43
|
+
export function getLogger(): TimberLogger {
|
|
42
44
|
return _logger;
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -71,12 +73,12 @@ export function logRequestCompleted(data: {
|
|
|
71
73
|
/** Number of concurrent in-flight requests (including this one) at completion time. */
|
|
72
74
|
concurrency?: number;
|
|
73
75
|
}): void {
|
|
74
|
-
_logger
|
|
76
|
+
_logger.info('request completed', withTraceContext(data));
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
/** Log request received. Level: debug. */
|
|
78
80
|
export function logRequestReceived(data: { method: string; path: string }): void {
|
|
79
|
-
_logger
|
|
81
|
+
_logger.debug('request received', withTraceContext(data));
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
/** Log a slow request warning. Level: warn. */
|
|
@@ -88,7 +90,7 @@ export function logSlowRequest(data: {
|
|
|
88
90
|
/** Number of concurrent in-flight requests at the time the slow request completed. */
|
|
89
91
|
concurrency?: number;
|
|
90
92
|
}): void {
|
|
91
|
-
_logger
|
|
93
|
+
_logger.warn('slow request exceeded threshold', withTraceContext(data));
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/** Log middleware short-circuit. Level: debug. */
|
|
@@ -97,54 +99,55 @@ export function logMiddlewareShortCircuit(data: {
|
|
|
97
99
|
path: string;
|
|
98
100
|
status: number;
|
|
99
101
|
}): void {
|
|
100
|
-
_logger
|
|
102
|
+
_logger.debug('middleware short-circuited', withTraceContext(data));
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
/** Log unhandled error in middleware phase. Level: error. */
|
|
104
106
|
export function logMiddlewareError(data: { method: string; path: string; error: unknown }): void {
|
|
105
|
-
|
|
106
|
-
_logger.error('unhandled error in middleware phase', withTraceContext(data));
|
|
107
|
-
} else if (isDebug()) {
|
|
108
|
-
console.error('[timber] middleware error', data.error);
|
|
109
|
-
}
|
|
107
|
+
_logger.error('unhandled error in middleware phase', withTraceContext(data));
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
/** Log unhandled render-phase error. Level: error. */
|
|
113
111
|
export function logRenderError(data: { method: string; path: string; error: unknown }): void {
|
|
114
|
-
|
|
115
|
-
_logger.error('unhandled render-phase error', withTraceContext(data));
|
|
116
|
-
} else if (isDebug()) {
|
|
117
|
-
// No logger configured — fall back to console.error in dev with
|
|
118
|
-
// cleaned-up error messages (vendor paths rewritten, hints added).
|
|
119
|
-
console.error('[timber] render error:', formatSsrError(data.error));
|
|
120
|
-
}
|
|
112
|
+
_logger.error('unhandled render-phase error', withTraceContext(data));
|
|
121
113
|
}
|
|
122
114
|
|
|
123
115
|
/** Log proxy.ts uncaught error. Level: error. */
|
|
124
116
|
export function logProxyError(data: { error: unknown }): void {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
117
|
+
_logger.error('proxy.ts threw uncaught error', withTraceContext(data));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Log unhandled error in server action. Level: error. */
|
|
121
|
+
export function logActionError(data: { method: string; path: string; error: unknown }): void {
|
|
122
|
+
_logger.error('unhandled server action error', withTraceContext(data));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Log unhandled error in route handler. Level: error. */
|
|
126
|
+
export function logRouteError(data: { method: string; path: string; error: unknown }): void {
|
|
127
|
+
_logger.error('unhandled route handler error', withTraceContext(data));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Log SSR streaming error (post-shell). Level: error. */
|
|
131
|
+
export function logStreamingError(data: { error: unknown }): void {
|
|
132
|
+
_logger.error('SSR streaming error (post-shell)', withTraceContext(data));
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
/** Log waitUntil() adapter missing (once at startup). Level: warn. */
|
|
133
136
|
export function logWaitUntilUnsupported(): void {
|
|
134
|
-
_logger
|
|
137
|
+
_logger.warn('adapter does not support waitUntil()');
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
/** Log waitUntil() promise rejection. Level: warn. */
|
|
138
141
|
export function logWaitUntilRejected(data: { error: unknown }): void {
|
|
139
|
-
_logger
|
|
142
|
+
_logger.warn('waitUntil() promise rejected', withTraceContext(data));
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
/** Log staleWhileRevalidate refetch failure. Level: warn. */
|
|
143
146
|
export function logSwrRefetchFailed(data: { cacheKey: string; error: unknown }): void {
|
|
144
|
-
_logger
|
|
147
|
+
_logger.warn('staleWhileRevalidate refetch failed', withTraceContext(data));
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
/** Log cache miss. Level: debug. */
|
|
148
151
|
export function logCacheMiss(data: { cacheKey: string }): void {
|
|
149
|
-
_logger
|
|
152
|
+
_logger.debug('timber.cache MISS', withTraceContext(data));
|
|
150
153
|
}
|