Created
May 15, 2025 16:29
-
-
Save liammcinroy/9683be868c6ec8d6094fe322d5f82363 to your computer and use it in GitHub Desktop.
Ways to extend typescript string enums with typing (even if compile-time only)
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
| /* | |
| * Extending string enums, especially if they aren't provided at runtime. | |
| * Playground: https://www.typescriptlang.org/play/?#code/PQKgUABCEKIB4BcCmA7AJgSxQcwgZwQCctdUBXAWzwBoIk8AHJAYwwEMAbDgTwgwDMICABZJebQqgDkCCA0IB7AG4Y0SNBDazCZFAgwUkAOkghgYMKAgAVUUOEkIASSkaOGANZIhCuolQaGLIA7kHCUObkFBAAgigKIkiE1txMEADekLHxiYQQALwQAERsOaKERWAAvhZWToKJvMFJ0rLyyqrqmtq6+oYQABR4vvH48khsaHgAlBGWwHR47noAtJh4bABGHEgrKEiIK8veAAIIqfTMxAwIK-TLCMDxe2yGjGzMSGBqzBwS3ihXvQGB9vDEMlkohB4moAPrnNKZCDIiDABb3LC3dZbHZ7A63Y4QM4XPBXDA3O5LTFPBQrPDCSYKYJZZHgwolSrImo1NGLB5rDAbba7faHQnEpik663DF6GkvN4gz7fFh-STQoHvT4QABCEORUJhSHhF31KN5sqxgpxIvxRywpwRl2llIe8vpjOZKN1BWKm05EG5FidEAAcgo1Ck0oUYkYjSa0gAfXVxiPGkPJuIJcpRpAAblqCwAIgp6CgZBBggpCB4wMwFCgCGG07mAGJsDAcPAALmbkYu4ZQoc1Su8hSRsV7HOoWR1U-9M+RRmXWdyueqFmDpvD-aYAHlBPkssmnQpBLH407j0ILmeU5eLtfT+eyklcwWQ2w8LD3MhCJwAB5rAAPl9aw-GQdA8HwIhHAAfggAADAASdJrCqRCIF7fYlCSD9TScCgGA4HckFzX0JwAbXIrA+zIi4D00aCvG4O9cwAXV7L8fyCJJANzSiWLYi52OAjd5ggAB1asPDwABCCTLQFIVcVFAkHWhWlJB+NUvnrRtZFI3Ne0I4ijNNccshiKc2CKRddXnOysmXIxVxzR8alqaB6nsMRNHVdoVDUDQhl8RhJEmaD-k0LgmXUWYzDAKEYkHQFFVBM02WKWyNyhHVUpHDKJz1dl-XEkNzKYQdh3S7UYwK2rvGTfKGzS4EMszV9kguAsJJLMsKyrGs6wbJtKqQarCs+dtOx7Ojc0mxqKOclcGvaz57JclqUDarUkE2lcuvXTyKpbBj+EW9axyfW9zzWvabqYO9tt20dHqQO83LfHqwBDUySLOqqGxqq7luRajTVo8aD0uvamIgITBA43sBMRmwRPE3lpJreTFKpVZsWFPExQ055tNVf4RoM+aB2BqakBMoiAd3Ca6aWyyl1W1r6YOowXp5lbXKOjyLCAA | |
| */ | |
| /* The thing I'd like to extend it with */ | |
| enum AnotherType { | |
| Another = "another" | |
| } | |
| /* If they weren't provided at runtime (so no spreads) */ | |
| // eslint-disable-next-line @typescript-eslint/no-namespace | |
| declare namespace A { | |
| enum node_type { | |
| // eslint-disable-next-line @typescript-eslint/no-shadow | |
| A = "a" | |
| } | |
| } | |
| // eslint-disable-next-line @typescript-eslint/no-namespace | |
| declare namespace B { | |
| enum node_type { | |
| // eslint-disable-next-line @typescript-eslint/no-shadow | |
| B = "b" | |
| } | |
| } | |
| type NodeType = A.node_type | B.node_type | AnotherType; | |
| // Doesn't work | |
| const NodeTypeFails: NodeTypeNonNamespace = { | |
| A: "a", | |
| B: "b", | |
| ...AnotherType | |
| } | |
| type NodeTypeOf = | |
| | typeof A.node_type | |
| | typeof B.node_type | |
| | typeof AnotherType; | |
| type as_literal<T> = T extends string ? `${T}` : never; | |
| type ImplNodeType = { | |
| [Type in NodeTypeOf as keyof Type]: as_literal<Type[keyof Type]> | |
| } | |
| // Works! | |
| // eslint-disable-next-line no-redeclare | |
| const NodeType: ImplNodeType = { | |
| A: "a", | |
| B: "b", | |
| ...AnotherType | |
| } | |
| /* If they are provided (so spreads are allowed) */ | |
| enum ANonnamespace { | |
| A = "a" | |
| } | |
| enum BNonnamespace { | |
| B = "b" | |
| } | |
| type NodeTypeNonNamespace = ANonnamespace | BNonnamespace | AnotherType; | |
| // Doesn't work | |
| const NodeTypeNonNamespaceFails: NodeTypeNonNamespace = { | |
| ...ANonnamespace, | |
| ...BNonnamespace, | |
| ...AnotherType | |
| } | |
| type NodeTypeOfNonNamespace = | |
| | typeof ANonnamespace | |
| | typeof BNonnamespace | |
| | typeof AnotherType; | |
| type ImplNodeTypeNonNamespace = { | |
| [Type in NodeTypeOfNonNamespace as keyof Type]: Type[keyof Type] | |
| } | |
| // Works! | |
| // eslint-disable-next-line no-redeclare | |
| const NodeTypeNonNamespace: ImplNodeTypeNonNamespace = { | |
| ...ANonnamespace, | |
| ...BNonnamespace, | |
| ...AnotherType | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment