Skip to content

Instantly share code, notes, and snippets.

@castarco
Last active March 11, 2021 12:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save castarco/d92024f1562b6ead115937de5c6b702b to your computer and use it in GitHub Desktop.
Save castarco/d92024f1562b6ead115937de5c6b702b to your computer and use it in GitHub Desktop.
Composable Nominal Types for Typescript
export type ChainNominal<BaseType, Tag extends string> = BaseType extends {
__type: infer Tag0
__baseType: infer BaseType0
}
? Tag0 extends [...infer NestedTags]
? BaseType0 & { __type: [Tag, ...NestedTags]; __baseType: BaseType0 }
: never
: BaseType & {
__type: [Tag] // Using an array allow us to use multiple type tags :3
__baseType: BaseType // Only here to ease type inference
}
export type FlatNominal<BaseType, Tag extends string> = BaseType extends {
__type: infer Tag0
__baseType: infer BaseType0
}
? BaseType0 & {
__type: Tag0 & { [key in Tag]: Tag }
__baseType: BaseType0
}
: BaseType & {
__type: { [key in Tag]: Tag }
__baseType: BaseType // Only here to ease type inference
}
// Similar to FlatNominal, but it's able to deal with "inheritance chains"
export type AddTag<BaseType, Tag extends string> = BaseType extends {
__baseType: infer BaseType0
__type: infer Tag0
}
? BaseType0 &
(Tag0 extends string // To account for traditional nominal types
? {
__baseType: BaseType0
__type: { [key in Tag]: Tag } & { [key in Tag0]: Tag0 }
}
: Tag0 extends [infer Tag1, ...infer DeeperTags]
? Tag1 extends string
? {
__baseType: BaseType0
__type: { [key in Tag1]: [Tag1, ...DeeperTags] } &
{ [key in Tag]: Tag }
}
: never // should not happen
: Tag0 extends { [key in string]: unknown }
? {
__baseType: BaseType0
__type: Tag0 & { [key in Tag]: Tag }
}
: never)
: BaseType & {
__baseType: BaseType
__type: { [key in Tag]: Tag }
}
// Similar to ChainNominal, but able to deal with more edge cases
export type InheritFrom<BaseType, Tag extends string> = BaseType extends {
__baseType: infer BaseType0
__type: infer Tag0
}
? Tag0 extends [...infer NestedTags]
? BaseType0 & {
__baseType: BaseType0
__type: [Tag, ...NestedTags]
}
: Tag0 extends { [key in string]: unknown }
? BaseType0 & {
__baseType: BaseType0
__type: [Tag, Tag0]
}
: never
: BaseType & {
__baseType: BaseType
__type: [Tag]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment