Typescript export inferred type instead of explicit type

Viewed 204

My context is within vue-router, though that's probably not important

I would like to define my routes like this

import { RouteLocationRaw } from 'vue-router'

type RouteNames = 'dashboard' | 'flowRun'
type RouteProperties = Record<RouteNames, (...param:string[]) => RouteLocationRaw>

const Route: RouteProperties = {
    dashboard: () => ({ name: 'dashboard' }),
    flowRun: (flowRunId: string) => ({name: 'flow-run', params: {flowRunId}})
} as const

however, when I go to use this my types are using the explicit RouteProperties, which is actually worse than it would be if I let typescript infer the types. If I import Routes and type Routes.flowRun, all I see for params is ...params:string[] which makes sense since that's how I explicitly defined it.

If I instead define my routes as

const Route = {
    dashboard: () => ({ name: 'dashboard' }),
    flowRun: (flowRunId: string) => ({name: 'flow-run', params: {flowRunId}})
} as const

now when I import Routes, and use Routes.flowRun, my parameter is named with an explicit type and will not let me call without providing the param.

Is it possible to have the type safety of my explicit types when defining new routes, but still export as the inferred types for a better type safety when used?

1 Answers

Yes, by using an identity function. The generic constraint will enforce that the input value extends the constraint, but will return the actual type of the input value:

TS Playground

import { RawLocation } from 'vue-router';
type RouteLocationRaw = RawLocation;

const routeNames = ['dashboard', 'flowRun'] as const;
type RouteName = typeof routeNames[number];
type RouteProperties = Record<RouteName, (...param:string[]) => RouteLocationRaw>;

function typeSafeRouteProps <T extends RouteProperties>(input: T): {
  [K in keyof T as K extends keyof RouteProperties ? K : never]: T[K]
} {
  const output = {...input};
  for (const key in output) {
    if (!(routeNames as unknown as string[]).includes(key)) {
      delete output[key];
    }
  }
  return output;
}

const Route = typeSafeRouteProps({
  dashboard: () => ({ name: 'dashboard' }),
  flowRun: (flowRunId: string) => ({name: 'flow-run', params: {flowRunId}}),
  test: 123,
});

console.log(Object.keys(Route)); // ['dashboard', 'flowRun']
Related