Reputation: 615
I'm trying to implement a transform stream with Node.js and Typescript that emits custom events while processing data. But I'm struggling to get the typing right.
I'm using Node.js 22, typescript
5.7.2, and @types/node
22.10.2.
Here's a rough sketch of the code:
import { Transform, type TransformCallback } from "node:stream";
export interface CustomEventData {
foo: "bar" | "baz"
}
export class CustomTransform extends Transform {
constructor() {
super();
}
_transform(
chunk: Buffer | string,
encoding: BufferEncoding,
callback: TransformCallback,
): void {
const data: CustomEventData = { foo: "bar" };
this.emit("custom-event", data);
callback(null, chunk);
}
}
The goal is to have TypeScript automatically detect the type of the data
parameter in the following scenario as CustomEventData
:
const myTransform = new CustomTransform();
myTransform.on("custom-event", (data) => {
console.log("received custom-event with data", data);
});
The only way I managed to make this work is by using declare interface
as seen below:
declare interface CustomTransform extends Transform {
addListener(event: "custom-event", listener: (data: CustomEventData) => void): this;
addListener(event: "close", listener: () => void): this;
addListener(event: "data", listener: (chunk: any) => void): this;
addListener(event: "drain", listener: () => void): this;
addListener(event: "end", listener: () => void): this;
addListener(event: "error", listener: (err: Error) => void): this;
addListener(event: "finish", listener: () => void): this;
addListener(event: "pause", listener: () => void): this;
addListener(event: "pipe", listener: (src: Readable) => void): this;
addListener(event: "readable", listener: () => void): this;
addListener(event: "resume", listener: () => void): this;
addListener(event: "unpipe", listener: (src: Readable) => void): this;
emit(event: "custom-event", data: CustomEventData): boolean;
emit(event: "close"): boolean;
emit(event: "data", chunk: any): boolean;
emit(event: "drain"): boolean;
emit(event: "end"): boolean;
emit(event: "error", err: Error): boolean;
emit(event: "finish"): boolean;
emit(event: "pause"): boolean;
emit(event: "pipe", src: Readable): boolean;
emit(event: "readable"): boolean;
emit(event: "resume"): boolean;
emit(event: "unpipe", src: Readable): boolean;
on(event: "custom-event", listener: (data: CustomEventData) => void): this;
on(event: "close", listener: () => void): this;
on(event: "data", listener: (chunk: any) => void): this;
on(event: "drain", listener: () => void): this;
on(event: "end", listener: () => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: "finish", listener: () => void): this;
on(event: "pause", listener: () => void): this;
on(event: "pipe", listener: (src: Readable) => void): this;
on(event: "readable", listener: () => void): this;
on(event: "resume", listener: () => void): this;
on(event: "unpipe", listener: (src: Readable) => void): this;
once(event: "custom-event", listener: (data: CustomEventData) => void): this;
once(event: "close", listener: () => void): this;
once(event: "data", listener: (chunk: any) => void): this;
once(event: "drain", listener: () => void): this;
once(event: "end", listener: () => void): this;
once(event: "error", listener: (err: Error) => void): this;
once(event: "finish", listener: () => void): this;
once(event: "pause", listener: () => void): this;
once(event: "pipe", listener: (src: Readable) => void): this;
once(event: "readable", listener: () => void): this;
once(event: "resume", listener: () => void): this;
once(event: "unpipe", listener: (src: Readable) => void): this;
prependListener(event: "custom-event", listener: (data: CustomEventData) => void): this;
prependListener(event: "close", listener: () => void): this;
prependListener(event: "data", listener: (chunk: any) => void): this;
prependListener(event: "drain", listener: () => void): this;
prependListener(event: "end", listener: () => void): this;
prependListener(event: "error", listener: (err: Error) => void): this;
prependListener(event: "finish", listener: () => void): this;
prependListener(event: "pause", listener: () => void): this;
prependListener(event: "pipe", listener: (src: Readable) => void): this;
prependListener(event: "readable", listener: () => void): this;
prependListener(event: "resume", listener: () => void): this;
prependListener(event: "unpipe", listener: (src: Readable) => void): this;
prependOnceListener(event: "custom-event", listener: (data: CustomEventData) => void): this;
prependOnceListener(event: "close", listener: () => void): this;
prependOnceListener(event: "data", listener: (chunk: any) => void): this;
prependOnceListener(event: "drain", listener: () => void): this;
prependOnceListener(event: "end", listener: () => void): this;
prependOnceListener(event: "error", listener: (err: Error) => void): this;
prependOnceListener(event: "finish", listener: () => void): this;
prependOnceListener(event: "pause", listener: () => void): this;
prependOnceListener(event: "pipe", listener: (src: Readable) => void): this;
prependOnceListener(event: "readable", listener: () => void): this;
prependOnceListener(event: "resume", listener: () => void): this;
prependOnceListener(event: "unpipe", listener: (src: Readable) => void): this;
removeListener(event: "custom-event", listener: (data: CustomEventData) => void): this;
removeListener(event: "close", listener: () => void): this;
removeListener(event: "data", listener: (chunk: any) => void): this;
removeListener(event: "drain", listener: () => void): this;
removeListener(event: "end", listener: () => void): this;
removeListener(event: "error", listener: (err: Error) => void): this;
removeListener(event: "finish", listener: () => void): this;
removeListener(event: "pause", listener: () => void): this;
removeListener(event: "pipe", listener: (src: Readable) => void): this;
removeListener(event: "readable", listener: () => void): this;
removeListener(event: "resume", listener: () => void): this;
removeListener(event: "unpipe", listener: (src: Readable) => void): this;
}
As you can see, this a long and awkward solution. Unfortunately, TypeScript does not allow me to omit the event methods that are already declared on the base class Transform
.
Any ideas how to simplify the code?
Upvotes: 2
Views: 72