@timber-js/app 0.2.0-alpha.198 → 0.2.0-alpha.199

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.
Files changed (246) hide show
  1. package/dist/_chunks/{actions-BS-m5SLv.js → actions-d1hCqnU3.js} +35 -8
  2. package/dist/_chunks/actions-d1hCqnU3.js.map +1 -0
  3. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
  4. package/dist/_chunks/{cache-api-DqzgTEqk.js → cache-api-ByagcC-J.js} +2 -2
  5. package/dist/_chunks/{cache-api-DqzgTEqk.js.map → cache-api-ByagcC-J.js.map} +1 -1
  6. package/dist/_chunks/canonicalize-CgHoscYO.js +66 -0
  7. package/dist/_chunks/canonicalize-CgHoscYO.js.map +1 -0
  8. package/dist/_chunks/{chains-CZG7E5zg.js → chains-Bpb0W4ax.js} +3 -3
  9. package/dist/_chunks/{chains-CZG7E5zg.js.map → chains-Bpb0W4ax.js.map} +1 -1
  10. package/dist/_chunks/{cli-check-dVDi1GQz.js → cli-check-D6VolrDV.js} +3 -3
  11. package/dist/_chunks/{cli-check-dVDi1GQz.js.map → cli-check-D6VolrDV.js.map} +1 -1
  12. package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js → cli-schema-sync-D6rO-VcS.js} +2 -2
  13. package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js.map → cli-schema-sync-D6rO-VcS.js.map} +1 -1
  14. package/dist/_chunks/{convention-lint-Ph6luW4c.js → convention-lint-fRkwVwEH.js} +25 -4
  15. package/dist/_chunks/convention-lint-fRkwVwEH.js.map +1 -0
  16. package/dist/_chunks/error-boundary-BfPHZjm0.js +1050 -0
  17. package/dist/_chunks/error-boundary-BfPHZjm0.js.map +1 -0
  18. package/dist/_chunks/{live-graph-BXDsdzBv.js → live-graph-D_2D32Ad.js} +3 -3
  19. package/dist/_chunks/{live-graph-BXDsdzBv.js.map → live-graph-D_2D32Ad.js.map} +1 -1
  20. package/dist/_chunks/{logger-DDirEsn7.js → logger-uLBuGKDI.js} +471 -440
  21. package/dist/_chunks/logger-uLBuGKDI.js.map +1 -0
  22. package/dist/_chunks/{navigation-root-B00jjGd5.js → navigation-context-D0TU0Jog.js} +3 -101
  23. package/dist/_chunks/navigation-context-D0TU0Jog.js.map +1 -0
  24. package/dist/_chunks/navigation-root-mHSK9psY.js +126 -0
  25. package/dist/_chunks/{navigation-root-B00jjGd5.js.map → navigation-root-mHSK9psY.js.map} +1 -1
  26. package/dist/_chunks/{poison-scan-BoDLgbix.js → poison-scan-Bm9Yyqk9.js} +2 -2
  27. package/dist/_chunks/{poison-scan-BoDLgbix.js.map → poison-scan-Bm9Yyqk9.js.map} +1 -1
  28. package/dist/_chunks/{scanner-tdFPvDYi.js → scanner-AiazgH_f.js} +6 -5
  29. package/dist/_chunks/scanner-AiazgH_f.js.map +1 -0
  30. package/dist/_chunks/{segment-keys-BhqoHiLc.js → segment-keys-lqtdookO.js} +2 -65
  31. package/dist/_chunks/segment-keys-lqtdookO.js.map +1 -0
  32. package/dist/_chunks/{ssr-data-BQGhTPAK.js → ssr-data-D6T6Y3ef.js} +4 -26
  33. package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +1 -0
  34. package/dist/_chunks/state-FippDgxN.js +52 -0
  35. package/dist/_chunks/state-FippDgxN.js.map +1 -0
  36. package/dist/_chunks/status-page-marker-DwQBrLBz.js +496 -0
  37. package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +1 -0
  38. package/dist/_chunks/{walkers-DNX05dC0.js → walkers-B6XUtmqK.js} +2 -2
  39. package/dist/_chunks/{walkers-DNX05dC0.js.map → walkers-B6XUtmqK.js.map} +1 -1
  40. package/dist/analyze/crawl-entry.js +2 -2
  41. package/dist/analyze/graph-command.js +2 -2
  42. package/dist/cache/index.js +1 -1
  43. package/dist/cli.js +2 -2
  44. package/dist/client/browser-entry/action-dispatch.d.ts +6 -4
  45. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  46. package/dist/client/browser-entry/action-queue.d.ts +44 -0
  47. package/dist/client/browser-entry/action-queue.d.ts.map +1 -0
  48. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  49. package/dist/client/deny-last-resort.d.ts +29 -0
  50. package/dist/client/deny-last-resort.d.ts.map +1 -0
  51. package/dist/client/error-boundary.d.ts +48 -2
  52. package/dist/client/error-boundary.d.ts.map +1 -1
  53. package/dist/client/error-boundary.js +2 -2
  54. package/dist/client/history.d.ts +21 -2
  55. package/dist/client/history.d.ts.map +1 -1
  56. package/dist/client/index.js +34 -14
  57. package/dist/client/index.js.map +1 -1
  58. package/dist/client/internal.d.ts +1 -0
  59. package/dist/client/internal.d.ts.map +1 -1
  60. package/dist/client/internal.js +272 -1225
  61. package/dist/client/internal.js.map +1 -1
  62. package/dist/client/link.d.ts.map +1 -1
  63. package/dist/client/navigation-commit.d.ts +12 -19
  64. package/dist/client/navigation-commit.d.ts.map +1 -1
  65. package/dist/client/navigation-transition.d.ts +62 -11
  66. package/dist/client/navigation-transition.d.ts.map +1 -1
  67. package/dist/client/router-effects.d.ts +9 -8
  68. package/dist/client/router-effects.d.ts.map +1 -1
  69. package/dist/client/router-lifecycle.d.ts +60 -17
  70. package/dist/client/router-lifecycle.d.ts.map +1 -1
  71. package/dist/client/router-pipeline.d.ts +7 -4
  72. package/dist/client/router-pipeline.d.ts.map +1 -1
  73. package/dist/client/router-types.d.ts +63 -8
  74. package/dist/client/router-types.d.ts.map +1 -1
  75. package/dist/client/router.d.ts.map +1 -1
  76. package/dist/client/rsc-fetch.d.ts +0 -9
  77. package/dist/client/rsc-fetch.d.ts.map +1 -1
  78. package/dist/client/segment-cache.d.ts +23 -8
  79. package/dist/client/segment-cache.d.ts.map +1 -1
  80. package/dist/client/state.d.ts +16 -0
  81. package/dist/client/state.d.ts.map +1 -1
  82. package/dist/client/status-page-marker.d.ts +25 -0
  83. package/dist/client/status-page-marker.d.ts.map +1 -0
  84. package/dist/cookies/index.js +1 -1
  85. package/dist/dev-tools/holding-server.d.ts +4 -17
  86. package/dist/dev-tools/holding-server.d.ts.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +74 -175
  89. package/dist/index.js.map +1 -1
  90. package/dist/plugins/dev-server.d.ts.map +1 -1
  91. package/dist/routing/index.js +2 -2
  92. package/dist/routing/interception.d.ts +2 -2
  93. package/dist/routing/slot-placement.d.ts +2 -2
  94. package/dist/server/access-gate.d.ts +73 -1
  95. package/dist/server/access-gate.d.ts.map +1 -1
  96. package/dist/server/action-handler.d.ts.map +1 -1
  97. package/dist/server/actions.d.ts +16 -1
  98. package/dist/server/actions.d.ts.map +1 -1
  99. package/dist/server/als-registry.d.ts +3 -9
  100. package/dist/server/als-registry.d.ts.map +1 -1
  101. package/dist/server/children-interception.d.ts +1 -1
  102. package/dist/server/default-status-page.d.ts +2 -2
  103. package/dist/server/default-status-page.d.ts.map +1 -1
  104. package/dist/server/deny-boundary.d.ts +15 -9
  105. package/dist/server/deny-boundary.d.ts.map +1 -1
  106. package/dist/server/deny-renderer.d.ts.map +1 -1
  107. package/dist/server/error-boundary-wrapper.d.ts +21 -4
  108. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  109. package/dist/server/error-response-headers.d.ts +3 -0
  110. package/dist/server/error-response-headers.d.ts.map +1 -0
  111. package/dist/server/index.js +3 -3
  112. package/dist/server/index.js.map +1 -1
  113. package/dist/server/internal.d.ts +1 -2
  114. package/dist/server/internal.d.ts.map +1 -1
  115. package/dist/server/internal.js +2339 -2506
  116. package/dist/server/internal.js.map +1 -1
  117. package/dist/server/metadata-collector.d.ts +2 -5
  118. package/dist/server/metadata-collector.d.ts.map +1 -1
  119. package/dist/server/param-coercion.d.ts +10 -3
  120. package/dist/server/param-coercion.d.ts.map +1 -1
  121. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  122. package/dist/server/pipeline-phases.d.ts +11 -0
  123. package/dist/server/pipeline-phases.d.ts.map +1 -1
  124. package/dist/server/port-resolution.d.ts +3 -89
  125. package/dist/server/port-resolution.d.ts.map +1 -1
  126. package/dist/server/primitives.d.ts +38 -10
  127. package/dist/server/primitives.d.ts.map +1 -1
  128. package/dist/server/response-cache-policy.d.ts +3 -0
  129. package/dist/server/response-cache-policy.d.ts.map +1 -0
  130. package/dist/server/route-element-builder.d.ts +11 -41
  131. package/dist/server/route-element-builder.d.ts.map +1 -1
  132. package/dist/server/route-element-helpers.d.ts +12 -0
  133. package/dist/server/route-element-helpers.d.ts.map +1 -0
  134. package/dist/server/route-module-loader.d.ts +37 -0
  135. package/dist/server/route-module-loader.d.ts.map +1 -0
  136. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
  137. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
  138. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  139. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  140. package/dist/server/rsc-entry/rsc-payload.d.ts +22 -1
  141. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  142. package/dist/server/rsc-entry/rsc-stream.d.ts +4 -11
  143. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  144. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  145. package/dist/server/rsc-error-envelope.d.ts +11 -0
  146. package/dist/server/rsc-error-envelope.d.ts.map +1 -0
  147. package/dist/server/skippable-prefix.d.ts +18 -15
  148. package/dist/server/skippable-prefix.d.ts.map +1 -1
  149. package/dist/server/slot-resolver.d.ts.map +1 -1
  150. package/dist/server/slot-subtree-contain.d.ts +54 -0
  151. package/dist/server/slot-subtree-contain.d.ts.map +1 -0
  152. package/dist/server/stream-utils.d.ts.map +1 -1
  153. package/dist/server/utils/element-type.d.ts +10 -0
  154. package/dist/server/utils/element-type.d.ts.map +1 -1
  155. package/dist/shared/rsc-error-envelope.d.ts +0 -9
  156. package/dist/shared/rsc-error-envelope.d.ts.map +1 -1
  157. package/dist/shared/status-reason-phrase.d.ts +26 -0
  158. package/dist/shared/status-reason-phrase.d.ts.map +1 -0
  159. package/docs/api/30-api-server.mdx +4 -2
  160. package/docs/api/31-api-client.mdx +5 -1
  161. package/docs/api/36-cli.mdx +5 -3
  162. package/docs/learn/12-error-handling.mdx +5 -1
  163. package/package.json +10 -10
  164. package/src/client/browser-entry/action-dispatch.ts +166 -99
  165. package/src/client/browser-entry/action-queue.ts +90 -0
  166. package/src/client/browser-entry/router-init.ts +60 -35
  167. package/src/client/deny-last-resort.tsx +54 -0
  168. package/src/client/error-boundary.tsx +144 -42
  169. package/src/client/history.ts +52 -3
  170. package/src/client/internal.ts +1 -0
  171. package/src/client/link.tsx +70 -35
  172. package/src/client/navigation-commit.ts +79 -27
  173. package/src/client/navigation-transition.ts +176 -127
  174. package/src/client/router-effects.ts +14 -17
  175. package/src/client/router-lifecycle.ts +181 -115
  176. package/src/client/router-pipeline.ts +94 -71
  177. package/src/client/router-types.ts +61 -7
  178. package/src/client/router.ts +147 -74
  179. package/src/client/rsc-fetch.ts +0 -13
  180. package/src/client/segment-cache.ts +43 -10
  181. package/src/client/state.ts +26 -0
  182. package/src/client/status-page-marker.tsx +32 -0
  183. package/src/dev-tools/holding-server.ts +4 -17
  184. package/src/index.ts +18 -34
  185. package/src/plugins/dev-server.ts +2 -1
  186. package/src/routing/interception.ts +2 -2
  187. package/src/routing/slot-placement.ts +2 -2
  188. package/src/server/access-gate.tsx +89 -21
  189. package/src/server/action-client.ts +2 -2
  190. package/src/server/action-handler.ts +23 -10
  191. package/src/server/actions.ts +81 -34
  192. package/src/server/als-registry.ts +3 -9
  193. package/src/server/children-interception.ts +1 -1
  194. package/src/server/default-status-page.ts +7 -47
  195. package/src/server/deny-boundary.ts +45 -28
  196. package/src/server/deny-renderer.ts +6 -2
  197. package/src/server/error-boundary-wrapper.ts +23 -4
  198. package/src/server/error-response-headers.ts +18 -0
  199. package/src/server/internal.ts +2 -10
  200. package/src/server/metadata-collector.ts +3 -18
  201. package/src/server/param-coercion.ts +13 -4
  202. package/src/server/pipeline-outcome.ts +35 -13
  203. package/src/server/pipeline-phases.ts +22 -16
  204. package/src/server/port-resolution.ts +3 -165
  205. package/src/server/prebuilt-builder.ts +4 -4
  206. package/src/server/primitives.ts +75 -11
  207. package/src/server/response-cache-policy.ts +45 -0
  208. package/src/server/route-element-builder.ts +149 -412
  209. package/src/server/route-element-helpers.ts +37 -0
  210. package/src/server/route-handler.ts +2 -2
  211. package/src/server/route-module-loader.ts +161 -0
  212. package/src/server/rsc-cache-key-guard.ts +2 -42
  213. package/src/server/rsc-entry/action-middleware-runner.ts +4 -4
  214. package/src/server/rsc-entry/api-handler.ts +5 -5
  215. package/src/server/rsc-entry/error-renderer.ts +3 -4
  216. package/src/server/rsc-entry/helpers.ts +1 -1
  217. package/src/server/rsc-entry/index.ts +3 -3
  218. package/src/server/rsc-entry/render-route.ts +4 -8
  219. package/src/server/rsc-entry/rsc-payload.ts +59 -42
  220. package/src/server/rsc-entry/rsc-stream.ts +48 -27
  221. package/src/server/rsc-entry/ssr-renderer.ts +6 -10
  222. package/src/server/rsc-error-envelope.ts +18 -0
  223. package/src/server/skippable-prefix.ts +105 -7
  224. package/src/server/slot-resolver.ts +43 -12
  225. package/src/server/slot-subtree-contain.ts +255 -0
  226. package/src/server/stream-utils.ts +12 -8
  227. package/src/server/utils/element-type.ts +18 -2
  228. package/src/shared/rsc-error-envelope.ts +0 -15
  229. package/src/shared/status-reason-phrase.ts +61 -0
  230. package/dist/_chunks/actions-BS-m5SLv.js.map +0 -1
  231. package/dist/_chunks/convention-lint-Ph6luW4c.js.map +0 -1
  232. package/dist/_chunks/error-boundary-BvRCCmbN.js +0 -353
  233. package/dist/_chunks/error-boundary-BvRCCmbN.js.map +0 -1
  234. package/dist/_chunks/logger-DDirEsn7.js.map +0 -1
  235. package/dist/_chunks/mdx-file-CXyHGUpS.js +0 -25
  236. package/dist/_chunks/mdx-file-CXyHGUpS.js.map +0 -1
  237. package/dist/_chunks/router-ref-8gr8qsxN.js +0 -28
  238. package/dist/_chunks/router-ref-8gr8qsxN.js.map +0 -1
  239. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +0 -40
  240. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +0 -1
  241. package/dist/_chunks/scanner-tdFPvDYi.js.map +0 -1
  242. package/dist/_chunks/segment-keys-BhqoHiLc.js.map +0 -1
  243. package/dist/_chunks/ssr-data-BQGhTPAK.js.map +0 -1
  244. package/dist/server/tree-builder.d.ts +0 -150
  245. package/dist/server/tree-builder.d.ts.map +0 -1
  246. package/src/server/tree-builder.ts +0 -313
@@ -4,214 +4,27 @@ import { a as assertValidCookieOptions, i as assertValidCookieName, n as parseSe
4
4
  import { r as appVisibleSearch } from "./rsc-cache-key-ClUiXQnK.js";
5
5
  import { a as mergePreservedSearchParams, o as resolveSegmentParams, r as validateExternalRedirectUrl, t as assertRelativeRedirectPath } from "./href-validation-BIrxavIy.js";
6
6
  import { randomUUID } from "node:crypto";
7
- //#region src/server/tracing.ts
8
- /**
9
- * Tracing — per-request trace ID via AsyncLocalStorage, OTEL span helpers.
10
- *
11
- * getTraceId() is always available in server code (middleware, access, components, actions).
12
- * Returns a 32-char lowercase hex string — the OTEL trace ID when an SDK is active,
13
- * or a crypto.randomUUID()-derived fallback otherwise.
14
- *
15
- * See design/17-logging.md §"trace_id is Always Set"
16
- */
17
- /**
18
- * Returns the current request's trace ID — always a 32-char lowercase hex string.
19
- *
20
- * With OTEL: the real OTEL trace ID (matches Jaeger/Honeycomb/Datadog).
21
- * Without OTEL: crypto.randomUUID() with hyphens stripped.
22
- *
23
- * Throws if called outside a request context (no ALS store).
24
- */
25
- function getTraceId() {
26
- const store = traceAls.getStore();
27
- if (!store) throw new Error("[timber] getTraceId() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
28
- return store.traceId;
29
- }
30
- /**
31
- * Returns the current OTEL span ID if available, undefined otherwise.
32
- */
33
- function getSpanId() {
34
- return traceAls.getStore()?.spanId;
35
- }
36
- /**
37
- * Generate a 32-char lowercase hex ID from crypto.randomUUID().
38
- * Same format as OTEL trace IDs — zero-friction upgrade path.
39
- */
40
- function generateTraceId() {
41
- return randomUUID().replace(/-/g, "");
42
- }
43
- /**
44
- * Run a callback within a trace context. Used by the pipeline to establish
45
- * per-request ALS scope.
46
- */
47
- function runWithTraceId(id, fn) {
48
- return traceAls.run({ traceId: id }, fn);
49
- }
50
- /**
51
- * Replace the trace ID in the current ALS store. Used when OTEL creates
52
- * a root span and we want to switch from the UUID fallback to the real
53
- * OTEL trace ID.
54
- */
55
- function replaceTraceId(newTraceId, newSpanId) {
56
- const store = traceAls.getStore();
57
- if (store) {
58
- store.traceId = newTraceId;
59
- store.spanId = newSpanId;
60
- }
61
- }
62
- /**
63
- * Update the span ID in the current ALS store. Used when entering a new
64
- * OTEL span to keep log–trace correlation accurate.
65
- */
66
- function updateSpanId(newSpanId) {
67
- const store = traceAls.getStore();
68
- if (store) store.spanId = newSpanId;
69
- }
70
- /**
71
- * Get the current trace store, or undefined if outside a request context.
72
- * Framework-internal — use getTraceId()/getSpanId() in user code.
73
- */
74
- function getTraceStore() {
75
- return traceAls.getStore();
76
- }
77
- var PLATFORM_TRACER_KEY = Symbol.for("timber:platform-tracer");
78
- /** The registered native platform tracer, if any. */
79
- function getPlatformTracer() {
80
- return globalThis[PLATFORM_TRACER_KEY];
81
- }
82
- /**
83
- * Attempt to get the @opentelemetry/api tracer. Returns undefined if the
84
- * package is not installed or no SDK is registered.
85
- *
86
- * timber.js depends on @opentelemetry/api as the vendor-neutral interface.
87
- * The API is a no-op by default — spans are only emitted when the developer
88
- * initializes an SDK in register().
89
- */
90
- var _otelApi;
91
- async function getOtelApi() {
92
- if (_otelApi === void 0) try {
93
- _otelApi = await import("@opentelemetry/api");
94
- } catch {
95
- _otelApi = null;
96
- }
97
- return _otelApi;
98
- }
99
- /** OTEL tracer instance, lazily created. */
100
- var _tracer;
101
- /**
102
- * Get the timber.js OTEL tracer. Returns null if @opentelemetry/api is not available.
103
- */
104
- async function getTracer() {
105
- if (_tracer === void 0) {
106
- const api = await getOtelApi();
107
- if (api) _tracer = api.trace.getTracer("timber.js");
108
- else _tracer = null;
109
- }
110
- return _tracer;
111
- }
7
+ //#region src/server/waituntil-bridge.ts
112
8
  /**
113
- * Run a function within a framework span. Composes two emission channels:
9
+ * Per-request waitUntil bridge — ALS bridge for platform adapters.
114
10
  *
115
- * - **Native platform span** — when an adapter registered a PlatformTracer
116
- * (Cloudflare Workers), the fn is wrapped in platformTracer.enterSpan()
117
- * so it appears in the platform's native trace view.
118
- * - **OTEL span** — when an OTEL SDK is active, the fn also runs inside an
119
- * OTEL span (dual emission). No SDK and no platform tracer = zero overhead.
11
+ * The generated entry point (Nitro, Cloudflare) wraps the handler with
12
+ * `runWithWaitUntil`, binding the platform's lifecycle extension function
13
+ * (e.g., h3's `event.waitUntil()` or CF's `ctx.waitUntil()`) for the
14
+ * request duration. The `waitUntil()` primitive reads from this ALS to
15
+ * dispatch background work to the correct platform API.
120
16
  *
121
- * Automatically:
122
- * - Creates the span as a child of the current context
123
- * - Updates the ALS span ID for log–trace correlation
124
- * - Ends the span when the function completes
125
- * - Records exceptions on error (OTEL channel)
126
- */
127
- async function withSpan(name, attributes, fn) {
128
- const platformTracer = getPlatformTracer();
129
- if (!platformTracer) return runOtelSpan(name, attributes, fn);
130
- return platformTracer.enterSpan(name, async (span) => {
131
- for (const key of Object.keys(attributes)) span.setAttribute(key, attributes[key]);
132
- const store = traceAls.getStore();
133
- const prevPlatformSpan = store?.platformSpan;
134
- if (store) store.platformSpan = span;
135
- try {
136
- return await runOtelSpan(name, attributes, fn);
137
- } finally {
138
- if (store) store.platformSpan = prevPlatformSpan;
139
- }
140
- });
141
- }
142
- /** The OTEL half of withSpan() — no-op passthrough when no SDK is active. */
143
- async function runOtelSpan(name, attributes, fn) {
144
- const tracer = await getTracer();
145
- if (!tracer) return fn();
146
- const api = await getOtelApi();
147
- return tracer.startActiveSpan(name, { attributes }, async (span) => {
148
- const prevSpanId = getSpanId();
149
- updateSpanId(span.spanContext().spanId);
150
- try {
151
- const result = await fn();
152
- span.setStatus({ code: api.SpanStatusCode.OK });
153
- return result;
154
- } catch (error) {
155
- span.setStatus({ code: api.SpanStatusCode.ERROR });
156
- if (error instanceof Error) span.recordException(error);
157
- throw error;
158
- } finally {
159
- span.end();
160
- updateSpanId(prevSpanId);
161
- }
162
- });
163
- }
164
- /**
165
- * Set an attribute on the current active span (if any).
166
- * Used for setting span attributes after span creation (e.g. timber.result on access spans).
167
- */
168
- async function setSpanAttribute(key, value) {
169
- const platformSpan = traceAls.getStore()?.platformSpan;
170
- if (platformSpan) platformSpan.setAttribute(key, value);
171
- const api = await getOtelApi();
172
- if (!api) return;
173
- const activeSpan = api.trace.getActiveSpan();
174
- if (activeSpan) activeSpan.setAttribute(key, value);
175
- }
176
- /**
177
- * Add a span event to the current active span (if any).
178
- * Used for timber.cache HIT/MISS events — recorded as span events, not child spans.
17
+ * Design doc: design/11-platform.md §"waitUntil()"
179
18
  */
180
- async function addSpanEvent(name, attributes) {
181
- const api = await getOtelApi();
182
- if (!api) return;
183
- const activeSpan = api.trace.getActiveSpan();
184
- if (activeSpan) activeSpan.addEvent(name, attributes);
185
- }
186
19
  /**
187
- * Fire-and-forget span event — no await, no microtask overhead.
188
- *
189
- * Used on the cache hot path where awaiting addSpanEvent creates an
190
- * unnecessary microtask per cache operation. If OTEL is not loaded yet,
191
- * the event is silently dropped (acceptable for diagnostics).
20
+ * Get the current request's waitUntil function, if available.
192
21
  *
193
- * See TIM-370 for perf motivation.
194
- */
195
- function addSpanEventSync(name, attributes) {
196
- if (!_otelApi) return;
197
- const activeSpan = _otelApi.trace.getActiveSpan();
198
- if (activeSpan) activeSpan.addEvent(name, attributes);
199
- }
200
- /**
201
- * Try to extract the OTEL trace ID from the current active span context.
202
- * Returns undefined if OTEL is not active or no span exists.
22
+ * Returns undefined when no platform adapter has installed a waitUntil
23
+ * handler for the current request (e.g., on platforms that don't support
24
+ * lifecycle extension, or outside a request context).
203
25
  */
204
- async function getOtelTraceId() {
205
- const api = await getOtelApi();
206
- if (!api) return void 0;
207
- const activeSpan = api.trace.getActiveSpan();
208
- if (!activeSpan) return void 0;
209
- const ctx = activeSpan.spanContext();
210
- if (!ctx.traceId || ctx.traceId === "00000000000000000000000000000000") return;
211
- return {
212
- traceId: ctx.traceId,
213
- spanId: ctx.spanId
214
- };
26
+ function getWaitUntil() {
27
+ return waitUntilAls.getStore();
215
28
  }
216
29
  //#endregion
217
30
  //#region src/server/debug.ts
@@ -319,237 +132,32 @@ function _readTimberDebugEnv() {
319
132
  return false;
320
133
  }
321
134
  //#endregion
322
- //#region src/server/error-formatter.ts
135
+ //#region src/server/cookie-parsing.ts
323
136
  /**
324
- * Error Formatter — rewrites SSR/RSC error messages to surface user code.
137
+ * Cookie parsing and serialization helpers — pure string ↔ structure
138
+ * functions with no ALS dependency. Split out of `cookie-context.ts`
139
+ * (TIM-853) so the API surface and the wire-format codecs can each be
140
+ * read on their own.
325
141
  *
326
- * When React or Vite throw errors during SSR, stack traces reference
327
- * vendored dependency paths (e.g. `.vite/deps_ssr/@vitejs_plugin-rsc_vendor_...`)
328
- * and mangled export names (`__vite_ssr_export_default__`). This module
329
- * rewrites error messages and stack traces to point at user code instead.
142
+ * The functions in this module are total over arbitrary input. They
143
+ * never throw and never call `assertValid*` (the security validators
144
+ * live in the API surface — `cookie-context.ts` invokes them at every
145
+ * jar entry point so the smuggling-primitive invariant from TIM-868
146
+ * is enforced regardless of which path produced the bytes).
330
147
  *
331
- * Dev-only — in production, errors go through the structured logger
332
- * without formatting.
148
+ * Delegates to the `cookie` package (RFC 6265, dependency-free,
149
+ * browser-safe) for wire codecs. Adapts at the boundary to preserve
150
+ * timber's Map-based API and never-throw contract.
333
151
  */
334
152
  /**
335
- * Patterns that identify internal Vite/RSC vendor paths in stack traces.
336
- * These are replaced with human-readable labels.
337
- */
338
- var VENDOR_PATH_PATTERNS = [
339
- {
340
- pattern: /node_modules\/\.vite\/deps_ssr\/@vitejs_plugin-rsc_vendor_react-server-dom[^\s)]+/g,
341
- replacement: "<react-server-dom>"
342
- },
343
- {
344
- pattern: /node_modules\/\.vite\/deps_ssr\/@vitejs_plugin-rsc_vendor[^\s)]+/g,
345
- replacement: "<rsc-vendor>"
346
- },
347
- {
348
- pattern: /node_modules\/\.vite\/deps_ssr\/[^\s)]+/g,
349
- replacement: "<vite-dep>"
350
- },
351
- {
352
- pattern: /node_modules\/\.vite\/deps\/[^\s)]+/g,
353
- replacement: "<vite-dep>"
354
- }
355
- ];
356
- /**
357
- * Patterns that identify Vite-mangled export names in error messages.
358
- */
359
- var MANGLED_NAME_PATTERNS = [{
360
- pattern: /__vite_ssr_export_default__/g,
361
- replacement: "<default export>"
362
- }, {
363
- pattern: /__vite_ssr_export_(\w+)__/g,
364
- replacement: "<export $1>"
365
- }];
366
- /**
367
- * Rewrite an error's message and stack to replace internal Vite paths
368
- * and mangled names with human-readable labels.
369
- */
370
- function formatSsrError(error) {
371
- if (!(error instanceof Error)) return String(error);
372
- let message = error.message;
373
- let stack = error.stack ?? "";
374
- for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) message = message.replace(pattern, replacement);
375
- for (const { pattern, replacement } of VENDOR_PATH_PATTERNS) stack = stack.replace(pattern, replacement);
376
- for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) stack = stack.replace(pattern, replacement);
377
- const hint = extractErrorHint(error.message);
378
- const parts = [];
379
- parts.push(message);
380
- if (hint) parts.push(` → ${hint}`);
381
- const userFrames = extractUserFrames(stack);
382
- if (userFrames.length > 0) {
383
- parts.push("");
384
- parts.push(" User code in stack:");
385
- for (const frame of userFrames) parts.push(` ${frame}`);
386
- }
387
- return parts.join("\n");
388
- }
389
- /**
390
- * Extract a human-readable hint from common React/RSC error messages.
391
- *
392
- * React error messages contain useful information but the surrounding
393
- * context (vendor paths, mangled names) obscures it. This extracts the
394
- * actionable part as a one-line hint.
395
- */
396
- function extractErrorHint(message) {
397
- if (message.match(/Functions cannot be passed directly to Client Components/)) {
398
- const propMatch = message.match(/<[^>]*?\s(\w+)=\{function/);
399
- if (propMatch) return `Prop "${propMatch[1]}" is a function — mark it "use server" or call it before passing`;
400
- return "A function prop was passed to a Client Component — mark it \"use server\" or call it before passing";
401
- }
402
- if (message.includes("Objects are not valid as a React child")) return "An object was rendered as JSX children — convert to string or extract the value";
403
- const nullRefMatch = message.match(/Cannot read propert(?:y|ies) of (undefined|null) \(reading '(\w+)'\)/);
404
- if (nullRefMatch) return `Accessed .${nullRefMatch[2]} on ${nullRefMatch[1]} — check that the value exists`;
405
- const notFnMatch = message.match(/(\w+) is not a function/);
406
- if (notFnMatch) return `"${notFnMatch[1]}" is not a function — check imports and exports`;
407
- if (message.includes("Element type is invalid")) return "A component resolved to undefined/null — check default exports and import paths";
408
- if (message.includes("Invalid hook call")) return "A hook was called outside of a React component render. If this is a 'use client' component, ensure the directive is at the very top of the file (before any imports) and that @vitejs/plugin-rsc is loaded correctly. Barrel re-exports from non-'use client' files do not propagate the directive.";
409
- return null;
410
- }
411
- /**
412
- * Extract stack frames that reference user code (not node_modules,
413
- * not framework internals).
414
- *
415
- * Returns at most 5 frames to keep output concise.
416
- */
417
- function extractUserFrames(stack) {
418
- const lines = stack.split("\n");
419
- const userFrames = [];
420
- for (const line of lines) {
421
- const trimmed = line.trim();
422
- if (!trimmed.startsWith("at ")) continue;
423
- if (trimmed.includes("node_modules") || trimmed.includes("<react-server-dom>") || trimmed.includes("<rsc-vendor>") || trimmed.includes("<vite-dep>") || trimmed.includes("node:internal")) continue;
424
- userFrames.push(trimmed);
425
- if (userFrames.length >= 5) break;
426
- }
427
- return userFrames;
428
- }
429
- //#endregion
430
- //#region src/server/default-logger.ts
431
- /**
432
- * DefaultLogger — human-readable stderr logging when no custom logger is configured.
433
- *
434
- * Ships as the fallback so production deployments always have error visibility,
435
- * even without an `instrumentation.ts` logger export. Output is one line per
436
- * event, designed for `fly logs`, `kubectl logs`, Cloudflare dashboard tails, etc.
437
- *
438
- * Format:
439
- * [timber] ERROR message key=value key=value trace_id=4bf92f35
440
- * [timber] WARN message key=value key=value trace_id=4bf92f35
441
- * [timber] INFO message method=GET path=/dashboard status=200 durationMs=43 trace_id=4bf92f35
442
- *
443
- * Behavior:
444
- * - Suppressed entirely in dev mode (dev logging handles all output)
445
- * - `debug` suppressed unless TIMBER_DEBUG is set
446
- * - Replaced entirely when a custom logger is set via `setLogger()`
447
- *
448
- * See design/17-logging.md §"DefaultLogger"
449
- */
450
- /**
451
- * Format data fields as `key=value` pairs for human-readable output.
452
- * - `error` key is serialized via formatSsrError for stack trace cleanup
453
- * - `trace_id` is truncated to 8 chars for readability (full ID in OTEL)
454
- * - Other values are stringified inline
455
- */
456
- function formatDataFields(data) {
457
- if (!data) return "";
458
- const parts = [];
459
- let traceId;
460
- for (const [key, value] of Object.entries(data)) {
461
- if (key === "trace_id") {
462
- traceId = typeof value === "string" ? value : String(value);
463
- continue;
464
- }
465
- if (key === "error") {
466
- parts.push(`error=${formatSsrError(value)}`);
467
- continue;
468
- }
469
- if (value === void 0 || value === null) continue;
470
- parts.push(`${key}=${value}`);
471
- }
472
- if (traceId) parts.push(`trace_id=${traceId.slice(0, 8)}`);
473
- return parts.length > 0 ? " " + parts.join(" ") : "";
474
- }
475
- /** Pad level string to fixed width for alignment. */
476
- function padLevel(level) {
477
- return level.padEnd(5);
478
- }
479
- function createDefaultLogger() {
480
- return {
481
- error(msg, data) {
482
- const fields = formatDataFields(data);
483
- process.stderr.write(`[timber] ${padLevel("ERROR")} ${msg}${fields}\n`);
484
- },
485
- warn(msg, data) {
486
- const fields = formatDataFields(data);
487
- process.stderr.write(`[timber] ${padLevel("WARN")} ${msg}${fields}\n`);
488
- },
489
- info(msg, data) {
490
- if (isDevMode()) return;
491
- if (!isDebug()) return;
492
- const fields = formatDataFields(data);
493
- process.stderr.write(`[timber] ${padLevel("INFO")} ${msg}${fields}\n`);
494
- },
495
- debug(msg, data) {
496
- if (isDevMode()) return;
497
- if (!isDebug()) return;
498
- const fields = formatDataFields(data);
499
- process.stderr.write(`[timber] ${padLevel("DEBUG")} ${msg}${fields}\n`);
500
- }
501
- };
502
- }
503
- //#endregion
504
- //#region src/server/waituntil-bridge.ts
505
- /**
506
- * Per-request waitUntil bridge — ALS bridge for platform adapters.
507
- *
508
- * The generated entry point (Nitro, Cloudflare) wraps the handler with
509
- * `runWithWaitUntil`, binding the platform's lifecycle extension function
510
- * (e.g., h3's `event.waitUntil()` or CF's `ctx.waitUntil()`) for the
511
- * request duration. The `waitUntil()` primitive reads from this ALS to
512
- * dispatch background work to the correct platform API.
513
- *
514
- * Design doc: design/11-platform.md §"waitUntil()"
515
- */
516
- /**
517
- * Get the current request's waitUntil function, if available.
518
- *
519
- * Returns undefined when no platform adapter has installed a waitUntil
520
- * handler for the current request (e.g., on platforms that don't support
521
- * lifecycle extension, or outside a request context).
522
- */
523
- function getWaitUntil() {
524
- return waitUntilAls.getStore();
525
- }
526
- //#endregion
527
- //#region src/server/cookie-parsing.ts
528
- /**
529
- * Cookie parsing and serialization helpers — pure string ↔ structure
530
- * functions with no ALS dependency. Split out of `cookie-context.ts`
531
- * (TIM-853) so the API surface and the wire-format codecs can each be
532
- * read on their own.
533
- *
534
- * The functions in this module are total over arbitrary input. They
535
- * never throw and never call `assertValid*` (the security validators
536
- * live in the API surface — `cookie-context.ts` invokes them at every
537
- * jar entry point so the smuggling-primitive invariant from TIM-868
538
- * is enforced regardless of which path produced the bytes).
539
- *
540
- * Delegates to the `cookie` package (RFC 6265, dependency-free,
541
- * browser-safe) for wire codecs. Adapts at the boundary to preserve
542
- * timber's Map-based API and never-throw contract.
543
- */
544
- /**
545
- * Parse a Cookie header string into a Map of name → value pairs.
546
- * Follows RFC 6265 §4.2.1: cookies are semicolon-separated key=value pairs.
547
- *
548
- * Values are auto-decoded with `decodeURIComponent` so they round-trip
549
- * losslessly with `getCookies().set()` (which auto-encodes). Malformed
550
- * `%`-escapes from third-party cookies fall back to the raw byte sequence
551
- * — the parser must be total over arbitrary inbound headers, including
552
- * non-conforming values from other servers, browser extensions, etc.
153
+ * Parse a Cookie header string into a Map of name → value pairs.
154
+ * Follows RFC 6265 §4.2.1: cookies are semicolon-separated key=value pairs.
155
+ *
156
+ * Values are auto-decoded with `decodeURIComponent` so they round-trip
157
+ * losslessly with `getCookies().set()` (which auto-encodes). Malformed
158
+ * `%`-escapes from third-party cookies fall back to the raw byte sequence
159
+ * — the parser must be total over arbitrary inbound headers, including
160
+ * non-conforming values from other servers, browser extensions, etc.
553
161
  */
554
162
  function parseCookieHeader(header) {
555
163
  const map = /* @__PURE__ */ new Map();
@@ -1110,28 +718,53 @@ function warnIfNotSerializable(data, callerName) {
1110
718
  const issue = findNonSerializable(data);
1111
719
  if (issue) console.warn(`[timber] ${callerName}: ${issue}. Data passed to deny() or RenderError must be JSON-serializable because the post-flush path uses JSON.stringify, not React Flight.`);
1112
720
  }
721
+ var DENY_BRAND = Symbol.for("timber:signal.deny");
722
+ var REDIRECT_BRAND = Symbol.for("timber:signal.redirect");
723
+ var RENDER_ERROR_BRAND = Symbol.for("timber:signal.render-error");
724
+ var SSR_STREAM_ERROR_BRAND = Symbol.for("timber:signal.ssr-stream-error");
725
+ function hasBrand(value, brand) {
726
+ return typeof value === "object" && value !== null && brand in value;
727
+ }
728
+ /** True for any `DenySignal`, regardless of which module evaluation created it. */
729
+ function isDenySignal(error) {
730
+ return hasBrand(error, DENY_BRAND);
731
+ }
732
+ /** True for any `RedirectSignal`, regardless of which module evaluation created it. */
733
+ function isRedirectSignal(error) {
734
+ return hasBrand(error, REDIRECT_BRAND);
735
+ }
736
+ /**
737
+ * True for any `SsrStreamError`. This one also crosses Vite environments:
738
+ * it is thrown in SSR and caught in RSC, which are separate module graphs in
739
+ * the same process — the brand is the only identity the two share.
740
+ */
741
+ function isSsrStreamError(error) {
742
+ return hasBrand(error, SSR_STREAM_ERROR_BRAND);
743
+ }
1113
744
  /**
1114
745
  * Render-phase signal thrown by `deny()`. Caught by the framework to produce
1115
746
  * the correct HTTP status code (segment context) or graceful degradation (slot context).
747
+ *
748
+ * Detect with `isDenySignal()`, never `instanceof` — see "Signal branding" above.
1116
749
  */
1117
750
  var DenySignal = class extends Error {
751
+ [DENY_BRAND] = true;
1118
752
  status;
1119
753
  data;
1120
754
  /**
1121
- * Segment key of the segment that owns the deny page matched for this
1122
- * signal — set by a framework catch site that matched an entry it may not
1123
- * render at its own position, and re-threw so the boundary at the owning
1124
- * segment renders it instead.
755
+ * Ordered list of segment keys that own matching deny pages for this
756
+ * signal, best match first. Present = addressed (a boundary should
757
+ * render it); absent = unplaced (the re-render fallback serves it).
1125
758
  *
1126
- * Written only by the three in-tree catch sites (`PageDenyBoundary`,
1127
- * `TracedLayout`, `AccessGate`), never by user code. Two readers depend on
1128
- * it: `TimberErrorBoundary`, which declines any deny whose `ownerKey` is
1129
- * not its own `segmentKey`, and `buildRscPayloadResponse`, which keeps
1130
- * streaming instead of re-rendering when it is set.
759
+ * In-tree hoists stamp a single-element list (`[entry.ownerKey]`) —
760
+ * identical to the pre-TIM-1450 `ownerKey`. Late addressing (TIM-1450)
761
+ * stamps the full candidate list so the client can find the first
762
+ * *reachable* owner via its ancestry context, eliminating the
763
+ * owner-below-throw-site escape.
1131
764
  *
1132
765
  * See design/04-authorization.md §"Where a Deny Page Renders", TIM-1356.
1133
766
  */
1134
- ownerKey;
767
+ owners;
1135
768
  /**
1136
769
  * When true, the pipeline must respond with `rscErrorEnvelope` so the
1137
770
  * client hard-navigates instead of attempting to render a deny page
@@ -1207,8 +840,11 @@ function deny(statusOrOptions, data) {
1207
840
  /**
1208
841
  * Render-phase signal thrown by `redirect()` and `redirectExternal()`.
1209
842
  * Caught by the framework to produce a 3xx response or client-side navigation.
843
+ *
844
+ * Detect with `isRedirectSignal()`, never `instanceof` — see "Signal branding" above.
1210
845
  */
1211
846
  var RedirectSignal = class extends Error {
847
+ [REDIRECT_BRAND] = true;
1212
848
  location;
1213
849
  status;
1214
850
  constructor(location, status) {
@@ -1226,7 +862,7 @@ var RedirectSignal = class extends Error {
1226
862
  * See also: isFrameworkSignalError() in client/browser-dev.ts (client-only).
1227
863
  */
1228
864
  function isControlFlowSignal(error) {
1229
- if (error instanceof DenySignal || error instanceof RedirectSignal) return true;
865
+ if (isDenySignal(error) || isRedirectSignal(error)) return true;
1230
866
  if (error && typeof error === "object") {
1231
867
  const digest = error.digest;
1232
868
  if (typeof digest === "string") try {
@@ -1297,8 +933,11 @@ function redirectExternal(url, allowList, status = 302) {
1297
933
  * resourceId: params.id,
1298
934
  * })
1299
935
  * ```
936
+ *
937
+ * Detect with `isRenderError()`, never `instanceof` — see "Signal branding" above.
1300
938
  */
1301
939
  var RenderError = class extends Error {
940
+ [RENDER_ERROR_BRAND] = true;
1302
941
  code;
1303
942
  digest;
1304
943
  status;
@@ -1340,6 +979,398 @@ function waitUntil(promise) {
1340
979
  }
1341
980
  }
1342
981
  //#endregion
982
+ //#region src/server/tracing.ts
983
+ /**
984
+ * Tracing — per-request trace ID via AsyncLocalStorage, OTEL span helpers.
985
+ *
986
+ * getTraceId() is always available in server code (middleware, access, components, actions).
987
+ * Returns a 32-char lowercase hex string — the OTEL trace ID when an SDK is active,
988
+ * or a crypto.randomUUID()-derived fallback otherwise.
989
+ *
990
+ * See design/17-logging.md §"trace_id is Always Set"
991
+ */
992
+ /**
993
+ * Returns the current request's trace ID — always a 32-char lowercase hex string.
994
+ *
995
+ * With OTEL: the real OTEL trace ID (matches Jaeger/Honeycomb/Datadog).
996
+ * Without OTEL: crypto.randomUUID() with hyphens stripped.
997
+ *
998
+ * Throws if called outside a request context (no ALS store).
999
+ */
1000
+ function getTraceId() {
1001
+ const store = traceAls.getStore();
1002
+ if (!store) throw new Error("[timber] getTraceId() called outside of a request context. It can only be used in middleware, access checks, server components, and server actions.");
1003
+ return store.traceId;
1004
+ }
1005
+ /**
1006
+ * Returns the current OTEL span ID if available, undefined otherwise.
1007
+ */
1008
+ function getSpanId() {
1009
+ return traceAls.getStore()?.spanId;
1010
+ }
1011
+ /**
1012
+ * Generate a 32-char lowercase hex ID from crypto.randomUUID().
1013
+ * Same format as OTEL trace IDs — zero-friction upgrade path.
1014
+ */
1015
+ function generateTraceId() {
1016
+ return randomUUID().replace(/-/g, "");
1017
+ }
1018
+ /**
1019
+ * Run a callback within a trace context. Used by the pipeline to establish
1020
+ * per-request ALS scope.
1021
+ */
1022
+ function runWithTraceId(id, fn) {
1023
+ return traceAls.run({ traceId: id }, fn);
1024
+ }
1025
+ /**
1026
+ * Replace the trace ID in the current ALS store. Used when OTEL creates
1027
+ * a root span and we want to switch from the UUID fallback to the real
1028
+ * OTEL trace ID.
1029
+ */
1030
+ function replaceTraceId(newTraceId, newSpanId) {
1031
+ const store = traceAls.getStore();
1032
+ if (store) {
1033
+ store.traceId = newTraceId;
1034
+ store.spanId = newSpanId;
1035
+ }
1036
+ }
1037
+ /**
1038
+ * Update the span ID in the current ALS store. Used when entering a new
1039
+ * OTEL span to keep log–trace correlation accurate.
1040
+ */
1041
+ function updateSpanId(newSpanId) {
1042
+ const store = traceAls.getStore();
1043
+ if (store) store.spanId = newSpanId;
1044
+ }
1045
+ /**
1046
+ * Get the current trace store, or undefined if outside a request context.
1047
+ * Framework-internal — use getTraceId()/getSpanId() in user code.
1048
+ */
1049
+ function getTraceStore() {
1050
+ return traceAls.getStore();
1051
+ }
1052
+ var PLATFORM_TRACER_KEY = Symbol.for("timber:platform-tracer");
1053
+ /** The registered native platform tracer, if any. */
1054
+ function getPlatformTracer() {
1055
+ return globalThis[PLATFORM_TRACER_KEY];
1056
+ }
1057
+ /**
1058
+ * Attempt to get the @opentelemetry/api tracer. Returns undefined if the
1059
+ * package is not installed or no SDK is registered.
1060
+ *
1061
+ * timber.js depends on @opentelemetry/api as the vendor-neutral interface.
1062
+ * The API is a no-op by default — spans are only emitted when the developer
1063
+ * initializes an SDK in register().
1064
+ */
1065
+ var _otelApi;
1066
+ async function getOtelApi() {
1067
+ if (_otelApi === void 0) try {
1068
+ _otelApi = await import("@opentelemetry/api");
1069
+ } catch {
1070
+ _otelApi = null;
1071
+ }
1072
+ return _otelApi;
1073
+ }
1074
+ /** OTEL tracer instance, lazily created. */
1075
+ var _tracer;
1076
+ /**
1077
+ * Get the timber.js OTEL tracer. Returns null if @opentelemetry/api is not available.
1078
+ */
1079
+ async function getTracer() {
1080
+ if (_tracer === void 0) {
1081
+ const api = await getOtelApi();
1082
+ if (api) _tracer = api.trace.getTracer("timber.js");
1083
+ else _tracer = null;
1084
+ }
1085
+ return _tracer;
1086
+ }
1087
+ /**
1088
+ * Run a function within a framework span. Composes two emission channels:
1089
+ *
1090
+ * - **Native platform span** — when an adapter registered a PlatformTracer
1091
+ * (Cloudflare Workers), the fn is wrapped in platformTracer.enterSpan()
1092
+ * so it appears in the platform's native trace view.
1093
+ * - **OTEL span** — when an OTEL SDK is active, the fn also runs inside an
1094
+ * OTEL span (dual emission). No SDK and no platform tracer = zero overhead.
1095
+ *
1096
+ * Automatically:
1097
+ * - Creates the span as a child of the current context
1098
+ * - Updates the ALS span ID for log–trace correlation
1099
+ * - Ends the span when the function completes
1100
+ * - Records exceptions on error (OTEL channel)
1101
+ */
1102
+ async function withSpan(name, attributes, fn) {
1103
+ const platformTracer = getPlatformTracer();
1104
+ if (!platformTracer) return runOtelSpan(name, attributes, fn);
1105
+ return platformTracer.enterSpan(name, async (span) => {
1106
+ for (const key of Object.keys(attributes)) span.setAttribute(key, attributes[key]);
1107
+ const store = traceAls.getStore();
1108
+ const prevPlatformSpan = store?.platformSpan;
1109
+ if (store) store.platformSpan = span;
1110
+ try {
1111
+ return await runOtelSpan(name, attributes, fn);
1112
+ } finally {
1113
+ if (store) store.platformSpan = prevPlatformSpan;
1114
+ }
1115
+ });
1116
+ }
1117
+ /** The OTEL half of withSpan() — no-op passthrough when no SDK is active. */
1118
+ async function runOtelSpan(name, attributes, fn) {
1119
+ const tracer = await getTracer();
1120
+ if (!tracer) return fn();
1121
+ const api = await getOtelApi();
1122
+ return tracer.startActiveSpan(name, { attributes }, async (span) => {
1123
+ const prevSpanId = getSpanId();
1124
+ updateSpanId(span.spanContext().spanId);
1125
+ try {
1126
+ const result = await fn();
1127
+ span.setStatus({ code: api.SpanStatusCode.OK });
1128
+ return result;
1129
+ } catch (error) {
1130
+ span.setStatus({ code: api.SpanStatusCode.ERROR });
1131
+ if (error instanceof Error) span.recordException(error);
1132
+ throw error;
1133
+ } finally {
1134
+ span.end();
1135
+ updateSpanId(prevSpanId);
1136
+ }
1137
+ });
1138
+ }
1139
+ /**
1140
+ * Set an attribute on the current active span (if any).
1141
+ * Used for setting span attributes after span creation (e.g. timber.result on access spans).
1142
+ */
1143
+ async function setSpanAttribute(key, value) {
1144
+ const platformSpan = traceAls.getStore()?.platformSpan;
1145
+ if (platformSpan) platformSpan.setAttribute(key, value);
1146
+ const api = await getOtelApi();
1147
+ if (!api) return;
1148
+ const activeSpan = api.trace.getActiveSpan();
1149
+ if (activeSpan) activeSpan.setAttribute(key, value);
1150
+ }
1151
+ /**
1152
+ * Add a span event to the current active span (if any).
1153
+ * Used for timber.cache HIT/MISS events — recorded as span events, not child spans.
1154
+ */
1155
+ async function addSpanEvent(name, attributes) {
1156
+ const api = await getOtelApi();
1157
+ if (!api) return;
1158
+ const activeSpan = api.trace.getActiveSpan();
1159
+ if (activeSpan) activeSpan.addEvent(name, attributes);
1160
+ }
1161
+ /**
1162
+ * Fire-and-forget span event — no await, no microtask overhead.
1163
+ *
1164
+ * Used on the cache hot path where awaiting addSpanEvent creates an
1165
+ * unnecessary microtask per cache operation. If OTEL is not loaded yet,
1166
+ * the event is silently dropped (acceptable for diagnostics).
1167
+ *
1168
+ * See TIM-370 for perf motivation.
1169
+ */
1170
+ function addSpanEventSync(name, attributes) {
1171
+ if (!_otelApi) return;
1172
+ const activeSpan = _otelApi.trace.getActiveSpan();
1173
+ if (activeSpan) activeSpan.addEvent(name, attributes);
1174
+ }
1175
+ /**
1176
+ * Try to extract the OTEL trace ID from the current active span context.
1177
+ * Returns undefined if OTEL is not active or no span exists.
1178
+ */
1179
+ async function getOtelTraceId() {
1180
+ const api = await getOtelApi();
1181
+ if (!api) return void 0;
1182
+ const activeSpan = api.trace.getActiveSpan();
1183
+ if (!activeSpan) return void 0;
1184
+ const ctx = activeSpan.spanContext();
1185
+ if (!ctx.traceId || ctx.traceId === "00000000000000000000000000000000") return;
1186
+ return {
1187
+ traceId: ctx.traceId,
1188
+ spanId: ctx.spanId
1189
+ };
1190
+ }
1191
+ //#endregion
1192
+ //#region src/server/error-formatter.ts
1193
+ /**
1194
+ * Error Formatter — rewrites SSR/RSC error messages to surface user code.
1195
+ *
1196
+ * When React or Vite throw errors during SSR, stack traces reference
1197
+ * vendored dependency paths (e.g. `.vite/deps_ssr/@vitejs_plugin-rsc_vendor_...`)
1198
+ * and mangled export names (`__vite_ssr_export_default__`). This module
1199
+ * rewrites error messages and stack traces to point at user code instead.
1200
+ *
1201
+ * Dev-only — in production, errors go through the structured logger
1202
+ * without formatting.
1203
+ */
1204
+ /**
1205
+ * Patterns that identify internal Vite/RSC vendor paths in stack traces.
1206
+ * These are replaced with human-readable labels.
1207
+ */
1208
+ var VENDOR_PATH_PATTERNS = [
1209
+ {
1210
+ pattern: /node_modules\/\.vite\/deps_ssr\/@vitejs_plugin-rsc_vendor_react-server-dom[^\s)]+/g,
1211
+ replacement: "<react-server-dom>"
1212
+ },
1213
+ {
1214
+ pattern: /node_modules\/\.vite\/deps_ssr\/@vitejs_plugin-rsc_vendor[^\s)]+/g,
1215
+ replacement: "<rsc-vendor>"
1216
+ },
1217
+ {
1218
+ pattern: /node_modules\/\.vite\/deps_ssr\/[^\s)]+/g,
1219
+ replacement: "<vite-dep>"
1220
+ },
1221
+ {
1222
+ pattern: /node_modules\/\.vite\/deps\/[^\s)]+/g,
1223
+ replacement: "<vite-dep>"
1224
+ }
1225
+ ];
1226
+ /**
1227
+ * Patterns that identify Vite-mangled export names in error messages.
1228
+ */
1229
+ var MANGLED_NAME_PATTERNS = [{
1230
+ pattern: /__vite_ssr_export_default__/g,
1231
+ replacement: "<default export>"
1232
+ }, {
1233
+ pattern: /__vite_ssr_export_(\w+)__/g,
1234
+ replacement: "<export $1>"
1235
+ }];
1236
+ /**
1237
+ * Rewrite an error's message and stack to replace internal Vite paths
1238
+ * and mangled names with human-readable labels.
1239
+ */
1240
+ function formatSsrError(error) {
1241
+ if (!(error instanceof Error)) return String(error);
1242
+ let message = error.message;
1243
+ let stack = error.stack ?? "";
1244
+ for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) message = message.replace(pattern, replacement);
1245
+ for (const { pattern, replacement } of VENDOR_PATH_PATTERNS) stack = stack.replace(pattern, replacement);
1246
+ for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) stack = stack.replace(pattern, replacement);
1247
+ const hint = extractErrorHint(error.message);
1248
+ const parts = [];
1249
+ parts.push(message);
1250
+ if (hint) parts.push(` → ${hint}`);
1251
+ const userFrames = extractUserFrames(stack);
1252
+ if (userFrames.length > 0) {
1253
+ parts.push("");
1254
+ parts.push(" User code in stack:");
1255
+ for (const frame of userFrames) parts.push(` ${frame}`);
1256
+ }
1257
+ return parts.join("\n");
1258
+ }
1259
+ /**
1260
+ * Extract a human-readable hint from common React/RSC error messages.
1261
+ *
1262
+ * React error messages contain useful information but the surrounding
1263
+ * context (vendor paths, mangled names) obscures it. This extracts the
1264
+ * actionable part as a one-line hint.
1265
+ */
1266
+ function extractErrorHint(message) {
1267
+ if (message.match(/Functions cannot be passed directly to Client Components/)) {
1268
+ const propMatch = message.match(/<[^>]*?\s(\w+)=\{function/);
1269
+ if (propMatch) return `Prop "${propMatch[1]}" is a function — mark it "use server" or call it before passing`;
1270
+ return "A function prop was passed to a Client Component — mark it \"use server\" or call it before passing";
1271
+ }
1272
+ if (message.includes("Objects are not valid as a React child")) return "An object was rendered as JSX children — convert to string or extract the value";
1273
+ const nullRefMatch = message.match(/Cannot read propert(?:y|ies) of (undefined|null) \(reading '(\w+)'\)/);
1274
+ if (nullRefMatch) return `Accessed .${nullRefMatch[2]} on ${nullRefMatch[1]} — check that the value exists`;
1275
+ const notFnMatch = message.match(/(\w+) is not a function/);
1276
+ if (notFnMatch) return `"${notFnMatch[1]}" is not a function — check imports and exports`;
1277
+ if (message.includes("Element type is invalid")) return "A component resolved to undefined/null — check default exports and import paths";
1278
+ if (message.includes("Invalid hook call")) return "A hook was called outside of a React component render. If this is a 'use client' component, ensure the directive is at the very top of the file (before any imports) and that @vitejs/plugin-rsc is loaded correctly. Barrel re-exports from non-'use client' files do not propagate the directive.";
1279
+ return null;
1280
+ }
1281
+ /**
1282
+ * Extract stack frames that reference user code (not node_modules,
1283
+ * not framework internals).
1284
+ *
1285
+ * Returns at most 5 frames to keep output concise.
1286
+ */
1287
+ function extractUserFrames(stack) {
1288
+ const lines = stack.split("\n");
1289
+ const userFrames = [];
1290
+ for (const line of lines) {
1291
+ const trimmed = line.trim();
1292
+ if (!trimmed.startsWith("at ")) continue;
1293
+ if (trimmed.includes("node_modules") || trimmed.includes("<react-server-dom>") || trimmed.includes("<rsc-vendor>") || trimmed.includes("<vite-dep>") || trimmed.includes("node:internal")) continue;
1294
+ userFrames.push(trimmed);
1295
+ if (userFrames.length >= 5) break;
1296
+ }
1297
+ return userFrames;
1298
+ }
1299
+ //#endregion
1300
+ //#region src/server/default-logger.ts
1301
+ /**
1302
+ * DefaultLogger — human-readable stderr logging when no custom logger is configured.
1303
+ *
1304
+ * Ships as the fallback so production deployments always have error visibility,
1305
+ * even without an `instrumentation.ts` logger export. Output is one line per
1306
+ * event, designed for `fly logs`, `kubectl logs`, Cloudflare dashboard tails, etc.
1307
+ *
1308
+ * Format:
1309
+ * [timber] ERROR message key=value key=value trace_id=4bf92f35
1310
+ * [timber] WARN message key=value key=value trace_id=4bf92f35
1311
+ * [timber] INFO message method=GET path=/dashboard status=200 durationMs=43 trace_id=4bf92f35
1312
+ *
1313
+ * Behavior:
1314
+ * - Suppressed entirely in dev mode (dev logging handles all output)
1315
+ * - `debug` suppressed unless TIMBER_DEBUG is set
1316
+ * - Replaced entirely when a custom logger is set via `setLogger()`
1317
+ *
1318
+ * See design/17-logging.md §"DefaultLogger"
1319
+ */
1320
+ /**
1321
+ * Format data fields as `key=value` pairs for human-readable output.
1322
+ * - `error` key is serialized via formatSsrError for stack trace cleanup
1323
+ * - `trace_id` is truncated to 8 chars for readability (full ID in OTEL)
1324
+ * - Other values are stringified inline
1325
+ */
1326
+ function formatDataFields(data) {
1327
+ if (!data) return "";
1328
+ const parts = [];
1329
+ let traceId;
1330
+ for (const [key, value] of Object.entries(data)) {
1331
+ if (key === "trace_id") {
1332
+ traceId = typeof value === "string" ? value : String(value);
1333
+ continue;
1334
+ }
1335
+ if (key === "error") {
1336
+ parts.push(`error=${formatSsrError(value)}`);
1337
+ continue;
1338
+ }
1339
+ if (value === void 0 || value === null) continue;
1340
+ parts.push(`${key}=${value}`);
1341
+ }
1342
+ if (traceId) parts.push(`trace_id=${traceId.slice(0, 8)}`);
1343
+ return parts.length > 0 ? " " + parts.join(" ") : "";
1344
+ }
1345
+ /** Pad level string to fixed width for alignment. */
1346
+ function padLevel(level) {
1347
+ return level.padEnd(5);
1348
+ }
1349
+ function createDefaultLogger() {
1350
+ return {
1351
+ error(msg, data) {
1352
+ const fields = formatDataFields(data);
1353
+ process.stderr.write(`[timber] ${padLevel("ERROR")} ${msg}${fields}\n`);
1354
+ },
1355
+ warn(msg, data) {
1356
+ const fields = formatDataFields(data);
1357
+ process.stderr.write(`[timber] ${padLevel("WARN")} ${msg}${fields}\n`);
1358
+ },
1359
+ info(msg, data) {
1360
+ if (isDevMode()) return;
1361
+ if (!isDebug()) return;
1362
+ const fields = formatDataFields(data);
1363
+ process.stderr.write(`[timber] ${padLevel("INFO")} ${msg}${fields}\n`);
1364
+ },
1365
+ debug(msg, data) {
1366
+ if (isDevMode()) return;
1367
+ if (!isDebug()) return;
1368
+ const fields = formatDataFields(data);
1369
+ process.stderr.write(`[timber] ${padLevel("DEBUG")} ${msg}${fields}\n`);
1370
+ }
1371
+ };
1372
+ }
1373
+ //#endregion
1343
1374
  //#region src/server/logger.ts
1344
1375
  /**
1345
1376
  * Logger — structured logging with environment-aware formatting.
@@ -1448,6 +1479,6 @@ function swallow(err, reason, opts) {
1448
1479
  } catch {}
1449
1480
  }
1450
1481
  //#endregion
1451
- export { setMatchedSegmentPath as A, addSpanEventSync as B, applyRequestHeaderOverlay as C, getSegmentParams as D, getSearchParams as E, getSetCookieHeaders as F, replaceTraceId as G, getOtelTraceId as H, getWaitUntil as I, withSpan as J, runWithTraceId as K, isDebug as L, setSegmentParams as M, getCookie as N, markResponseFlushed as O, getCookieJar as P, isDevMode as R, waitUntil as S, getHeaders as T, getSpanId as U, generateTraceId as V, getTraceId as W, RedirectSignal as _, logProxyError as a, redirect as b, logRequestReceived as c, logSwrRefetchFailed as d, logWaitUntilRejected as f, DenySignal as g, swallow as h, logMiddlewareShortCircuit as i, setMutableCookieContext as j, runWithRequestContext as k, logRouteError as l, setLogger as m, logCacheMiss as n, logRenderError as o, logWaitUntilUnsupported as p, setSpanAttribute as q, logMiddlewareError as r, logRequestCompleted as s, getLogger as t, logSlowRequest as u, RenderError as v, getHeader as w, redirectExternal as x, deny as y, addSpanEvent as z };
1482
+ export { isDenySignal as A, getSegmentParams as B, runWithTraceId as C, RedirectSignal as D, DenySignal as E, waitUntil as F, setSegmentParams as G, runWithRequestContext as H, applyRequestHeaderOverlay as I, getSetCookieHeaders as J, getCookie as K, getHeader as L, isSsrStreamError as M, redirect as N, RenderError as O, redirectExternal as P, getHeaders as R, replaceTraceId as S, withSpan as T, setMatchedSegmentPath as U, markResponseFlushed as V, setMutableCookieContext as W, isDevMode as X, isDebug as Y, getWaitUntil as Z, addSpanEventSync as _, logProxyError as a, getSpanId as b, logRequestReceived as c, logSwrRefetchFailed as d, logWaitUntilRejected as f, addSpanEvent as g, swallow as h, logMiddlewareShortCircuit as i, isRedirectSignal as j, deny as k, logRouteError as l, setLogger as m, logCacheMiss as n, logRenderError as o, logWaitUntilUnsupported as p, getCookieJar as q, logMiddlewareError as r, logRequestCompleted as s, getLogger as t, logSlowRequest as u, generateTraceId as v, setSpanAttribute as w, getTraceId as x, getOtelTraceId as y, getSearchParams as z };
1452
1483
 
1453
- //# sourceMappingURL=logger-DDirEsn7.js.map
1484
+ //# sourceMappingURL=logger-uLBuGKDI.js.map