miking-the-viking/react-route-manager

Typesafety for dynamic route path based on params object

Closed this issue · 1 comments

Try to complete the idea below

type DynamicParam<S extends string> = `:${S}`
type DynamicParamRoute<T extends string> = `${string}/${DynamicParam<T>}/${string}` | `${DynamicParam<T>}/${string}` | `${string}/${DynamicParam<T>}` | `${DynamicParam<T>}`

type UserParamRoute = DynamicParamRoute<'user'>

const u1: UserParamRoute = ':user'
const u2: UserParamRoute = 'prefix/:user'
const u3: UserParamRoute = ':user/suffix'
const u4: UserParamRoute = 'prefix/:user/suffix'

type TeamParamRoute = DynamicParamRoute<'team'>

const t1: TeamParamRoute = ':team'
const t2: TeamParamRoute = 'prefix/:team'
const t3: TeamParamRoute = ':team/suffix'
const t4: TeamParamRoute = 'prefix/:team/suffix'

type UserTeamParamRoute = UserParamRoute & TeamParamRoute
// const ut1: UserTeamParamRoute = 'user/team'  // Type '"user/team"' is not assignable to type 'UserTeamParamRoute'.ts(2322)
// const ut1: UserTeamParamRoute = ':user'      // Type '":user"' is not assignable to type 'UserTeamParamRoute'.ts(2322)
// const ut1: UserTeamParamRoute = ':team'      // Type '":team"' is not assignable to type 'UserTeamParamRoute'.ts(2322)
const ut1: UserTeamParamRoute = ':user/:team'
const ut2: UserTeamParamRoute = 'prefix/:user/:team'
const ut3: UserTeamParamRoute = ':user/:team/suffix'
const ut4: UserTeamParamRoute = ':user/middle/params/:team'


const params = {
  currency: 'someting',
  meme: 'someotherthing'
} as const

const ps = ['currency', 'meme'] as const
const ps1 = ['currency', 'meme']

type Params = keyof typeof params
// type SafeParams = UnionToTuple<Params>

// type RouteParams = DynamicParamRoute<Params>
type RouteParams = DynamicParamRoute<'currency'> & DynamicParamRoute<'meme'>
const r1: RouteParams = ':currency'
const p1: RouteParams = ':meme'
// const rp: RouteParams = 'currencymeme'
// const rp0: RouteParams = ''
// const rp01: RouteParams = 'missingall'
const rp1: RouteParams = ':currency/:meme'
const rp2: RouteParams = 'prefix/:currency/:meme'
const rp3: RouteParams = ':currency/:meme/suffix'
const rp4: RouteParams = ':currency/middle/params/:meme'
const pr1: RouteParams = ':meme/:currency'
const pr2: RouteParams = 'prefix/:meme/:currency'
const pr3: RouteParams = ':meme/:currency/suffix'
const pr4: RouteParams = ':meme/middle/params/:currency'

// // // add an element to the end of a tuple
// type Push<L extends any[], T> =
//   ((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ?
//   { [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never

// // convert a union to an intersection: X | Y | Z ==> X & Y & Z
// type UnionToIntersection<U> =
//   (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

// // convert a union to an overloaded function X | Y ==> ((x: X)=>void) & ((y:Y)=>void)     
// type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;

// // convert a union to a tuple X | Y => [X, Y]
// // a union of too many elements will become an array instead
// type UnionToTuple<U> = UTT0<U> extends infer T ? T extends any[] ?
//   Exclude<U, T[number]> extends never ? T : U[] : never : never

// // each type function below pulls the last element off the union and 
// // pushes it onto the list it builds
// type UTT0<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT1<Exclude<U, A>>, A> : []
// type UTT1<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT2<Exclude<U, A>>, A> : []
// type UTT2<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT3<Exclude<U, A>>, A> : []
// type UTT3<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT4<Exclude<U, A>>, A> : []
// type UTT4<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT5<Exclude<U, A>>, A> : []
// type UTT5<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTTX<Exclude<U, A>>, A> : []
// type UTTX<U> = []; // bail out

https://stackoverflow.com/questions/68025792/define-an-generic-intersection-type-of-1-n-string-template-types

type DynamicParamRoute<T extends string> = (T extends any ? (x:
    `${string}/${DynamicParam<T>}/${string}` | 
    `${DynamicParam<T>}/${string}` | 
    `${string}/${DynamicParam<T>}` | 
    `${DynamicParam<T>}`
) => void : never) extends ((x: infer I) => void) ? I : never;