From 3e52a161de03e702f0fba183ce22ef328e27d2ba Mon Sep 17 00:00:00 2001 From: jfhr Date: Mon, 1 Jul 2024 22:29:07 +0200 Subject: [PATCH] Add generic type for opaque object This adds a `TOpaque` generic type parameter to the type definitions for request(), connect(), stream(), and pipeline(). The type parameter defaults to null, which is the default value of the opaque property. If an opaque value is passed in the options, its type can usually be inferred automatically, such that no explicit type declaration is necessary. This commit also adds tsd tests to make sure the type definitions work as expected. Previously, the type of `opaque` was `unknown`, which means it needed to be either type-checked or casted to another type before anything could be done with it. Such code should not be broken by this commit, although some type checks or assertions might become redundant. Code that disabled type checks (e.g. by casting to `any` or using `@ts-ignore` should be unaffected. Code that does not use typescript at all is also unaffected. This closes #3378 --- test/types/api.test-d.ts | 12 +++++++- test/types/dispatcher.test-d.ts | 22 ++++++++++++++ types/api.d.ts | 26 ++++++++--------- types/dispatcher.d.ts | 52 ++++++++++++++++----------------- 4 files changed, 72 insertions(+), 40 deletions(-) diff --git a/test/types/api.test-d.ts b/test/types/api.test-d.ts index c64b1315018..85d5e6723a5 100644 --- a/test/types/api.test-d.ts +++ b/test/types/api.test-d.ts @@ -1,5 +1,5 @@ import { Duplex, Readable, Writable } from 'stream' -import { expectAssignable } from 'tsd' +import { expectAssignable, expectType } from 'tsd' import { Dispatcher, request, stream, pipeline, connect, upgrade } from '../..' // request @@ -10,12 +10,22 @@ expectAssignable>(request('', { method: 'GET', // stream expectAssignable>(stream('', { method: 'GET' }, data => { expectAssignable(data) + expectType(data.opaque) + return new Writable() +})) +expectAssignable>>(stream('', { method: 'GET', opaque: { example: '' } }, data => { + expectType<{ example: string }>(data.opaque) return new Writable() })) // pipeline expectAssignable(pipeline('', { method: 'GET' }, data => { expectAssignable(data) + expectType(data.opaque) + return new Readable() +})) +expectAssignable(pipeline('', { method: 'GET', opaque: { example: '' } }, data => { + expectType<{ example: string }>(data.opaque) return new Readable() })) diff --git a/test/types/dispatcher.test-d.ts b/test/types/dispatcher.test-d.ts index 057cfe66d8f..303abd0ade7 100644 --- a/test/types/dispatcher.test-d.ts +++ b/test/types/dispatcher.test-d.ts @@ -66,6 +66,7 @@ expectAssignable(new Dispatcher()) })) expectAssignable>(dispatcher.request({ origin: '', path: '', method: 'GET', responseHeaders: 'raw' })) expectAssignable>(dispatcher.request({ origin: '', path: '', method: 'GET', responseHeaders: null })) + expectAssignable>>(dispatcher.request({ origin: '', path: '', method: 'GET', opaque: { example: '' } })) // pipeline expectAssignable(dispatcher.pipeline({ origin: '', path: '', method: 'GET', maxRedirections: 0 }, data => { @@ -84,6 +85,11 @@ expectAssignable(new Dispatcher()) expectAssignable(data) return new Readable() })) + expectAssignable(dispatcher.pipeline({ origin: '', path: '', method: 'GET', opaque: { example: '' } }, data => { + expectAssignable>(data) + expectType<{ example: string }>(data.opaque) + return new Readable() + })) // stream expectAssignable>(dispatcher.stream({ origin: '', path: '', method: 'GET', maxRedirections: 0 }, data => { @@ -94,6 +100,10 @@ expectAssignable(new Dispatcher()) expectAssignable(data) return new Writable() })) + expectAssignable>>(dispatcher.stream({ origin: '', path: '', method: 'GET', opaque: { example: '' } }, data => { + expectType<{ example: string }>(data.opaque); + return new Writable(); + })); expectAssignable(dispatcher.stream( { origin: '', path: '', method: 'GET', reset: false }, data => { @@ -116,6 +126,18 @@ expectAssignable(new Dispatcher()) expectAssignable(data) } )) + expectAssignable(dispatcher.stream( + { origin: new URL('http://localhost'), path: '', method: 'GET', opaque: { example: '' } }, + data => { + expectAssignable>(data) + return new Writable() + }, + (err, data) => { + expectAssignable(err) + expectAssignable>(data) + expectType<{ example: string }>(data.opaque) + } + )) expectAssignable>(dispatcher.stream({ origin: '', path: '', method: 'GET', responseHeaders: 'raw' }, data => { expectAssignable(data) return new Writable() diff --git a/types/api.d.ts b/types/api.d.ts index 400341dddc0..4fbebb6cd2e 100644 --- a/types/api.d.ts +++ b/types/api.d.ts @@ -11,30 +11,30 @@ export { } /** Performs an HTTP request. */ -declare function request( +declare function request( url: string | URL | UrlObject, - options?: { dispatcher?: Dispatcher } & Omit & Partial>, -): Promise; + options?: { dispatcher?: Dispatcher } & Omit, 'origin' | 'path' | 'method'> & Partial>, +): Promise>; /** A faster version of `request`. */ -declare function stream( +declare function stream( url: string | URL | UrlObject, - options: { dispatcher?: Dispatcher } & Omit, - factory: Dispatcher.StreamFactory -): Promise; + options: { dispatcher?: Dispatcher } & Omit, 'origin' | 'path'>, + factory: Dispatcher.StreamFactory +): Promise>; /** For easy use with `stream.pipeline`. */ -declare function pipeline( +declare function pipeline( url: string | URL | UrlObject, - options: { dispatcher?: Dispatcher } & Omit, - handler: Dispatcher.PipelineHandler + options: { dispatcher?: Dispatcher } & Omit, 'origin' | 'path'>, + handler: Dispatcher.PipelineHandler ): Duplex; /** Starts two-way communications with the requested resource. */ -declare function connect( +declare function connect( url: string | URL | UrlObject, - options?: { dispatcher?: Dispatcher } & Omit -): Promise; + options?: { dispatcher?: Dispatcher } & Omit, 'origin' | 'path'> +): Promise>; /** Upgrade to a different protocol. */ declare function upgrade( diff --git a/types/dispatcher.d.ts b/types/dispatcher.d.ts index 05f0093c3fd..6a6e96cfbd3 100644 --- a/types/dispatcher.d.ts +++ b/types/dispatcher.d.ts @@ -16,19 +16,19 @@ declare class Dispatcher extends EventEmitter { /** Dispatches a request. This API is expected to evolve through semver-major versions and is less stable than the preceding higher level APIs. It is primarily intended for library developers who implement higher level APIs on top of this. */ dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandlers): boolean; /** Starts two-way communications with the requested resource. */ - connect(options: Dispatcher.ConnectOptions): Promise; - connect(options: Dispatcher.ConnectOptions, callback: (err: Error | null, data: Dispatcher.ConnectData) => void): void; + connect(options: Dispatcher.ConnectOptions): Promise>; + connect(options: Dispatcher.ConnectOptions, callback: (err: Error | null, data: Dispatcher.ConnectData) => void): void; /** Compose a chain of dispatchers */ compose(dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher; compose(...dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher; /** Performs an HTTP request. */ - request(options: Dispatcher.RequestOptions): Promise; - request(options: Dispatcher.RequestOptions, callback: (err: Error | null, data: Dispatcher.ResponseData) => void): void; + request(options: Dispatcher.RequestOptions): Promise>; + request(options: Dispatcher.RequestOptions, callback: (err: Error | null, data: Dispatcher.ResponseData) => void): void; /** For easy use with `stream.pipeline`. */ - pipeline(options: Dispatcher.PipelineOptions, handler: Dispatcher.PipelineHandler): Duplex; + pipeline(options: Dispatcher.PipelineOptions, handler: Dispatcher.PipelineHandler): Duplex; /** A faster version of `Dispatcher.request`. */ - stream(options: Dispatcher.RequestOptions, factory: Dispatcher.StreamFactory): Promise; - stream(options: Dispatcher.RequestOptions, factory: Dispatcher.StreamFactory, callback: (err: Error | null, data: Dispatcher.StreamData) => void): void; + stream(options: Dispatcher.RequestOptions, factory: Dispatcher.StreamFactory): Promise>; + stream(options: Dispatcher.RequestOptions, factory: Dispatcher.StreamFactory, callback: (err: Error | null, data: Dispatcher.StreamData) => void): void; /** Upgrade to a different protocol. */ upgrade(options: Dispatcher.UpgradeOptions): Promise; upgrade(options: Dispatcher.UpgradeOptions, callback: (err: Error | null, data: Dispatcher.UpgradeData) => void): void; @@ -125,7 +125,7 @@ declare namespace Dispatcher { /** For H2, it appends the expect: 100-continue header, and halts the request body until a 100-continue is received from the remote server*/ expectContinue?: boolean; } - export interface ConnectOptions { + export interface ConnectOptions { origin: string | URL; path: string; /** Default: `null` */ @@ -133,7 +133,7 @@ declare namespace Dispatcher { /** Default: `null` */ signal?: AbortSignal | EventEmitter | null; /** This argument parameter is passed through to `ConnectData` */ - opaque?: unknown; + opaque?: TOpaque; /** Default: 0 */ maxRedirections?: number; /** Default: false */ @@ -141,9 +141,9 @@ declare namespace Dispatcher { /** Default: `null` */ responseHeaders?: 'raw' | null; } - export interface RequestOptions extends DispatchOptions { + export interface RequestOptions extends DispatchOptions { /** Default: `null` */ - opaque?: unknown; + opaque?: TOpaque; /** Default: `null` */ signal?: AbortSignal | EventEmitter | null; /** Default: 0 */ @@ -157,7 +157,7 @@ declare namespace Dispatcher { /** Default: `64 KiB` */ highWaterMark?: number; } - export interface PipelineOptions extends RequestOptions { + export interface PipelineOptions extends RequestOptions { /** `true` if the `handler` will return an object stream. Default: `false` */ objectMode?: boolean; } @@ -178,43 +178,43 @@ declare namespace Dispatcher { /** Default: `null` */ responseHeaders?: 'raw' | null; } - export interface ConnectData { + export interface ConnectData { statusCode: number; headers: IncomingHttpHeaders; socket: Duplex; - opaque: unknown; + opaque: TOpaque; } - export interface ResponseData { + export interface ResponseData { statusCode: number; headers: IncomingHttpHeaders; body: BodyReadable & BodyMixin; trailers: Record; - opaque: unknown; + opaque: TOpaque; context: object; } - export interface PipelineHandlerData { + export interface PipelineHandlerData { statusCode: number; headers: IncomingHttpHeaders; - opaque: unknown; + opaque: TOpaque; body: BodyReadable; context: object; } - export interface StreamData { - opaque: unknown; + export interface StreamData { + opaque: TOpaque; trailers: Record; } - export interface UpgradeData { + export interface UpgradeData { headers: IncomingHttpHeaders; socket: Duplex; - opaque: unknown; + opaque: TOpaque; } - export interface StreamFactoryData { + export interface StreamFactoryData { statusCode: number; headers: IncomingHttpHeaders; - opaque: unknown; + opaque: TOpaque; context: object; } - export type StreamFactory = (data: StreamFactoryData) => Writable; + export type StreamFactory = (data: StreamFactoryData) => Writable; export interface DispatchHandlers { /** Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails. */ onConnect?(abort: (err?: Error) => void): void; @@ -233,7 +233,7 @@ declare namespace Dispatcher { /** Invoked when a body chunk is sent to the server. May be invoked multiple times for chunked requests */ onBodySent?(chunkSize: number, totalBytesSent: number): void; } - export type PipelineHandler = (data: PipelineHandlerData) => Readable; + export type PipelineHandler = (data: PipelineHandlerData) => Readable; export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH'; /**