.ts
TypeScript
(application/typescript)
import { AppRoute, AppRoutePaths, AppRouteParams } from "../../routes.defs";

interface BuildLinkOptions {
  // @default true
  encodeURIComponent?: boolean;
}

export default function buildRouteLink<P extends AppRoute>(
  route: P,
  routeParams: "params" extends keyof AppRouteParams[P]
    ? AppRouteParams[P]["params"]
    : {} | null,
  options?: BuildLinkOptions
): typeof AppRoutePaths[P] {
  const path = AppRoutePaths[route];
  return buildPathLink(path, routeParams, options);
}

export function buildPathLink<P extends AppRoute>(
  path: string,
  routeParams: "params" extends keyof AppRouteParams[P]
    ? AppRouteParams[P]["params"]
    : {} | null,
  options?: BuildLinkOptions
): string {
  if (
    routeParams == null ||
    (routeParams != null &&
      typeof routeParams === "object" &&
      Object.keys(routeParams as any).length <= 0)
  ) {
    return path;
  }

  const paramsEntries = Object.entries(routeParams as never);

  let pathParts = path.split("/");
  let linkBuilder = [] as string[];

  pathParts.forEach((part) => {
    if (part.trim() === "") {
      linkBuilder.push("");
    } else if (
      "*" in routeParams &&
      (routeParams as any)["*"] != null &&
      part === "*"
    ) {
      linkBuilder.push((routeParams as any)["*"]);
    } else if (part.includes(":")) {
      paramsEntries.forEach(([k, v]) => {
        if (k == null || k.trim() === "" || k === "*") return;
        const keyRegExp = new RegExp(`:${k}`, "g");
        if (Array.isArray(part.match(keyRegExp))) {
          linkBuilder.push(part.replace(keyRegExp, `${String(v)}`));
        }
      });
    } else {
      linkBuilder.push(part);
    }
  });

  const shouldEncodeURIComponents = !!(
    options == null ||
    options.encodeURIComponent == null ||
    options.encodeURIComponent !== false
  );

  return linkBuilder
    .map((param) =>
      shouldEncodeURIComponents ? encodeURIComponent(param) : param
    )
    .join("/");
}