Created
December 21, 2023 16:29
-
-
Save lotus128/1bc46578896b56775ff116d2e8e5dc2c to your computer and use it in GitHub Desktop.
fiddle-typescript-type-maps
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
| import { ValueOf } from 'type-fest'; | |
| /** | |
| * a "type map" is a map of type tag/ discriminator to type. | |
| * https://javascript.plainenglish.io/tagged-union-types-in-typescript-leveraging-type-safety-and-flexibility-be0e60145815 | |
| * | |
| * type maps are useful for creating event maps or action maps. | |
| * | |
| * Actions as in Redux action: https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#actions | |
| */ | |
| /** | |
| * An "untagged" tag map is a map | |
| * where key: | |
| * - is the tag for the type | |
| * where value: | |
| * - is the type | |
| * - lacks the a tag/ discriminator for the type. | |
| * @example | |
| * ```typescript | |
| * interface MyUntaggedTypeMap { | |
| * Foo: { | |
| * foo: string | |
| * }, | |
| * Bar: { | |
| * bar: string | |
| * } | |
| * } | |
| * ``` | |
| */ | |
| /** | |
| * A "tagged" type map is a map | |
| * where key: | |
| * - is the tag for the type | |
| * where value: | |
| * - is the type | |
| * - has the tag/ discriminator for the type | |
| * @example | |
| * ```typescript | |
| * interface MyTaggedTypeMap { | |
| * Foo: { | |
| * type: "Foo", | |
| * foo: string | |
| * }, | |
| * Bar: { | |
| * type: "Bar", | |
| * bar: string | |
| * } | |
| * } | |
| * ``` | |
| */ | |
| /** | |
| * A "split" and "tagged" type map is a map | |
| * where key: | |
| * - is the tag for the type | |
| * where value: | |
| * - has the tag/ discriminator for the type | |
| * - has the type | |
| * @example | |
| * ```typescript | |
| * interface MySplitAndTaggedTypeMap { | |
| * Foo: { | |
| * type: "Foo", | |
| * data: { | |
| * foo: string | |
| * } | |
| * }, | |
| * Bar: { | |
| * type: "Bar", | |
| * data: { | |
| * bar: string | |
| * } | |
| * } | |
| * } | |
| * ``` | |
| */ | |
| /** | |
| * create a tagged type map from an untagged type map. | |
| * @template TagKey key of the tag/ discriminator property in the value. eg, "type". | |
| */ | |
| export type TaggedTypeMapFromUntaggedTypeMap< | |
| UntaggedTypeMap extends {}, // TODO does not have TagKey | |
| TagKey extends PropertyKey = 'type' | |
| > = { | |
| [TypeName in keyof UntaggedTypeMap]: UntaggedTypeMap[TypeName] & { | |
| [Key in TagKey]: TypeName; | |
| }; | |
| }; | |
| // @region-begin | |
| interface MyUntaggedTypeMap { | |
| Foo: { | |
| foo: string; | |
| }; | |
| Bar: { | |
| bar: string; | |
| }; | |
| } | |
| type MyTaggedTypeMap = TaggedTypeMapFromUntaggedTypeMap< | |
| MyUntaggedTypeMap, | |
| 'type' | |
| >; | |
| type AnyMyEvent = ValueOf<MyTaggedTypeMap>; | |
| const event: AnyMyEvent = { | |
| type: 'Foo', | |
| foo: 'Hello, world!', | |
| }; | |
| // @region-end | |
| // @region-begin | |
| // this is useful for Redux/ Flux standard actions. | |
| /** | |
| * Create a split and tagged type map from an untagged type map. | |
| * @remarks | |
| * Useful for creating {@link https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#actions| actions}. | |
| * @template ValueTypeKey key of the tag/ discriminator property in the value. eg, "type". | |
| * @template ValueTypeKey key of the type property in the value. eg, "payload". | |
| */ | |
| export type SplitAndTaggedTypeMapFromUntaggedTypeMap< | |
| UntaggedTagMap extends {}, // TODO does not have TagKey | |
| ValueTagKey extends PropertyKey = 'type', | |
| ValueTypeKey extends PropertyKey = 'payload' | |
| > = { | |
| [TypeName in keyof UntaggedTagMap]: { | |
| [Key in ValueTagKey]: TypeName; | |
| } & { [Key in ValueTypeKey]: UntaggedTagMap[TypeName] }; | |
| }; | |
| type MyActionTypeMap = SplitAndTaggedTypeMapFromUntaggedTypeMap< | |
| MyUntaggedTypeMap, | |
| 'type', | |
| 'payload' | |
| >; | |
| type AnyMyAction = ValueOf<MyActionTypeMap>; | |
| const splitEvent: AnyMyAction = { | |
| type: 'Foo', | |
| payload: { | |
| foo: 'Hello, world!', | |
| }, | |
| }; | |
| // @region-end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment