ZiiMakc
ZiiMakc

Reputation: 36846

Typescript Pick for literal type

Is there a way to Pick (or any other solution) from literal type for connection to main Roles interface?

Reason for this, if i in future will change or delete something in Roles, i can see where i need to change/delete it to.

Playground.

type Roles = 'admin' | 'user' | 'guest'

// no connection to Roles
type ApiRoute = { roles: 'admin' | 'user' }

// how to?
type ApiRouteWithCheck = { roles: Pick<Roles, 'admin' | 'user'> }

Upvotes: 15

Views: 5721

Answers (4)

sod
sod

Reputation: 3928

Given that Extract doesn't help with type safety, as it doesn't enforce the picked values to be a strict subset of given type, here an alternative.

Create a new helper type:

export type PickStringLiteral<A, B extends A> = B;

Use it like:

type Roles = 'admin' | 'user' | 'guest'

type ApiRouteWithCheck = { roles: PickStringLiteral<Roles, 'admin' | 'user'> }

It does what you expect:

  • the type will be exactly 'admin' | 'user'
  • you'll get a typescript error if one of 'admin' or 'user' is missing from Roles
  • your IDE will provide code completion to fill out the subset

Upvotes: 2

jonrsharpe
jonrsharpe

Reputation: 122032

You can either Exclude the values you don't want:

type ApiRoute = { roles: Exclude<Roles, 'guest'> };

or Extract the values you do:

type ApiRoute = { roles: Extract<Roles, 'admin' | 'user'> };

Upvotes: 27

Asaf Aviv
Asaf Aviv

Reputation: 11770

Another way is to split it up into different types and export types based on those types, this way if you add new types in the future you just add them to AllRoles and if you need to use one of those types you can just import them directly instead of picking/excluding them

export type AdminRoles = 'admin' | 'superadmin'
export type UserRoles = 'user'
export type PublicRoles = 'guest'

export type AllRoles = AdminRoles | UserRoles | PublicRoles
export type AuthRoles = Exclude<AllRoles, PublicRoles>

Now in the future if you add another type of role you can just add it to AllRoutes because we are excluding PublicRoles

export type AdminRoles = 'admin' | 'superadmin'
export type ModeratorRoles = 'moderator' | 'supermoderator'
export type UserRoles = 'user' | 'superuser'
export type PublicRoles = 'guest'

export type AllRoles = AdminRoles | UserRoles | PublicRoles | ModeratorRoles
export type AuthRoles = Exclude<AllRoles, PublicRoles>

Upvotes: 0

axiac
axiac

Reputation: 72226

Change Roles to be an enum. This way its name must be used where its values are used.
When you add new values to Roles you can use "find in files" (or the "code lens" feature of VSCode) to find out where it is used.

enum Roles { admin = 'admin', user = 'user', guest = 'guest' }

type ApiRoutePublic = { roles: Roles }
type ApiRouteAuth = { roles: Roles.admin | Roles.user }
type ApiRouteAdmin = { roles: Roles.admin }

Upvotes: 1

Related Questions