/** * @typed/fp/Path is a collection of helpers for constructing paths that follow * the path-to-regexp syntax and type-level combinators for parsing that syntax and * for interpolating values. * @since 0.13.0 */ import { A, N } from 'ts-toolbelt' /* Start Region: Parameter DSL */ /** * Template for parameters * @category Model * @since 0.13.0 */ export type Param = `:${A}` /** * @category Constructor * @since 0.13.0 */ export const param = (param: A): Param => `:${param}` as Param /** * Template for optional path parts * @category Model * @since 0.13.0 */ export type Optional = `${A}?` /** * @category Constructor * @since 0.13.0 */ export const optional = (param: A): Optional => `${param}?` as Optional /** * Construct a custom prefix * @category Model * @since 0.13.0 */ export type Prefix

= `{${P}${A}}` /** * @category Constructor * @since 0.13.0 */ export const prefix =

| Unnamed>(prefix: P, param: A) => `{${prefix}${param}}` as Prefix /** * Construct query params * @category Model * @since 0.13.0 */ export type QueryParam = `` extends V ? K : `${K}=${V}` /** * Construct query params * @category Constructor * @since 0.13.0 */ export const queryParam = (key: K, value: V) => `${key}=${value}` as QueryParam /** * zero or more path parts will be matched to this param * @category Model * @since 0.13.0 */ export type ZeroOrMore = `${Param}*` /** * @category Constructor * @since 0.13.0 */ export const zeroOrMore = (param: A): ZeroOrMore => `:${param}*` as ZeroOrMore /** * @category Model * @since 0.13.0 */ export type OneOrMore = `${Param}+` /** * one or more path parts will be matched to this param * @category Constructor * @since 0.13.0 */ export const oneOrMore = (param: A): OneOrMore => `:${param}+` as OneOrMore /** * Creates the path-to-regexp syntax for query parameters * @category Model * @since 0.13.0 */ export type QueryParams< Q extends readonly QueryParam[], R extends string = ``, > = Q extends readonly [infer Head, ...infer Tail] ? QueryParams< A.Cast[]>, `` extends R ? `\\?${A.Cast}` : `${R}&${A.Cast}` > : R /** * @category Constructor * @since 0.13.0 */ export const queryParams =

, ...QueryParam]>( ...params: P ): QueryParams

=> `\\?${params.join('&')}` as QueryParams

/** * @category Constructor * @since 0.13.0 */ export const unnamed = `(.*)` as const /** * @category Model * @since 0.13.0 */ export type Unnamed = typeof unnamed /** * Composes other path parts into a single path * @category Type-level * @since 0.13.0 */ export type PathJoin> = A extends readonly [ infer Head, ...infer Tail ] ? `${FormatPart>}${PathJoin>>}` : `` /** * @category Combinator * @since 0.13.0 */ export const pathJoin =

>(...parts: P): PathJoin

=> { if (parts.length === 0) { return `` as PathJoin

} const [head, ...tail] = parts return `${formatPart(head)}${pathJoin(...tail)}` as PathJoin

} /** * Formats a piece of a path * @category Combinator * @since 0.13.0 */ export const formatPart = (part: string) => { part = removeLeadingSlash(part) if (part.startsWith('{')) { return part } if (part.startsWith('\\?')) { return part } return part === '' ? '' : `/${part}` } /** * @category Type-level * @since 0.13.0 */ export type FormatPart

= `` extends P ? P : RemoveLeadingSlash

extends `\\?${infer _}` ? RemoveLeadingSlash

: RemoveLeadingSlash

extends `{${infer _}` ? RemoveLeadingSlash

: P extends QueryParam | QueryParams ? P : `/${RemoveLeadingSlash

}` /** * Remove forward slashes prefixes recursively * @category Type-level * @since 0.13.0 */ export type RemoveLeadingSlash = A extends `/${infer R}` ? RemoveLeadingSlash : A /** * @category Combinator * @since 0.13.0 */ export const removeLeadingSlash = (a: A): RemoveLeadingSlash => { let s = a.slice() while (s.startsWith('/')) { s = s.slice(1) } return s as RemoveLeadingSlash } /* End Region: Parameter DSL */ /* Start Region: Extract Parameters from Path */ // Extract the parameters out of a path /** * @category Type-level * @since 0.13.0 */ export type ParamsOf = Compact>> // Extract the Query parameters out of a path /** * @category Type-level * @since 0.13.0 */ export type QueryParamsOf

= Compact>> // Convert a path back into a tuple of path parts /** * @category Type-level * @since 0.13.0 */ export type PathToParts

= P extends `${infer Head}\\?${infer Tail}` ? readonly [...PathToParts, `\\?${Tail}`] : P extends `${infer Head}/${infer Tail}` ? readonly [...PathToParts, ...PathToParts] : P extends `${infer Head}{${infer Q}}?${infer Tail}` ? readonly [...PathToParts, `{${Q}}?`, ...PathToParts<`${Tail}`>] : P extends `${infer Head}{${infer Q}}${infer Tail}` ? readonly [...PathToParts, `{${Q}}`, ...PathToParts<`${Tail}`>] : `` extends P ? readonly [] : readonly [P] /** * @category Type-level * @since 0.13.0 */ export type PartsToParams, AST = {}> = A extends readonly [ infer Head, ...infer Tail ] ? PartsToParams, AST & PartToParam, AST>> : AST /** * @category Type-level * @since 0.13.0 */ export type PartToParam = A extends `\\${infer R}` ? PartsToParams, AST> : A extends Unnamed ? { readonly [K in FindNextIndex extends number ? FindNextIndex : never]: string } : A extends `${infer _}${Unnamed}}?` ? { readonly [K in FindNextIndex extends number ? FindNextIndex : never]?: string } : A extends `${infer _}${Unnamed}}` ? { readonly [K in FindNextIndex extends number ? FindNextIndex : never]: string } : A extends `${infer _}${Param}}?` ? { readonly [K in R]?: string } : A extends `${infer _}${Param}}` ? { readonly [K in R]: string } : A extends `${infer _}${Param}?` ? { readonly [K in R]?: string } : A extends `${infer _}${Param}+` ? { readonly [K in R]: readonly [string, ...string[]] } : A extends `${infer _}${Param}*` ? { readonly [K in R]: readonly string[] } : A extends `${infer _}${Param}` ? { readonly [K in R]: string } : {} /** * @category Type-level * @since 0.13.0 */ export type QueryParamsToParts< Q extends string, R extends ReadonlyArray, > = Q extends `\\?${infer Q}` ? QueryParamsToParts : Q extends `?${infer Q}` ? QueryParamsToParts : Q extends `${infer Head}&${infer Tail}` ? QueryParamsToParts> : readonly [...R, QueryParamValue] /** * @category Type-level * @since 0.13.0 */ export type QueryToParams = Q extends `${infer Head}&${infer Tail}` ? QueryToParams> : Q extends `?${infer K}` ? QueryToParams : Q extends `${infer K}?` ? AST & Partial> : Q extends `${infer K}` ? AST & QueryParamAst : AST // Increments up from I until it finds an index that is not taken /** * @category Type-level * @since 0.13.0 */ export type FindNextIndex = I extends keyof AST ? FindNextIndex> : I /** * @category Type-level * @since 0.13.0 */ export type PathToQuery

= P extends `${infer _}\\${infer Q}` ? Q : `` type QueryParamValue = K extends `${infer _}=${infer R}` ? R : string type QueryParamAst = Readonly, QueryParamAstValue>> type QueryParamAstKey = K extends `${infer K}=${infer _}` ? K : K type QueryParamAstValue = K extends `${infer _}=${infer R}` ? R extends Param | Unnamed ? string : R : string type Compact = { readonly [K in keyof A]: A[K] } /* End Region: Extract Parameters from Path */ /* Start Region: Interpolations */ /** * @category Type-level * @since 0.13.0 */ export type Interpolate< P extends string, Params extends ParamsOf

