fix: inject grouped route codecs

This commit is contained in:
DongHyeonka
2026-08-15 20:51:22 +09:00
parent adb8613cb9
commit 01316763e3
5 changed files with 149 additions and 37 deletions
+8 -8
View File
@@ -42,7 +42,6 @@ import {
buildRouteUrlFromDefinition,
parseRouteInputFromContract,
type RouteCodecRegistry,
type RouteId,
} from "./route-codecs.ts";
import {
ROUTE_CODECS,
@@ -142,12 +141,12 @@ function RouteLifecycle({
return null;
}
function CanonicalRouteRedirect({
function CanonicalRouteRedirect<RouteIdValue extends string>({
input,
definition,
codecs,
}: {
input: ParsedRouteInput;
input: ParsedRouteInput<RouteIdValue>;
definition: RouteDefinition;
codecs: RouteCodecRegistry;
}) {
@@ -251,14 +250,14 @@ function ProtectedRoute({
);
}
function RegisteredRoute({
function RegisteredRoute<RouteIdValue extends string>({
routeId,
buildId,
definition,
runtime,
codecs,
}: {
routeId: RouteId;
routeId: RouteIdValue;
buildId: string;
definition: RouteDefinition;
runtime: GroupedRouteRuntimeDefinition;
@@ -276,7 +275,7 @@ function RegisteredRoute({
search,
);
if (!parsed.success) return <InvalidRouteSurface code={parsed.code} />;
const routeInput = parsed.data as ParsedRouteInput;
const routeInput = parsed.data;
const RuntimeComponent = runtime.Component;
const content = (
@@ -334,7 +333,7 @@ export function createGroupedRouteObjects(
runtime: GroupedRouteRuntime,
layouts: GroupedRouteLayouts,
buildId: string,
codecs: RouteCodecRegistry = ROUTE_CODECS,
codecs: RouteCodecRegistry,
): RouteObject[] {
for (const routeId of Object.keys(runtime)) {
if (!registry[routeId]) {
@@ -347,7 +346,7 @@ export function createGroupedRouteObjects(
STUDIO: [],
};
for (const definition of Object.values(registry)) {
const routeId = definition.routeId as RouteId;
const routeId = definition.routeId;
const routeRuntime = runtime[definition.routeId];
if (!routeRuntime) {
throw new TypeError(`Missing route runtime: ${definition.routeId}`);
@@ -411,6 +410,7 @@ export function AppRouter({
STUDIO: <Outlet />,
},
buildId,
ROUTE_CODECS,
),
{ basename },
),
+7 -17
View File
@@ -24,19 +24,9 @@ type RouteCodec = Readonly<{
}>;
export type RouteCodecRegistry = Readonly<Record<string, RouteCodec>>;
export type ContractRouteInputResult =
| Readonly<{
success: true;
data: Readonly<{
routeId: string;
params: Readonly<Record<string, unknown>>;
search: Readonly<Record<string, unknown>>;
}>;
}>
| Readonly<{
success: false;
code: "ROUTE_PARAMS_INVALID" | "ROUTE_SEARCH_INVALID";
}>;
export type ContractRouteInputResult<
RouteIdValue extends string = string,
> = RouteInputResult<RouteIdValue>;
function codecById(codecId: string, codecs: RouteCodecRegistry = ROUTE_CODECS) {
const codec = codecs[codecId];
@@ -55,16 +45,16 @@ export function parseRouteInput(
ROUTE_CODECS,
rawParams,
rawSearch,
) as RouteInputResult;
);
}
export function parseRouteInputFromContract(
routeId: string,
export function parseRouteInputFromContract<RouteIdValue extends string>(
routeId: RouteIdValue,
definition: Pick<RouteDefinition, "paramsSchema" | "searchSchema">,
codecs: RouteCodecRegistry,
rawParams: Readonly<Record<string, string | undefined>>,
rawSearch: URLSearchParams,
): ContractRouteInputResult {
): ContractRouteInputResult<RouteIdValue> {
const params = codecById(
definition.paramsSchema ?? "none",
codecs,
+4 -4
View File
@@ -2,14 +2,14 @@ import { ROUTE_RUNTIME_CONTRACT } from "../../features/installed-feature-contrac
export type RouteId = keyof typeof ROUTE_RUNTIME_CONTRACT;
export type ParsedRouteInput = Readonly<{
routeId: RouteId;
export type ParsedRouteInput<RouteIdValue extends string = RouteId> = Readonly<{
routeId: RouteIdValue;
params: Readonly<Record<string, unknown>>;
search: Readonly<Record<string, unknown>>;
}>;
export type RouteInputResult =
| Readonly<{ success: true; data: ParsedRouteInput }>
export type RouteInputResult<RouteIdValue extends string = RouteId> =
| Readonly<{ success: true; data: ParsedRouteInput<RouteIdValue> }>
| Readonly<{
success: false;
code: "ROUTE_PARAMS_INVALID" | "ROUTE_SEARCH_INVALID";
+8 -6
View File
@@ -4,15 +4,15 @@ import {
useContext,
} from "react";
import type { ParsedRouteInput } from "./route-contract.ts";
import type { ParsedRouteInput, RouteId } from "./route-contract.ts";
const RouteInputContext = createContext<ParsedRouteInput | null>(null);
const RouteInputContext = createContext<ParsedRouteInput<string> | null>(null);
export function RouteInputProvider({
export function RouteInputProvider<RouteIdValue extends string>({
input,
children,
}: Readonly<{
input: ParsedRouteInput;
input: ParsedRouteInput<RouteIdValue>;
children: ReactNode;
}>) {
return (
@@ -22,8 +22,10 @@ export function RouteInputProvider({
);
}
export function useRouteInput(): ParsedRouteInput {
export function useRouteInput<
RouteIdValue extends string = RouteId,
>(): ParsedRouteInput<RouteIdValue> {
const input = useContext(RouteInputContext);
if (!input) throw new Error("Registered route input is required");
return input;
return input as ParsedRouteInput<RouteIdValue>;
}