@reckona/mreact-router 0.0.148 → 0.0.150

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import type { ReactCompatNode } from "@reckona/mreact-compat";
2
2
  import type { QueryClient } from "@reckona/mreact-query";
3
- export type InferLoaderData<TLoader extends (...args: never[]) => unknown> = Awaited<ReturnType<TLoader>>;
3
+ export type RouteLoader = (...args: never[]) => unknown;
4
+ export type InferLoaderData<TLoader extends RouteLoader> = Awaited<ReturnType<TLoader>>;
4
5
  export type RouteParams = Record<string, readonly string[] | string>;
6
+ export type InferLoaderParams<TLoader extends RouteLoader> = TLoader extends (context: infer TContext, ...args: never[]) => unknown ? TContext extends {
7
+ params: infer TParams;
8
+ } ? TParams extends RouteParams ? TParams : RouteParams : RouteParams : RouteParams;
5
9
  export interface LoaderContext<TParams extends RouteParams = RouteParams> {
6
10
  env?: unknown;
7
11
  params: TParams;
@@ -22,6 +26,8 @@ export interface PageProps<TData = unknown, TParams extends RouteParams = RouteP
22
26
  params: TParams;
23
27
  request: Request;
24
28
  }
29
+ export type PageComponent<TLoader extends RouteLoader> = (props: PageProps<InferLoaderData<TLoader>, InferLoaderParams<TLoader>>) => ReactCompatNode;
30
+ export declare function definePage<TLoader extends RouteLoader>(component: PageComponent<TLoader>): PageComponent<TLoader>;
25
31
  export interface LayoutProps<TParams extends RouteParams = RouteParams> {
26
32
  children: ReactCompatNode;
27
33
  params: TParams;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -1,2 +1,4 @@
1
- export {};
1
+ export function definePage(component) {
2
+ return component;
3
+ }
2
4
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReactCompatNode } from \"@reckona/mreact-compat\";\nimport type { QueryClient } from \"@reckona/mreact-query\";\n\nexport type InferLoaderData<TLoader extends (...args: never[]) => unknown> = Awaited<\n ReturnType<TLoader>\n>;\n\nexport type RouteParams = Record<string, readonly string[] | string>;\n\nexport interface LoaderContext<TParams extends RouteParams = RouteParams> {\n env?: unknown;\n params: TParams;\n queryClient: QueryClient;\n request: Request;\n}\n\nexport interface GenerateMetadataContext<\n TData = unknown,\n TParams extends RouteParams = RouteParams,\n> {\n data: TData;\n params: TParams;\n request: Request;\n}\n\nexport interface RouteHandlerContext<TParams extends RouteParams = RouteParams> {\n params: TParams;\n request: Request;\n}\n\nexport interface PageProps<TData = unknown, TParams extends RouteParams = RouteParams> {\n data: TData;\n params: TParams;\n request: Request;\n}\n\nexport interface LayoutProps<TParams extends RouteParams = RouteParams> {\n children: ReactCompatNode;\n params: TParams;\n request: Request;\n}\n\nexport type MReactNode = ReactCompatNode;\n\nexport type MetadataScalar = boolean | number | string;\n\nexport interface MetadataImage {\n alt?: MetadataScalar;\n height?: MetadataScalar;\n type?: MetadataScalar;\n url: MetadataScalar;\n width?: MetadataScalar;\n}\n\nexport interface RouteMetadata {\n alternates?: {\n canonical?: MetadataScalar;\n };\n csp?: {\n disable?: boolean;\n directives?: Record<string, readonly string[] | string>;\n nonce?: string;\n remove?: readonly string[];\n replace?: Record<string, readonly string[] | string>;\n };\n description?: MetadataScalar;\n head?: readonly RouteHeadDescriptor[];\n icons?: {\n apple?: MetadataScalar;\n icon?: MetadataScalar;\n };\n openGraph?: {\n description?: MetadataScalar;\n image?: MetadataImage | MetadataScalar;\n images?: readonly (MetadataImage | MetadataScalar)[];\n title?: MetadataScalar;\n };\n lang?: MetadataScalar;\n robots?:\n | string\n | {\n follow?: boolean;\n index?: boolean;\n };\n security?: RouteSecurityHeaders;\n themeColor?: MetadataScalar | MetadataThemeColor;\n title?: MetadataScalar;\n viewport?: MetadataScalar | MetadataViewport;\n}\n\nexport type MetadataViewport = Record<string, MetadataScalar | null | undefined>;\n\nexport interface MetadataThemeColor {\n color?: MetadataScalar;\n media?: MetadataScalar;\n}\n\nexport interface RouteHeadDescriptor {\n attrs?: Record<string, boolean | number | string | undefined>;\n content?: string;\n nonce?: boolean | string;\n tag: \"base\" | \"link\" | \"meta\" | \"script\" | \"style\";\n}\n\nexport interface RouteSecurityHeaders {\n contentTypeOptions?: \"nosniff\" | null | undefined;\n frameOptions?: \"DENY\" | \"SAMEORIGIN\" | null | undefined;\n hsts?: RouteStrictTransportSecurity | false | null | undefined;\n permissionsPolicy?: Record<string, readonly string[] | null | undefined> | null | undefined;\n referrerPolicy?: string | null | undefined;\n}\n\nexport interface RouteStrictTransportSecurity {\n includeSubDomains?: boolean | undefined;\n maxAge: number;\n preload?: boolean | undefined;\n}\n\nexport interface RobotsContext {\n baseUrl: string;\n host: string;\n request: Request;\n}\n\nexport interface RobotsManifest {\n host?: string | undefined;\n rules?: RobotsRule | readonly RobotsRule[] | undefined;\n sitemap?: string | readonly string[] | undefined;\n}\n\nexport interface RobotsRule {\n allow?: string | readonly string[] | undefined;\n disallow?: string | readonly string[] | undefined;\n userAgent: string | readonly string[];\n}\n\nexport interface SitemapContext {\n baseUrl: string;\n host: string;\n request: Request;\n}\n\nexport interface SitemapEntry {\n changeFrequency?: string | undefined;\n lastModified?: Date | string | number | undefined;\n priority?: number | undefined;\n url: string;\n}\n\nexport interface ManifestContext {\n baseUrl: string;\n host: string;\n request: Request;\n}\n\nexport type ManifestDescriptor = Record<string, unknown>;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAmDA,MAAM,UAAU,UAAU,CACxB,SAAiC;IAEjC,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import type { ReactCompatNode } from \"@reckona/mreact-compat\";\nimport type { QueryClient } from \"@reckona/mreact-query\";\n\nexport type RouteLoader = (...args: never[]) => unknown;\n\nexport type InferLoaderData<TLoader extends RouteLoader> = Awaited<ReturnType<TLoader>>;\n\nexport type RouteParams = Record<string, readonly string[] | string>;\n\nexport type InferLoaderParams<TLoader extends RouteLoader> = TLoader extends (\n context: infer TContext,\n ...args: never[]\n) => unknown\n ? TContext extends { params: infer TParams }\n ? TParams extends RouteParams\n ? TParams\n : RouteParams\n : RouteParams\n : RouteParams;\n\nexport interface LoaderContext<TParams extends RouteParams = RouteParams> {\n env?: unknown;\n params: TParams;\n queryClient: QueryClient;\n request: Request;\n}\n\nexport interface GenerateMetadataContext<\n TData = unknown,\n TParams extends RouteParams = RouteParams,\n> {\n data: TData;\n params: TParams;\n request: Request;\n}\n\nexport interface RouteHandlerContext<TParams extends RouteParams = RouteParams> {\n params: TParams;\n request: Request;\n}\n\nexport interface PageProps<TData = unknown, TParams extends RouteParams = RouteParams> {\n data: TData;\n params: TParams;\n request: Request;\n}\n\nexport type PageComponent<TLoader extends RouteLoader> = (\n props: PageProps<InferLoaderData<TLoader>, InferLoaderParams<TLoader>>,\n) => ReactCompatNode;\n\nexport function definePage<TLoader extends RouteLoader>(\n component: PageComponent<TLoader>,\n): PageComponent<TLoader> {\n return component;\n}\n\nexport interface LayoutProps<TParams extends RouteParams = RouteParams> {\n children: ReactCompatNode;\n params: TParams;\n request: Request;\n}\n\nexport type MReactNode = ReactCompatNode;\n\nexport type MetadataScalar = boolean | number | string;\n\nexport interface MetadataImage {\n alt?: MetadataScalar;\n height?: MetadataScalar;\n type?: MetadataScalar;\n url: MetadataScalar;\n width?: MetadataScalar;\n}\n\nexport interface RouteMetadata {\n alternates?: {\n canonical?: MetadataScalar;\n };\n csp?: {\n disable?: boolean;\n directives?: Record<string, readonly string[] | string>;\n nonce?: string;\n remove?: readonly string[];\n replace?: Record<string, readonly string[] | string>;\n };\n description?: MetadataScalar;\n head?: readonly RouteHeadDescriptor[];\n icons?: {\n apple?: MetadataScalar;\n icon?: MetadataScalar;\n };\n openGraph?: {\n description?: MetadataScalar;\n image?: MetadataImage | MetadataScalar;\n images?: readonly (MetadataImage | MetadataScalar)[];\n title?: MetadataScalar;\n };\n lang?: MetadataScalar;\n robots?:\n | string\n | {\n follow?: boolean;\n index?: boolean;\n };\n security?: RouteSecurityHeaders;\n themeColor?: MetadataScalar | MetadataThemeColor;\n title?: MetadataScalar;\n viewport?: MetadataScalar | MetadataViewport;\n}\n\nexport type MetadataViewport = Record<string, MetadataScalar | null | undefined>;\n\nexport interface MetadataThemeColor {\n color?: MetadataScalar;\n media?: MetadataScalar;\n}\n\nexport interface RouteHeadDescriptor {\n attrs?: Record<string, boolean | number | string | undefined>;\n content?: string;\n nonce?: boolean | string;\n tag: \"base\" | \"link\" | \"meta\" | \"script\" | \"style\";\n}\n\nexport interface RouteSecurityHeaders {\n contentTypeOptions?: \"nosniff\" | null | undefined;\n frameOptions?: \"DENY\" | \"SAMEORIGIN\" | null | undefined;\n hsts?: RouteStrictTransportSecurity | false | null | undefined;\n permissionsPolicy?: Record<string, readonly string[] | null | undefined> | null | undefined;\n referrerPolicy?: string | null | undefined;\n}\n\nexport interface RouteStrictTransportSecurity {\n includeSubDomains?: boolean | undefined;\n maxAge: number;\n preload?: boolean | undefined;\n}\n\nexport interface RobotsContext {\n baseUrl: string;\n host: string;\n request: Request;\n}\n\nexport interface RobotsManifest {\n host?: string | undefined;\n rules?: RobotsRule | readonly RobotsRule[] | undefined;\n sitemap?: string | readonly string[] | undefined;\n}\n\nexport interface RobotsRule {\n allow?: string | readonly string[] | undefined;\n disallow?: string | readonly string[] | undefined;\n userAgent: string | readonly string[];\n}\n\nexport interface SitemapContext {\n baseUrl: string;\n host: string;\n request: Request;\n}\n\nexport interface SitemapEntry {\n changeFrequency?: string | undefined;\n lastModified?: Date | string | number | undefined;\n priority?: number | undefined;\n url: string;\n}\n\nexport interface ManifestContext {\n baseUrl: string;\n host: string;\n request: Request;\n}\n\nexport type ManifestDescriptor = Record<string, unknown>;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reckona/mreact-router",
3
- "version": "0.0.148",
3
+ "version": "0.0.150",
4
4
  "description": "File-system app router, SSR, actions, and deployment adapters for mreact.",
5
5
  "keywords": [
6
6
  "jsx",
@@ -105,20 +105,20 @@
105
105
  },
106
106
  "dependencies": {
107
107
  "typescript": "^6.0.3",
108
- "@reckona/mreact": "0.0.148",
109
- "@reckona/mreact-compat": "0.0.148",
110
- "@reckona/mreact-compiler": "0.0.148",
111
- "@reckona/mreact-devtools": "0.0.148",
112
- "@reckona/mreact-reactive-core": "0.0.148",
113
- "@reckona/mreact-query": "0.0.148",
114
- "@reckona/mreact-reactive-dom": "0.0.148",
115
- "@reckona/mreact-server": "0.0.148",
116
- "@reckona/mreact-shared": "0.0.148"
108
+ "@reckona/mreact": "0.0.150",
109
+ "@reckona/mreact-compiler": "0.0.150",
110
+ "@reckona/mreact-devtools": "0.0.150",
111
+ "@reckona/mreact-query": "0.0.150",
112
+ "@reckona/mreact-reactive-core": "0.0.150",
113
+ "@reckona/mreact-compat": "0.0.150",
114
+ "@reckona/mreact-reactive-dom": "0.0.150",
115
+ "@reckona/mreact-server": "0.0.150",
116
+ "@reckona/mreact-shared": "0.0.150"
117
117
  },
118
118
  "peerDependencies": {
119
119
  "vite": ">=8 <9"
120
120
  },
121
121
  "optionalDependencies": {
122
- "@reckona/mreact-router-native": "0.0.148"
122
+ "@reckona/mreact-router-native": "0.0.150"
123
123
  }
124
124
  }
package/src/actions.ts CHANGED
@@ -286,6 +286,11 @@ export async function dispatchServerActionRequest(options: {
286
286
  appDir: string;
287
287
  importPolicy?: AppRouterImportPolicy | undefined;
288
288
  request: Request;
289
+ renderSingleFlightNavigation?: ((options: {
290
+ path: string;
291
+ request: Request;
292
+ revalidatedPaths: readonly string[];
293
+ }) => Promise<Response | undefined>) | undefined;
289
294
  routeCache?: AppRouterCache | undefined;
290
295
  serverActionCacheVersion?: string | undefined;
291
296
  serverActions?: AppRouterServerActionOptions | undefined;
@@ -293,8 +298,14 @@ export async function dispatchServerActionRequest(options: {
293
298
  const { revalidatedPaths, value } = await withRouteCacheContext(options.routeCache, () =>
294
299
  dispatchServerActionRequestWithoutCacheContext(options),
295
300
  );
301
+ const singleFlight = await renderSingleFlightActionResponse({
302
+ render: options.renderSingleFlightNavigation,
303
+ request: options.request,
304
+ response: value,
305
+ revalidatedPaths,
306
+ });
296
307
 
297
- return withRevalidationHeader(value, revalidatedPaths);
308
+ return withRevalidationHeader(singleFlight ?? value, revalidatedPaths);
298
309
  }
299
310
 
300
311
  async function dispatchServerActionRequestWithoutCacheContext(options: {
@@ -568,6 +579,70 @@ function redirectToFormReferer(request: Request): Response {
568
579
  });
569
580
  }
570
581
 
582
+ async function renderSingleFlightActionResponse(options: {
583
+ render:
584
+ | ((options: {
585
+ path: string;
586
+ request: Request;
587
+ revalidatedPaths: readonly string[];
588
+ }) => Promise<Response | undefined>)
589
+ | undefined;
590
+ request: Request;
591
+ response: Response;
592
+ revalidatedPaths: readonly string[];
593
+ }): Promise<Response | undefined> {
594
+ if (
595
+ options.render === undefined ||
596
+ options.revalidatedPaths.length === 0 ||
597
+ options.request.headers.get("x-mreact-action-single-flight") !== "1" ||
598
+ !isFormActionRedirectResponse(options.response)
599
+ ) {
600
+ return undefined;
601
+ }
602
+
603
+ const path = sameOriginResponseLocationPath(options.request, options.response);
604
+
605
+ if (path === undefined || !options.revalidatedPaths.includes(normalizeActionPath(path))) {
606
+ return undefined;
607
+ }
608
+
609
+ const rendered = await options.render({
610
+ path,
611
+ request: options.request,
612
+ revalidatedPaths: options.revalidatedPaths,
613
+ });
614
+
615
+ if (rendered === undefined) {
616
+ return undefined;
617
+ }
618
+
619
+ rendered.headers.set("x-mreact-action-single-flight", "1");
620
+ return rendered;
621
+ }
622
+
623
+ function isFormActionRedirectResponse(response: Response): boolean {
624
+ return response.status >= 300 && response.status < 400 && response.headers.has("location");
625
+ }
626
+
627
+ function sameOriginResponseLocationPath(request: Request, response: Response): string | undefined {
628
+ const location = response.headers.get("location");
629
+
630
+ if (location === null) {
631
+ return undefined;
632
+ }
633
+
634
+ try {
635
+ const requestUrl = new URL(request.url);
636
+ const locationUrl = new URL(location, requestUrl);
637
+
638
+ return locationUrl.origin === requestUrl.origin
639
+ ? `${locationUrl.pathname}${locationUrl.search}`
640
+ : undefined;
641
+ } catch {
642
+ return undefined;
643
+ }
644
+ }
645
+
571
646
  function sameOriginRefererPath(request: Request): string | undefined {
572
647
  const referer = request.headers.get("referer");
573
648
 
@@ -587,6 +662,13 @@ function sameOriginRefererPath(request: Request): string | undefined {
587
662
  }
588
663
  }
589
664
 
665
+ function normalizeActionPath(path: string): string {
666
+ const pathname = path.startsWith("/") ? path : `/${path}`;
667
+ const withoutTrailing = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
668
+
669
+ return withoutTrailing === "" ? "/" : withoutTrailing;
670
+ }
671
+
590
672
  function withRevalidationHeader(response: Response, paths: string[]): Response {
591
673
  if (paths.length > 0) {
592
674
  response.headers.set("x-mreact-revalidate", paths.join(","));
package/src/build.ts CHANGED
@@ -4909,7 +4909,7 @@ export { defer, isDeferredLoaderData } from ${JSON.stringify(routerDeferredPath)
4909
4909
  export { defineMessages, detectLocale } from ${JSON.stringify(routerI18nPath)};
4910
4910
  export { Link, linkProps } from ${JSON.stringify(routerLinkPath)};
4911
4911
  export { parseMultipartStream } from ${JSON.stringify(routerMultipartPath)};
4912
- export { cookies, headers, html, json, next, notFound, redirect, redirectExternal, rewrite } from ${JSON.stringify(routerNavigationPath)};
4912
+ export { cookies, headers, html, json, next, notFound, redirect, redirectExternal, rewrite, throwNotFound } from ${JSON.stringify(routerNavigationPath)};
4913
4913
  export { getServerRuntimeState } from ${JSON.stringify(routerRuntimeStatePath)};`,
4914
4914
  loader: "js",
4915
4915
  resolveDir: dirname(routerNavigationPath),
package/src/client.ts CHANGED
@@ -3512,6 +3512,22 @@ function __mreactInstallNavigation() {
3512
3512
  }
3513
3513
  });
3514
3514
  __mreactObserveViewportPrefetchAnchors(document);
3515
+ document.addEventListener("submit", (event) => {
3516
+ if (event.defaultPrevented || typeof fetch !== "function") {
3517
+ return;
3518
+ }
3519
+
3520
+ const submission = __mreactServerActionSubmissionFromEvent(event);
3521
+
3522
+ if (submission === null) {
3523
+ return;
3524
+ }
3525
+
3526
+ event.preventDefault();
3527
+ void __mreactSubmitServerActionForm(submission).catch(() => {
3528
+ location.href = submission.action;
3529
+ });
3530
+ });
3515
3531
  document.addEventListener("click", (event) => {
3516
3532
  if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
3517
3533
  return;
@@ -3562,6 +3578,100 @@ function __mreactInstallNavigation() {
3562
3578
  });
3563
3579
  }
3564
3580
 
3581
+ function __mreactServerActionSubmissionFromEvent(event) {
3582
+ const target = event.target;
3583
+ const form = target instanceof Element ? target.closest("form") : null;
3584
+ const submitter = __mreactFormSubmitterFromEvent(event);
3585
+
3586
+ if (!(form instanceof HTMLFormElement)) {
3587
+ return null;
3588
+ }
3589
+
3590
+ const action = submitter?.getAttribute("formaction") ?? form.action;
3591
+ const method = submitter?.getAttribute("formmethod") ?? form.method;
3592
+ const targetName = submitter?.getAttribute("formtarget") ?? form.target;
3593
+ const actionUrl = new URL(action, location.href);
3594
+
3595
+ if (
3596
+ targetName !== "" ||
3597
+ actionUrl.origin !== location.origin ||
3598
+ actionUrl.pathname !== "/_mreact/actions" ||
3599
+ method.toUpperCase() !== "POST"
3600
+ ) {
3601
+ return null;
3602
+ }
3603
+
3604
+ return { action: actionUrl.href, form, submitter };
3605
+ }
3606
+
3607
+ function __mreactFormSubmitterFromEvent(event) {
3608
+ const submitter = event.submitter;
3609
+
3610
+ return submitter instanceof HTMLElement ? submitter : null;
3611
+ }
3612
+
3613
+ async function __mreactSubmitServerActionForm(submission) {
3614
+ const response = await fetch(submission.action, {
3615
+ body: __mreactServerActionFormData(submission.form, submission.submitter),
3616
+ headers: { "x-mreact-action-single-flight": "1" },
3617
+ method: "POST",
3618
+ });
3619
+ __mreactApplyRevalidationHeader(response);
3620
+
3621
+ const contentType = response.headers.get("content-type") ?? "";
3622
+ const singleFlight = response.headers.get("x-mreact-action-single-flight") === "1";
3623
+ if (
3624
+ !singleFlight &&
3625
+ !contentType.includes("text/html")
3626
+ ) {
3627
+ location.href = response.url || submission.action;
3628
+ return;
3629
+ }
3630
+
3631
+ const html = await response.text();
3632
+ const applied = __mreactApplyServerActionHtml(html, singleFlight);
3633
+
3634
+ if (!applied) {
3635
+ location.href = response.url || submission.action;
3636
+ }
3637
+ }
3638
+
3639
+ function __mreactServerActionFormData(form, submitter) {
3640
+ if (submitter !== null) {
3641
+ try {
3642
+ return new FormData(form, submitter);
3643
+ } catch {
3644
+ // Older DOM implementations only accept the form argument.
3645
+ }
3646
+ }
3647
+
3648
+ return new FormData(form);
3649
+ }
3650
+
3651
+ function __mreactApplyServerActionHtml(html, singleFlight) {
3652
+ __mreactSaveCurrentHistoryState();
3653
+ const applied = __mreactApplyNavigationHtml(html, location.href);
3654
+
3655
+ if (!applied) {
3656
+ if (
3657
+ !singleFlight ||
3658
+ typeof document === "undefined" ||
3659
+ typeof document.open !== "function"
3660
+ ) {
3661
+ return false;
3662
+ }
3663
+
3664
+ document.open();
3665
+ document.write(html);
3666
+ document.close();
3667
+ return true;
3668
+ }
3669
+
3670
+ __mreactResetNavigationFocus();
3671
+ __mreactAnnounceNavigation();
3672
+ return true;
3673
+ }
3674
+
3565
3675
  function __mreactInstallNavigationFetchRevalidation() {
3566
3676
  if (
3567
3677
  __mreactNavigationState.fetchRevalidationInstalled ||
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { deleteCookie, parseCookieHeader, serializeCookie, setCookie } from "./c
5
5
  export { defineMessages, detectLocale } from "./i18n.js";
6
6
  export { defer, isDeferredLoaderData } from "./deferred.js";
7
7
  export type { DeferredLoaderData } from "./deferred.js";
8
+ export { definePage } from "./types.js";
8
9
  export { Link, linkProps } from "./link.js";
9
10
  export { href } from "./typed-routes.js";
10
11
  export { parseMultipartStream } from "./multipart.js";
@@ -32,6 +33,7 @@ export {
32
33
  redirectExternal,
33
34
  rewrite,
34
35
  textError,
36
+ throwNotFound,
35
37
  } from "./navigation.js";
36
38
  export type { ParseSchema } from "./navigation.js";
37
39
  export { createMemoryPrerenderStore } from "./prerender-store.js";
@@ -93,6 +95,7 @@ export type {
93
95
  } from "./typed-routes.js";
94
96
  export type {
95
97
  InferLoaderData,
98
+ InferLoaderParams,
96
99
  LayoutProps,
97
100
  LoaderContext,
98
101
  GenerateMetadataContext,
@@ -104,11 +107,13 @@ export type {
104
107
  MetadataViewport,
105
108
  MReactNode,
106
109
  PageProps,
110
+ PageComponent,
107
111
  RobotsContext,
108
112
  RobotsManifest,
109
113
  RobotsRule,
110
114
  RouteHeadDescriptor,
111
115
  RouteHandlerContext,
116
+ RouteLoader,
112
117
  RouteMetadata,
113
118
  RouteParams,
114
119
  RouteSecurityHeaders,
package/src/navigation.ts CHANGED
@@ -133,6 +133,10 @@ export function notFound(): never {
133
133
  });
134
134
  }
135
135
 
136
+ export function throwNotFound(): never {
137
+ return notFound();
138
+ }
139
+
136
140
  export function next(): MiddlewareNext {
137
141
  return undefined;
138
142
  }
package/src/render.ts CHANGED
@@ -812,6 +812,16 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
812
812
  appDir: options.appDir,
813
813
  importPolicy: options.importPolicy,
814
814
  request: options.request,
815
+ renderSingleFlightNavigation: async (singleFlight) =>
816
+ renderAppRequestInternal({
817
+ ...options,
818
+ matchedRoute: undefined,
819
+ request: singleFlightNavigationRequest({
820
+ path: singleFlight.path,
821
+ request: singleFlight.request,
822
+ }),
823
+ requestUrl: undefined,
824
+ }),
815
825
  routeCache: options.routeCache,
816
826
  ...(options.serverModuleCacheVersion === undefined
817
827
  ? {}
@@ -1672,6 +1682,25 @@ function isNavigationRouteCacheReloadRequest(request: Request): boolean {
1672
1682
  );
1673
1683
  }
1674
1684
 
1685
+ function singleFlightNavigationRequest(options: { path: string; request: Request }): Request {
1686
+ const actionUrl = new URL(options.request.url);
1687
+ const targetUrl = new URL(options.path, actionUrl);
1688
+ const headers = new Headers();
1689
+ const cookie = options.request.headers.get("cookie");
1690
+
1691
+ if (cookie !== null) {
1692
+ headers.set("cookie", cookie);
1693
+ }
1694
+
1695
+ headers.set("x-mreact-navigation", "1");
1696
+ headers.set("x-mreact-navigation-cache", "reload");
1697
+
1698
+ return new Request(targetUrl, {
1699
+ headers,
1700
+ method: "GET",
1701
+ });
1702
+ }
1703
+
1675
1704
  async function nearestBoundaryFileForPage(options: {
1676
1705
  appDir: string;
1677
1706
  filename: string;
package/src/types.ts CHANGED
@@ -1,12 +1,23 @@
1
1
  import type { ReactCompatNode } from "@reckona/mreact-compat";
2
2
  import type { QueryClient } from "@reckona/mreact-query";
3
3
 
4
- export type InferLoaderData<TLoader extends (...args: never[]) => unknown> = Awaited<
5
- ReturnType<TLoader>
6
- >;
4
+ export type RouteLoader = (...args: never[]) => unknown;
5
+
6
+ export type InferLoaderData<TLoader extends RouteLoader> = Awaited<ReturnType<TLoader>>;
7
7
 
8
8
  export type RouteParams = Record<string, readonly string[] | string>;
9
9
 
10
+ export type InferLoaderParams<TLoader extends RouteLoader> = TLoader extends (
11
+ context: infer TContext,
12
+ ...args: never[]
13
+ ) => unknown
14
+ ? TContext extends { params: infer TParams }
15
+ ? TParams extends RouteParams
16
+ ? TParams
17
+ : RouteParams
18
+ : RouteParams
19
+ : RouteParams;
20
+
10
21
  export interface LoaderContext<TParams extends RouteParams = RouteParams> {
11
22
  env?: unknown;
12
23
  params: TParams;
@@ -34,6 +45,16 @@ export interface PageProps<TData = unknown, TParams extends RouteParams = RouteP
34
45
  request: Request;
35
46
  }
36
47
 
48
+ export type PageComponent<TLoader extends RouteLoader> = (
49
+ props: PageProps<InferLoaderData<TLoader>, InferLoaderParams<TLoader>>,
50
+ ) => ReactCompatNode;
51
+
52
+ export function definePage<TLoader extends RouteLoader>(
53
+ component: PageComponent<TLoader>,
54
+ ): PageComponent<TLoader> {
55
+ return component;
56
+ }
57
+
37
58
  export interface LayoutProps<TParams extends RouteParams = RouteParams> {
38
59
  children: ReactCompatNode;
39
60
  params: TParams;