aleung aleung - 2 months ago 26
TypeScript Question

How to declare a Stream-like interface in TypeScript?

The npm package csv-parse implements a Node.JS Stream like API:

const parser = parse();
parser.on('readable', () => {
let record: any; // <-- it should be string[]
while (record = parser.read()) {
output.push(record);
}
});


Its type declaration is:

interface parse {
(options?: options): NodeJS.ReadWriteStream;
}


The return type of
parse
is
NodeJS.ReadWriteStream
, which has a method
read
returning
string | Buffer
.

interface ReadWriteStream extends ReadableStream, WritableStream {
// ...
}

interface ReadableStream extends EventEmitter {
read(size?: number): string | Buffer;
// ...
}


By this definition, I can't define the variable
let record: any = parser.read()
to its actual type
string[]
.

I have tried to modify the type definition of csv-parse like this:

interface ParserStream extends NodeJS.ReadWriteStream {
read(size?: number): string[];
}

interface parse {
(options?: options): ParserStream;
}


It doesn't work because TypeScript not allow to extend an interface and change method's return type.

What I can think of is to change NodeJS Stream to a generic type like:

interface GenericReadableStream <T> extends EventEmitter {
read(size?: number): T
// ...
}

interface ReadableStream extends GenericReadableStream <string | Buffer> {}


Then I can define:

interface ParserStream extends GenericReadableStream <string[]> {}


I don't know if it's a good way. It impacts a lot in current Node.JS type definitions.

Answer

To begin with, CsvParser implementation uses steams in object mode, so string | Buffer isn't really a good return type for CsvParser.read.

Fortunately, TypeScript allows to extend an interface and change method's return type to any. So you can change csv-parse type definition to

  interface CsvParser extends NodeJS.ReadWriteStream {
      read(size?: number): any;

allowing this code to compile

let record: string[];  
while (record = parser.read()) {

But this is not strict enough, because this will compile too:

let record: number[];  
while (record = parser.read()) {

To disallow that, you can use intersection type to express that CsvParser read actually returns an array of strings, while keeping its return type compatible with the declaration in the base class:

  interface CsvParser extends NodeJS.ReadWriteStream {
      read(size?: number): any & string[];

Anyhow, this will require you to modify csv-parser type definitions.

Comments