@timber-js/app 0.1.20 → 0.1.22

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 (137) hide show
  1. package/dist/_chunks/als-registry-c0AGnbqS.js +39 -0
  2. package/dist/_chunks/als-registry-c0AGnbqS.js.map +1 -0
  3. package/dist/_chunks/{interception-c-a3uODY.js → interception-DGDIjDbR.js} +10 -3
  4. package/dist/_chunks/interception-DGDIjDbR.js.map +1 -0
  5. package/dist/_chunks/{metadata-routes-BDnswgRO.js → metadata-routes-CQCnF4VK.js} +14 -2
  6. package/dist/_chunks/metadata-routes-CQCnF4VK.js.map +1 -0
  7. package/dist/_chunks/{request-context-BzES06i1.js → request-context-C69VW4xS.js} +2 -4
  8. package/dist/_chunks/request-context-C69VW4xS.js.map +1 -0
  9. package/dist/_chunks/ssr-data-B2yikEEB.js +90 -0
  10. package/dist/_chunks/ssr-data-B2yikEEB.js.map +1 -0
  11. package/dist/_chunks/{tracing-BtOwb8O6.js → tracing-tIvqStk8.js} +2 -3
  12. package/dist/_chunks/tracing-tIvqStk8.js.map +1 -0
  13. package/dist/_chunks/{use-cookie-D2cZu0jK.js → use-cookie-D5aS4slY.js} +2 -2
  14. package/dist/_chunks/{use-cookie-D2cZu0jK.js.map → use-cookie-D5aS4slY.js.map} +1 -1
  15. package/dist/_chunks/{use-query-states-wEXY2JQB.js → use-query-states-DAhgj8Gx.js} +1 -1
  16. package/dist/_chunks/{use-query-states-wEXY2JQB.js.map → use-query-states-DAhgj8Gx.js.map} +1 -1
  17. package/dist/cache/index.js +2 -1
  18. package/dist/cache/index.js.map +1 -1
  19. package/dist/client/error-boundary.js +1 -1
  20. package/dist/client/index.d.ts +1 -1
  21. package/dist/client/index.d.ts.map +1 -1
  22. package/dist/client/index.js +40 -26
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/client/router-ref.d.ts.map +1 -1
  25. package/dist/client/router.d.ts.map +1 -1
  26. package/dist/client/ssr-data.d.ts +3 -0
  27. package/dist/client/ssr-data.d.ts.map +1 -1
  28. package/dist/client/state.d.ts +47 -0
  29. package/dist/client/state.d.ts.map +1 -0
  30. package/dist/client/types.d.ts +10 -1
  31. package/dist/client/types.d.ts.map +1 -1
  32. package/dist/client/unload-guard.d.ts +3 -0
  33. package/dist/client/unload-guard.d.ts.map +1 -1
  34. package/dist/client/use-params.d.ts +19 -6
  35. package/dist/client/use-params.d.ts.map +1 -1
  36. package/dist/client/use-search-params.d.ts +3 -0
  37. package/dist/client/use-search-params.d.ts.map +1 -1
  38. package/dist/cookies/index.js +4 -2
  39. package/dist/cookies/index.js.map +1 -1
  40. package/dist/index.js +4 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/plugins/shims.d.ts.map +1 -1
  43. package/dist/routing/index.js +1 -1
  44. package/dist/routing/scanner.d.ts.map +1 -1
  45. package/dist/rsc-runtime/browser.d.ts +13 -0
  46. package/dist/rsc-runtime/browser.d.ts.map +1 -0
  47. package/dist/rsc-runtime/rsc.d.ts +14 -0
  48. package/dist/rsc-runtime/rsc.d.ts.map +1 -0
  49. package/dist/rsc-runtime/ssr.d.ts +13 -0
  50. package/dist/rsc-runtime/ssr.d.ts.map +1 -0
  51. package/dist/search-params/builtin-codecs.d.ts +105 -0
  52. package/dist/search-params/builtin-codecs.d.ts.map +1 -0
  53. package/dist/search-params/index.d.ts +1 -0
  54. package/dist/search-params/index.d.ts.map +1 -1
  55. package/dist/search-params/index.js +167 -2
  56. package/dist/search-params/index.js.map +1 -1
  57. package/dist/server/actions.d.ts +2 -7
  58. package/dist/server/actions.d.ts.map +1 -1
  59. package/dist/server/als-registry.d.ts +80 -0
  60. package/dist/server/als-registry.d.ts.map +1 -0
  61. package/dist/server/early-hints-sender.d.ts.map +1 -1
  62. package/dist/server/form-flash.d.ts.map +1 -1
  63. package/dist/server/index.d.ts +1 -0
  64. package/dist/server/index.d.ts.map +1 -1
  65. package/dist/server/index.js +242 -76
  66. package/dist/server/index.js.map +1 -1
  67. package/dist/server/metadata-routes.d.ts +27 -0
  68. package/dist/server/metadata-routes.d.ts.map +1 -1
  69. package/dist/server/pipeline.d.ts +7 -0
  70. package/dist/server/pipeline.d.ts.map +1 -1
  71. package/dist/server/primitives.d.ts +14 -6
  72. package/dist/server/primitives.d.ts.map +1 -1
  73. package/dist/server/request-context.d.ts +2 -32
  74. package/dist/server/request-context.d.ts.map +1 -1
  75. package/dist/server/route-matcher.d.ts +5 -0
  76. package/dist/server/route-matcher.d.ts.map +1 -1
  77. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  78. package/dist/server/rsc-entry/rsc-payload.d.ts +25 -0
  79. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -0
  80. package/dist/server/rsc-entry/rsc-stream.d.ts +43 -0
  81. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -0
  82. package/dist/server/rsc-entry/ssr-renderer.d.ts +52 -0
  83. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -0
  84. package/dist/server/rsc-prop-warnings.d.ts +53 -0
  85. package/dist/server/rsc-prop-warnings.d.ts.map +1 -0
  86. package/dist/server/server-timing.d.ts +49 -0
  87. package/dist/server/server-timing.d.ts.map +1 -0
  88. package/dist/server/tracing.d.ts +2 -6
  89. package/dist/server/tracing.d.ts.map +1 -1
  90. package/dist/server/types.d.ts +11 -0
  91. package/dist/server/types.d.ts.map +1 -1
  92. package/package.json +1 -1
  93. package/src/client/browser-entry.ts +1 -1
  94. package/src/client/index.ts +1 -1
  95. package/src/client/router-ref.ts +6 -12
  96. package/src/client/router.ts +14 -4
  97. package/src/client/ssr-data.ts +25 -9
  98. package/src/client/state.ts +83 -0
  99. package/src/client/types.ts +18 -1
  100. package/src/client/unload-guard.ts +6 -3
  101. package/src/client/use-params.ts +42 -32
  102. package/src/client/use-search-params.ts +9 -5
  103. package/src/plugins/shims.ts +26 -2
  104. package/src/routing/scanner.ts +18 -2
  105. package/src/rsc-runtime/browser.ts +18 -0
  106. package/src/rsc-runtime/rsc.ts +19 -0
  107. package/src/rsc-runtime/ssr.ts +13 -0
  108. package/src/search-params/builtin-codecs.ts +228 -0
  109. package/src/search-params/index.ts +11 -0
  110. package/src/server/action-handler.ts +1 -1
  111. package/src/server/actions.ts +4 -10
  112. package/src/server/als-registry.ts +116 -0
  113. package/src/server/deny-renderer.ts +1 -1
  114. package/src/server/early-hints-sender.ts +1 -3
  115. package/src/server/form-flash.ts +1 -5
  116. package/src/server/index.ts +1 -0
  117. package/src/server/metadata-routes.ts +61 -0
  118. package/src/server/pipeline.ts +164 -38
  119. package/src/server/primitives.ts +110 -6
  120. package/src/server/request-context.ts +8 -36
  121. package/src/server/route-matcher.ts +25 -2
  122. package/src/server/rsc-entry/error-renderer.ts +1 -1
  123. package/src/server/rsc-entry/index.ts +42 -380
  124. package/src/server/rsc-entry/rsc-payload.ts +126 -0
  125. package/src/server/rsc-entry/rsc-stream.ts +162 -0
  126. package/src/server/rsc-entry/ssr-renderer.ts +228 -0
  127. package/src/server/rsc-prop-warnings.ts +187 -0
  128. package/src/server/server-timing.ts +132 -0
  129. package/src/server/ssr-entry.ts +1 -1
  130. package/src/server/tracing.ts +3 -11
  131. package/src/server/types.ts +16 -0
  132. package/dist/_chunks/interception-c-a3uODY.js.map +0 -1
  133. package/dist/_chunks/metadata-routes-BDnswgRO.js.map +0 -1
  134. package/dist/_chunks/request-context-BzES06i1.js.map +0 -1
  135. package/dist/_chunks/ssr-data-BgSwMbN9.js +0 -38
  136. package/dist/_chunks/ssr-data-BgSwMbN9.js.map +0 -1
  137. package/dist/_chunks/tracing-BtOwb8O6.js.map +0 -1
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Centralized AsyncLocalStorage registry for server-side per-request state.
3
+ *
4
+ * ALL ALS instances used by the server framework live here. Individual
5
+ * modules (request-context.ts, tracing.ts, actions.ts, etc.) import from
6
+ * this registry and re-export public accessor functions.
7
+ *
8
+ * Why: ALS instances require singleton semantics — if two copies of the
9
+ * same ALS exist (one from a relative import, one from a barrel import),
10
+ * one module writes to its copy and another reads from an empty copy.
11
+ * Centralizing ALS creation in a single module eliminates this class of bug.
12
+ *
13
+ * The `timber-shims` plugin ensures `@timber-js/app/server` resolves to
14
+ * src/ in RSC and SSR environments, so all import paths converge here.
15
+ *
16
+ * DO NOT create ALS instances outside this file. If you need a new ALS,
17
+ * add it here and import from `./als-registry.js` in the consuming module.
18
+ *
19
+ * See design/18-build-system.md §"Module Singleton Strategy" and
20
+ * §"Singleton State Registry".
21
+ */
22
+ import { AsyncLocalStorage } from 'node:async_hooks';
23
+ /** @internal — import via request-context.ts public API */
24
+ export declare const requestContextAls: AsyncLocalStorage<RequestContextStore>;
25
+ export interface RequestContextStore {
26
+ /** Incoming request headers (read-only view). */
27
+ headers: Headers;
28
+ /** Raw cookie header string, parsed lazily into a Map on first access. */
29
+ cookieHeader: string;
30
+ /** Lazily-parsed cookie map (mutable — reflects write-overlay from set()). */
31
+ parsedCookies?: Map<string, string>;
32
+ /** Original (pre-overlay) frozen headers, kept for overlay merging. */
33
+ originalHeaders: Headers;
34
+ /**
35
+ * Promise resolving to the route's typed search params (when search-params.ts
36
+ * exists) or to the raw URLSearchParams. Stored as a Promise so the framework
37
+ * can later support partial pre-rendering where param resolution is deferred.
38
+ */
39
+ searchParamsPromise: Promise<URLSearchParams | Record<string, unknown>>;
40
+ /** Outgoing Set-Cookie entries (name → serialized value + options). Last write wins. */
41
+ cookieJar: Map<string, CookieEntry>;
42
+ /** Whether the response has flushed (headers committed). */
43
+ flushed: boolean;
44
+ /** Whether the current context allows cookie mutation. */
45
+ mutableContext: boolean;
46
+ }
47
+ /** A single outgoing cookie entry in the cookie jar. */
48
+ export interface CookieEntry {
49
+ name: string;
50
+ value: string;
51
+ options: import('./request-context.js').CookieOptions;
52
+ }
53
+ export interface TraceStore {
54
+ /** 32-char lowercase hex trace ID (OTEL or UUID fallback). */
55
+ traceId: string;
56
+ /** OTEL span ID if available, undefined otherwise. */
57
+ spanId?: string;
58
+ }
59
+ /** @internal — import via tracing.ts public API */
60
+ export declare const traceAls: AsyncLocalStorage<TraceStore>;
61
+ export interface TimingStore {
62
+ entries: import('./server-timing.js').TimingEntry[];
63
+ }
64
+ /** @internal — import via server-timing.ts public API */
65
+ export declare const timingAls: AsyncLocalStorage<TimingStore>;
66
+ export interface RevalidationState {
67
+ /** Paths to re-render (populated by revalidatePath calls). */
68
+ paths: string[];
69
+ /** Tags to invalidate (populated by revalidateTag calls). */
70
+ tags: string[];
71
+ }
72
+ /** @internal — import via actions.ts public API */
73
+ export declare const revalidationAls: AsyncLocalStorage<RevalidationState>;
74
+ /** @internal — import via form-flash.ts public API */
75
+ export declare const formFlashAls: AsyncLocalStorage<import("./form-flash.js").FormFlashData>;
76
+ /** Function that sends Link header values as a 103 Early Hints response. */
77
+ export type EarlyHintsSenderFn = (links: string[]) => void;
78
+ /** @internal — import via early-hints-sender.ts public API */
79
+ export declare const earlyHintsSenderAls: AsyncLocalStorage<EarlyHintsSenderFn>;
80
+ //# sourceMappingURL=als-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"als-registry.d.ts","sourceRoot":"","sources":["../../src/server/als-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAMrD,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,wCAA+C,CAAC;AAE9E,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,uEAAuE;IACvE,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,mBAAmB,EAAE,OAAO,CAAC,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxE,wFAAwF;IACxF,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,4DAA4D;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,sBAAsB,EAAE,aAAa,CAAC;CACvD;AAMD,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,eAAO,MAAM,QAAQ,+BAAsC,CAAC;AAM5D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,oBAAoB,EAAE,WAAW,EAAE,CAAC;CACrD;AAED,yDAAyD;AACzD,eAAO,MAAM,SAAS,gCAAuC,CAAC;AAM9D,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,mDAAmD;AACnD,eAAO,MAAM,eAAe,sCAA6C,CAAC;AAM1E,sDAAsD;AACtD,eAAO,MAAM,YAAY,4DAAmE,CAAC;AAM7F,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAE3D,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,uCAA8C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"early-hints-sender.d.ts","sourceRoot":"","sources":["../../src/server/early-hints-sender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAI3D;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CASvD"}
1
+ {"version":3,"file":"early-hints-sender.d.ts","sourceRoot":"","sources":["../../src/server/early-hints-sender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAE3D;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CASvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"form-flash.d.ts","sourceRoot":"","sources":["../../src/server/form-flash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,uDAAuD;IACvD,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAChE;AAQD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,YAAY,IAAI,aAAa,GAAG,IAAI,CAEnD;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEvE"}
1
+ {"version":3,"file":"form-flash.d.ts","sourceRoot":"","sources":["../../src/server/form-flash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,uDAAuD;IACvD,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAChE;AAID;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,YAAY,IAAI,aAAa,GAAG,IAAI,CAEnD;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEvE"}
@@ -6,6 +6,7 @@ export { headers, cookies, searchParams, setParsedSearchParams, runWithRequestCo
6
6
  export type { ReadonlyHeaders, RequestCookies, CookieOptions } from './request-context';
7
7
  export { deny, notFound, redirect, permanentRedirect, redirectExternal, RedirectType, RenderError, waitUntil, DenySignal, RedirectSignal, } from './primitives';
8
8
  export type { RenderErrorDigest, WaitUntilAdapter } from './primitives';
9
+ export type { JsonSerializable } from './types';
9
10
  export { createPipeline } from './pipeline';
10
11
  export type { PipelineConfig, RouteMatch, RouteMatcher, RouteRenderer, EarlyHintsEmitter, } from './pipeline';
11
12
  export { collectEarlyHintHeaders, formatLinkHeader } from './early-hints';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,60 @@
1
1
  import { a as warnDenyAfterFlush, c as warnRedirectInAccess, d as warnSlowSlotWithoutSuspense, f as warnStaticRequestApi, i as warnCacheRequestProps, l as warnRedirectInSlotAccess, n as WarningId, o as warnDenyInSuspense, p as warnSuspenseWrappingChildren, r as setViteServer, s as warnDynamicApiInStaticBuild, t as formatSize, u as warnRedirectInSuspense } from "../_chunks/format-DNt20Kt8.js";
2
- import { i as getMetadataRouteServePath, n as classifyMetadataRoute, r as getMetadataRouteAutoLink, t as METADATA_ROUTE_CONVENTIONS } from "../_chunks/metadata-routes-BDnswgRO.js";
3
- import { a as markResponseFlushed, c as setCookieSecrets, i as headers, l as setMutableCookieContext, n as cookies, o as runWithRequestContext, r as getSetCookieHeaders, s as searchParams, t as applyRequestHeaderOverlay, u as setParsedSearchParams } from "../_chunks/request-context-BzES06i1.js";
4
- import { a as replaceTraceId, c as spanId, i as getTraceStore, l as traceId, n as generateTraceId, o as runWithTraceId, r as getOtelTraceId, s as setSpanAttribute, t as addSpanEvent, u as withSpan } from "../_chunks/tracing-BtOwb8O6.js";
5
- import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { i as getMetadataRouteServePath, n as classifyMetadataRoute, r as getMetadataRouteAutoLink, t as METADATA_ROUTE_CONVENTIONS } from "../_chunks/metadata-routes-CQCnF4VK.js";
3
+ import { a as timingAls, i as revalidationAls, n as formFlashAls, t as earlyHintsSenderAls } from "../_chunks/als-registry-c0AGnbqS.js";
4
+ import { a as markResponseFlushed, c as setCookieSecrets, i as headers, l as setMutableCookieContext, n as cookies, o as runWithRequestContext, r as getSetCookieHeaders, s as searchParams, t as applyRequestHeaderOverlay, u as setParsedSearchParams } from "../_chunks/request-context-C69VW4xS.js";
5
+ import { a as replaceTraceId, c as spanId, i as getTraceStore, l as traceId, n as generateTraceId, o as runWithTraceId, r as getOtelTraceId, s as setSpanAttribute, t as addSpanEvent, u as withSpan } from "../_chunks/tracing-tIvqStk8.js";
6
+ import { readFile } from "node:fs/promises";
6
7
  //#region src/server/primitives.ts
7
8
  /**
9
+ * Check if a value is JSON-serializable without data loss.
10
+ * Returns a description of the first non-serializable value found, or null if OK.
11
+ *
12
+ * @internal Exported for testing only.
13
+ */
14
+ function findNonSerializable(value, path = "data") {
15
+ if (value === null || value === void 0) return null;
16
+ switch (typeof value) {
17
+ case "string":
18
+ case "number":
19
+ case "boolean": return null;
20
+ case "bigint": return `${path} contains a BigInt — BigInt throws in JSON.stringify`;
21
+ case "function": return `${path} is a function — functions are not JSON-serializable`;
22
+ case "symbol": return `${path} is a symbol — symbols are not JSON-serializable`;
23
+ case "object": break;
24
+ default: return `${path} has unsupported type "${typeof value}"`;
25
+ }
26
+ if (value instanceof Date) return `${path} is a Date — Dates silently coerce to strings in JSON.stringify`;
27
+ if (value instanceof Map) return `${path} is a Map — Maps serialize as {} in JSON.stringify (data loss)`;
28
+ if (value instanceof Set) return `${path} is a Set — Sets serialize as {} in JSON.stringify (data loss)`;
29
+ if (value instanceof RegExp) return `${path} is a RegExp — RegExps serialize as {} in JSON.stringify`;
30
+ if (value instanceof Error) return `${path} is an Error — Errors serialize as {} in JSON.stringify`;
31
+ if (Array.isArray(value)) {
32
+ for (let i = 0; i < value.length; i++) {
33
+ const result = findNonSerializable(value[i], `${path}[${i}]`);
34
+ if (result) return result;
35
+ }
36
+ return null;
37
+ }
38
+ const proto = Object.getPrototypeOf(value);
39
+ if (proto === null) return `${path} is a null-prototype object — React Flight rejects null prototypes`;
40
+ if (proto !== Object.prototype) return `${path} is a ${value.constructor?.name ?? "unknown"} instance — class instances may lose data in JSON.stringify`;
41
+ for (const key of Object.keys(value)) {
42
+ const result = findNonSerializable(value[key], `${path}.${key}`);
43
+ if (result) return result;
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Emit a dev-mode warning if data is not JSON-serializable.
49
+ * No-op in production.
50
+ */
51
+ function warnIfNotSerializable(data, callerName) {
52
+ if (process.env.NODE_ENV === "production") return;
53
+ if (data === void 0) return;
54
+ const issue = findNonSerializable(data);
55
+ 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.`);
56
+ }
57
+ /**
8
58
  * Render-phase signal thrown by `deny()`. Caught by the framework to produce
9
59
  * the correct HTTP status code (segment context) or graceful degradation (slot context).
10
60
  */
@@ -47,10 +97,11 @@ var DenySignal = class extends Error {
47
97
  * - Inside Suspense (after flush): error boundary + noindex meta
48
98
  *
49
99
  * @param status - Any 4xx HTTP status code. Defaults to 403.
50
- * @param data - Optional data passed as `dangerouslyPassData` prop to status-code files.
100
+ * @param data - Optional JSON-serializable data passed as `dangerouslyPassData` prop to status-code files.
51
101
  */
52
102
  function deny(status = 403, data) {
53
103
  if (status < 400 || status > 499) throw new Error(`deny() requires a 4xx status code, got ${status}. For 5xx errors, throw a RenderError instead.`);
104
+ warnIfNotSerializable(data, "deny()");
54
105
  throw new DenySignal(status, data);
55
106
  }
56
107
  /**
@@ -157,6 +208,7 @@ var RenderError = class extends Error {
157
208
  code,
158
209
  data
159
210
  };
211
+ warnIfNotSerializable(data, "RenderError");
160
212
  const status = options?.status ?? 500;
161
213
  if (status < 400 || status > 599) throw new Error(`RenderError status must be 4xx or 5xx, got ${status}.`);
162
214
  this.status = status;
@@ -278,6 +330,82 @@ async function runMiddleware(middlewareFn, ctx) {
278
330
  if (result instanceof Response) return result;
279
331
  }
280
332
  //#endregion
333
+ //#region src/server/server-timing.ts
334
+ /**
335
+ * Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.
336
+ *
337
+ * Collects timing entries per request using ALS. Each pipeline phase
338
+ * (proxy, middleware, render, SSR, access, fetch) records an entry.
339
+ * Before response flush, entries are formatted into a Server-Timing header.
340
+ *
341
+ * Only active in dev mode — zero overhead in production.
342
+ *
343
+ * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing
344
+ * Task: LOCAL-290
345
+ */
346
+ /**
347
+ * Run a callback with a per-request timing collector.
348
+ * Must be called at the top of the request pipeline (wraps the full request).
349
+ */
350
+ function runWithTimingCollector(fn) {
351
+ return timingAls.run({ entries: [] }, fn);
352
+ }
353
+ /**
354
+ * Run a function and automatically record its duration as a timing entry.
355
+ * Returns the function's result. No-ops the recording if outside a collector.
356
+ */
357
+ async function withTiming(name, desc, fn) {
358
+ const store = timingAls.getStore();
359
+ if (!store) return fn();
360
+ const start = performance.now();
361
+ try {
362
+ return await fn();
363
+ } finally {
364
+ const dur = Math.round(performance.now() - start);
365
+ store.entries.push({
366
+ name,
367
+ dur,
368
+ desc
369
+ });
370
+ }
371
+ }
372
+ /**
373
+ * Get the Server-Timing header value for the current request.
374
+ * Returns null if no entries exist or outside a collector.
375
+ *
376
+ * Format: `name;dur=123;desc="description", name2;dur=456`
377
+ * See RFC 6797 / Server-Timing spec for format details.
378
+ */
379
+ function getServerTimingHeader() {
380
+ const store = timingAls.getStore();
381
+ if (!store || store.entries.length === 0) return null;
382
+ const nameCounts = /* @__PURE__ */ new Map();
383
+ const parts = store.entries.map((entry) => {
384
+ const count = nameCounts.get(entry.name) ?? 0;
385
+ nameCounts.set(entry.name, count + 1);
386
+ const uniqueName = count > 0 ? `${entry.name}-${count}` : entry.name;
387
+ return {
388
+ ...entry,
389
+ name: uniqueName
390
+ };
391
+ }).map((entry) => {
392
+ let part = `${entry.name};dur=${entry.dur}`;
393
+ if (entry.desc) {
394
+ const safeDesc = entry.desc.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
395
+ part += `;desc="${safeDesc}"`;
396
+ }
397
+ return part;
398
+ });
399
+ const MAX_HEADER_SIZE = 4096;
400
+ let result = "";
401
+ for (let i = 0; i < parts.length; i++) {
402
+ const candidate = result ? `${result}, ${parts[i]}` : parts[i];
403
+ if (candidate.length > MAX_HEADER_SIZE) break;
404
+ result = candidate;
405
+ }
406
+ return result || null;
407
+ }
408
+ //#endregion
281
409
  //#region src/server/error-formatter.ts
282
410
  /**
283
411
  * Error Formatter — rewrites SSR/RSC error messages to surface user code.
@@ -554,7 +682,7 @@ function hasOnRequestError() {
554
682
  * and produces a Response. This is the top-level entry point for the server.
555
683
  */
556
684
  function createPipeline(config) {
557
- const { proxy, matchRoute, render, earlyHints, stripTrailingSlash = true, slowRequestMs = 3e3, onPipelineError } = config;
685
+ const { proxy, matchRoute, render, earlyHints, stripTrailingSlash = true, slowRequestMs = 3e3, enableServerTiming = false, onPipelineError } = config;
558
686
  return async (req) => {
559
687
  const url = new URL(req.url);
560
688
  const method = req.method;
@@ -562,37 +690,47 @@ function createPipeline(config) {
562
690
  const startTime = performance.now();
563
691
  return runWithTraceId(generateTraceId(), async () => {
564
692
  return runWithRequestContext(req, async () => {
565
- logRequestReceived({
566
- method,
567
- path
568
- });
569
- const response = await withSpan("http.server.request", {
570
- "http.request.method": method,
571
- "url.path": path
572
- }, async () => {
573
- const otelIds = await getOtelTraceId();
574
- if (otelIds) replaceTraceId(otelIds.traceId, otelIds.spanId);
575
- let result;
576
- if (proxy || config.proxyLoader) result = await runProxyPhase(req, method, path);
577
- else result = await handleRequest(req, method, path);
578
- await setSpanAttribute("http.response.status_code", result.status);
579
- return result;
580
- });
581
- const durationMs = Math.round(performance.now() - startTime);
582
- const status = response.status;
583
- logRequestCompleted({
584
- method,
585
- path,
586
- status,
587
- durationMs
588
- });
589
- if (slowRequestMs > 0 && durationMs > slowRequestMs) logSlowRequest({
590
- method,
591
- path,
592
- durationMs,
593
- threshold: slowRequestMs
594
- });
595
- return response;
693
+ const runRequest = async () => {
694
+ logRequestReceived({
695
+ method,
696
+ path
697
+ });
698
+ const response = await withSpan("http.server.request", {
699
+ "http.request.method": method,
700
+ "url.path": path
701
+ }, async () => {
702
+ const otelIds = await getOtelTraceId();
703
+ if (otelIds) replaceTraceId(otelIds.traceId, otelIds.spanId);
704
+ let result;
705
+ if (proxy || config.proxyLoader) result = await runProxyPhase(req, method, path);
706
+ else result = await handleRequest(req, method, path);
707
+ await setSpanAttribute("http.response.status_code", result.status);
708
+ if (enableServerTiming) {
709
+ const serverTiming = getServerTimingHeader();
710
+ if (serverTiming) {
711
+ result = ensureMutableResponse(result);
712
+ result.headers.set("Server-Timing", serverTiming);
713
+ }
714
+ }
715
+ return result;
716
+ });
717
+ const durationMs = Math.round(performance.now() - startTime);
718
+ const status = response.status;
719
+ logRequestCompleted({
720
+ method,
721
+ path,
722
+ status,
723
+ durationMs
724
+ });
725
+ if (slowRequestMs > 0 && durationMs > slowRequestMs) logSlowRequest({
726
+ method,
727
+ path,
728
+ durationMs,
729
+ threshold: slowRequestMs
730
+ });
731
+ return response;
732
+ };
733
+ return enableServerTiming ? runWithTimingCollector(runRequest) : runRequest();
596
734
  });
597
735
  });
598
736
  };
@@ -601,7 +739,8 @@ function createPipeline(config) {
601
739
  let proxyExport;
602
740
  if (config.proxyLoader) proxyExport = (await config.proxyLoader()).default;
603
741
  else proxyExport = config.proxy;
604
- return await withSpan("timber.proxy", {}, () => runProxy(proxyExport, req, () => handleRequest(req, method, path)));
742
+ const proxyFn = () => runProxy(proxyExport, req, () => handleRequest(req, method, path));
743
+ return await withSpan("timber.proxy", {}, () => enableServerTiming ? withTiming("proxy", "proxy.ts", proxyFn) : proxyFn());
605
744
  } catch (error) {
606
745
  logProxyError({ error });
607
746
  await fireOnRequestError(error, req, "proxy");
@@ -616,6 +755,7 @@ function createPipeline(config) {
616
755
  if (config.matchMetadataRoute) {
617
756
  const metaMatch = config.matchMetadataRoute(canonicalPathname);
618
757
  if (metaMatch) try {
758
+ if (metaMatch.isStatic) return await serveStaticMetadataFile(metaMatch);
619
759
  const mod = await metaMatch.file.load();
620
760
  if (typeof mod.default !== "function") return new Response("Metadata route must export a default function", { status: 500 });
621
761
  const handlerResult = await mod.default();
@@ -684,16 +824,18 @@ function createPipeline(config) {
684
824
  };
685
825
  try {
686
826
  setMutableCookieContext(true);
687
- const middlewareResponse = await withSpan("timber.middleware", {}, () => runMiddleware(match.middleware, ctx));
827
+ const middlewareFn = () => runMiddleware(match.middleware, ctx);
828
+ const middlewareResponse = await withSpan("timber.middleware", {}, () => enableServerTiming ? withTiming("mw", "middleware.ts", middlewareFn) : middlewareFn());
688
829
  setMutableCookieContext(false);
689
830
  if (middlewareResponse) {
690
- applyCookieJar(middlewareResponse.headers);
831
+ const finalResponse = ensureMutableResponse(middlewareResponse);
832
+ applyCookieJar(finalResponse.headers);
691
833
  logMiddlewareShortCircuit({
692
834
  method,
693
835
  path,
694
- status: middlewareResponse.status
836
+ status: finalResponse.status
695
837
  });
696
- return middlewareResponse;
838
+ return finalResponse;
697
839
  }
698
840
  applyRequestHeaderOverlay(requestHeaderOverlay);
699
841
  } catch (error) {
@@ -726,7 +868,8 @@ function createPipeline(config) {
726
868
  }
727
869
  applyCookieJar(responseHeaders);
728
870
  try {
729
- const response = await withSpan("timber.render", { "http.route": canonicalPathname }, () => render(req, match, responseHeaders, requestHeaderOverlay, interception));
871
+ const renderFn = () => render(req, match, responseHeaders, requestHeaderOverlay, interception);
872
+ const response = await withSpan("timber.render", { "http.route": canonicalPathname }, () => enableServerTiming ? withTiming("render", "RSC + SSR render", renderFn) : renderFn());
730
873
  markResponseFlushed();
731
874
  return response;
732
875
  } catch (error) {
@@ -811,6 +954,29 @@ function applyCookieJar(headers) {
811
954
  for (const value of getSetCookieHeaders()) headers.append("Set-Cookie", value);
812
955
  }
813
956
  /**
957
+ * Ensure a Response has mutable headers so the pipeline can safely append
958
+ * Set-Cookie and Server-Timing entries.
959
+ *
960
+ * `Response.redirect()` and some platform-level responses return objects
961
+ * with immutable headers. Calling `.set()` or `.append()` on them throws
962
+ * `TypeError: immutable`. This helper detects the immutable case by
963
+ * attempting a no-op write and, on failure, clones into a fresh Response
964
+ * with mutable headers.
965
+ */
966
+ function ensureMutableResponse(response) {
967
+ try {
968
+ response.headers.set("X-Timber-Probe", "1");
969
+ response.headers.delete("X-Timber-Probe");
970
+ return response;
971
+ } catch {
972
+ return new Response(response.body, {
973
+ status: response.status,
974
+ statusText: response.statusText,
975
+ headers: new Headers(response.headers)
976
+ });
977
+ }
978
+ }
979
+ /**
814
980
  * Serialize a sitemap array to XML.
815
981
  * Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html
816
982
  */
@@ -831,6 +997,39 @@ function serializeSitemap(entries) {
831
997
  function escapeXml(str) {
832
998
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
833
999
  }
1000
+ /**
1001
+ * Content types that are text-based and should include charset=utf-8.
1002
+ * Binary formats (images) should not include charset.
1003
+ */
1004
+ var TEXT_CONTENT_TYPES = new Set([
1005
+ "application/xml",
1006
+ "text/plain",
1007
+ "application/json",
1008
+ "application/manifest+json",
1009
+ "image/svg+xml"
1010
+ ]);
1011
+ /**
1012
+ * Serve a static metadata file by reading it from disk.
1013
+ *
1014
+ * Static metadata route files (.xml, .txt, .json, .png, .ico, .svg, etc.)
1015
+ * are served as-is with the appropriate Content-Type header.
1016
+ * Text files include charset=utf-8; binary files do not.
1017
+ *
1018
+ * See design/16-metadata.md §"Metadata Routes"
1019
+ */
1020
+ async function serveStaticMetadataFile(metaMatch) {
1021
+ const { contentType, file } = metaMatch;
1022
+ const isText = TEXT_CONTENT_TYPES.has(contentType);
1023
+ const body = await readFile(file.filePath);
1024
+ const headers = {
1025
+ "Content-Type": isText ? `${contentType}; charset=utf-8` : contentType,
1026
+ "Content-Length": String(body.byteLength)
1027
+ };
1028
+ return new Response(body, {
1029
+ status: 200,
1030
+ headers
1031
+ });
1032
+ }
834
1033
  //#endregion
835
1034
  //#region src/server/build-manifest.ts
836
1035
  /**
@@ -996,7 +1195,6 @@ function collectEarlyHintHeaders(segments, manifest, options) {
996
1195
  *
997
1196
  * Design doc: 02-rendering-pipeline.md §"Early Hints (103)"
998
1197
  */
999
- var earlyHintsSenderAls = new AsyncLocalStorage();
1000
1198
  /**
1001
1199
  * Run a function with a per-request early hints sender installed.
1002
1200
  *
@@ -2655,22 +2853,6 @@ function stripFiles(value) {
2655
2853
  //#endregion
2656
2854
  //#region src/server/form-flash.ts
2657
2855
  /**
2658
- * Form Flash — ALS-based store for no-JS form action results.
2659
- *
2660
- * When a no-JS form action completes, the server re-renders the page with
2661
- * the action result injected via AsyncLocalStorage instead of redirecting
2662
- * (which would discard the result). Server components read the flash and
2663
- * pass it to client form components as the initial `useActionState` value.
2664
- *
2665
- * This follows the Remix/Rails pattern — the form component becomes the
2666
- * single source of truth for both with-JS (React state) and no-JS (flash).
2667
- *
2668
- * The flash data is server-side only — never serialized to cookies or headers.
2669
- *
2670
- * See design/08-forms-and-actions.md §"No-JS Error Round-Trip"
2671
- */
2672
- var formFlashAls = new AsyncLocalStorage();
2673
- /**
2674
2856
  * Read the form flash data for the current request.
2675
2857
  *
2676
2858
  * Returns `null` if no flash data is present (i.e., this is a normal page
@@ -2701,22 +2883,6 @@ function getFormFlash() {
2701
2883
  //#endregion
2702
2884
  //#region src/server/actions.ts
2703
2885
  /**
2704
- * Server action primitives: revalidatePath, revalidateTag, and the action handler.
2705
- *
2706
- * - revalidatePath(path) re-renders the route at that path and returns the RSC
2707
- * flight payload for inline reconciliation.
2708
- * - revalidateTag(tag) invalidates cached shells and 'use cache' entries by tag.
2709
- *
2710
- * Both are callable from anywhere on the server — actions, API routes, handlers.
2711
- *
2712
- * The action handler processes incoming action requests, validates CSRF,
2713
- * enforces body limits, executes the action, and returns the response
2714
- * (with piggybacked RSC payload if revalidatePath was called).
2715
- *
2716
- * See design/08-forms-and-actions.md
2717
- */
2718
- var revalidationAls = new AsyncLocalStorage();
2719
- /**
2720
2886
  * Get the current revalidation state. Throws if called outside an action context.
2721
2887
  * @internal
2722
2888
  */