@timber-js/app 0.2.0-alpha.5 → 0.2.0-alpha.51
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-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-TK8C1M3x.js +279 -0
- package/dist/_chunks/define-TK8C1M3x.js.map +1 -0
- package/dist/_chunks/define-cookie-k9btcEfI.js +93 -0
- package/dist/_chunks/define-cookie-k9btcEfI.js.map +1 -0
- package/dist/_chunks/error-boundary-B9vT_YK_.js +211 -0
- package/dist/_chunks/error-boundary-B9vT_YK_.js.map +1 -0
- package/dist/_chunks/{format-DviM89f0.js → format-cX7wzEp2.js} +2 -2
- package/dist/_chunks/{format-DviM89f0.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-DIkVh_jG.js → request-context-0h-6Voad.js} +95 -69
- package/dist/_chunks/request-context-0h-6Voad.js.map +1 -0
- package/dist/_chunks/segment-context-DBn-nrMN.js +69 -0
- package/dist/_chunks/segment-context-DBn-nrMN.js.map +1 -0
- package/dist/_chunks/stale-reload-4L-_skC7.js +47 -0
- package/dist/_chunks/stale-reload-4L-_skC7.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → tracing-JI4cYUdz.js} +17 -3
- package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-JI4cYUdz.js.map} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-wEXY2JQB.js} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-wEXY2JQB.js.map} +1 -1
- package/dist/_chunks/wrappers-C9XPg7-U.js +63 -0
- package/dist/_chunks/wrappers-C9XPg7-U.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 +90 -20
- 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 +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/index.d.ts +112 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +467 -246
- 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 +105 -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.map +1 -1
- package/dist/plugins/fonts.d.ts +7 -0
- package/dist/plugins/fonts.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 +159 -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 +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/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/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-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 +66 -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 +51 -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 +1974 -1648
- 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 +113 -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/ssr-wrappers.d.ts +50 -0
- package/dist/server/ssr-wrappers.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 +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 +40 -29
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +133 -93
- 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/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 +5 -7
- package/src/plugins/fonts.ts +93 -42
- 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 +518 -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 +7 -1
- 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 +16 -5
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +1 -1
- 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 +113 -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 +261 -117
- package/src/server/index.ts +9 -4
- package/src/server/logger.ts +38 -35
- package/src/server/node-stream-transforms.ts +504 -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 +119 -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 +108 -43
- package/src/server/rsc-entry/rsc-payload.ts +52 -12
- package/src/server/rsc-entry/rsc-stream.ts +49 -12
- package/src/server/rsc-entry/ssr-renderer.ts +40 -13
- package/src/server/slot-resolver.ts +222 -217
- package/src/server/ssr-entry.ts +209 -30
- package/src/server/ssr-render.ts +289 -67
- package/src/server/ssr-wrappers.tsx +139 -0
- 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-gwlJkDuf.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/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,179 @@
|
|
|
7
7
|
* Design docs: 02-rendering-pipeline.md, 18-build-system.md §"Entry Files"
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
// ─── Buffered Transform ──────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for the buffered transform stream.
|
|
14
|
+
*/
|
|
15
|
+
export interface BufferedTransformOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Flush synchronously once the buffer reaches this many bytes.
|
|
18
|
+
* Prevents unbounded memory growth for very large Fizz flushes.
|
|
19
|
+
* Default: Infinity (no byte limit — flush only on tick boundary).
|
|
20
|
+
*/
|
|
21
|
+
readonly maxBufferByteLength?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Buffer incoming chunks and coalesce them within a single event loop tick.
|
|
26
|
+
*
|
|
27
|
+
* React Fizz may emit multiple micro-chunks within a single flush (e.g.,
|
|
28
|
+
* opening tags, attributes, closing tags as separate writes). Without
|
|
29
|
+
* buffering, downstream transforms (especially flight injection) could
|
|
30
|
+
* see chunk boundaries in the middle of HTML tags or attribute values.
|
|
31
|
+
*
|
|
32
|
+
* This transform collects all chunks that arrive in the same tick and
|
|
33
|
+
* emits them as a single concatenated chunk on the next `setImmediate`.
|
|
34
|
+
* This ensures each output chunk represents a coherent HTML fragment
|
|
35
|
+
* from a single Fizz flush — safe for downstream script injection at
|
|
36
|
+
* chunk boundaries.
|
|
37
|
+
*
|
|
38
|
+
* **Not a polling loop.** Uses a single-shot `setImmediate` per flush
|
|
39
|
+
* cycle — no recursive scheduling, no busy-wait. See design/02 §"No Polling".
|
|
40
|
+
*
|
|
41
|
+
* Inspired by Next.js `createBufferedTransformStream`.
|
|
42
|
+
*/
|
|
43
|
+
export function createBufferedTransformStream(
|
|
44
|
+
options: BufferedTransformOptions = {}
|
|
45
|
+
): TransformStream<Uint8Array, Uint8Array> {
|
|
46
|
+
const { maxBufferByteLength = Infinity } = options;
|
|
47
|
+
|
|
48
|
+
let bufferedChunks: Uint8Array[] = [];
|
|
49
|
+
let bufferByteLength = 0;
|
|
50
|
+
let pendingFlush: Promise<void> | undefined;
|
|
51
|
+
|
|
52
|
+
const flush = (controller: TransformStreamDefaultController<Uint8Array>) => {
|
|
53
|
+
if (bufferedChunks.length === 0) return;
|
|
54
|
+
|
|
55
|
+
// Concatenate all buffered chunks into a single output chunk
|
|
56
|
+
const merged = new Uint8Array(bufferByteLength);
|
|
57
|
+
let offset = 0;
|
|
58
|
+
for (const chunk of bufferedChunks) {
|
|
59
|
+
merged.set(chunk, offset);
|
|
60
|
+
offset += chunk.byteLength;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
bufferedChunks = [];
|
|
64
|
+
bufferByteLength = 0;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
controller.enqueue(merged);
|
|
68
|
+
} catch {
|
|
69
|
+
// Controller may be errored (e.g., stream cancelled) — ignore
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const scheduleFlush = (controller: TransformStreamDefaultController<Uint8Array>) => {
|
|
74
|
+
if (pendingFlush) return;
|
|
75
|
+
|
|
76
|
+
// Single-shot setImmediate — fires once at the end of the current
|
|
77
|
+
// event loop iteration (check phase), then the promise resolves.
|
|
78
|
+
// NOT a recursive loop — no CPU spin risk.
|
|
79
|
+
pendingFlush = new Promise<void>((resolve) => {
|
|
80
|
+
setImmediate(() => {
|
|
81
|
+
try {
|
|
82
|
+
flush(controller);
|
|
83
|
+
} finally {
|
|
84
|
+
pendingFlush = undefined;
|
|
85
|
+
resolve();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return new TransformStream<Uint8Array, Uint8Array>({
|
|
92
|
+
transform(chunk, controller) {
|
|
93
|
+
bufferedChunks.push(chunk);
|
|
94
|
+
bufferByteLength += chunk.byteLength;
|
|
95
|
+
|
|
96
|
+
if (bufferByteLength >= maxBufferByteLength) {
|
|
97
|
+
// Synchronous flush — buffer is too large to hold
|
|
98
|
+
flush(controller);
|
|
99
|
+
} else {
|
|
100
|
+
// Schedule a deferred flush for end of this tick
|
|
101
|
+
scheduleFlush(controller);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
flush() {
|
|
105
|
+
// Wait for any pending scheduled flush to complete
|
|
106
|
+
return pendingFlush;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Move Suffix Transform ───────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
const SUFFIX = '</body></html>';
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Move `</body></html>` to the end of the stream.
|
|
117
|
+
*
|
|
118
|
+
* React's renderToReadableStream emits `</body></html>` as part of the
|
|
119
|
+
* shell chunk. Content that arrives after the shell (Suspense resolutions,
|
|
120
|
+
* RSC script tags) would appear after the closing tags, producing invalid
|
|
121
|
+
* HTML like `</body></html><script>...</script>`.
|
|
122
|
+
*
|
|
123
|
+
* This transform strips the suffix when first encountered and re-emits
|
|
124
|
+
* it in `flush()` — after all content has passed through. If no suffix
|
|
125
|
+
* is found, it's appended anyway to ensure well-formed HTML.
|
|
126
|
+
*
|
|
127
|
+
* Equivalent to Next.js's `createMoveSuffixStream`.
|
|
128
|
+
*/
|
|
129
|
+
export function createMoveSuffixStream(): TransformStream<Uint8Array, Uint8Array> {
|
|
130
|
+
const encoder = new TextEncoder();
|
|
131
|
+
const suffixBytes = encoder.encode(SUFFIX);
|
|
132
|
+
let foundSuffix = false;
|
|
133
|
+
|
|
134
|
+
return new TransformStream<Uint8Array, Uint8Array>({
|
|
135
|
+
transform(chunk, controller) {
|
|
136
|
+
if (foundSuffix) {
|
|
137
|
+
controller.enqueue(chunk);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Search for the suffix in this chunk
|
|
142
|
+
const text = new TextDecoder().decode(chunk, { stream: true });
|
|
143
|
+
const idx = text.indexOf(SUFFIX);
|
|
144
|
+
if (idx === -1) {
|
|
145
|
+
controller.enqueue(chunk);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
foundSuffix = true;
|
|
150
|
+
|
|
151
|
+
// If the entire chunk is exactly the suffix, skip it
|
|
152
|
+
if (chunk.byteLength === suffixBytes.byteLength) return;
|
|
153
|
+
|
|
154
|
+
// Emit content before the suffix
|
|
155
|
+
const before = text.slice(0, idx);
|
|
156
|
+
const after = text.slice(idx + SUFFIX.length);
|
|
157
|
+
if (before) controller.enqueue(encoder.encode(before));
|
|
158
|
+
// Emit content after the suffix (shouldn't normally exist,
|
|
159
|
+
// but handle it for robustness)
|
|
160
|
+
if (after) controller.enqueue(encoder.encode(after));
|
|
161
|
+
},
|
|
162
|
+
flush(controller) {
|
|
163
|
+
// Always emit the suffix at the very end — even if we didn't
|
|
164
|
+
// find it in the input (ensures well-formed HTML).
|
|
165
|
+
controller.enqueue(suffixBytes);
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Injection Transforms ────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
import { createMachine } from '../utils/state-machine.js';
|
|
173
|
+
import { flightChunkScript } from './flight-scripts.js';
|
|
174
|
+
import {
|
|
175
|
+
flightInjectionTransitions,
|
|
176
|
+
isHtmlDone,
|
|
177
|
+
isPullDone,
|
|
178
|
+
type FlightInjectionState,
|
|
179
|
+
type FlightInjectionEvent,
|
|
180
|
+
} from './flight-injection-state.js';
|
|
181
|
+
import { withTimeout, RenderTimeoutError } from './render-timeout.js';
|
|
182
|
+
|
|
10
183
|
/**
|
|
11
184
|
* Inject HTML content before a closing tag in the stream.
|
|
12
185
|
*
|
|
@@ -95,22 +268,6 @@ export function injectScripts(
|
|
|
95
268
|
return createInjector(stream, scriptsHtml, '</body>');
|
|
96
269
|
}
|
|
97
270
|
|
|
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
271
|
/**
|
|
115
272
|
* Transform an RSC Flight stream into a stream of inline `<script>` tags.
|
|
116
273
|
*
|
|
@@ -118,42 +275,42 @@ function htmlEscapeJsonString(str: string): string {
|
|
|
118
275
|
* transform) drives reads from the RSC stream on demand. No background
|
|
119
276
|
* reader, no shared mutable arrays, no race conditions.
|
|
120
277
|
*
|
|
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)
|
|
278
|
+
* Each RSC chunk becomes a `<script>self.__timber_f.push([1,"data"])</script>`.
|
|
279
|
+
* The init script (which creates __timber_f) is in `<head>` via
|
|
280
|
+
* flightInitScript() — see flight-scripts.ts.
|
|
130
281
|
*/
|
|
131
282
|
export function createInlinedRscStream(
|
|
132
|
-
rscStream: ReadableStream<Uint8Array
|
|
283
|
+
rscStream: ReadableStream<Uint8Array>,
|
|
284
|
+
renderTimeoutMs?: number
|
|
133
285
|
): ReadableStream<Uint8Array> {
|
|
134
286
|
const encoder = new TextEncoder();
|
|
135
287
|
const rscReader = rscStream.getReader();
|
|
136
288
|
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
289
|
+
const timeoutMs = renderTimeoutMs ?? 30_000;
|
|
137
290
|
|
|
138
291
|
return new ReadableStream<Uint8Array>({
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
controller.enqueue(encoder.encode(bootstrap));
|
|
143
|
-
},
|
|
292
|
+
// No bootstrap signal here — the init script is in <head> via
|
|
293
|
+
// flightInitScript() (see flight-scripts.ts). This ensures the
|
|
294
|
+
// __timber_f array exists before any chunk scripts execute.
|
|
144
295
|
async pull(controller) {
|
|
145
296
|
try {
|
|
146
|
-
const
|
|
297
|
+
const readPromise = rscReader.read();
|
|
298
|
+
const { done, value } =
|
|
299
|
+
timeoutMs > 0
|
|
300
|
+
? await withTimeout(readPromise, timeoutMs, 'RSC stream read timed out')
|
|
301
|
+
: await readPromise;
|
|
147
302
|
if (done) {
|
|
148
303
|
controller.close();
|
|
149
304
|
return;
|
|
150
305
|
}
|
|
151
306
|
if (value) {
|
|
152
307
|
const decoded = decoder.decode(value, { stream: true });
|
|
153
|
-
|
|
154
|
-
controller.enqueue(encoder.encode(`<script>self.__timber_f.push(${escaped})</script>`));
|
|
308
|
+
controller.enqueue(encoder.encode(flightChunkScript(decoded)));
|
|
155
309
|
}
|
|
156
310
|
} catch (error) {
|
|
311
|
+
if (error instanceof RenderTimeoutError) {
|
|
312
|
+
rscReader.cancel(error).catch(() => {});
|
|
313
|
+
}
|
|
157
314
|
controller.error(error);
|
|
158
315
|
}
|
|
159
316
|
},
|
|
@@ -162,67 +319,70 @@ export function createInlinedRscStream(
|
|
|
162
319
|
|
|
163
320
|
/**
|
|
164
321
|
* Merge RSC script stream into the HTML stream, injecting scripts
|
|
165
|
-
*
|
|
322
|
+
* between HTML chunks at safe boundaries.
|
|
166
323
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
324
|
+
* Suffix stripping (</body></html>) is handled upstream by
|
|
325
|
+
* createMoveSuffixStream. This transform only interleaves RSC
|
|
326
|
+
* scripts — no suffix tracking needed.
|
|
169
327
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* 3. Re-emits `</body></html>` at the very end after all RSC scripts.
|
|
328
|
+
* RSC scripts are buffered in pending[] by pullLoop and only
|
|
329
|
+
* drained from transform() (after a complete HTML chunk) or
|
|
330
|
+
* flush(). Never pushed directly from pullLoop to avoid mid-tag
|
|
331
|
+
* injection (TIM-527).
|
|
175
332
|
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* scanning or depth tracking needed — the suffix removal is the
|
|
180
|
-
* structural guarantee.
|
|
333
|
+
* State machine phases:
|
|
334
|
+
* init → streaming → flushing → done
|
|
335
|
+
* (any) → error
|
|
181
336
|
*
|
|
182
337
|
* Inspired by Next.js createFlightDataInjectionTransformStream.
|
|
183
338
|
*/
|
|
184
339
|
function createFlightInjectionTransform(
|
|
185
|
-
rscScriptStream: ReadableStream<Uint8Array
|
|
340
|
+
rscScriptStream: ReadableStream<Uint8Array>,
|
|
341
|
+
renderTimeoutMs?: number
|
|
186
342
|
): TransformStream<Uint8Array, Uint8Array> {
|
|
187
|
-
const encoder = new TextEncoder();
|
|
188
|
-
const decoder = new TextDecoder();
|
|
189
|
-
const suffix = '</body></html>';
|
|
190
|
-
const suffixBytes = encoder.encode(suffix);
|
|
191
|
-
|
|
192
343
|
const rscReader = rscScriptStream.getReader();
|
|
344
|
+
const timeoutMs = renderTimeoutMs ?? 30_000;
|
|
345
|
+
|
|
346
|
+
const machine = createMachine<FlightInjectionState, FlightInjectionEvent>({
|
|
347
|
+
initial: { phase: 'init' },
|
|
348
|
+
transitions: flightInjectionTransitions,
|
|
349
|
+
});
|
|
350
|
+
|
|
193
351
|
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
352
|
|
|
200
|
-
// RSC script chunks waiting to be
|
|
353
|
+
// RSC script chunks waiting to be drained at a safe boundary.
|
|
201
354
|
const pending: Uint8Array[] = [];
|
|
202
355
|
|
|
203
356
|
async function pullLoop(): Promise<void> {
|
|
204
|
-
//
|
|
205
|
-
// transform()
|
|
206
|
-
|
|
207
|
-
await new Promise<void>((r) => setTimeout(r, 0));
|
|
357
|
+
// Yield once so the first HTML shell chunk flows through
|
|
358
|
+
// transform() before we start reading RSC data.
|
|
359
|
+
await new Promise<void>((r) => setImmediate(r));
|
|
208
360
|
|
|
209
361
|
try {
|
|
210
362
|
for (;;) {
|
|
211
|
-
|
|
363
|
+
// Guard each RSC read with a timeout.
|
|
364
|
+
// See design/02 §"Streaming Constraints".
|
|
365
|
+
const readPromise = rscReader.read();
|
|
366
|
+
const { done, value } =
|
|
367
|
+
timeoutMs > 0
|
|
368
|
+
? await withTimeout(readPromise, timeoutMs, 'RSC stream read timed out')
|
|
369
|
+
: await readPromise;
|
|
212
370
|
if (done) {
|
|
213
|
-
|
|
371
|
+
machine.send({ type: 'PULL_DONE' });
|
|
214
372
|
return;
|
|
215
373
|
}
|
|
216
374
|
pending.push(value);
|
|
217
|
-
// Yield between reads so HTML chunks get
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
375
|
+
// Yield between reads so HTML chunks get priority.
|
|
376
|
+
// Once flush() fires, drain without yielding.
|
|
377
|
+
if (!isHtmlDone(machine.state)) {
|
|
378
|
+
await new Promise<void>((r) => setImmediate(r));
|
|
379
|
+
}
|
|
222
380
|
}
|
|
223
381
|
} catch (err) {
|
|
224
|
-
|
|
225
|
-
|
|
382
|
+
if (err instanceof RenderTimeoutError) {
|
|
383
|
+
rscReader.cancel(err).catch(() => {});
|
|
384
|
+
}
|
|
385
|
+
machine.send({ type: 'PULL_ERROR', error: err });
|
|
226
386
|
}
|
|
227
387
|
}
|
|
228
388
|
|
|
@@ -231,59 +391,34 @@ function createFlightInjectionTransform(
|
|
|
231
391
|
while (pending.length > 0) {
|
|
232
392
|
controller.enqueue(pending.shift()!);
|
|
233
393
|
}
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
pullError = null;
|
|
237
|
-
controller.error(err);
|
|
394
|
+
if (machine.state.phase === 'error') {
|
|
395
|
+
controller.error(machine.state.error);
|
|
238
396
|
}
|
|
239
397
|
}
|
|
240
398
|
|
|
241
399
|
return new TransformStream<Uint8Array, Uint8Array>({
|
|
242
400
|
transform(chunk, controller) {
|
|
243
|
-
|
|
244
|
-
|
|
401
|
+
if (machine.state.phase === 'init') {
|
|
402
|
+
machine.send({ type: 'FIRST_CHUNK' });
|
|
245
403
|
pullPromise = pullLoop();
|
|
246
404
|
}
|
|
247
405
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Look for </body></html> in the shell chunk.
|
|
257
|
-
const text = decoder.decode(chunk, { stream: true });
|
|
258
|
-
const idx = text.indexOf(suffix);
|
|
259
|
-
if (idx !== -1) {
|
|
260
|
-
foundSuffix = true;
|
|
261
|
-
// Emit everything before the suffix (still inside <body>'s
|
|
262
|
-
// child elements — don't inject scripts here).
|
|
263
|
-
const before = text.slice(0, idx);
|
|
264
|
-
const after = text.slice(idx + suffix.length);
|
|
265
|
-
if (before) controller.enqueue(encoder.encode(before));
|
|
266
|
-
// Now we're at body level — drain buffered scripts
|
|
267
|
-
if (pending.length > 0) drainPending(controller);
|
|
268
|
-
// Emit any content after the suffix (shouldn't normally exist)
|
|
269
|
-
if (after) controller.enqueue(encoder.encode(after));
|
|
270
|
-
} else {
|
|
271
|
-
// Pre-suffix: inside nested elements. Pass through, don't
|
|
272
|
-
// inject scripts (they'd become children of nested elements).
|
|
273
|
-
controller.enqueue(chunk);
|
|
274
|
-
}
|
|
406
|
+
// Emit the HTML chunk, then drain any buffered RSC scripts.
|
|
407
|
+
// Scripts always come AFTER a complete HTML chunk — never mid-tag.
|
|
408
|
+
// The buffered transform upstream (TIM-528) ensures coherent chunks.
|
|
409
|
+
// Suffix stripping is upstream via createMoveSuffixStream (TIM-530).
|
|
410
|
+
controller.enqueue(chunk);
|
|
411
|
+
if (pending.length > 0) drainPending(controller);
|
|
275
412
|
},
|
|
276
413
|
flush(controller) {
|
|
277
|
-
// HTML
|
|
414
|
+
// All HTML chunks emitted. Pull loop stops yielding.
|
|
415
|
+
machine.send({ type: 'HTML_DONE' });
|
|
416
|
+
|
|
278
417
|
const finish = () => {
|
|
279
418
|
drainPending(controller);
|
|
280
|
-
// Re-emit the suffix at the very end so HTML is well-formed
|
|
281
|
-
if (foundSuffix) {
|
|
282
|
-
controller.enqueue(suffixBytes);
|
|
283
|
-
}
|
|
284
419
|
};
|
|
285
420
|
|
|
286
|
-
if (
|
|
421
|
+
if (isPullDone(machine.state)) {
|
|
287
422
|
finish();
|
|
288
423
|
return;
|
|
289
424
|
}
|
|
@@ -314,16 +449,25 @@ function createFlightInjectionTransform(
|
|
|
314
449
|
*/
|
|
315
450
|
export function injectRscPayload(
|
|
316
451
|
htmlStream: ReadableStream<Uint8Array>,
|
|
317
|
-
rscStream: ReadableStream<Uint8Array> | undefined
|
|
452
|
+
rscStream: ReadableStream<Uint8Array> | undefined,
|
|
453
|
+
renderTimeoutMs?: number
|
|
318
454
|
): ReadableStream<Uint8Array> {
|
|
319
455
|
if (!rscStream) return htmlStream;
|
|
320
456
|
|
|
321
457
|
// Transform RSC binary stream → stream of <script> tags
|
|
322
|
-
const rscScriptStream = createInlinedRscStream(rscStream);
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
|
|
458
|
+
const rscScriptStream = createInlinedRscStream(rscStream, renderTimeoutMs);
|
|
459
|
+
|
|
460
|
+
// Pipeline: flightInjection → moveSuffix
|
|
461
|
+
//
|
|
462
|
+
// 1. flightInjection interleaves RSC scripts between HTML chunks
|
|
463
|
+
// 2. moveSuffix strips </body></html> and re-emits at end
|
|
464
|
+
//
|
|
465
|
+
// The flight injector is upstream and interleaves scripts between
|
|
466
|
+
// HTML chunks. The moveSuffix transform then ensures </body></html>
|
|
467
|
+
// appears after all injected scripts, producing well-formed HTML.
|
|
468
|
+
return htmlStream
|
|
469
|
+
.pipeThrough(createFlightInjectionTransform(rscScriptStream, renderTimeoutMs))
|
|
470
|
+
.pipeThrough(createMoveSuffixStream());
|
|
327
471
|
}
|
|
328
472
|
|
|
329
473
|
/**
|
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
|
}
|