, > = P extends `${infer Head}\\?${infer Tail}` ? PathJoin< InterpolateWithQueryParams< SplitQueryParams, Params, InpterpolateParts, Params> >[0] > : PathJoin, Params>[0]> /** * @category Type-level * @since 0.13.0 */ export type InpterpolateParts< Parts extends readonly any[], Params extends {}, R extends readonly any[] = [], AST = {}, > = Parts extends readonly [infer H, ...infer T] ? H extends Optional> ? FindNextIndex extends keyof Params ? InpterpolateParts< T, Params, AppendPrefix], string | number>}`>, AST & Record]> > : InpterpolateParts : H extends Prefix ? InpterpolateParts< T, Params, AppendPrefix< R, Pre, `${A.Cast, keyof Params>], string | number>}` >, AST & Record, keyof Params>]> > : H extends Optional>> ? P extends keyof Params ? InpterpolateParts< T, Params, AppendPrefix], string>>, AST & Record]> > : InpterpolateParts : H extends Prefix> ? InpterpolateParts< T, Params, AppendPrefix], string>>, AST & Record]> > : InterpolatePart extends readonly [infer A, infer B] ? InpterpolatePartsWithNext : InpterpolateParts : readonly [R, AST] /** * @category Type-level * @since 0.13.0 */ export type AppendPrefix< R extends readonly any[], Pre extends string, P extends string, > = R extends readonly [...infer Init, infer L] ? readonly [...Init, `${A.Cast}${Pre}${P}`] : R /** * @category Type-level * @since 0.13.0 */ export type InpterpolatePartsWithNext< Parts extends readonly any[], Params extends {}, R extends readonly any[], Next extends readonly [any, any], > = InpterpolateParts /** * @category Type-level * @since 0.13.0 */ export type InterpolatePart = P extends Optional> ? R extends keyof Params ? readonly [Params[R], AST & Record] : readonly ['', AST] : P extends Param ? R extends keyof Params ? readonly [Params[R], AST & Record] : readonly [P, AST] : P extends Unnamed ? FindNextIndex extends keyof Params ? InterpolateUnnamedPart, AST> : readonly [P, AST] : P extends Prefix> ? R extends keyof Params ? [`${Pre}${A.Cast}`, AST & Record] : [] : P extends Optional>> ? R extends keyof Params ? [`${Pre}${A.Cast}`, AST & Partial>] : [] : readonly [P, AST] /** * @category Type-level * @since 0.13.0 */ export type InterpolateUnnamedPart = readonly [ Params[K], AST & Record, ] type InterpolateWithQueryParams< Q extends readonly string[], Params, Previous extends readonly [any, any], First extends boolean = true, > = Q extends readonly [infer Head, ...infer Tail] ? InterpolateWithQueryParams< A.Cast, Params, InterpolateQueryParamPart[0], InterpolateQueryParamPart[1] > : Previous /** * @category Type-level * @since 0.13.0 */ export type InterpolateQueryParamPart< Part, Params, Previous extends readonly [readonly string[], any], First extends boolean, > = Part extends QueryParam ? InterpolateQueryParamPartWithKey< First extends true ? `?${K}` : `&${K}`, Previous[0], InterpolatePart, First > : readonly [[[...Previous[0], Part], Previous[1]], false] type InterpolateQueryParamPartWithKey< K extends string, Parts extends readonly string[], Previous extends readonly [string, any], First extends boolean, > = '' extends Previous[0] ? [[Parts, Previous[1]], First] : [[[...Parts, `${K}=${Previous[0]}`], Previous[1]], false] type SplitQueryParams

= P extends `${infer Head}&${infer Tail}` ? readonly [Head, ...SplitQueryParams] : readonly [P]