1

I have an enum in TypeScript.

I want to write a function that returns an exhaustive array that contains exactly one reference to each property on the enum.

The return of the function will be an Array<{ id: EnumValue, label: string }>.

Sample code:

// I have this enum:
enum StepId {
  STEP_ONE = 'step-one-id',
  STEP_TWO = 'step-two-id',
}


// I want to define some function...
function someFunc() {
  return ...
}


// which returns an Array where every value from `StepId` is represented as an object with that value as the `id` field.
someFunc() === [
  { id: CustomStepIds.STEP_ONE, label: 'Step One', },
  { id: CustomStepIds.STEP_TWO, label: 'Step Two', },
];

I have tried to first solve the problem of enforcing all enum values are represented in an array. I've tried this:

enum CustomStepId {
  STEP_ONE = 'step-one-id',
  STEP_TWO = 'step-two-id',
}

type ValueOf<T> = T[keyof T];

const stepConfig: Array<{ id: ValueOf<typeof CustomStepId>, label: string }> = [
  { id: CustomStepId.STEP_ONE, label: 'Step One', },
  { id: CustomStepId.STEP_TWO, label: 'Step Two', },
  { id: CustomStepId.STEP_TWO, label: 'Step Two', }, // this should error, as STEP_TWO is already defined
];

However as noted in the comment above, while this enforces id is a CustomStepId, it does not enforce uniqueness of the field.

tdc
  • 5,174
  • 12
  • 53
  • 102
  • There is no native concept of "exhaustive/unique array" in TypeScript, so you'd have to build something manually. How many members can we expect `StepId` to have? If it's just a handful, then we can write an exhaustive array as a specific union of all possible accepted permutations of the members, as shown [in this playground link](https://tsplay.dev/Ndv1dW). But that doesn't scale well since the union grows superexponentially in the size of the enum. If you've got even like eight or nine members then the [compiler will be unhappy](//tsplay.dev/N73aPN). How would you like to proceed? – jcalz Apr 24 '23 at 20:16
  • Does this answer your question? [Enforce that an array is exhaustive over a union type](https://stackoverflow.com/questions/55265679/enforce-that-an-array-is-exhaustive-over-a-union-type) – Dimava Apr 24 '23 at 22:30

1 Answers1

0

I propose the following, on the basis that I (and much of the Typescript world) eschew Enums in favour of const arrays See typescript playground

I drafted mechanisms based on a map or a tuple, depending what you prefer or need.

type MemberOf<Arr extends readonly unknown[]> = Arr[number]

const STEP_IDS = ['step-one-id', 'step-two-id'] as const;
type StepId = MemberOf<typeof STEP_IDS>;

type StepConfigMap = {[k in StepId]: {
    label:string
}}

type ConfigTuple<Tuple extends readonly [...unknown[]]> = {
  [Index in keyof Tuple]: {
    id:Tuple[Index],
    label:string
  };
} & {length: Tuple['length']};

type StepConfigTuple = ConfigTuple<typeof STEP_IDS>;

const CONFIG_MAP: StepConfigMap  = {
    "step-one-id":{
        label:"something"
    },
    "step-two-id":{
        label:"something else"
    },
    // errors as it's already defined
    // An object literal cannot have multiple properties with the same name.(1117)
    "step-two-id":{
        label:"something else"
    }
} as const;

const CONFIG_TUPLE = [
    {
        id:"step-one-id",
        label:"something",
    },
    {
        id:"step-two-id",
        label:"something",
    },
    // errors as it's already defined
    // Source has 3 element(s) but target allows only 2.
    {
        id:"step-two-id",
        label:"something",
    }

] as const satisfies StepConfigTuple;
cefn
  • 2,895
  • 19
  • 28