feat: add removable reference feature vertical slice
This commit is contained in:
@@ -21,8 +21,8 @@ import {
|
||||
import {
|
||||
getRoute,
|
||||
ROUTE_REGISTRY,
|
||||
type RouteDefinition,
|
||||
} from "../../contracts/routes.js";
|
||||
} from "../../features/installed-feature-contracts.js";
|
||||
import type { RouteDefinition } from "../../contracts/routes.js";
|
||||
import {
|
||||
FeatureBoundary,
|
||||
RouteBoundary,
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
type ParsedRouteInput,
|
||||
type RouteId,
|
||||
} from "./route-codecs.js";
|
||||
import { ROUTE_RUNTIME } from "./route-runtime.js";
|
||||
import { ROUTE_RUNTIME } from "../../features/installed-feature-runtimes.js";
|
||||
|
||||
const RouteInputContext = createContext<ParsedRouteInput | null>(null);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getRoute } from "../../contracts/routes.js";
|
||||
import { getRoute } from "../../features/installed-feature-contracts.js";
|
||||
|
||||
/**
|
||||
* @param {string} routeId
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const PLATFORM_ROUTE_CODECS = {
|
||||
none: z.object({}).strict(),
|
||||
NotFoundSplat: z.object({ "*": z.string().optional() }).strict(),
|
||||
} as const;
|
||||
@@ -1,36 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { getRoute } from "../../contracts/routes.js";
|
||||
import { ROUTE_RUNTIME_CONTRACT } from "../../contracts/route-runtime-contract.js";
|
||||
import { getRoute, ROUTE_RUNTIME_CONTRACT } from "../../features/installed-feature-contracts.js";
|
||||
import { ROUTE_CODECS } from "../../features/installed-feature-runtimes.js";
|
||||
|
||||
export type RouteId = keyof typeof ROUTE_RUNTIME_CONTRACT;
|
||||
|
||||
const emptyCodec = z.object({}).strict();
|
||||
const notFoundSplatCodec = z.object({ "*": z.string().optional() }).strict();
|
||||
const sampleResourceListQuery = z
|
||||
.object({
|
||||
cursor: z.string().min(1).optional(),
|
||||
limit: z.coerce.number().int().min(1).max(100).default(20),
|
||||
tags: z
|
||||
.preprocess(
|
||||
(value) =>
|
||||
value === undefined
|
||||
? undefined
|
||||
: Array.isArray(value)
|
||||
? value
|
||||
: [value],
|
||||
z.array(z.string().trim().min(1)),
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const codecs = {
|
||||
none: emptyCodec,
|
||||
NotFoundSplat: notFoundSplatCodec,
|
||||
SampleResourceListQuery: sampleResourceListQuery,
|
||||
} as const;
|
||||
|
||||
export type ParsedRouteInput = Readonly<{
|
||||
routeId: RouteId;
|
||||
params: Readonly<Record<string, unknown>>;
|
||||
@@ -44,17 +16,23 @@ export type RouteInputResult =
|
||||
code: "ROUTE_PARAMS_INVALID" | "ROUTE_SEARCH_INVALID";
|
||||
}>;
|
||||
|
||||
function codecById(codecId: string) {
|
||||
const codec = ROUTE_CODECS[codecId as keyof typeof ROUTE_CODECS];
|
||||
if (!codec) throw new TypeError(`Unregistered route codec: ${codecId}`);
|
||||
return codec;
|
||||
}
|
||||
|
||||
export function parseRouteInput(
|
||||
routeId: RouteId,
|
||||
rawParams: Readonly<Record<string, string | undefined>>,
|
||||
rawSearch: URLSearchParams,
|
||||
): RouteInputResult {
|
||||
const runtime = ROUTE_RUNTIME_CONTRACT[routeId];
|
||||
const params = codecs[runtime.paramsCodec].safeParse(rawParams);
|
||||
const params = codecById(runtime.paramsCodec).safeParse(rawParams);
|
||||
if (!params.success) {
|
||||
return { success: false, code: "ROUTE_PARAMS_INVALID" };
|
||||
}
|
||||
const search = codecs[runtime.searchCodec].safeParse(
|
||||
const search = codecById(runtime.searchCodec).safeParse(
|
||||
searchRecord(rawSearch),
|
||||
);
|
||||
if (!search.success) {
|
||||
@@ -84,14 +62,18 @@ export function buildRouteUrl(
|
||||
throw new TypeError("The not-found route cannot build a canonical URL");
|
||||
}
|
||||
const runtime = ROUTE_RUNTIME_CONTRACT[routeId];
|
||||
const params = codecs[runtime.paramsCodec].parse(input.params ?? {});
|
||||
const search = codecs[runtime.searchCodec].parse(input.search ?? {});
|
||||
const params = codecById(runtime.paramsCodec).parse(input.params ?? {});
|
||||
const search = codecById(runtime.searchCodec).parse(input.search ?? {});
|
||||
const parsedParams: Record<string, unknown> = { ...params };
|
||||
const parsedSearch: Record<string, unknown> = { ...search };
|
||||
let path = definition.path;
|
||||
path = path.replace(
|
||||
/:([A-Za-z][A-Za-z0-9_]*)|\{([A-Za-z][A-Za-z0-9_]*)\}/g,
|
||||
(_token, colonName: string | undefined, braceName: string | undefined) => {
|
||||
(
|
||||
_token: string,
|
||||
colonName: string | undefined,
|
||||
braceName: string | undefined,
|
||||
) => {
|
||||
const name = colonName ?? braceName ?? "";
|
||||
const value = parsedParams[name];
|
||||
if (typeof value !== "string" && typeof value !== "number") {
|
||||
|
||||
@@ -4,8 +4,7 @@ import {
|
||||
type LazyExoticComponent,
|
||||
} from "react";
|
||||
|
||||
import { ROUTE_RUNTIME_CONTRACT } from "../../contracts/route-runtime-contract.js";
|
||||
import type { RouteId } from "./route-codecs.js";
|
||||
import { PLATFORM_ROUTE_RUNTIME_CONTRACT } from "../../contracts/route-runtime-contract.js";
|
||||
|
||||
type RouteModule = Readonly<{ default: ComponentType }>;
|
||||
type RouteRuntime = Readonly<{
|
||||
@@ -14,16 +13,16 @@ type RouteRuntime = Readonly<{
|
||||
}>;
|
||||
|
||||
function runtime(
|
||||
routeId: RouteId,
|
||||
routeId: keyof typeof PLATFORM_ROUTE_RUNTIME_CONTRACT,
|
||||
load: () => Promise<RouteModule>,
|
||||
): RouteRuntime {
|
||||
return Object.freeze({
|
||||
moduleId: ROUTE_RUNTIME_CONTRACT[routeId].moduleId,
|
||||
moduleId: PLATFORM_ROUTE_RUNTIME_CONTRACT[routeId].moduleId,
|
||||
Component: lazy(load),
|
||||
});
|
||||
}
|
||||
|
||||
export const ROUTE_RUNTIME = {
|
||||
export const PLATFORM_ROUTE_RUNTIME = {
|
||||
APP_HOME: runtime("APP_HOME", () => import("../pages/home-page.jsx")),
|
||||
EXAMPLES_UI: runtime(
|
||||
"EXAMPLES_UI",
|
||||
@@ -37,12 +36,8 @@ export const ROUTE_RUNTIME = {
|
||||
"EXAMPLES_AUTH",
|
||||
() => import("../examples/auth-example-page.jsx"),
|
||||
),
|
||||
SAMPLE_RESOURCE_LIST: runtime(
|
||||
"SAMPLE_RESOURCE_LIST",
|
||||
() => import("../pages/sample-contract-page.jsx"),
|
||||
),
|
||||
NOT_FOUND: runtime(
|
||||
"NOT_FOUND",
|
||||
() => import("../pages/not-found-page.jsx"),
|
||||
),
|
||||
} satisfies Record<RouteId, RouteRuntime>;
|
||||
} satisfies Record<keyof typeof PLATFORM_ROUTE_RUNTIME_CONTRACT, RouteRuntime>;
|
||||
|
||||
Reference in New Issue
Block a user