Search code examples
typescriptenumsdocx

Extract enum from Array with type restriction


This is similar to extract property values from array but I also need to strict the type of array.

Take the following code as example:

type ElemProp = {id: number, name: string, quickFormat?: boolean }

const formatList:ElemProp[] = [
    {id:1, name: 'ABC'},
    {id: 3, name: 'XYZ'},
    {id: 98, name: 'GOGOGO', quickFormat: true}
] as const

type IdEnum = (typeof formatList)[number]['id']

The IdEnum is evaluated as number instead of 1|3|98.

Nonetheless, removing :ElemProp[] will give the enum, but now I lose all type support.

The question is: how to obtain the enum while keeping type support for the original array?

I tried changing things to readonly, adding as const but as long as the ElemProp[] is there, IdEnum returns number.

Post-answer update:

This is the original intent: I was trying to limit the type of paragraph styles supplied to paragraphs when using https://github.com/dolanmiu/docx/ to create docx files in NodeJS. This is not very elegant at the moment but I'm seeing where this is going.

import dx from 'docx';
import { IPropertiesOptions } from 'docx/build/file/core-properties';

type StylesAndNumberings = Required<Pick<IPropertiesOptions, 'styles' | 'numbering'>>; // for reference only

export type ExtractParagraphIds<U extends Partial<IPropertiesOptions>> = U extends {
    readonly styles: { readonly paragraphStyles: infer U extends readonly { id: string }[] };
}
    ? U[number]['id']
    : never;

const docProps = {
  styles: {
    paragraphStyles: [
      {
        id: 'normal',
        name: 'Normal',
        basedOn: 'Normal',
        next: 'Normal',
        quickFormat: true,
        run: {
          font: 'Arial',
          size: 22,
        },
      }]
  }
} as const satisfies Partial<IPropertiesOptions>;

type IdEnum = ExtractParagraphIds<typeof docProps>;

export const textToParagraph = <T extends Partial<IPropertiesOptions>>(
    t: string,
    runFormat: dx.IRunOptions = {},
    paragraphOptions: dx.IParagraphOptions & { style?: ExtractParagraphIds<T> } = {},
    paragraphFreeChildren: dx.IParagraphOptions['children'] = []
) => {
    const text = t.split('\n');
    if (text.length === 1) {
        return new dx.Paragraph({
            wordWrap: true,
            children: [
                new dx.TextRun({
                    text: t,
                    ...runFormat,
                }),
                ...paragraphFreeChildren,
            ],
            ...paragraphOptions,
        });
    }
    return new dx.Paragraph({
        children: [
            ...text.map((line, i) => {
                return new dx.TextRun({
                    text: line,
                    ...runFormat,
                    break: i ? 1 : 0,
                });
            }),
            ...paragraphFreeChildren,
        ],
        ...paragraphOptions,
    });
};

Solution

  • Use satisfies:

    type ElemProp = {id: number, name: string, quickFormat?: boolean }
    
    const formatList = [
        {id:1, name: 'ABC'},
        {id: 3, name: 'XYZ'},
        {id: 98, name: 'GOGOGO', quickFormat: true}
    ] as const satisfies ElemProp[];
    
    type IdEnum = (typeof formatList)[number]['id']