{"version":3,"file":"Routeways.cjs","sources":["../../src/lib/Routeways.ts"],"sourcesContent":["import { UrlParserError } from \"./errors/UrlParserError\";\nimport {\n  type CodecMap,\n  type CodecsToRecord,\n  type PathLike,\n  type RouteParams,\n  safeKeys,\n} from \"./helpers/common\";\n\nimport type { Codec } from \"./Codecs\";\n\n/**\n * Recursively creates a union of string literals from a {@link PathLike}\n * string xtracting the path variables sections. I.e. anything that starts with\n * `:` on the path.\n *\n * @param P the {@link PathLike} string\n */\ntype PathVarsCapture<P extends PathLike> =\n  P extends `${string}/:${infer P1}/${string}:${infer P2}`\n    ? P1 | PathVarsCapture<`/:${P2}`>\n    : P extends `${string}/:${infer P3}/${string}`\n      ? P3\n      : P extends `${string}/:${infer P4}`\n        ? P4\n        : never;\n\n/**\n * Conditional constraint for path variables based on the result of capturing\n * the literals on the {@link PathLike} string.\n *\n * @param P the {@link PathLike} string\n */\ntype PathVars<P extends PathLike> =\n  PathVarsCapture<P> extends never\n    ? Record<never, never>\n    : Record<PathVarsCapture<P>, Codec<unknown>>;\n\n/**\n * Conditionally create the `makeUrl` function based on the codec map of path\n * variables and query parameters.\n *\n * @param V the path vars codec record\n * @param Q the query param codec record\n */\ntype MakeUrl<\n  V extends CodecMap,\n  Q extends CodecMap,\n> = keyof V extends never\n      ? {\n          /**\n           * Creates a raw string URL for the route using the provided\n           * parameters.\n           *\n           * @param params the parameters used to build the route\n           * @returns the built URL of the route\n           */\n          makeUrl(params?: RouteParams<V, Q>): string;\n        }\n      : {\n          /**\n           * Creates a raw string URL for the route using the provided\n           * parameters.\n           *\n           * @param params the parameters used to build the route\n           * @returns the built URL of the route\n           */\n          makeUrl(params: RouteParams<V, Q>): string;\n        };\n\n/**\n * A `Routeway` route instance. It may contain more keys with other subroutes.\n *\n * @param P the {@link PathLike} string\n * @param V the path vars codec record\n * @param Q the query param codec record\n * @param S the record of `Routeway` subroutes\n */\nexport type Routeway<\n  P extends PathLike = PathLike,\n  V extends CodecMap = Record<never, Codec<unknown>>,\n  Q extends CodecMap = Record<never, Codec<unknown>>,\n  S extends Record<string, Routeway> = Record<never, never>,\n> = MakeUrl<V, Q> & {\n  /**\n   * Convenience method that returns the configuration of the route.\n   *\n   * @returns an object with all the configuration of the route\n   */\n  $config(): {\n    /**\n     * A record of the path variables configuration. The key refers to the name\n     * of the path variable and the value is the specific codec for the\n     * variable.\n     */\n    pathVars: V;\n    /**\n     * A record of the query parameters configuration. The key refers to the\n     * name of the query parameters and the value is the specific codec for the\n     * parameter.\n     */\n    queryParams: Q;\n    /**\n     * The template of this route segment. Differently from the `.template()`\n     * method, this property does not contain the template of the full path,\n     * but only of the specific route.\n     */\n    segment: P;\n    /**\n     * A record of the nested `Routeway` instances of the route (if any).\n     */\n    subRoutes: S;\n  };\n  /**\n   * Parse a raw URL to get the path variables and query parameters from it.\n   *\n   * @param uri the raw URL to parse the params from\n   * @returns an object with the parsed path variables and query parameters\n   */\n  parseUrl(uri: string): {\n    pathVars: CodecsToRecord<V>;\n    queryParams: Partial<CodecsToRecord<Q>>;\n  };\n  /**\n   * Creates the complete template of the route. Useful when working with other\n   * routing libraries that need the context of the path with its variables.\n   *\n   * @returns the route template\n   */\n  template(): string;\n} & S;\n\ntype DefinedSubRoutes<B extends RoutewaysBuilder<Record<string, Routeway>>> =\n  B extends RoutewaysBuilder<infer M>\n    ? M extends Record<string, Routeway>\n      ? { [K in keyof M]: GetDefinedRoute<M[K]> }\n      : never\n    : never;\n\ntype GetDefinedRoute<S extends Routeway> =\n  S extends Routeway<infer P, infer V, infer Q, infer SR>\n    ? Routeway<P, V, Q, { [K in keyof SR]: GetDefinedRoute<SR[K]> }>\n    : never;\n\ntype ResultSubRoutes<B extends RoutewaysBuilder<Record<string, Routeway>>, V extends Record<string, unknown>> =\n  B extends RoutewaysBuilder<infer M>\n    ? M extends Record<string, Routeway>\n      ? { [K in keyof M]: GetResultRoute<M[K], V>; }\n      : never\n    : never;\n\ntype GetResultRoute<S extends Routeway, V1 extends Record<string, unknown>> =\n  S extends Routeway<infer G, infer V2, infer Q, infer SR>\n    ? Routeway<\n        G,\n        { [K in keyof (V1 & V2)]: (V1 & V2)[K]; },\n        Q,\n        { [K in keyof SR]: GetResultRoute<SR[K], { [K2 in keyof (V1 & V2)]: (V1 & V2)[K2] }>; }\n      >\n    : never;\n\n/**\n * Conditionally create a route configuratiion based on the `path` property\n * string. If the path contains path variables, the `pathVars` property is\n * required and it must be defined with path variables names as its keys.\n *\n * @param N the name of the route\n * @param P the {@link PathLike} string\n * @param V the path vars codec record\n * @param Q the query param codec record\n */\nexport type PathConfig<\n  N extends string,\n  P extends PathLike,\n  V extends PathVars<P>,\n  Q extends CodecMap,\n> = PathVarsCapture<P> extends never\n      ? { name: N; path: P; queryParams?: Q; }\n      : { name: N; path: P; pathVars: V; queryParams?: Q; };\n\n/**\n * Conditionally create a route configuratiion based on the `path` property\n * string. If the path contains path variables, the `pathVars` property is\n * required and it must be defined with path variables names as its keys.\n * Aditionally, this configuration requires a `subRoutes` property.\n *\n * @param N the name of the route\n * @param P the {@link PathLike} string\n * @param V the path vars codec record\n * @param Q the query param codec record\n * @param S the `RoutewaysBuilder` for the subroutes\n */\nexport type NestConfig<\n  N extends string,\n  P extends PathLike,\n  V extends PathVars<P>,\n  Q extends CodecMap,\n  S extends RoutewaysBuilder<Record<string, Routeway>>,\n> = PathVarsCapture<P> extends never\n      ? { name: N; path: P; queryParams?: Q; subRoutes: S; }\n      : { name: N; path: P; pathVars: V; queryParams?: Q; subRoutes: S; };\n\n/**\n * The Routeways builder API.\n */\nexport class RoutewaysBuilder<M extends Record<string, Routeway>> {\n  private readonly routes: M;\n\n  public constructor(routes: M) {\n    this.routes = routes;\n\n    this.path = this.path.bind(this);\n    this.nest = this.nest.bind(this);\n    this.build = this.build.bind(this);\n  }\n\n  /**\n   * Create a single path on the route under construction. Single paths do not\n   * allow nesting and can be considered the latest point of a branch in the\n   * router.\n   *\n   * If you need to nest routes use {@link RoutewaysBuilder.nest() .nest(..)}\n   * instead.\n   *\n   * @param config a configuration object for the route\n   * @returns the Routeways instance to continue building\n   */\n  public path<\n    N extends string,\n    P extends PathLike,\n    V extends Record<PathVarsCapture<P>, Codec<unknown>>,\n    Q extends CodecMap,\n  >(\n    config: PathConfig<N, P, V, Q>,\n  ): RoutewaysBuilder<{ [K in keyof M]: M[K]; } & { [K in N]: Routeway<P, V, Q>; }> {\n    const { name, path, pathVars, queryParams = { } as Q } = \"pathVars\" in config\n      ? config\n      : { ...config, pathVars: { } as V };\n\n    return new RoutewaysBuilder({\n      ...this.routes,\n      [name]: {\n        $config: () => ({\n          pathVars,\n          queryParams,\n          segment: path,\n          subRoutes: { },\n        }),\n        makeUrl: () => path,\n        parseUrl: () => ({\n          pathVars: { } as CodecsToRecord<V>,\n          queryParams: { } as Partial<CodecsToRecord<Q>>,\n        }),\n        template: () => path,\n      },\n    });\n  }\n\n  /**\n   * Create a path on the route under construction that allows creating nested\n   * routes under it. The `subRoutes` are required in this method, if you need\n   * to create a terminal route, use {@link RoutewaysBuilder.path() .path(..)}\n   * instead.\n   *\n   * @param config a configuration object for the route\n   * @returns the Routeways instance to continue building\n   */\n  public nest<\n    N extends string,\n    P extends PathLike,\n    V extends PathVars<P>,\n    Q extends CodecMap,\n    S extends RoutewaysBuilder<DefinedSubRoutes<S>>,\n  >(\n    config: NestConfig<N, P, V, Q, S>,\n  ): RoutewaysBuilder<{ [K in keyof M]: M[K] } & { [K in N]: Routeway<P, V, Q, ResultSubRoutes<S, V>> }> {\n    const { name, path, pathVars, queryParams = { } as Q, subRoutes } = \"pathVars\" in config\n      ? config\n      : { ...config, pathVars: { } as V };\n    const subRouteRecord = subRoutes.routes as ResultSubRoutes<S, V>;\n\n    const newRoute: Routeway<P, V, Q, ResultSubRoutes<S, V>> = {\n      $config: () => ({\n        pathVars,\n        queryParams,\n        segment: path,\n        subRoutes: subRouteRecord,\n      }),\n      makeUrl: () => \"\",\n      parseUrl: () => ({\n        pathVars: { } as CodecsToRecord<V>,\n        queryParams: { } as Partial<CodecsToRecord<Q>>,\n      }),\n      template: () => path,\n      ...subRouteRecord,\n    };\n\n    return new RoutewaysBuilder({\n      ...this.routes,\n      [name]: newRoute,\n    });\n  }\n\n  /**\n   * Builds the routes defined by the API and returns a `Routeways` instance\n   * shaped by the names of the paths.\n   *\n   * @returns the built `Routeways` instance\n   */\n  public build(): M {\n    return safeKeys(this.routes).reduce((acc, key) => {\n      const route = this.routes[key];\n\n      if (route !== undefined) {\n        return {\n          ...acc,\n          [key]: injectParentData(route),\n        };\n      }\n\n      return acc;\n    }, { } as M);\n  }\n}\n\nfunction injectParentData<\n  R extends Routeway<PathLike, V, Q, S>,\n  S extends Record<string, Routeway> = Record<never, never>,\n  V extends CodecMap = Record<never, Codec<unknown>>,\n  Q extends CodecMap = Record<never, Codec<unknown>>,\n>(\n  route: R,\n  path = \"\",\n  pathVars: V = { } as V,\n): R {\n  const routeConfig = route.$config();\n  const fullPath = `${path}${route.template()}`;\n  const allPathVars = { ...pathVars, ...routeConfig.pathVars };\n\n  return safeKeys(route)\n    .reduce((acc, routeName) => {\n      const subRoute = route[routeName];\n\n      return {\n        ...acc,\n        $config: () => ({\n          ...routeConfig,\n          pathVars: allPathVars,\n        }),\n        makeUrl: (params?: RouteParams<V, Q>): string => {\n          if (params === undefined) {\n            return fullPath;\n          }\n\n          const queryKeys = safeKeys(routeConfig.queryParams).filter(key => safeKeys(params).includes(key));\n          const queryParams = queryKeys.reduce<string>((search, key) => {\n            const codec = routeConfig.queryParams[key];\n            const paramValue = params[key];\n\n            if (codec !== undefined && paramValue !== undefined) {\n              const encodedValue = codec.encode(paramValue, key);\n              const joinChar: string = search === \"?\" ? \"\" : \"&\";\n\n              return encodedValue.includes(`${key}=`)\n                ? `${search}${joinChar}${encodeURI(encodedValue)}`\n                : `${search}${joinChar}${key}=${encodeURIComponent(encodedValue)}`;\n            }\n\n            return search;\n          }, \"?\");\n          const baseUrl = safeKeys(allPathVars)\n            .reduce((url, key) => {\n              const paramValue = params[key];\n              const codec = allPathVars[key];\n\n              return codec !== undefined\n                ? url.replaceAll(`:${String(key)}`, codec.encode(paramValue))\n                : url;\n            }, fullPath);\n\n          return `${baseUrl}${queryKeys.length > 0 ? queryParams : \"\"}`;\n        },\n        parseUrl: uri => {\n          const url = new URL(uri.startsWith(\"http\") ? uri : `http://localhost${uri}`);\n          const pathnameChunks = url.pathname.split(\"/\");\n          const templateChunks = fullPath.split(\"/\");\n          const allChuncksMatch = templateChunks.every((chunck, i) =>\n            pathnameChunks[i] === chunck || chunck.startsWith(\":\"),\n          );\n\n          if (pathnameChunks.length === templateChunks.length && allChuncksMatch) {\n            return {\n              pathVars: safeKeys(allPathVars)\n                .reduce((params, key) => {\n                  const templateIndex = templateChunks.indexOf(`:${String(key)}`);\n                  const pathVar = pathnameChunks[templateIndex];\n                  const codec = allPathVars[key];\n\n                  return codec !== undefined && pathVar !== undefined\n                    ? { ...params, [key]: codec.decode(pathVar) }\n                    : params;\n                }, { } as V),\n              queryParams: safeKeys(routeConfig.queryParams)\n                .reduce((params, key) => {\n                  const codec = routeConfig.queryParams[key];\n                  const first = url.searchParams.get(`${key}`);\n                  const { search } = url;\n\n                  if (first && codec) {\n                    return {\n                      ...params,\n                      [key]: codec.decode(first, { key, search }),\n                    };\n                  }\n\n                  return params;\n                }, { } as Q),\n            };\n          }\n\n          throw new UrlParserError(`Unable to parse \"${uri}\". The url does not match the template \"${fullPath}\"`);\n        },\n        [routeName]: isRouteway<V, Q, S>(subRoute)\n          ? injectParentData(subRoute, fullPath, allPathVars)\n          : subRoute,\n        template: () => fullPath,\n      };\n    }, { } as R);\n}\n\nfunction isRouteway<\n  V extends CodecMap,\n  Q extends CodecMap,\n  S extends Record<string, Routeway>,\n>(value: unknown): value is Routeway<PathLike, V, Q, S> {\n  return typeof value !== \"function\";\n}\n"],"names":["safeKeys","UrlParserError"],"mappings":"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"}