Skip to content

Commit 10caf6d

Browse files
authored
Add generic type for opaque object (#3385)
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
1 parent 98dae4e commit 10caf6d

File tree

4 files changed

+72
-40
lines changed

4 files changed

+72
-40
lines changed

test/types/api.test-d.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Duplex, Readable, Writable } from 'stream'
2-
import { expectAssignable } from 'tsd'
2+
import { expectAssignable, expectType } from 'tsd'
33
import { Dispatcher, request, stream, pipeline, connect, upgrade } from '../..'
44

55
// request
@@ -10,12 +10,22 @@ expectAssignable<Promise<Dispatcher.ResponseData>>(request('', { method: 'GET',
1010
// stream
1111
expectAssignable<Promise<Dispatcher.StreamData>>(stream('', { method: 'GET' }, data => {
1212
expectAssignable<Dispatcher.StreamFactoryData>(data)
13+
expectType<null>(data.opaque)
14+
return new Writable()
15+
}))
16+
expectAssignable<Promise<Dispatcher.StreamData<{ example: string }>>>(stream('', { method: 'GET', opaque: { example: '' } }, data => {
17+
expectType<{ example: string }>(data.opaque)
1318
return new Writable()
1419
}))
1520

1621
// pipeline
1722
expectAssignable<Duplex>(pipeline('', { method: 'GET' }, data => {
1823
expectAssignable<Dispatcher.PipelineHandlerData>(data)
24+
expectType<null>(data.opaque)
25+
return new Readable()
26+
}))
27+
expectAssignable<Duplex>(pipeline('', { method: 'GET', opaque: { example: '' } }, data => {
28+
expectType<{ example: string }>(data.opaque)
1929
return new Readable()
2030
}))
2131

test/types/dispatcher.test-d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ expectAssignable<Dispatcher>(new Dispatcher())
6666
}))
6767
expectAssignable<Promise<Dispatcher.ResponseData>>(dispatcher.request({ origin: '', path: '', method: 'GET', responseHeaders: 'raw' }))
6868
expectAssignable<Promise<Dispatcher.ResponseData>>(dispatcher.request({ origin: '', path: '', method: 'GET', responseHeaders: null }))
69+
expectAssignable<Promise<Dispatcher.ResponseData<{ example: string }>>>(dispatcher.request({ origin: '', path: '', method: 'GET', opaque: { example: '' } }))
6970

7071
// pipeline
7172
expectAssignable<Duplex>(dispatcher.pipeline({ origin: '', path: '', method: 'GET', maxRedirections: 0 }, data => {
@@ -84,6 +85,11 @@ expectAssignable<Dispatcher>(new Dispatcher())
8485
expectAssignable<Dispatcher.PipelineHandlerData>(data)
8586
return new Readable()
8687
}))
88+
expectAssignable<Duplex>(dispatcher.pipeline({ origin: '', path: '', method: 'GET', opaque: { example: '' } }, data => {
89+
expectAssignable<Dispatcher.PipelineHandlerData<{ example: string }>>(data)
90+
expectType<{ example: string }>(data.opaque)
91+
return new Readable()
92+
}))
8793

8894
// stream
8995
expectAssignable<Promise<Dispatcher.StreamData>>(dispatcher.stream({ origin: '', path: '', method: 'GET', maxRedirections: 0 }, data => {
@@ -94,6 +100,10 @@ expectAssignable<Dispatcher>(new Dispatcher())
94100
expectAssignable<Dispatcher.StreamFactoryData>(data)
95101
return new Writable()
96102
}))
103+
expectAssignable<Promise<Dispatcher.StreamData<{ example: string }>>>(dispatcher.stream({ origin: '', path: '', method: 'GET', opaque: { example: '' } }, data => {
104+
expectType<{ example: string }>(data.opaque);
105+
return new Writable();
106+
}));
97107
expectAssignable<void>(dispatcher.stream(
98108
{ origin: '', path: '', method: 'GET', reset: false },
99109
data => {
@@ -116,6 +126,18 @@ expectAssignable<Dispatcher>(new Dispatcher())
116126
expectAssignable<Dispatcher.StreamData>(data)
117127
}
118128
))
129+
expectAssignable<void>(dispatcher.stream(
130+
{ origin: new URL('http://localhost'), path: '', method: 'GET', opaque: { example: '' } },
131+
data => {
132+
expectAssignable<Dispatcher.StreamFactoryData<{ example: string }>>(data)
133+
return new Writable()
134+
},
135+
(err, data) => {
136+
expectAssignable<Error | null>(err)
137+
expectAssignable<Dispatcher.StreamData<{ example: string }>>(data)
138+
expectType<{ example: string }>(data.opaque)
139+
}
140+
))
119141
expectAssignable<Promise<Dispatcher.StreamData>>(dispatcher.stream({ origin: '', path: '', method: 'GET', responseHeaders: 'raw' }, data => {
120142
expectAssignable<Dispatcher.StreamFactoryData>(data)
121143
return new Writable()

types/api.d.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,30 @@ export {
1111
}
1212

1313
/** Performs an HTTP request. */
14-
declare function request(
14+
declare function request<TOpaque = null>(
1515
url: string | URL | UrlObject,
16-
options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions, 'origin' | 'path' | 'method'> & Partial<Pick<Dispatcher.RequestOptions, 'method'>>,
17-
): Promise<Dispatcher.ResponseData>;
16+
options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions<TOpaque>, 'origin' | 'path' | 'method'> & Partial<Pick<Dispatcher.RequestOptions, 'method'>>,
17+
): Promise<Dispatcher.ResponseData<TOpaque>>;
1818

1919
/** A faster version of `request`. */
20-
declare function stream(
20+
declare function stream<TOpaque = null>(
2121
url: string | URL | UrlObject,
22-
options: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions, 'origin' | 'path'>,
23-
factory: Dispatcher.StreamFactory
24-
): Promise<Dispatcher.StreamData>;
22+
options: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions<TOpaque>, 'origin' | 'path'>,
23+
factory: Dispatcher.StreamFactory<TOpaque>
24+
): Promise<Dispatcher.StreamData<TOpaque>>;
2525

2626
/** For easy use with `stream.pipeline`. */
27-
declare function pipeline(
27+
declare function pipeline<TOpaque = null>(
2828
url: string | URL | UrlObject,
29-
options: { dispatcher?: Dispatcher } & Omit<Dispatcher.PipelineOptions, 'origin' | 'path'>,
30-
handler: Dispatcher.PipelineHandler
29+
options: { dispatcher?: Dispatcher } & Omit<Dispatcher.PipelineOptions<TOpaque>, 'origin' | 'path'>,
30+
handler: Dispatcher.PipelineHandler<TOpaque>
3131
): Duplex;
3232

3333
/** Starts two-way communications with the requested resource. */
34-
declare function connect(
34+
declare function connect<TOpaque = null>(
3535
url: string | URL | UrlObject,
36-
options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.ConnectOptions, 'origin' | 'path'>
37-
): Promise<Dispatcher.ConnectData>;
36+
options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.ConnectOptions<TOpaque>, 'origin' | 'path'>
37+
): Promise<Dispatcher.ConnectData<TOpaque>>;
3838

3939
/** Upgrade to a different protocol. */
4040
declare function upgrade(

types/dispatcher.d.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ declare class Dispatcher extends EventEmitter {
1616
/** 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. */
1717
dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandlers): boolean;
1818
/** Starts two-way communications with the requested resource. */
19-
connect(options: Dispatcher.ConnectOptions): Promise<Dispatcher.ConnectData>;
20-
connect(options: Dispatcher.ConnectOptions, callback: (err: Error | null, data: Dispatcher.ConnectData) => void): void;
19+
connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>): Promise<Dispatcher.ConnectData<TOpaque>>;
20+
connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>, callback: (err: Error | null, data: Dispatcher.ConnectData<TOpaque>) => void): void;
2121
/** Compose a chain of dispatchers */
2222
compose(dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher;
2323
compose(...dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher;
2424
/** Performs an HTTP request. */
25-
request(options: Dispatcher.RequestOptions): Promise<Dispatcher.ResponseData>;
26-
request(options: Dispatcher.RequestOptions, callback: (err: Error | null, data: Dispatcher.ResponseData) => void): void;
25+
request<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>): Promise<Dispatcher.ResponseData<TOpaque>>;
26+
request<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, callback: (err: Error | null, data: Dispatcher.ResponseData<TOpaque>) => void): void;
2727
/** For easy use with `stream.pipeline`. */
28-
pipeline(options: Dispatcher.PipelineOptions, handler: Dispatcher.PipelineHandler): Duplex;
28+
pipeline<TOpaque = null>(options: Dispatcher.PipelineOptions<TOpaque>, handler: Dispatcher.PipelineHandler<TOpaque>): Duplex;
2929
/** A faster version of `Dispatcher.request`. */
30-
stream(options: Dispatcher.RequestOptions, factory: Dispatcher.StreamFactory): Promise<Dispatcher.StreamData>;
31-
stream(options: Dispatcher.RequestOptions, factory: Dispatcher.StreamFactory, callback: (err: Error | null, data: Dispatcher.StreamData) => void): void;
30+
stream<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, factory: Dispatcher.StreamFactory<TOpaque>): Promise<Dispatcher.StreamData<TOpaque>>;
31+
stream<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, factory: Dispatcher.StreamFactory<TOpaque>, callback: (err: Error | null, data: Dispatcher.StreamData<TOpaque>) => void): void;
3232
/** Upgrade to a different protocol. */
3333
upgrade(options: Dispatcher.UpgradeOptions): Promise<Dispatcher.UpgradeData>;
3434
upgrade(options: Dispatcher.UpgradeOptions, callback: (err: Error | null, data: Dispatcher.UpgradeData) => void): void;
@@ -125,25 +125,25 @@ declare namespace Dispatcher {
125125
/** For H2, it appends the expect: 100-continue header, and halts the request body until a 100-continue is received from the remote server*/
126126
expectContinue?: boolean;
127127
}
128-
export interface ConnectOptions {
128+
export interface ConnectOptions<TOpaque = null> {
129129
origin: string | URL;
130130
path: string;
131131
/** Default: `null` */
132132
headers?: IncomingHttpHeaders | string[] | null;
133133
/** Default: `null` */
134134
signal?: AbortSignal | EventEmitter | null;
135135
/** This argument parameter is passed through to `ConnectData` */
136-
opaque?: unknown;
136+
opaque?: TOpaque;
137137
/** Default: 0 */
138138
maxRedirections?: number;
139139
/** Default: false */
140140
redirectionLimitReached?: boolean;
141141
/** Default: `null` */
142142
responseHeaders?: 'raw' | null;
143143
}
144-
export interface RequestOptions extends DispatchOptions {
144+
export interface RequestOptions<TOpaque = null> extends DispatchOptions {
145145
/** Default: `null` */
146-
opaque?: unknown;
146+
opaque?: TOpaque;
147147
/** Default: `null` */
148148
signal?: AbortSignal | EventEmitter | null;
149149
/** Default: 0 */
@@ -157,7 +157,7 @@ declare namespace Dispatcher {
157157
/** Default: `64 KiB` */
158158
highWaterMark?: number;
159159
}
160-
export interface PipelineOptions extends RequestOptions {
160+
export interface PipelineOptions<TOpaque = null> extends RequestOptions<TOpaque> {
161161
/** `true` if the `handler` will return an object stream. Default: `false` */
162162
objectMode?: boolean;
163163
}
@@ -178,43 +178,43 @@ declare namespace Dispatcher {
178178
/** Default: `null` */
179179
responseHeaders?: 'raw' | null;
180180
}
181-
export interface ConnectData {
181+
export interface ConnectData<TOpaque = null> {
182182
statusCode: number;
183183
headers: IncomingHttpHeaders;
184184
socket: Duplex;
185-
opaque: unknown;
185+
opaque: TOpaque;
186186
}
187-
export interface ResponseData {
187+
export interface ResponseData<TOpaque = null> {
188188
statusCode: number;
189189
headers: IncomingHttpHeaders;
190190
body: BodyReadable & BodyMixin;
191191
trailers: Record<string, string>;
192-
opaque: unknown;
192+
opaque: TOpaque;
193193
context: object;
194194
}
195-
export interface PipelineHandlerData {
195+
export interface PipelineHandlerData<TOpaque = null> {
196196
statusCode: number;
197197
headers: IncomingHttpHeaders;
198-
opaque: unknown;
198+
opaque: TOpaque;
199199
body: BodyReadable;
200200
context: object;
201201
}
202-
export interface StreamData {
203-
opaque: unknown;
202+
export interface StreamData<TOpaque = null> {
203+
opaque: TOpaque;
204204
trailers: Record<string, string>;
205205
}
206-
export interface UpgradeData {
206+
export interface UpgradeData<TOpaque = null> {
207207
headers: IncomingHttpHeaders;
208208
socket: Duplex;
209-
opaque: unknown;
209+
opaque: TOpaque;
210210
}
211-
export interface StreamFactoryData {
211+
export interface StreamFactoryData<TOpaque = null> {
212212
statusCode: number;
213213
headers: IncomingHttpHeaders;
214-
opaque: unknown;
214+
opaque: TOpaque;
215215
context: object;
216216
}
217-
export type StreamFactory = (data: StreamFactoryData) => Writable;
217+
export type StreamFactory<TOpaque = null> = (data: StreamFactoryData<TOpaque>) => Writable;
218218
export interface DispatchHandlers {
219219
/** 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. */
220220
onConnect?(abort: (err?: Error) => void): void;
@@ -233,7 +233,7 @@ declare namespace Dispatcher {
233233
/** Invoked when a body chunk is sent to the server. May be invoked multiple times for chunked requests */
234234
onBodySent?(chunkSize: number, totalBytesSent: number): void;
235235
}
236-
export type PipelineHandler = (data: PipelineHandlerData) => Readable;
236+
export type PipelineHandler<TOpaque = null> = (data: PipelineHandlerData<TOpaque>) => Readable;
237237
export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';
238238

239239
/**

0 commit comments

Comments
 (0)