A utility type for prefixing a type's properties with a string:
export type WithPrefix<Prefix extends string, T, Separator extends string = '/'> = { [K in keyof T as `${Prefix}${Separator}${string & K}`]: T[K]; };
Simply provide a prefix, type and optional separator (defaults to /).
type UserApi = { get: GetFn<User>; set: SetFn<User>; all: ListFn<User>; }; const api: WithPrefix<'/api', UserApi> = { '/api/get': getUser, '/api/set': setUser, '/api/all': listUsers, };
Example: API routes
When handling interactions between a front-end and back-end we might have an API that looks like this:
type CartApi = { checkout: CheckoutFn; view: GetFn<Cart>; add: PostFn<CartItem | CartItem[]>; remove: DeleteFn<CartItem | CartItem[]>; update: PatchFn<CartItem | CartItem[]>; };
To ensure that every API method has a corresponding server route we can use the WithPrefix utility type:
const cartApi: WithPrefix<'/cart', CartApi> = { '/cart/checkout': checkout, '/cart/view': viewCart, '/cart/add': addToCart, '/cart/remove': removeFromCart, '/cart/update': updateCart, }; for (const [route, handler] of Object.entries(cartApi)) { app[handler.method](route, handler); }
Whenever we define additional methods on our CartApi
:
type CartApi = { checkout: CheckoutFn; view: GetFn<Cart>; add: PostFn<CartItem | CartItem[]>; remove: DeleteFn<CartItem | CartItem[]>; update: PatchFn<CartItem | CartItem[]>; empty: DeleteFn<Cart>; };
TypeScript will show an error:
// TS2741: Property '/cart/empty' is missing in type WithPrefix<'/cart', CartApi> const cartApi: WithPrefix<'/cart', CartApi> = { '/cart/checkout': checkout, '/cart/view': viewCart, '/cart/add': addToCart, '/cart/remove': removeFromCart, '/cart/update': updateCart, };
Breaking it down
This utility uses mapped types, template literal types and key remapping:
export type WithPrefix<Prefix extends string, T, Separator extends string = '/'> = { [K in keyof T as `${Prefix}${Separator}${string & K}`]: T[K]; };
- We define a type with:
- A
Prefix
to add to each property. - A type
T
to prefix. - A
Separator
to separate the prefix from the property name.
- A
- We iterate over each property of
T
and interpolate the prefix and separator with the property name${Prefix}${Separator}${string & K}
.
This gives us a resulting type with our prefixed property names.
Hopefully you find this useful :)
Top comments (0)