Last active
March 11, 2021 12:59
-
-
Save castarco/d92024f1562b6ead115937de5c6b702b to your computer and use it in GitHub Desktop.
Composable Nominal Types for Typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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