veryfront 0.1.992 → 0.1.994

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 (40) hide show
  1. package/esm/deno.js +3 -3
  2. package/esm/src/build/production-build/templates.d.ts.map +1 -1
  3. package/esm/src/build/production-build/templates.js +1 -1
  4. package/esm/src/html/html-injection.d.ts +7 -0
  5. package/esm/src/html/html-injection.d.ts.map +1 -1
  6. package/esm/src/html/html-injection.js +7 -1
  7. package/esm/src/html/hydration-script-builder/templates/renderer.d.ts.map +1 -1
  8. package/esm/src/html/hydration-script-builder/templates/renderer.js +6 -2
  9. package/esm/src/html/hydration-script-builder/templates/router.d.ts.map +1 -1
  10. package/esm/src/html/hydration-script-builder/templates/router.js +49 -8
  11. package/esm/src/react/runtime/core.d.ts +57 -5
  12. package/esm/src/react/runtime/core.d.ts.map +1 -1
  13. package/esm/src/react/runtime/core.js +168 -6
  14. package/esm/src/rendering/layouts/layout-applicator.d.ts.map +1 -1
  15. package/esm/src/rendering/layouts/layout-applicator.js +2 -5
  16. package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
  17. package/esm/src/rendering/orchestrator/html.js +1 -0
  18. package/esm/src/rendering/page-rendering.d.ts.map +1 -1
  19. package/esm/src/rendering/page-rendering.js +2 -5
  20. package/esm/src/rendering/rsc/client-boot.ts +5 -1
  21. package/esm/src/rendering/rsc/client-module-strategy.d.ts +14 -0
  22. package/esm/src/rendering/rsc/client-module-strategy.d.ts.map +1 -1
  23. package/esm/src/rendering/rsc/client-module-strategy.js +11 -0
  24. package/esm/src/rendering/rsc/hydrate-client.ts +9 -2
  25. package/esm/src/rendering/script-page-handling.d.ts.map +1 -1
  26. package/esm/src/rendering/script-page-handling.js +2 -5
  27. package/esm/src/routing/api/context-builder.d.ts +4 -0
  28. package/esm/src/routing/api/context-builder.d.ts.map +1 -1
  29. package/esm/src/routing/api/context-builder.js +6 -5
  30. package/esm/src/routing/flatten-route-params.d.ts +11 -0
  31. package/esm/src/routing/flatten-route-params.d.ts.map +1 -0
  32. package/esm/src/routing/flatten-route-params.js +20 -0
  33. package/esm/src/routing/index.d.ts +1 -0
  34. package/esm/src/routing/index.d.ts.map +1 -1
  35. package/esm/src/routing/index.js +1 -0
  36. package/esm/src/server/services/rsc/endpoints/rsc-bundles.generated.d.ts.map +1 -1
  37. package/esm/src/server/services/rsc/endpoints/rsc-bundles.generated.js +1 -1
  38. package/esm/src/utils/version-constant.d.ts +1 -1
  39. package/esm/src/utils/version-constant.js +1 -1
  40. package/package.json +1 -1
@@ -50,11 +50,160 @@ function collectHead(data) {
50
50
  const collector = dntShim.dntGlobalThis[HEAD_COLLECTOR_SYMBOL];
51
51
  collector?.(data);
52
52
  }
53
- /** Provides the router context value used by `useRouter()`. */
54
- export function RouterProvider({ children, router, }) {
55
- return React.createElement(RouterContext.Provider, { value: router ?? defaultRouter }, children);
53
+ const NAVIGATION_STORE_KEY = Symbol.for("veryfront.navigation.store.v1");
54
+ function getNavigationStore() {
55
+ const holder = dntShim.dntGlobalThis;
56
+ const existing = holder[NAVIGATION_STORE_KEY];
57
+ if (existing)
58
+ return existing;
59
+ const listeners = new Set();
60
+ let navigator = null;
61
+ const store = {
62
+ subscribe(listener) {
63
+ listeners.add(listener);
64
+ return () => {
65
+ listeners.delete(listener);
66
+ };
67
+ },
68
+ getHref() {
69
+ const loc = globalThis.location;
70
+ return loc ? `${loc.pathname}${loc.search}${loc.hash}` : "/";
71
+ },
72
+ notify() {
73
+ for (const listener of [...listeners]) {
74
+ try {
75
+ listener();
76
+ }
77
+ catch {
78
+ // A subscriber threw; ignore it and continue notifying the others.
79
+ }
80
+ }
81
+ },
82
+ navigate(href, options) {
83
+ if (navigator)
84
+ return navigator(href, options);
85
+ globalThis.location?.assign(href);
86
+ return Promise.resolve();
87
+ },
88
+ setNavigator(next) {
89
+ navigator = next;
90
+ },
91
+ };
92
+ holder[NAVIGATION_STORE_KEY] = store;
93
+ return store;
94
+ }
95
+ function hrefFromRouter(router) {
96
+ if (!router)
97
+ return undefined;
98
+ const search = new URLSearchParams(router.query ?? {}).toString();
99
+ return search ? `${router.pathname}?${search}` : router.pathname;
100
+ }
101
+ function splitHref(href) {
102
+ const queryIndex = href.indexOf("?");
103
+ if (queryIndex === -1) {
104
+ const hashIndex = href.indexOf("#");
105
+ return { pathname: hashIndex === -1 ? href : href.slice(0, hashIndex), search: "" };
106
+ }
107
+ const afterQuery = href.slice(queryIndex + 1);
108
+ const hashIndex = afterQuery.indexOf("#");
109
+ return {
110
+ pathname: href.slice(0, queryIndex),
111
+ search: hashIndex === -1 ? afterQuery : afterQuery.slice(0, hashIndex),
112
+ };
113
+ }
114
+ /**
115
+ * Provides the router context. `pathname`/`query` track the live URL through the
116
+ * shared navigation store's `useSyncExternalStore` surface; `params`/`domain`
117
+ * are seeded from the `router` prop. One component serves both sides: React uses
118
+ * `getServerSnapshot` (the seed href) during SSR and the live store on the
119
+ * client, so there is no environment branch — the server render and the first
120
+ * client render match by construction.
121
+ *
122
+ * The store is a stable singleton that exists on first access, so there is no
123
+ * "is the router mounted yet?" race: the subscription is live from the first
124
+ * render, and the router's navigations notify through the same object. Page
125
+ * context (frontmatter/slug/headings) is a separate concern, provided by
126
+ * `PageContextProvider`, which derives its live location from this router.
127
+ */
128
+ export function RouterProvider({ router, children }) {
129
+ const store = getNavigationStore();
130
+ const seed = router ?? defaultRouter;
131
+ // The server snapshot is derived from the router itself, so the first client
132
+ // render matches the server exactly (both come from one `RouterValue`).
133
+ const seedHref = hrefFromRouter(router) ?? "/";
134
+ const getServerSnapshot = React.useCallback(() => seedHref, [seedHref]);
135
+ const href = React.useSyncExternalStore(store.subscribe, store.getHref, getServerSnapshot);
136
+ const { pathname, search } = splitHref(href);
137
+ const query = React.useMemo(() => Object.fromEntries(new URLSearchParams(search)), [search]);
138
+ // `isMounted` is `false` on the server and the first client render (so they
139
+ // agree — hydration-safe), then flips `true` after mount. Consumers guard on
140
+ // this (e.g. `if (!router.isMounted) return null`).
141
+ const [isMounted, setIsMounted] = React.useState(false);
142
+ React.useEffect(() => setIsMounted(true), []);
143
+ const routerValue = React.useMemo(() => ({
144
+ domain: seed.domain || (globalThis.location?.hostname ?? ""),
145
+ path: pathname,
146
+ pathname,
147
+ params: seed.params,
148
+ query,
149
+ isPreview: seed.isPreview,
150
+ isMounted,
151
+ navigate: (url) => store.navigate(url, { history: "push" }),
152
+ push: (url) => store.navigate(url, { history: "push" }),
153
+ replace: (url) => store.navigate(url, { history: "replace" }),
154
+ reload: async () => {
155
+ globalThis.location?.reload();
156
+ },
157
+ }), [pathname, query, seed.params, seed.domain, seed.isPreview, isMounted, store]);
158
+ return React.createElement(RouterContext.Provider, { value: routerValue }, children);
159
+ }
160
+ /**
161
+ * Wraps a hydrated client component in `RouterProvider` (router state) nested
162
+ * with `PageContextProvider` (frontmatter), seeded from the live location plus
163
+ * the initial route match — mirroring how SSR wraps the tree.
164
+ *
165
+ * The RSC hydration path calls this through a runtime import of
166
+ * `veryfront/router`, so it runs under the app's React instance — the same one
167
+ * the hydrated component uses, and the same providers and `React` this module
168
+ * already reference. That is why the caller does not (and must not) pass a
169
+ * `React` across the module boundary: the wrapping happens here, inside the
170
+ * module that owns React.
171
+ */
172
+ export function wrapForHydration(child, options = {}) {
173
+ const loc = globalThis.location;
174
+ const pathname = loc?.pathname ?? "/";
175
+ const params = options.params ?? {};
176
+ const query = loc
177
+ ? Object.fromEntries(new URLSearchParams(loc.search))
178
+ : {};
179
+ const router = {
180
+ ...defaultRouter,
181
+ domain: loc?.hostname ?? "",
182
+ path: pathname,
183
+ pathname,
184
+ params,
185
+ query,
186
+ };
187
+ // `PageContextProvider` derives its live location from the router above; only
188
+ // the page-authored bits (frontmatter/slug) are seeded here.
189
+ const pageContext = {
190
+ ...defaultPageContext,
191
+ slug: pathname,
192
+ path: pathname,
193
+ params,
194
+ query,
195
+ frontmatter: options.frontmatter ?? {},
196
+ };
197
+ return React.createElement(RouterProvider, {
198
+ router,
199
+ children: React.createElement(PageContextProvider, { pageContext, children: child }),
200
+ });
56
201
  }
57
- /** Reads the current router context. */
202
+ /**
203
+ * Reads the router context: `pathname`, `query`, `params`, and the navigation
204
+ * actions. Reactive across client-side navigation — this is the single hook for
205
+ * location and navigation state.
206
+ */
58
207
  export function useRouter() {
59
208
  return React.useContext(RouterContext);
60
209
  }
@@ -62,9 +211,22 @@ export function useRouter() {
62
211
  export function Link({ prefetch = true, children, ...rest }) {
63
212
  return React.createElement("a", { ...rest, "data-prefetch": prefetch ? "true" : "false" }, children);
64
213
  }
65
- /** Provides page context to route and MDX descendants. */
214
+ /**
215
+ * Provides page context to route and MDX descendants. Page-authored fields
216
+ * (`frontmatter`, `slug`, `headings`) come from the `pageContext` prop; the
217
+ * location fields (`path`, `query`, `params`) are derived from the router so
218
+ * they stay reactive and there is a single source of truth — `usePageContext()`
219
+ * exposes the same `query`/`pathname` as `useRouter()`. When rendered outside a
220
+ * `RouterProvider` (no live router) it falls back to the seed's own location.
221
+ */
66
222
  export function PageContextProvider({ children, pageContext, }) {
67
- return React.createElement(PageContextContext.Provider, { value: pageContext ?? defaultPageContext }, children);
223
+ const seed = pageContext ?? defaultPageContext;
224
+ const router = React.useContext(RouterContext);
225
+ const hasRouter = router !== defaultRouter;
226
+ const value = React.useMemo(() => hasRouter
227
+ ? { ...seed, path: router.pathname, query: router.query, params: router.params }
228
+ : seed, [seed, hasRouter, router.pathname, router.query, router.params]);
229
+ return React.createElement(PageContextContext.Provider, { value }, children);
68
230
  }
69
231
  /** Reads the current page context. */
70
232
  export function usePageContext() {
@@ -1 +1 @@
1
- {"version":3,"file":"layout-applicator.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/layouts/layout-applicator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,YAAY,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAKzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAiBxE,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC5C,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,oBAAoB,CAAC;IAClC,gBAAgB,EAAE,aAAa,CAAC;IAChC,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/D;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,IAAI,CAA+B;IAC3C,OAAO,CAAC,UAAU,CAAC,CAAM;IACzB,OAAO,CAAC,MAAM,CAAC,CAAoC;IACnD,OAAO,CAAC,WAAW,CAAC,CAA0B;IAC9C,OAAO,CAAC,QAAQ,CAAC,CAAqD;IACtE,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,kBAAkB,CAAC,CAAyB;IACpD,OAAO,CAAC,+BAA+B,CAAC,CAGrC;gBAES,OAAO,EAAE,wBAAwB;IAiBvC,YAAY,CAChB,WAAW,EAAE,YAAY,CAAC,YAAY,EACtC,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,SAAS,GAAG,SAAS,EACnC,aAAa,EAAE,UAAU,EAAE,EAC3B,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACnD,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC;YA+FvB,gBAAgB;YAuDhB,sBAAsB;YAWtB,8BAA8B;YA4D9B,oBAAoB;YAqDpB,mBAAmB;YAuCnB,0BAA0B;CAgEzC"}
1
+ {"version":3,"file":"layout-applicator.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/layouts/layout-applicator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,YAAY,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAKzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAiBxE,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC5C,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,oBAAoB,CAAC;IAClC,gBAAgB,EAAE,aAAa,CAAC;IAChC,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/D;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,IAAI,CAA+B;IAC3C,OAAO,CAAC,UAAU,CAAC,CAAM;IACzB,OAAO,CAAC,MAAM,CAAC,CAAoC;IACnD,OAAO,CAAC,WAAW,CAAC,CAA0B;IAC9C,OAAO,CAAC,QAAQ,CAAC,CAAqD;IACtE,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,kBAAkB,CAAC,CAAyB;IACpD,OAAO,CAAC,+BAA+B,CAAC,CAGrC;gBAES,OAAO,EAAE,wBAAwB;IAiBvC,YAAY,CAChB,WAAW,EAAE,YAAY,CAAC,YAAY,EACtC,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,SAAS,GAAG,SAAS,EACnC,aAAa,EAAE,UAAU,EAAE,EAC3B,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACnD,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC;YAyFvB,gBAAgB;YAuDhB,sBAAsB;YAWtB,8BAA8B;YA4D9B,oBAAoB;YAqDpB,mBAAmB;YAuCnB,0BAA0B;CAgEzC"}
@@ -1,5 +1,6 @@
1
1
  import { dirname, join } from "../../platform/compat/path/index.js";
2
2
  import { rendererLogger } from "../../utils/index.js";
3
+ import { flattenRouteParams } from "../../routing/index.js";
3
4
  import { withSpan } from "../../observability/tracing/otlp-setup.js";
4
5
  import { SpanNames } from "../../observability/tracing/span-names.js";
5
6
  import { resolve as resolveContract } from "../../extensions/contracts.js";
@@ -66,11 +67,7 @@ export class LayoutApplicator {
66
67
  }
67
68
  const React = await getProjectReact();
68
69
  const headingsArray = this.headings ?? [];
69
- const flatParams = this.params
70
- ? Object.fromEntries(Object.entries(this.params)
71
- .map(([key, value]) => [key, Array.isArray(value) ? value[0] : value])
72
- .filter((entry) => entry[1] !== undefined))
73
- : {};
70
+ const flatParams = flattenRouteParams(this.params);
74
71
  const query = this.requestUrl ? Object.fromEntries(this.requestUrl.searchParams) : {};
75
72
  const pageContext = {
76
73
  slug: pageInfo.entity.slug || "",
@@ -1 +1 @@
1
- {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/html.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAClF,YAAY,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AA+FlF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,EAAE,mBAAmB;IAIjC,gBAAgB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BjE,kBAAkB,CACtB,WAAW,EAAE,cAAc,EAC3B,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,GAC3C,OAAO,CAAC,cAAc,CAAC;YAqEZ,sBAAsB;YAuDtB,4BAA4B;YAiB5B,wBAAwB;YAmBxB,gBAAgB;YAyBhB,kBAAkB;IAwDhC,OAAO,CAAC,cAAc;YAQR,eAAe;YAaf,gBAAgB;IAoF9B;;;;OAIG;YACW,gBAAgB;CAc/B"}
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/html.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAClF,YAAY,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AA+FlF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,EAAE,mBAAmB;IAIjC,gBAAgB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BjE,kBAAkB,CACtB,WAAW,EAAE,cAAc,EAC3B,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,GAC3C,OAAO,CAAC,cAAc,CAAC;YAqEZ,sBAAsB;YAwDtB,4BAA4B;YAiB5B,wBAAwB;YAmBxB,gBAAgB;YAyBhB,kBAAkB;IAwDhC,OAAO,CAAC,cAAc;YAQR,eAAe;YAaf,gBAAgB;IAoF9B;;;;OAIG;YACW,gBAAgB;CAc/B"}
@@ -163,6 +163,7 @@ export class HTMLGenerator {
163
163
  pagePath,
164
164
  projectDir: this.config.projectDir,
165
165
  isClientPage,
166
+ params: context.options?.params,
166
167
  environment: context.options?.environment,
167
168
  isLocalProject: this.config.mode === "development",
168
169
  nonce: context.options?.nonce,
@@ -1 +1 @@
1
- {"version":3,"file":"page-rendering.d.ts","sourceRoot":"","sources":["../../../src/src/rendering/page-rendering.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAa,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAQrG,UAAU,aAAa;IACrB,WAAW,EAAE,YAAY,CAAC,YAAY,CAAC;IACvC,UAAU,EAAE,UAAU,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C;AAED,UAAU,sBAAsB;IAC9B,UAAU,EAAE,UAAU,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,0BAA0B;IAClC,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAInE;AAED,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,UAAU,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;IACR,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GACA,OAAO,CAAC,sBAAsB,CAAC,CA4CjC;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,UAAU,EACpB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,aAAa,EAC/B,WAAW,EAAE,CACX,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,SAAS,CAAC,EACvB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,aAAa,CAAC,CAyHxB"}
1
+ {"version":3,"file":"page-rendering.d.ts","sourceRoot":"","sources":["../../../src/src/rendering/page-rendering.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAa,UAAU,EAAE,MAAM,mBAAmB,CAAC;AASrG,UAAU,aAAa;IACrB,WAAW,EAAE,YAAY,CAAC,YAAY,CAAC;IACvC,UAAU,EAAE,UAAU,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C;AAED,UAAU,sBAAsB;IAC9B,UAAU,EAAE,UAAU,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,0BAA0B;IAClC,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAInE;AAED,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,UAAU,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;IACR,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GACA,OAAO,CAAC,sBAAsB,CAAC,CA4CjC;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,UAAU,EACpB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,aAAa,EAC/B,WAAW,EAAE,CACX,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,SAAS,CAAC,EACvB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,aAAa,CAAC,CAmHxB"}
@@ -3,6 +3,7 @@ import { RENDER_ERROR } from "../errors/error-registry.js";
3
3
  import { mdxRenderer } from "../transforms/mdx/index.js";
4
4
  import { clearMdxEsmCacheNamespace } from "../transforms/mdx/esm-module-loader/index.js";
5
5
  import { getProjectReact } from "../react/index.js";
6
+ import { flattenRouteParams } from "../routing/index.js";
6
7
  import { compileContent } from "../transforms/mdx/compiler/index.js";
7
8
  import { ensureError, getErrorMessage } from "../errors/veryfront-error.js";
8
9
  import { withSpan } from "../observability/tracing/otlp-setup.js";
@@ -77,11 +78,7 @@ export function handleMDXPage(pageInfo, slug, projectDir, mergedComponents, _com
77
78
  }
78
79
  if (typeof mod.generateMetadata === "function") {
79
80
  try {
80
- const params = options?.params
81
- ? Object.fromEntries(Object.entries(options.params)
82
- .map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])
83
- .filter((entry) => entry[1] !== undefined))
84
- : {};
81
+ const params = flattenRouteParams(options?.params);
85
82
  const query = options?.url ? Object.fromEntries(options.url.searchParams) : {};
86
83
  const gen = await mod.generateMetadata({
87
84
  params,
@@ -14,6 +14,7 @@ import {
14
14
  import { validateTrustedHtml } from "#veryfront/security/client/html-sanitizer.ts";
15
15
  import { consumeNdjsonStream, getContainer } from "./client-dom.ts";
16
16
  import { hydrateAllClientBoundaries } from "./hydrate-client.ts";
17
+ import { wrapWithRouterProvider } from "./hydration-router.ts";
17
18
  import { RSC_PATH_PREFIX, RSC_ROOT_ID } from "./constants.ts";
18
19
 
19
20
  /**
@@ -173,7 +174,10 @@ async function hydratePageComponent(
173
174
  const hydrationRoot = shouldWrapPageHydrationRoot(root, document.body)
174
175
  ? createPageHydrationRoot(bodyChildren, document.body)
175
176
  : root;
176
- const component = React.createElement(Component, {});
177
+ const component = await wrapWithRouterProvider(
178
+ React.createElement(Component, {}),
179
+ readHydrationData(document),
180
+ );
177
181
 
178
182
  if (shouldRenderPageComponent(strategy)) {
179
183
  ReactDOM.createRoot(hydrationRoot).render(component);
@@ -7,6 +7,12 @@ export interface ClientRuntimeHydrationData {
7
7
  pagePath?: string;
8
8
  clientModuleStrategy?: ClientModuleStrategy;
9
9
  dev?: boolean;
10
+ /** Route slug for the current page (from the route match). */
11
+ slug?: string;
12
+ /** Route params from the initial match — used to seed the reactive router. */
13
+ params?: Record<string, string | string[]>;
14
+ /** Page frontmatter — exposed reactively via `usePageContext()`. */
15
+ frontmatter?: Record<string, unknown>;
10
16
  }
11
17
  export interface ClientModuleUrlOptions {
12
18
  strategy: ClientModuleStrategy;
@@ -24,4 +30,12 @@ export declare function getHydrationReactImportSpecifiers(doc?: Document, versio
24
30
  react: string;
25
31
  reactDomClient: string;
26
32
  };
33
+ /**
34
+ * The import specifier for the framework router module, or `null` if the page's
35
+ * import map does not own it. Returned as a value (not a literal) so callers can
36
+ * `import(specifier)` and have the bundler leave it as a runtime import — the
37
+ * module resolves to the app's React instance, which is required for the
38
+ * provider's hooks to run under the same React as the hydrated component.
39
+ */
40
+ export declare function getHydrationRouterImportSpecifier(doc?: Document): string | null;
27
41
  //# sourceMappingURL=client-module-strategy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-module-strategy.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/rsc/client-module-strategy.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,YAAY,CAAC;AAEvD,MAAM,WAAW,2BAA2B;IAC1C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;CACxC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,2BAA2B,GACnC,oBAAoB,CAUtB;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,GAAE,QAAmB,GACvB,0BAA0B,GAAG,IAAI,CASnC;AAED,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,0BAA0B,GAAG,IAAI,GAC/C,oBAAoB,CAMtB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAGvE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAOnF;AAED,wBAAgB,iCAAiC,CAC/C,GAAG,GAAE,QAAmB,EACxB,OAAO,GAAE,MAA8B,GACtC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAS3C"}
1
+ {"version":3,"file":"client-module-strategy.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/rsc/client-module-strategy.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,YAAY,CAAC;AAEvD,MAAM,WAAW,2BAA2B;IAC1C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;CACxC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,2BAA2B,GACnC,oBAAoB,CAUtB;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,GAAE,QAAmB,GACvB,0BAA0B,GAAG,IAAI,CASnC;AAED,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,0BAA0B,GAAG,IAAI,GAC/C,oBAAoB,CAMtB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAGvE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAOnF;AAED,wBAAgB,iCAAiC,CAC/C,GAAG,GAAE,QAAmB,EACxB,OAAO,GAAE,MAA8B,GACtC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAS3C;AAED;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAAC,GAAG,GAAE,QAAmB,GAAG,MAAM,GAAG,IAAI,CAGzF"}
@@ -54,3 +54,14 @@ export function getHydrationReactImportSpecifiers(doc = document, version = REAC
54
54
  : getReactDOMClientCDNUrl(version),
55
55
  };
56
56
  }
57
+ /**
58
+ * The import specifier for the framework router module, or `null` if the page's
59
+ * import map does not own it. Returned as a value (not a literal) so callers can
60
+ * `import(specifier)` and have the bundler leave it as a runtime import — the
61
+ * module resolves to the app's React instance, which is required for the
62
+ * provider's hooks to run under the same React as the hydrated component.
63
+ */
64
+ export function getHydrationRouterImportSpecifier(doc = document) {
65
+ const imports = getDocumentImportMapImports(doc);
66
+ return importMapOwnsSpecifier("veryfront/router", imports) ? "veryfront/router" : null;
67
+ }
@@ -10,6 +10,7 @@ import {
10
10
  readHydrationData,
11
11
  resolveClientModuleStrategy,
12
12
  } from "./client-module-strategy.ts";
13
+ import { wrapWithRouterProvider } from "./hydration-router.ts";
13
14
  type Manifest = {
14
15
  version: number;
15
16
  hash?: string;
@@ -143,7 +144,8 @@ export async function hydrateAllClientBoundaries(doc: Document = document): Prom
143
144
  return;
144
145
  }
145
146
 
146
- const clientModuleStrategy = resolveClientModuleStrategy(readHydrationData(doc));
147
+ const hydrationData = readHydrationData(doc);
148
+ const clientModuleStrategy = resolveClientModuleStrategy(hydrationData);
147
149
 
148
150
  try {
149
151
  if (globalThis.__VF_TEST_MODE__) {
@@ -176,7 +178,12 @@ export async function hydrateAllClientBoundaries(doc: Document = document): Prom
176
178
 
177
179
  try {
178
180
  const root: ReactRoot = createRoot(el);
179
- root.render(React.createElement(Cmp as React.FC, {}));
181
+ const tree = await wrapWithRouterProvider(
182
+ React.createElement(Cmp as React.FC, {}),
183
+ hydrationData,
184
+ doc,
185
+ );
186
+ root.render(tree);
180
187
  el.dataset.hydrated = "true";
181
188
  } catch (e) {
182
189
  rscLogger.warn("hydrate: render failed", e);
@@ -1 +1 @@
1
- {"version":3,"file":"script-page-handling.d.ts","sourceRoot":"","sources":["../../../src/src/rendering/script-page-handling.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EAGV,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAenE,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqHD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,UAAU,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,YAAY,CAAC,CAiDvB"}
1
+ {"version":3,"file":"script-page-handling.d.ts","sourceRoot":"","sources":["../../../src/src/rendering/script-page-handling.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EAGV,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAenE,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+GD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,UAAU,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,YAAY,CAAC,CAiDvB"}
@@ -9,6 +9,7 @@ import { rewriteNpmImports } from "../transforms/npm-import-rewrites.js";
9
9
  import { dirname, join } from "../platform/compat/path/index.js";
10
10
  import { cwd } from "../platform/compat/process.js";
11
11
  import { RENDER_ERROR } from "../errors/error-registry.js";
12
+ import { flattenRouteParams } from "../routing/index.js";
12
13
  import { createError, toError } from "../errors/veryfront-error.js";
13
14
  import { computeHash } from "./utils/index.js";
14
15
  import { wrapInHTMLShell } from "../html/index.js";
@@ -75,11 +76,7 @@ function extractHtmlAndMetadata(output) {
75
76
  }));
76
77
  }
77
78
  function buildPageContext(pageInfo, slug, params, url) {
78
- const flatParams = params
79
- ? Object.fromEntries(Object.entries(params)
80
- .map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])
81
- .filter((entry) => entry[1] !== undefined))
82
- : {};
79
+ const flatParams = flattenRouteParams(params);
83
80
  return {
84
81
  params: flatParams,
85
82
  query: url ? Object.fromEntries(url.searchParams) : {},
@@ -16,5 +16,9 @@ export interface APIContext {
16
16
  fs: FileSystemAdapter;
17
17
  }
18
18
  export declare function createContext(request: Request, match: RouteMatch, fs: FileSystemAdapter): APIContext;
19
+ /**
20
+ * @deprecated Use {@link flattenRouteParams} directly. Kept as a thin alias so
21
+ * the routing barrel exposes a single flattener implementation (issue #2742).
22
+ */
19
23
  export declare function normalizeParams(params: Record<string, string | string[]>): Record<string, string>;
20
24
  //# sourceMappingURL=context-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-builder.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/context-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,QAAQ,CAAC;IACvD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,QAAQ,CAAC;IACtD,EAAE,EAAE,iBAAiB,CAAC;CACvB;AAgBD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,iBAAiB,GACpB,UAAU,CAmBZ;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACxC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB"}
1
+ {"version":3,"file":"context-builder.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/context-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,QAAQ,CAAC;IACvD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,QAAQ,CAAC;IACtD,EAAE,EAAE,iBAAiB,CAAC;CACvB;AAgBD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,iBAAiB,GACpB,UAAU,CAmBZ;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACxC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAExB"}
@@ -1,4 +1,5 @@
1
1
  import { parseCookies } from "../../utils/cookie-utils.js";
2
+ import { flattenRouteParams } from "../flatten-route-params.js";
2
3
  export { parseCookies };
3
4
  function createResponse(body, contentType, init) {
4
5
  return new Response(body, {
@@ -26,10 +27,10 @@ export function createContext(request, match, fs) {
26
27
  fs,
27
28
  };
28
29
  }
30
+ /**
31
+ * @deprecated Use {@link flattenRouteParams} directly. Kept as a thin alias so
32
+ * the routing barrel exposes a single flattener implementation (issue #2742).
33
+ */
29
34
  export function normalizeParams(params) {
30
- const out = {};
31
- for (const [key, value] of Object.entries(params)) {
32
- out[key] = Array.isArray(value) ? value.join("/") : value;
33
- }
34
- return out;
35
+ return flattenRouteParams(params);
35
36
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Flattens matched route params (which may be arrays for catch-all `[...slug]`
3
+ * segments) into the `Record<string, string>` shape the router value exposes.
4
+ *
5
+ * Catch-all segments are **joined with `/`** so no path information is lost —
6
+ * `/docs/guides/intro` -> `{ slug: "guides/intro" }`, not `{ slug: "guides" }`.
7
+ * This matches the client SPA normalizer and the RSC hydration normalizer, so
8
+ * server and client agree.
9
+ */
10
+ export declare function flattenRouteParams(params?: Record<string, string | string[]>): Record<string, string>;
11
+ //# sourceMappingURL=flatten-route-params.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flatten-route-params.d.ts","sourceRoot":"","sources":["../../../src/src/routing/flatten-route-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACzC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Flattens matched route params (which may be arrays for catch-all `[...slug]`
3
+ * segments) into the `Record<string, string>` shape the router value exposes.
4
+ *
5
+ * Catch-all segments are **joined with `/`** so no path information is lost —
6
+ * `/docs/guides/intro` -> `{ slug: "guides/intro" }`, not `{ slug: "guides" }`.
7
+ * This matches the client SPA normalizer and the RSC hydration normalizer, so
8
+ * server and client agree.
9
+ */
10
+ export function flattenRouteParams(params) {
11
+ if (!params)
12
+ return {};
13
+ const flat = {};
14
+ for (const [key, value] of Object.entries(params)) {
15
+ if (value === undefined)
16
+ continue;
17
+ flat[key] = Array.isArray(value) ? value.join("/") : value;
18
+ }
19
+ return flat;
20
+ }
@@ -5,6 +5,7 @@
5
5
  * @module routing
6
6
  */
7
7
  export type { Route, RouteMatch } from "./matchers/index.js";
8
+ export { flattenRouteParams } from "./flatten-route-params.js";
8
9
  export { getSpecificityScore, matchRoute, normalizePath, PageRouteMatcher, parseRoute, } from "./matchers/index.js";
9
10
  export type { PathCandidates, RouteParams } from "./slug-mapper/index.js";
10
11
  export { extractParams, getPathCandidates, getSlugFromPath, getSupportedExtensions, isDynamicRoute, matchesPattern, normalizeSlug, pathToSlug, slugToPath, } from "./slug-mapper/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/routing/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,GACX,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,aAAa,EACb,UAAU,EACV,UAAU,GACX,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,UAAU,EACV,cAAc,EACd,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EACL,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,mBAAmB,EACnB,IAAI,EACJ,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/routing/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,GACX,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,aAAa,EACb,UAAU,EACV,UAAU,GACX,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,UAAU,EACV,cAAc,EACd,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EACL,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,mBAAmB,EACnB,IAAI,EACJ,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,gBAAgB,CAAC"}
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * @module routing
6
6
  */
7
+ export { flattenRouteParams } from "./flatten-route-params.js";
7
8
  export { getSpecificityScore, matchRoute, normalizePath, PageRouteMatcher, parseRoute, } from "./matchers/index.js";
8
9
  export { extractParams, getPathCandidates, getSlugFromPath, getSupportedExtensions, isDynamicRoute, matchesPattern, normalizeSlug, pathToSlug, slugToPath, } from "./slug-mapper/index.js";
9
10
  export { extractPageDataFromScript, NavigationHandlers, PageLoader, PageTransition, ViewportPrefetch, } from "./client/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"rsc-bundles.generated.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/services/rsc/endpoints/rsc-bundles.generated.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,kBAAkB,EAAE,MAC2u2B,CAAC;AAE7w2B,eAAO,MAAM,iBAAiB,EAAE,MACsunB,CAAC"}
1
+ {"version":3,"file":"rsc-bundles.generated.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/services/rsc/endpoints/rsc-bundles.generated.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,kBAAkB,EAAE,MAC8v3B,CAAC;AAEhy3B,eAAO,MAAM,iBAAiB,EAAE,MACsunB,CAAC"}