DEV Community

Cover image for Utility Type: WithPrefix
teamradhq
teamradhq

Posted on

Utility Type: WithPrefix

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]; }; 
Enter fullscreen mode Exit fullscreen mode

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, }; 
Enter fullscreen mode Exit fullscreen mode

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[]>; }; 
Enter fullscreen mode Exit fullscreen mode

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); } 
Enter fullscreen mode Exit fullscreen mode

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>; }; 
Enter fullscreen mode Exit fullscreen mode

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, }; 
Enter fullscreen mode Exit fullscreen mode

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]; }; 
Enter fullscreen mode Exit fullscreen mode
  • We define a type with:
    • A Prefix to add to each property.
    • A typeT to prefix.
    • ASeparator to separate the prefix from the property name.
  • 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 :)


Reference

Top comments (0)