Skip to content

Instantly share code, notes, and snippets.

@lotus128
Created December 21, 2023 16:29
Show Gist options
  • Select an option

  • Save lotus128/1bc46578896b56775ff116d2e8e5dc2c to your computer and use it in GitHub Desktop.

Select an option

Save lotus128/1bc46578896b56775ff116d2e8e5dc2c to your computer and use it in GitHub Desktop.
fiddle-typescript-type-maps
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