From beddc9f5a90c2b3538070032b807ddf6ec64e447 Mon Sep 17 00:00:00 2001 From: Tony Wooster Date: Thu, 14 Mar 2019 20:35:18 +0100 Subject: [PATCH] Add types for centra and phin (#33887) --- types/centra/centra-tests.ts | 52 +++++++++++ types/centra/index.d.ts | 54 +++++++++++ types/centra/tsconfig.json | 23 +++++ types/centra/tslint.json | 1 + types/phin/index.d.ts | 123 +++++++++++++++++++++++++ types/phin/phin-tests.ts | 172 +++++++++++++++++++++++++++++++++++ types/phin/tsconfig.json | 23 +++++ types/phin/tslint.json | 1 + 8 files changed, 449 insertions(+) create mode 100644 types/centra/centra-tests.ts create mode 100644 types/centra/index.d.ts create mode 100644 types/centra/tsconfig.json create mode 100644 types/centra/tslint.json create mode 100644 types/phin/index.d.ts create mode 100644 types/phin/phin-tests.ts create mode 100644 types/phin/tsconfig.json create mode 100644 types/phin/tslint.json diff --git a/types/centra/centra-tests.ts b/types/centra/centra-tests.ts new file mode 100644 index 0000000000..908d94b41b --- /dev/null +++ b/types/centra/centra-tests.ts @@ -0,0 +1,52 @@ +import centra = require('centra'); +import { URL } from 'url'; + +function typeTests() { + // Test chaining + centra('http://google.com') + .query('param', 'value') + .path('mail') + .body({ a: 1, b: 2 }) + .header({ a: 'b' }) + .header('a', 'b') + .timeout(123) + .stream() + .compress() + .option('host', '123') + .option('port', 123) + .send().then(resp => { + resp.json().then((s: string) => s); + resp.text().then((s: string) => s); + resp.statusCode; + resp.coreRes; + resp.headers['asdf']; + resp.body.buffer.byteLength; + }); + + centra(new URL('google.com')) + .query('page', '2'); +} + +async function functionalTests() { + await centra('http://example.com').send().then(async r => { + if (r.statusCode !== 200) { + throw new Error('Bad status code'); + } + r.text().then(t => t); + }); + + await centra(new URL('https://jsonplaceholder.typicode.com/todos')) + .query('userId', 1) + .timeout(1000) + .compress() + .send() + .then(r => { + r.json().then((todos: any[]) => { + if (!todos.every(t => t.userId === 1)) { + throw new Error('Query params did not work'); + } + }); + }); +} + +functionalTests(); diff --git a/types/centra/index.d.ts b/types/centra/index.d.ts new file mode 100644 index 0000000000..ae08d8ac2b --- /dev/null +++ b/types/centra/index.d.ts @@ -0,0 +1,54 @@ +// Type definitions for centra 2.2 +// Project: https://github.com/ethanent/centra +// Definitions by: Tony Wooster +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.1 + +/// + +import { RequestOptions, IncomingMessage } from 'http'; +import { URL } from 'url'; + +interface CentraFactory { + (url: URL | string, method?: string): Centra.Request; +} + +declare const Centra: CentraFactory; + +declare namespace Centra { + interface Response { + coreRes: IncomingMessage; + headers: IncomingMessage['headers']; + statusCode: IncomingMessage['statusCode']; + body: Buffer; + + json(): Promise; + text(): Promise; + } + + interface Request { + url: URL; + method: string; + data: string | Buffer | null; + sendDataAs: 'form' | 'json' | 'buffer' | null; + reqHeaders: { [k: string]: string }; + streamEnabled: boolean; + compressionEnabled: boolean; + timeoutTime: number | null; + coreOptions: RequestOptions; + + query(key: string, value: any): this; + query(params: { [k: string]: any }): this; + path(relativePath: string): this; + body(data: any, sendAs?: 'json' | 'buffer' | 'form'): this; + header(key: string, value: string): this; + header(headers: { [k: string]: string }): this; + timeout(timeMs: number): this; + option(key: T, value: RequestOptions[T]): this; + stream(): this; + compress(): this; + send(): Promise; + } +} + +export = Centra; diff --git a/types/centra/tsconfig.json b/types/centra/tsconfig.json new file mode 100644 index 0000000000..6d690a3120 --- /dev/null +++ b/types/centra/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "centra-tests.ts" + ] +} diff --git a/types/centra/tslint.json b/types/centra/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/centra/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" } diff --git a/types/phin/index.d.ts b/types/phin/index.d.ts new file mode 100644 index 0000000000..00130597e5 --- /dev/null +++ b/types/phin/index.d.ts @@ -0,0 +1,123 @@ +// Type definitions for phin 3.3 +// Project: https://github.com/ethanent/phin +// Definitions by: Tony Wooster +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 3.0 + +/// + +import { ClientRequestArgs, IncomingMessage } from 'http'; +import { URL } from 'url'; +import { Response as CentraResponse } from 'centra'; + +type Options = Phin.Options; +type BufferResponse = Phin.BufferResponse; +type StreamResponse = Phin.StreamResponse; +type JsonResponse = Phin.JsonResponse; +type DefaultOpts = Phin.DefaultOpts; +type BatchResponse = JsonResponse | BufferResponse; +type AnyResponse = BatchResponse | StreamResponse; + +interface PhinFactory { + (optsOrUrl: (Options & { parse?: 'none', stream?: false }) | string): Promise; + (optsOrUrl: Options & { parse: 'json', stream?: false }): Promise; + (optsOrUrl: Options & { stream: true }): Promise; + (optsOrUrl: Options): Promise; + + promisified(optsOrUrl: (Options & { parse?: 'none', stream?: false }) | string): Promise; + promisified(optsOrUrl: Options & { parse: 'json', stream?: false }): Promise; + promisified(optsOrUrl: Options & { stream: true }): Promise; + promisified(optsOrUrl: Options): Promise; + + unpromisified(optsOrUrl: (Options & { parse?: 'none', stream?: false }) | string, cb: (err: unknown, resp: BufferResponse) => unknown): void; + unpromisified(optsOrUrl: Options & { parse: 'json', stream?: false }, cb: (err: unknown, resp: JsonResponse) => unknown): void; + unpromisified(optsOrUrl: Options & { stream: true }, cb: (err: unknown, resp: StreamResponse) => unknown): void; + unpromisified(optsOrUrl: Options, cb: (err: unknown, resp: AnyResponse) => unknown): void; + + defaults(defaultOpts: DefaultOpts & { parse: 'json', stream?: false }): + { + (optsOrUrl: Options & { stream: true }): Promise + (optsOrUrl: Options & { parse: 'none', stream?: false }): Promise + (optsOrUrl: (Options & { parse?: 'json', stream?: false }) | string): Promise + (optsOrUrl: Options): Promise + }; + + defaults(defaultOpts: DefaultOpts & { parse?: 'none', stream?: false }): + { + (optsOrUrl: Options & { stream: true }): Promise + (optsOrUrl: Options & { parse: 'json', stream?: false }): Promise + (optsOrUrl: (Options & { parse?: 'none', stream?: false }) | string): Promise + (optsOrUrl: Options): Promise + }; + + defaults(defaultOpts: DefaultOpts & { parse: 'json', stream: true }): + { + (optsOrUrl: Options & { parse: 'none', stream: false }): Promise + (optsOrUrl: Options & { parse?: 'json', stream: false }): Promise + (optsOrUrl: (Options & { stream?: true }) | string): Promise + (optsOrUrl: Options | string): Promise + }; + + defaults(defaultOpts: DefaultOpts & { parse?: 'none', stream: true }): + { + (opts: Options & { parse: 'json', stream: false }): Promise + (opts: Options & { parse?: 'none', stream: false }): Promise + (opts: (Options & { stream?: true }) | string): Promise + (opts: Options): Promise + }; + + defaults(defaultOpts: DefaultOpts): + { + (optsOrUrl: Options & { stream: true }): Promise + (optsOrUrl: Options & { parse: 'json', stream: false }): Promise + (optsOrUrl: Options & { parse: 'none', stream: false }): Promise + (optsOrUrl: Options | string): Promise + }; +} + +declare const Phin: PhinFactory; + +declare namespace Phin { + interface DefaultOpts { + url?: string; + method?: string; + data?: any; + form?: { [k: string]: string }; + headers?: { [k: string]: string }; + core?: ClientRequestArgs; + parse?: 'none' | 'json'; + followRedirects?: boolean; + compression?: boolean; + timeout?: number | null; + hostname?: string; + port?: number; + path?: string; + stream?: boolean; + } + + interface Options extends DefaultOpts { + url: string; + } + + interface BatchOptions extends Options { + stream: false; + } + + interface StreamOptions extends Options { + stream: true; + } + + interface StreamResponse extends IncomingMessage { + stream: StreamResponse; + } + + interface BufferResponse extends IncomingMessage { + body: Buffer; + } + + interface JsonResponse extends IncomingMessage { + body: any; + } +} + +export = Phin; diff --git a/types/phin/phin-tests.ts b/types/phin/phin-tests.ts new file mode 100644 index 0000000000..9c42e2e128 --- /dev/null +++ b/types/phin/phin-tests.ts @@ -0,0 +1,172 @@ +import phin = require('phin'); + +export function typeTests() { + function testParseNoneStreamFalse(o: { url: string, parse?: 'none', stream?: false } | string) { + phin(o); // $ExpectType Promise + } + + function testParseJsonStreamFalse(o: { url: string, parse: 'json', stream?: false }) { + phin(o); // $ExpectType Promise + } + + function testStreamTrue(o: { url: string, parse?: 'json' | 'none', stream: true }) { + phin(o); // $ExpectType Promise + } + + function testCallbackParseNoneStreamFalse(o: { url: string, parse?: 'none', stream?: false } | string) { + phin.unpromisified(o, (_err, r) => { + r; // $ExpectType BufferResponse + }); + } + + function testCallbackParseJsonStreamFalse(o: { url: string, parse: 'json', stream?: false }) { + phin.unpromisified(o, (_err, r) => { + r; // $ExpectType JsonResponse + }); + } + + function testCallbackStreamTrue(o: { url: string, parse?: 'json' | 'none', stream: true }) { + phin.unpromisified(o, (_err, r) => { + r; // $ExpectType StreamResponse + }); + } + + function testCallbackAny(o: { url: string, parse?: 'json' | 'none', stream?: boolean }) { + phin.unpromisified(o, (_err, r) => { + r; // $ExpectType AnyResponse + }); + } + + function testDefaultsParseNoneStreamFalse(d: { parse?: 'none', stream?: false }) { + const p = phin.defaults(d); + return { + testSame(o: phin.Options & { parse?: 'none', stream?: false } | string) { + p(o); // $ExpectType Promise + }, + + testJson(o: phin.Options & { parse: 'json', stream?: false }) { + p(o); // $ExpectType Promise + }, + + testStream(o: phin.Options & { stream: true }) { + p(o); // $ExpectType Promise + }, + + testOthers(o: phin.Options) { + p(o); // $ExpectType Promise + } + }; + } + + function testDefaultsParseJsonStreamFalse(d: { parse: 'json', stream?: false }) { + const p = phin.defaults(d); + return { + testSame(o: phin.Options & { parse: 'none', stream?: false }) { + p(o); // $ExpectType Promise + }, + + testJson(o: phin.Options & { parse?: 'json', stream?: false } | string) { + p(o); // $ExpectType Promise + }, + + testStream(o: phin.Options & { stream: true }) { + p(o); // $ExpectType Promise + }, + + testOthers(o: phin.Options) { + p(o); // $ExpectType Promise + } + }; + } + + function testDefaultsParseJsonStreamTrue(d: { parse: 'json', stream: true }) { + const p = phin.defaults(d); + return { + testSame(o: phin.Options & { parse: 'none', stream: false }) { + p(o); // $ExpectType Promise + }, + + testJson(o: phin.Options & { parse?: 'json', stream: false }) { + p(o); // $ExpectType Promise + }, + + testStream(o: phin.Options & { stream?: true } | string) { + p(o); // $ExpectType Promise + }, + + testOthers(o: phin.Options) { + p(o); // $ExpectType Promise + } + }; + } + + function testDefaultsParseNoneStreamTrue(d: { parse?: 'none', stream: true }) { + const p = phin.defaults(d); + return { + testSame(o: phin.Options & { parse?: 'none', stream: false }) { + p(o); // $ExpectType Promise + }, + + testJson(o: phin.Options & { parse: 'json', stream: false }) { + p(o); // $ExpectType Promise + }, + + testStream(o: phin.Options & { stream?: true } | string) { + p(o); // $ExpectType Promise + }, + + testOthers(o: phin.Options) { + p(o); // $ExpectType Promise + } + }; + } + + function testDefaultsAny(d: { parse?: 'none' | 'json', stream?: boolean }) { + const p = phin.defaults(d); + return { + testSame(o: phin.Options & { parse: 'none', stream: false }) { + p(o); // $ExpectType Promise + }, + + testJson(o: phin.Options & { parse: 'json', stream: false }) { + p(o); // $ExpectType Promise + }, + + testStream(o: phin.Options & { stream: true }) { + p(o); // $ExpectType Promise + }, + + testOthers(o: phin.Options | string) { + p(o); // $ExpectType Promise + } + }; + } +} + +async function functionalTests() { + await phin('http://example.com').then(r => { + if (!Buffer.isBuffer(r.body)) { throw new Error('Not a buffer?'); } + }); + + await phin('http://placekitten.com/50/50').then(r => { + if (r.headers['content-type'] !== 'image/jpeg') { + throw new Error('expected a jpeg'); + } + }); + + await phin({ url: 'https://jsonplaceholder.typicode.com/todos/1', parse: 'json' }).then(r => { + if (typeof r.body === 'string') { + throw new Error('Did not parse json'); + } + + if (typeof r.body.userId !== 'number') { + throw new Error('Did not get userId'); + } + }); + + await phin({ url: 'https://jsonplaceholder.typicode.com/todos/1', stream: true }).then(r => { + r.on('end', () => {}); + }); +} + +functionalTests(); diff --git a/types/phin/tsconfig.json b/types/phin/tsconfig.json new file mode 100644 index 0000000000..3d4214514a --- /dev/null +++ b/types/phin/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "phin-tests.ts" + ] +} diff --git a/types/phin/tslint.json b/types/phin/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/phin/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }