DEV Community

Cover image for Utility Type: WithPrefix
teamradhq
teamradhq

Posted on

1

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

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay