@timber-js/app 0.2.0-alpha.36 → 0.2.0-alpha.37

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 (61) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -1
  3. package/dist/_chunks/{define-cookie-w5GWm_bL.js → define-cookie-BmKbSyp0.js} +4 -4
  4. package/dist/_chunks/{define-cookie-w5GWm_bL.js.map → define-cookie-BmKbSyp0.js.map} +1 -1
  5. package/dist/_chunks/{error-boundary-TYEQJZ1-.js → error-boundary-BAN3751q.js} +1 -1
  6. package/dist/_chunks/{error-boundary-TYEQJZ1-.js.map → error-boundary-BAN3751q.js.map} +1 -1
  7. package/dist/_chunks/{request-context-CZz_T0Bc.js → request-context-BxYIJM24.js} +59 -4
  8. package/dist/_chunks/request-context-BxYIJM24.js.map +1 -0
  9. package/dist/_chunks/segment-context-C6byCyZU.js +69 -0
  10. package/dist/_chunks/segment-context-C6byCyZU.js.map +1 -0
  11. package/dist/_chunks/{tracing-BPyIzIdu.js → tracing-CuXiCP5p.js} +1 -1
  12. package/dist/_chunks/{tracing-BPyIzIdu.js.map → tracing-CuXiCP5p.js.map} +1 -1
  13. package/dist/_chunks/{wrappers-C1SN725w.js → wrappers-C6J0nNji.js} +2 -2
  14. package/dist/_chunks/{wrappers-C1SN725w.js.map → wrappers-C6J0nNji.js.map} +1 -1
  15. package/dist/cache/index.js +1 -1
  16. package/dist/client/error-boundary.js +1 -1
  17. package/dist/client/index.d.ts +1 -0
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +25 -8
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/client/link.d.ts +15 -1
  22. package/dist/client/link.d.ts.map +1 -1
  23. package/dist/cookies/index.js +1 -1
  24. package/dist/params/index.js +1 -1
  25. package/dist/search-params/index.js +1 -1
  26. package/dist/server/access-gate.d.ts.map +1 -1
  27. package/dist/server/als-registry.d.ts +14 -0
  28. package/dist/server/als-registry.d.ts.map +1 -1
  29. package/dist/server/index.d.ts +2 -2
  30. package/dist/server/index.d.ts.map +1 -1
  31. package/dist/server/index.js +42 -26
  32. package/dist/server/index.js.map +1 -1
  33. package/dist/server/pipeline.d.ts.map +1 -1
  34. package/dist/server/primitives.d.ts +30 -3
  35. package/dist/server/primitives.d.ts.map +1 -1
  36. package/dist/server/request-context.d.ts +39 -0
  37. package/dist/server/request-context.d.ts.map +1 -1
  38. package/dist/server/route-element-builder.d.ts.map +1 -1
  39. package/dist/server/slot-resolver.d.ts +1 -1
  40. package/dist/server/slot-resolver.d.ts.map +1 -1
  41. package/dist/server/tree-builder.d.ts +7 -4
  42. package/dist/server/tree-builder.d.ts.map +1 -1
  43. package/dist/shared/merge-search-params.d.ts +22 -0
  44. package/dist/shared/merge-search-params.d.ts.map +1 -0
  45. package/package.json +6 -7
  46. package/src/cli.ts +0 -0
  47. package/src/client/index.ts +1 -0
  48. package/src/client/link.tsx +57 -3
  49. package/src/server/access-gate.tsx +6 -5
  50. package/src/server/als-registry.ts +14 -0
  51. package/src/server/index.ts +3 -0
  52. package/src/server/pipeline.ts +6 -0
  53. package/src/server/primitives.ts +47 -5
  54. package/src/server/request-context.ts +69 -1
  55. package/src/server/route-element-builder.ts +10 -16
  56. package/src/server/slot-resolver.ts +10 -19
  57. package/src/server/tree-builder.ts +13 -15
  58. package/src/shared/merge-search-params.ts +48 -0
  59. package/dist/_chunks/request-context-CZz_T0Bc.js.map +0 -1
  60. package/dist/_chunks/segment-context-Dpq2XOKg.js +0 -34
  61. package/dist/_chunks/segment-context-Dpq2XOKg.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAkC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,uDAAuD;IACvD,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;AAEnE,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,CACjC,QAAQ,EAAE,MAAM,KACb,OAAO,oBAAoB,EAAE,kBAAkB,GAAG,IAAI,CAAC;AAE5D,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,YAAY,CAAC,EAAE,mBAAmB,KAC/B,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElC,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAI1B,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACtD,qEAAqE;IACrE,UAAU,EAAE,YAAY,CAAC;IACzB,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,kEAAkE;IAClE,MAAM,EAAE,aAAa,CAAC;IACtB,kEAAkE;IAClE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzF,kFAAkF;IAClF,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yGAAyG;IACzG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,2BAA2B,EAAE,mBAAmB,EAAE,CAAC;IACjF;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,KAAK,CAAC;IAC5C;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,CACpB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC;AAwCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAyY1F"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAmC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,uDAAuD;IACvD,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;AAEnE,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,CACjC,QAAQ,EAAE,MAAM,KACb,OAAO,oBAAoB,EAAE,kBAAkB,GAAG,IAAI,CAAC;AAE5D,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,YAAY,CAAC,EAAE,mBAAmB,KAC/B,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElC,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAI1B,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACtD,qEAAqE;IACrE,UAAU,EAAE,YAAY,CAAC;IACzB,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,kEAAkE;IAClE,MAAM,EAAE,aAAa,CAAC;IACtB,kEAAkE;IAClE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzF,kFAAkF;IAClF,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yGAAyG;IACzG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,2BAA2B,EAAE,mBAAmB,EAAE,CAAC;IACjF;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,KAAK,CAAC;IAC5C;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,CACpB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC;AAwCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA8Y1F"}
@@ -61,14 +61,40 @@ export declare class RedirectSignal extends Error {
61
61
  readonly status: number;
62
62
  constructor(location: string, status: number);
63
63
  }
64
+ /**
65
+ * Options for redirect() — alternative to passing a bare status code.
66
+ */
67
+ export interface RedirectOptions {
68
+ /** HTTP redirect status code (3xx). Defaults to 302. */
69
+ status?: number;
70
+ /**
71
+ * Preserve search params from the current request URL on the redirect target.
72
+ *
73
+ * - `true` — preserve ALL current search params (target params take precedence)
74
+ * - `string[]` — preserve only the named params (e.g. `['private', 'token']`)
75
+ *
76
+ * Target path's own query params always take precedence over preserved ones.
77
+ */
78
+ preserveSearchParams?: true | string[];
79
+ }
64
80
  /**
65
81
  * Redirect to a relative path. Rejects absolute and protocol-relative URLs.
66
82
  * Use `redirectExternal()` for external redirects with an allow-list.
67
83
  *
68
84
  * @param path - Relative path (e.g. '/login', 'settings', '/login?returnTo=/dash')
69
- * @param status - HTTP redirect status code (3xx). Defaults to 302.
85
+ * @param statusOrOptions - HTTP status code (3xx, default 302) or options object.
86
+ *
87
+ * @example
88
+ * // Simple redirect
89
+ * redirect('/login');
90
+ *
91
+ * // With status code
92
+ * redirect('/login', 301);
93
+ *
94
+ * // With preserved search params
95
+ * redirect(`/docs/${version}/${slug}`, { preserveSearchParams: ['foo'] });
70
96
  */
71
- export declare function redirect(path: string, status?: number): never;
97
+ export declare function redirect(path: string, statusOrOptions?: number | RedirectOptions): never;
72
98
  /**
73
99
  * Permanent redirect to a relative path. Shorthand for `redirect(path, 308)`.
74
100
  *
@@ -76,8 +102,9 @@ export declare function redirect(path: string, status?: number): never;
76
102
  * will replay POST requests to the new location. This matches Next.js behavior.
77
103
  *
78
104
  * @param path - Relative path (e.g. '/new-page', '/dashboard')
105
+ * @param options - Optional redirect options (e.g. preserveSearchParams).
79
106
  */
80
- export declare function permanentRedirect(path: string): never;
107
+ export declare function permanentRedirect(path: string, options?: Omit<RedirectOptions, 'status'>): never;
81
108
  /**
82
109
  * Redirect to an external URL. The hostname must be in the provided allow-list.
83
110
  *
@@ -1 +1 @@
1
- {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/server/primitives.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMnD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAS,GAAG,MAAM,GAAG,IAAI,CA8DhF;AAsBD;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,CAAC;gBAEhC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB;IAOnD;;;;OAIG;IACH,IAAI,UAAU,IAAI,MAAM,GAAG,SAAS,CAqBnC;CACF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,MAAM,GAAE,MAAY,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,KAAK,CASzE;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,IAAI,KAAK,CAEhC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;CAGf,CAAC;AAIX;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAM7C;AAKD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAY,GAAG,KAAK,CAWlE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,MAAM,GAAE,MAAY,GAAG,KAAK,CAoB9F;AAID;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAChC,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,KAAK,SAAS,gBAAgB,GAAG,gBAAgB;IAEjD,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,EAAE,KAAK,CAAC;CACb;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAW,CACtB,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,KAAK,SAAS,gBAAgB,GAAG,gBAAgB,CACjD,SAAQ,KAAK;IACb,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;CAcpE;AAID,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC7C;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAqBrF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAID;;;;;;;;;;GAUG;AACH,qBAAa,cAAe,SAAQ,KAAK;aAGrB,KAAK,EAAE,OAAO;gBAD9B,OAAO,EAAE,MAAM,EACC,KAAK,EAAE,OAAO;CAKjC"}
1
+ {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/server/primitives.ts"],"names":[],"mappings":"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"}
@@ -56,6 +56,45 @@ export declare function cookies(): RequestCookies;
56
56
  * Throws if called outside a request context.
57
57
  */
58
58
  export declare function rawSearchParams(): Promise<URLSearchParams>;
59
+ /**
60
+ * Returns a Promise resolving to the current request's coerced segment params.
61
+ *
62
+ * Segment params are set by the pipeline after route matching and param
63
+ * coercion (via params.ts codecs). When no params.ts exists, values are
64
+ * raw strings. When codecs are defined, values are already coerced
65
+ * (e.g., `id` is a `number` if `defineSegmentParams({ id: z.coerce.number() })`).
66
+ *
67
+ * This is the primary way page and layout components access route params:
68
+ *
69
+ * ```ts
70
+ * import { rawSegmentParams } from '@timber-js/app/server'
71
+ *
72
+ * export default async function Page() {
73
+ * const { slug } = await rawSegmentParams()
74
+ * // ...
75
+ * }
76
+ * ```
77
+ *
78
+ * Throws if called outside a request context.
79
+ */
80
+ export declare function rawSegmentParams(): Promise<Record<string, string | string[]>>;
81
+ /**
82
+ * Set the segment params promise on the current request context.
83
+ * Called by the pipeline after route matching and param coercion.
84
+ *
85
+ * @internal — framework use only
86
+ */
87
+ export declare function setSegmentParams(params: Record<string, string | string[]>): void;
88
+ /**
89
+ * Returns the raw search string from the current request URL (e.g. "?foo=bar").
90
+ * Synchronous — safe for use in `redirect()` which throws synchronously.
91
+ *
92
+ * Returns empty string if called outside a request context (non-throwing for
93
+ * use in redirect's optional preserveSearchParams path).
94
+ *
95
+ * @internal — used by redirect() for preserveSearchParams support.
96
+ */
97
+ export declare function getRequestSearchString(): string;
59
98
  /**
60
99
  * Read-only Headers interface. The standard Headers class is mutable;
61
100
  * this type narrows it to read-only methods. The underlying object is
@@ -1 +1 @@
1
- {"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/server/request-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,iBAAiB,EAA8C,MAAM,mBAAmB,CAAC;AAIlG,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAQ7B;;;;;GAKG;AACH,wBAAgB,OAAO,IAAI,eAAe,CASzC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,IAAI,cAAc,CA0FxC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAAC,eAAe,CAAC,CAS1D;AAID;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,OAAO,EACP,KAAK,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,MAAM,CAAC,QAAQ,CACnF,CAAC;AAEF,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AASD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACtC,gCAAgC;IAChC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,4DAA4D;IAC5D,MAAM,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAChE,2DAA2D;IAC3D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAC;IAC7E,8DAA8D;IAC9D,KAAK,IAAI,IAAI,CAAC;IACd,mDAAmD;IACnD,QAAQ,IAAI,MAAM,CAAC;CACpB;AAID;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAYrE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAK9D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBtD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAI9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmBhE"}
1
+ {"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/server/request-context.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"route-element-builder.d.ts","sourceRoot":"","sources":["../../src/server/route-element-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAM7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAKzD;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAID,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACvC;AAED,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IAC3C,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC;IAC5B,2CAA2C;IAC3C,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,wDAAwD;IACxD,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,qCAAqC;IACrC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,MAAM,EAAE,UAAU,GAAG,cAAc;aACnC,gBAAgB,EAAE,oBAAoB,EAAE;aACxC,QAAQ,EAAE,mBAAmB,EAAE;gBAF/B,MAAM,EAAE,UAAU,GAAG,cAAc,EACnC,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,QAAQ,EAAE,mBAAmB,EAAE;CAIlD;AA4DD;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,YAAY,CAAC,EAAE,mBAAmB,EAClC,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GACnC,OAAO,CAAC,kBAAkB,CAAC,CAqS7B"}
1
+ {"version":3,"file":"route-element-builder.d.ts","sourceRoot":"","sources":["../../src/server/route-element-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAM7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAKzD;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAID,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACvC;AAED,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IAC3C,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC;IAC5B,2CAA2C;IAC3C,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,wDAAwD;IACxD,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,qCAAqC;IACrC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,MAAM,EAAE,UAAU,GAAG,cAAc;aACnC,gBAAgB,EAAE,oBAAoB,EAAE;aACxC,QAAQ,EAAE,mBAAmB,EAAE;gBAF/B,MAAM,EAAE,UAAU,GAAG,cAAc,EACnC,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,QAAQ,EAAE,mBAAmB,EAAE;CAIlD;AA8DD;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,YAAY,CAAC,EAAE,mBAAmB,EAClC,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GACnC,OAAO,CAAC,kBAAkB,CAAC,CA6R7B"}
@@ -29,6 +29,6 @@ type CreateElementFn = (...args: unknown[]) => React.ReactElement;
29
29
  * 2. Layouts from each segment in the slot's matched chain
30
30
  * 3. SlotAccessGate if the slot root has access.ts
31
31
  */
32
- export declare function resolveSlotElement(slotNode: ManifestSegmentNode, match: RouteMatch, paramsPromise: Promise<Record<string, string | string[]>>, h: CreateElementFn, interception?: InterceptionContext): Promise<React.ReactElement | null>;
32
+ export declare function resolveSlotElement(slotNode: ManifestSegmentNode, match: RouteMatch, h: CreateElementFn, interception?: InterceptionContext): Promise<React.ReactElement | null>;
33
33
  export {};
34
34
  //# sourceMappingURL=slot-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slot-resolver.d.ts","sourceRoot":"","sources":["../../src/server/slot-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,KAAK,eAAe,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,YAAY,CAAC;AAmHlE;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,UAAU,EACjB,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,EACzD,CAAC,EAAE,eAAe,EAClB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CA4EpC"}
1
+ {"version":3,"file":"slot-resolver.d.ts","sourceRoot":"","sources":["../../src/server/slot-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,KAAK,eAAe,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,YAAY,CAAC;AAkHlE;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,UAAU,EACjB,CAAC,EAAE,eAAe,EAClB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CA0EpC"}
@@ -32,8 +32,13 @@ export type SlotElements = Map<string, ReactElement>;
32
32
  export interface TreeBuilderConfig {
33
33
  /** The matched segment chain from root to leaf. */
34
34
  segments: SegmentNode[];
35
- /** Route params extracted by the matcher (catch-all segments produce string[]). */
36
- params: Record<string, string | string[]>;
35
+ /**
36
+ * Route params extracted by the matcher (catch-all segments produce string[]).
37
+ * @deprecated Params are now accessed via rawSegmentParams() from ALS.
38
+ * This field is kept for backward compatibility but is no longer used
39
+ * by the tree builder itself.
40
+ */
41
+ params?: Record<string, string | string[]>;
37
42
  /** Loads a route file's module. */
38
43
  loadModule: ModuleLoader;
39
44
  /** React.createElement or equivalent. */
@@ -61,7 +66,6 @@ export interface AccessGateProps {
61
66
  accessFn: (ctx: {
62
67
  params: Record<string, string | string[]>;
63
68
  }) => unknown;
64
- params: Record<string, string | string[]>;
65
69
  /** Segment name for dev logging (e.g. "authenticated", "dashboard"). */
66
70
  segmentName?: string;
67
71
  /**
@@ -85,7 +89,6 @@ export interface SlotAccessGateProps {
85
89
  accessFn: (ctx: {
86
90
  params: Record<string, string | string[]>;
87
91
  }) => unknown;
88
- params: Record<string, string | string[]>;
89
92
  /** The denied.tsx component (not a pre-built element). null if no denied.tsx exists. */
90
93
  DeniedComponent: ((...args: unknown[]) => unknown) | null;
91
94
  /** Slot directory name without @ prefix (e.g. "admin", "sidebar"). */
@@ -1 +1 @@
1
- {"version":3,"file":"tree-builder.d.ts","sourceRoot":"","sources":["../../src/server/tree-builder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tree-builder.d.ts","sourceRoot":"","sources":["../../src/server/tree-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared utility for merging preserved search params into a target URL.
3
+ *
4
+ * Used by both <Link> (client) and redirect() (server). Extracted to a shared
5
+ * module to avoid importing client code ('use client') from server modules.
6
+ */
7
+ /**
8
+ * Merge preserved search params from the current URL into a target href.
9
+ *
10
+ * When `preserve` is `true`, all current search params are merged.
11
+ * When `preserve` is a `string[]`, only the named params are merged.
12
+ *
13
+ * The target href's own search params take precedence — preserved params
14
+ * are only added if the target doesn't already define them.
15
+ *
16
+ * @param targetHref - The resolved target href (may already contain query string)
17
+ * @param currentSearch - The current URL's search string (e.g. "?private=access&page=2")
18
+ * @param preserve - `true` to preserve all, or `string[]` to preserve specific params
19
+ * @returns The target href with preserved search params merged in
20
+ */
21
+ export declare function mergePreservedSearchParams(targetHref: string, currentSearch: string, preserve: true | string[]): string;
22
+ //# sourceMappingURL=merge-search-params.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-search-params.d.ts","sourceRoot":"","sources":["../../src/shared/merge-search-params.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,IAAI,GAAG,MAAM,EAAE,GACxB,MAAM,CAsBR"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.36",
3
+ "version": "0.2.0-alpha.37",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -83,11 +83,6 @@
83
83
  "publishConfig": {
84
84
  "access": "public"
85
85
  },
86
- "scripts": {
87
- "build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
88
- "typecheck": "tsgo --noEmit",
89
- "prepublishOnly": "pnpm run build"
90
- },
91
86
  "dependencies": {
92
87
  "@opentelemetry/api": "^1.9.1",
93
88
  "@opentelemetry/context-async-hooks": "^2.6.1",
@@ -126,5 +121,9 @@
126
121
  },
127
122
  "engines": {
128
123
  "node": ">=22.12.0"
124
+ },
125
+ "scripts": {
126
+ "build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
127
+ "typecheck": "tsgo --noEmit"
129
128
  }
130
- }
129
+ }
package/src/cli.ts CHANGED
File without changes
@@ -5,6 +5,7 @@ export type { JsonSerializable, RenderErrorDigest } from './types';
5
5
 
6
6
  // Navigation
7
7
  export { Link, interpolateParams, resolveHref, validateLinkHref, buildLinkProps } from './link';
8
+ export { mergePreservedSearchParams } from '#/shared/merge-search-params.js';
8
9
  export type { LinkProps, LinkPropsWithHref, LinkPropsWithParams } from './link';
9
10
  export type { OnNavigateHandler, OnNavigateEvent } from './link';
10
11
  export { createRouter } from './router';
@@ -22,6 +22,24 @@ import type { AnchorHTMLAttributes, ReactNode, MouseEvent as ReactMouseEvent } f
22
22
  import type { SearchParamsDefinition } from '#/search-params/define.js';
23
23
  import { LinkStatusProvider } from './link-status-provider.js';
24
24
  import { getRouterOrNull } from './router-ref.js';
25
+ import { getSsrData } from './ssr-data.js';
26
+ import { mergePreservedSearchParams } from '#/shared/merge-search-params.js';
27
+
28
+ // ─── Current Search Params ────────────────────────────────────────
29
+
30
+ /**
31
+ * Read the current URL's search string without requiring a React hook.
32
+ * On the client, reads window.location.search. During SSR, reads from
33
+ * the request context (getSsrData). Returns empty string if unavailable.
34
+ */
35
+ function getCurrentSearch(): string {
36
+ if (typeof window !== 'undefined') return window.location.search;
37
+ const data = getSsrData();
38
+ if (!data) return '';
39
+ const sp = new URLSearchParams(data.searchParams);
40
+ const str = sp.toString();
41
+ return str ? `?${str}` : '';
42
+ }
25
43
 
26
44
  // ─── Types ───────────────────────────────────────────────────────
27
45
 
@@ -42,6 +60,20 @@ interface LinkBaseProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'h
42
60
  * Set to false for tabbed interfaces where content changes within a fixed layout.
43
61
  */
44
62
  scroll?: boolean;
63
+ /**
64
+ * Preserve search params from the current URL across navigation.
65
+ *
66
+ * - `true` — preserve ALL current search params (target params take precedence)
67
+ * - `string[]` — preserve only the named params (e.g. `['private', 'token']`)
68
+ *
69
+ * Useful for route-group gating where a search param (e.g. `?private=access`)
70
+ * must persist across internal navigations. The target href's own search params
71
+ * always take precedence over preserved ones.
72
+ *
73
+ * During SSR, reads search params from the request context. On the client,
74
+ * reads from the current URL and updates reactively when the URL changes.
75
+ */
76
+ preserveSearchParams?: true | string[];
45
77
  /**
46
78
  * Called before client-side navigation commits. Call `e.preventDefault()`
47
79
  * to cancel the default navigation — the caller is then responsible for
@@ -312,13 +344,25 @@ export function Link({
312
344
  scroll,
313
345
  segmentParams,
314
346
  searchParams,
347
+ preserveSearchParams,
315
348
  onNavigate,
316
349
  onClick: userOnClick,
317
350
  onMouseEnter: userOnMouseEnter,
318
351
  children,
319
352
  ...rest
320
353
  }: LinkProps) {
321
- const { href: resolvedHref } = buildLinkProps({ href, params: segmentParams, searchParams });
354
+ const { href: baseHref } = buildLinkProps({ href, params: segmentParams, searchParams });
355
+
356
+ // Preserve search params from the current URL when requested.
357
+ // useSearchParams() works during both SSR (reads from request context)
358
+ // and on the client (reads from window.location, reactive to URL changes).
359
+ // We read current search params directly to avoid unconditional hook calls.
360
+ // On the client, window.location.search is always current; during SSR,
361
+ // getSsrData() provides the request's search params.
362
+ const resolvedHref = preserveSearchParams
363
+ ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams)
364
+ : baseHref;
365
+
322
366
  const internal = isInternalHref(resolvedHref);
323
367
 
324
368
  // ─── Click handler ───────────────────────────────────────────
@@ -351,7 +395,13 @@ export function Link({
351
395
 
352
396
  event.preventDefault();
353
397
  const shouldScroll = scroll !== false;
354
- void router.navigate(resolvedHref, { scroll: shouldScroll });
398
+
399
+ // Re-merge preserved search params at click time to pick up any
400
+ // URL changes since render (e.g. from other navigations or pushState).
401
+ const navHref = preserveSearchParams
402
+ ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams)
403
+ : resolvedHref;
404
+ void router.navigate(navHref, { scroll: shouldScroll });
355
405
  }
356
406
  : userOnClick; // External links — just pass through user's onClick
357
407
 
@@ -362,7 +412,11 @@ export function Link({
362
412
  userOnMouseEnter?.(event);
363
413
  const router = getRouterOrNull();
364
414
  if (router) {
365
- router.prefetch(resolvedHref);
415
+ // Re-merge preserved search params at hover time for fresh prefetch URL
416
+ const prefetchHref = preserveSearchParams
417
+ ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams)
418
+ : resolvedHref;
419
+ router.prefetch(prefetchHref);
366
420
  }
367
421
  }
368
422
  : userOnMouseEnter;
@@ -17,6 +17,7 @@ import { DenySignal, RedirectSignal } from './primitives.js';
17
17
  import type { AccessGateProps, SlotAccessGateProps, ReactElement } from './tree-builder.js';
18
18
  import { withSpan, setSpanAttribute } from './tracing.js';
19
19
  import { isDebug } from './debug.js';
20
+ import { rawSegmentParams } from './request-context.js';
20
21
 
21
22
  // ─── AccessGate ─────────────────────────────────────────────────────────────
22
23
 
@@ -35,7 +36,7 @@ import { isDebug } from './debug.js';
35
36
  * gets the same data by calling the same cached functions (React.cache dedup).
36
37
  */
37
38
  export function AccessGate(props: AccessGateProps): ReactElement | Promise<ReactElement> {
38
- const { accessFn, params, segmentName, verdict, children } = props;
39
+ const { accessFn, segmentName, verdict, children } = props;
39
40
 
40
41
  // Fast path: replay pre-computed verdict from the pre-render pass.
41
42
  // This is synchronous — Suspense boundaries cannot interfere with the
@@ -52,7 +53,7 @@ export function AccessGate(props: AccessGateProps): ReactElement | Promise<React
52
53
 
53
54
  // Fallback: call accessFn directly (used by tree-builder.ts which
54
55
  // doesn't run a pre-render pass, and for backward compat).
55
- return accessGateFallback(accessFn, params, segmentName, children);
56
+ return accessGateFallback(accessFn, segmentName, children);
56
57
  }
57
58
 
58
59
  /**
@@ -61,12 +62,12 @@ export function AccessGate(props: AccessGateProps): ReactElement | Promise<React
61
62
  */
62
63
  async function accessGateFallback(
63
64
  accessFn: AccessGateProps['accessFn'],
64
- params: AccessGateProps['params'],
65
65
  segmentName: AccessGateProps['segmentName'],
66
66
  children: ReactElement
67
67
  ): Promise<ReactElement> {
68
68
  await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {
69
69
  try {
70
+ const params = await rawSegmentParams();
70
71
  await accessFn({ params });
71
72
  await setSpanAttribute('timber.result', 'pass');
72
73
  } catch (error: unknown) {
@@ -103,10 +104,10 @@ async function accessGateFallback(
103
104
  * slot doesn't make architectural sense.
104
105
  */
105
106
  export async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactElement> {
106
- const { accessFn, params, DeniedComponent, slotName, createElement, defaultFallback, children } =
107
- props;
107
+ const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children } = props;
108
108
 
109
109
  try {
110
+ const params = await rawSegmentParams();
110
111
  await accessFn({ params });
111
112
  } catch (error: unknown) {
112
113
  // DenySignal → graceful degradation (denied.tsx → default.tsx → null)
@@ -44,6 +44,20 @@ export interface RequestContextStore {
44
44
  * call `.parse(searchParams())`.
45
45
  */
46
46
  searchParamsPromise: Promise<URLSearchParams>;
47
+ /**
48
+ * Raw search string from the request URL (e.g. "?foo=bar&baz=1").
49
+ * Available synchronously for use in `redirect()` with `preserveSearchParams`.
50
+ */
51
+ searchString: string;
52
+ /**
53
+ * Promise resolving to the coerced segment params for the current request.
54
+ * Set by the pipeline after route matching and param coercion, before
55
+ * middleware and rendering. Pages and layouts read params via
56
+ * `rawSegmentParams()` instead of receiving them as a prop.
57
+ *
58
+ * See design/07-routing.md §"params.ts — Convention File for Typed Params"
59
+ */
60
+ segmentParamsPromise?: Promise<Record<string, string | string[]>>;
47
61
  /** Outgoing Set-Cookie entries (name → serialized value + options). Last write wins. */
48
62
  cookieJar: Map<string, CookieEntry>;
49
63
  /** Whether the response has flushed (headers committed). */
@@ -13,6 +13,8 @@ export {
13
13
  headers,
14
14
  cookies,
15
15
  rawSearchParams,
16
+ rawSegmentParams,
17
+ setSegmentParams,
16
18
  runWithRequestContext,
17
19
  setMutableCookieContext,
18
20
  markResponseFlushed,
@@ -32,6 +34,7 @@ export {
32
34
  waitUntil,
33
35
  DenySignal,
34
36
  RedirectSignal,
37
+ type RedirectOptions,
35
38
  } from './primitives';
36
39
  export type { RenderErrorDigest, WaitUntilAdapter } from './primitives';
37
40
  export type { JsonSerializable } from './types';
@@ -21,6 +21,7 @@ import {
21
21
  setMutableCookieContext,
22
22
  getSetCookieHeaders,
23
23
  markResponseFlushed,
24
+ setSegmentParams,
24
25
  } from './request-context.js';
25
26
  import {
26
27
  generateTraceId,
@@ -484,6 +485,11 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
484
485
  throw error;
485
486
  }
486
487
 
488
+ // Store coerced segment params in ALS so components can access them
489
+ // via rawSegmentParams() instead of receiving them as a prop.
490
+ // See design/07-routing.md §"params.ts — Convention File for Typed Params"
491
+ setSegmentParams(match.params);
492
+
487
493
  // Stage 3: Leaf middleware.ts (only the leaf route's middleware runs)
488
494
  if (match.middleware) {
489
495
  const ctx: MiddlewareContext = {
@@ -6,6 +6,8 @@
6
6
  import type { JsonSerializable } from './types.js';
7
7
  import { getWaitUntil as _getWaitUntil } from './waituntil-bridge.js';
8
8
  import { isDebug } from './debug.js';
9
+ import { getRequestSearchString } from './request-context.js';
10
+ import { mergePreservedSearchParams } from '#/shared/merge-search-params.js';
9
11
 
10
12
  // ─── Dev-mode validation ────────────────────────────────────────────────────
11
13
 
@@ -209,14 +211,46 @@ export class RedirectSignal extends Error {
209
211
  /** Pattern matching absolute URLs: http(s):// or protocol-relative // */
210
212
  const ABSOLUTE_URL_RE = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/)/;
211
213
 
214
+ /**
215
+ * Options for redirect() — alternative to passing a bare status code.
216
+ */
217
+ export interface RedirectOptions {
218
+ /** HTTP redirect status code (3xx). Defaults to 302. */
219
+ status?: number;
220
+ /**
221
+ * Preserve search params from the current request URL on the redirect target.
222
+ *
223
+ * - `true` — preserve ALL current search params (target params take precedence)
224
+ * - `string[]` — preserve only the named params (e.g. `['private', 'token']`)
225
+ *
226
+ * Target path's own query params always take precedence over preserved ones.
227
+ */
228
+ preserveSearchParams?: true | string[];
229
+ }
230
+
212
231
  /**
213
232
  * Redirect to a relative path. Rejects absolute and protocol-relative URLs.
214
233
  * Use `redirectExternal()` for external redirects with an allow-list.
215
234
  *
216
235
  * @param path - Relative path (e.g. '/login', 'settings', '/login?returnTo=/dash')
217
- * @param status - HTTP redirect status code (3xx). Defaults to 302.
236
+ * @param statusOrOptions - HTTP status code (3xx, default 302) or options object.
237
+ *
238
+ * @example
239
+ * // Simple redirect
240
+ * redirect('/login');
241
+ *
242
+ * // With status code
243
+ * redirect('/login', 301);
244
+ *
245
+ * // With preserved search params
246
+ * redirect(`/docs/${version}/${slug}`, { preserveSearchParams: ['foo'] });
218
247
  */
219
- export function redirect(path: string, status: number = 302): never {
248
+ export function redirect(path: string, statusOrOptions?: number | RedirectOptions): never {
249
+ const status =
250
+ typeof statusOrOptions === 'number' ? statusOrOptions : (statusOrOptions?.status ?? 302);
251
+ const preserveSearchParams =
252
+ typeof statusOrOptions === 'object' ? statusOrOptions.preserveSearchParams : undefined;
253
+
220
254
  if (status < 300 || status > 399) {
221
255
  throw new Error(`redirect() requires a 3xx status code, got ${status}.`);
222
256
  }
@@ -226,7 +260,14 @@ export function redirect(path: string, status: number = 302): never {
226
260
  'Use redirectExternal(url, allowList) for external redirects.'
227
261
  );
228
262
  }
229
- throw new RedirectSignal(path, status);
263
+
264
+ let resolvedPath = path;
265
+ if (preserveSearchParams) {
266
+ const currentSearch = getRequestSearchString();
267
+ resolvedPath = mergePreservedSearchParams(path, currentSearch, preserveSearchParams);
268
+ }
269
+
270
+ throw new RedirectSignal(resolvedPath, status);
230
271
  }
231
272
 
232
273
  /**
@@ -236,9 +277,10 @@ export function redirect(path: string, status: number = 302): never {
236
277
  * will replay POST requests to the new location. This matches Next.js behavior.
237
278
  *
238
279
  * @param path - Relative path (e.g. '/new-page', '/dashboard')
280
+ * @param options - Optional redirect options (e.g. preserveSearchParams).
239
281
  */
240
- export function permanentRedirect(path: string): never {
241
- redirect(path, 308);
282
+ export function permanentRedirect(path: string, options?: Omit<RedirectOptions, 'status'>): never {
283
+ redirect(path, { status: 308, ...options });
242
284
  }
243
285
 
244
286
  /**