简体   繁体   中英

Infer tuple type from enum?

export enum DataType {
    UNKNOWN = 0x00,
    STRING = 0x01,
    FIXED_LENGTH_STRING = 0x02,
    FLOAT32 = 0x03,
    FLOAT64 = 0x04,
    DECIMAL = 0x05,
    NULLABLE_COMPACT_INT = 0x06,
}

export async function createTableWriteStream<T extends ReadonlyArray<any>>(path: string, options: FileOptions&TableWriteStreamOptions): Promise<TableWriteStream<T>> { ... }

const writeStream = await createTableWriteStream<[string,number|null|bigint]>(testFile, {
    flags: 'w',
    columns: [
        {
            name: "col1",
            type: DataType.STRING,
        },
        {
            name: "column 2",
            type: DataType.NULLABLE_COMPACT_INT,
        },
    ]
})

I would like to create a mapping between DataType and TS types so that I don't need to specify T for createTableWriteStream .

Something like:

interface TypeMap {
    [DataType.UNKNOWN]: never,
    [DataType.STRING]: string,
    [DataType.FIXED_LENGTH_STRING]: string,
    [DataType.FLOAT32]: number,
    [DataType.FLOAT64]: number,
    [DataType.DECIMAL]: number|string,
    [DataType.NULLABLE_COMPACT_INT]: number|bigint|null,
}

But then I don't know how to use that to infer the return type of createTableWriteStream from options.columns.type . Is this possible?

TypeScript Playground

Ok, that is a little bit tricky.

First of all you need to prepare mapping from your enum to expected result types.

type DataTypeMapping = {
  [DataType.UNKNOWN]: never,
  [DataType.STRING]: string,
  [DataType.FIXED_LENGTH_STRING]: string,
  [DataType.FLOAT32]: number,
  [DataType.FLOAT64]: number,
  [DataType.DECIMAL]: number|string,
  [DataType.NULLABLE_COMPACT_INT]: number|bigint|null,
}

Next you need to create some type that will map your source tuple of columns to the target tuple of results. For now (as of typescript 4.2.3) mapping tuple to another tuple is a little bit tricky, especially if you are trying to map some specific (not abstract) tuple to another on. It is because tuple itself is an array and has some properties besides its elements (like length, array methods, iterator, etc.). Below is the construction that works:

type Tuple<T> = [T, ...T[]];

type MapDataTypeImpl<T extends Tuple<ColumnSpec>> = {
  [K in keyof T]: T[K] extends ColumnSpec ? DataTypeMapping[T[K]['type']] : never
}
type MapDataType<T> = T extends Tuple<ColumnSpec> ? MapDataTypeImpl<T> : never;

(credits for initial idea to How to 'map' a Tuple to another Tuple type in Typescript 3.0 )

Also you could notice two step implementation with MapDataType and MapDataTypeImpl here. I do not know exactly why, but it seems that currently it is impossible to merge it into one type.

Last step is to make your columns to be tuple

interface TableWriteStreamOptions {
    columns: Tuple<ColumnSpec>
    ...
}

And finally you could implement your method like that:

export async function createTableWriteStream<T extends TableWriteStreamOptions>(
  path: string, options: FileOptions & T): Promise<TableWriteStream<MapDataType<T['columns']>>> { 
    //  ...
    return new TableWriteStream()
}

And as you requested, now createTableWriteStream can be used without explicit type argument.

Here is a working example .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM