diff --git a/types/needle/index.d.ts b/types/needle/index.d.ts index 8cc61f2a2f..cc3fa4ff32 100644 --- a/types/needle/index.d.ts +++ b/types/needle/index.d.ts @@ -1,118 +1,327 @@ -// Type definitions for needle 1.4 +// Type definitions for needle 2.0 // Project: https://github.com/tomas/needle -// Definitions by: San Chen , Niklas Mollenhauer +// Definitions by: San Chen , Niklas Mollenhauer , Matanel Sindilevich // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.2 /// -declare module "needle" { - import * as http from 'http'; - import * as Buffer from 'buffer'; - import * as https from 'https'; - namespace Needle { - interface NeedleResponse extends http.IncomingMessage { - body: any; - raw: Buffer; - bytes: number; - } +import * as http from 'http'; +import * as Buffer from 'buffer'; +import * as https from 'https'; - type ReadableStream = NodeJS.ReadableStream; - - type NeedleCallback = (error: Error, response: NeedleResponse, body: any) => void; - - interface Cookies { - [name: string]: any; - } - - type NeedleOptions = RequestOptions & ResponseOptions & RedirectOptions & https.RequestOptions; - - interface RequestOptions { - open_timeout?: number; - read_timeout?: number; - /** - * Alias for open_timeout - */ - timeout?: number; - - follow_max?: number; - /** - * Alias for follow_max - */ - follow?: number; - - multipart?: boolean; - agent?: http.Agent | boolean; - proxy?: string; - headers?: {}; - auth?: "auto" | "digest" | "basic"; - json?: boolean; - - // These properties are overwritten by those in the 'headers' field - cookies?: Cookies; - compressed?: boolean; - // Overwritten if present in the URI - username?: string; - password?: string; - accept?: string; - connection?: string; - user_agent?: string; - } - - interface ResponseOptions { - decode_response?: boolean; - /** - * Alias for decode_response - */ - decode?: boolean; - parse_response?: boolean; - /** - * Alias for parse_response - */ - parse?: boolean; - - parse_cookies?: boolean; - output?: string; - } - - interface RedirectOptions { - follow_set_cookie?: boolean; - follow_set_referer?: boolean; - follow_keep_method?: boolean; - follow_if_same_host?: boolean; - follow_if_same_protocol?: boolean; - } - - interface KeyValue { - [key: string]: any; - } - - type BodyData = Buffer | KeyValue | NodeJS.ReadableStream | string | null; - - interface NeedleStatic { - defaults(options: NeedleOptions): void; - - head(url: string, callback?: NeedleCallback): ReadableStream; - head(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; - - get(url: string, callback?: NeedleCallback): ReadableStream; - get(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; - - post(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; - post(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; - - put(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; - put(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; - - patch(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; - patch(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; - - delete(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; - delete(url: string, data: BodyData, options?: NeedleOptions, callback ?: NeedleCallback): ReadableStream; - - request(method: string, url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; - request(method: string, url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; - } +declare namespace core { + interface NeedleResponse extends http.IncomingMessage { + body: any; + raw: Buffer; + bytes: number; } - const needle: Needle.NeedleStatic; - export = needle; + + type ReadableStream = NodeJS.ReadableStream; + + type NeedleCallback = (error: Error, response: NeedleResponse, body: any) => void; + + interface Cookies { + [name: string]: any; + } + + type NeedleOptions = RequestOptions & ResponseOptions & RedirectOptions & https.RequestOptions; + + type NeedleReadonlyHttpVerbs = 'get' | 'head'; + + type NeedleReadWriteHttpVerbs = 'delete' | 'patch' | 'post' | 'put'; + + type NeedleHttpVerbs = NeedleReadonlyHttpVerbs | NeedleReadWriteHttpVerbs; + + interface RequestOptions { + /** + * Returns error if connection takes longer than X milisecs to establish. + * Defaults to 10000 (10 secs). 0 means no timeout. + */ + open_timeout?: number; + /** + * Alias for open_timeout + */ + timeout?: number; + + /** + * Returns error if data transfer takes longer than X milisecs, + * after connection is established. Defaults to 0 (no timeout). + */ + read_timeout?: number; + /** + * Number of redirects to follow. Defaults to 0. + */ + follow_max?: number; + /** + * Alias for follow_max + */ + follow?: number; + + /** + * Enables multipart/form-data encoding. Defaults to false. + * Use it when uploading files. + */ + multipart?: boolean; + /** + * Uses an http.Agent of your choice, instead of the global, default one. + * Useful for tweaking the behaviour at the connection level, such as when doing tunneling. + */ + agent?: http.Agent | boolean; + /** + * Forwards request through HTTP(s) proxy. + * Eg. proxy: 'http://user:pass@proxy.server.com:3128'. + * For more advanced proxying/tunneling use a custom agent. + */ + proxy?: string; + /** + * Object containing custom HTTP headers for request. + */ + headers?: {}; + /** + * Determines what to do with provided username/password. + * Options are auto, digest or basic (default). + * auto will detect the type of authentication depending on the response headers. + */ + auth?: "auto" | "digest" | "basic"; + /** + * When true, sets content type to application/json and sends request body as JSON string, + * instead of a query string. + */ + json?: boolean; + /** + * When sending streams, this lets manually set the Content-Length header + * --if the stream's bytecount is known beforehand--, + * preventing ECONNRESET (socket hang up) errors on some servers that misbehave + * when receiving payloads of unknown size. + * Set it to 0 and Needle will get and set the stream's length, + * or leave unset for the default behavior, + * which is no Content-Length header for stream payloads. + */ + stream_length?: number; + + // These properties are overwritten by those in the 'headers' field + /** + * Builds and sets a Cookie header from a { key: 'value' } object. + */ + cookies?: Cookies; + /** + * If true, sets 'Accept-Encoding' header to 'gzip,deflate', + * and inflates content if zipped. + * Defaults to false. + */ + compressed?: boolean; + // Overwritten if present in the URI + /** + * For HTTP basic auth. + */ + username?: string; + /** + * For HTTP basic auth. Requires username to be passed, but is optional. + */ + password?: string; + /** + * Sets 'Accept' HTTP header. Defaults to */*. + */ + accept?: string; + /** + * Sets 'Connection' HTTP header. + * Not set by default, unless running Node < 0.11.4 + * in which case it defaults to close. + */ + connection?: string; + /** + * Sets the 'User-Agent' HTTP header. + * Defaults to Needle/{version} (Node.js {node_version}). + */ + user_agent?: string; + /** + * Sets the 'Content-Type' header. + * Unset by default, unless you're sending data + * in which case it's set accordingly to whatever is being sent + * (application/x-www-form-urlencoded, application/json or multipart/form-data). + * That is, of course, unless the option is passed, + * either here or through options.headers. + */ + content_type?: string; + } + + interface ResponseOptions { + /** + * Whether to decode the text responses to UTF-8, + * if Content-Type header shows a different charset. Defaults to true. + */ + decode_response?: boolean; + /** + * Alias for decode_response + */ + decode?: boolean; + + /** + * Whether to parse XML or JSON response bodies automagically. + * Defaults to true. + * You can also set this to 'xml' or 'json' in which case Needle + * will only parse the response if the content type matches. + */ + parse_response?: boolean; + /** + * Alias for parse_response + */ + parse?: boolean; + + /** + * Whether to parse response’s Set-Cookie header. + * Defaults to true. + * If parsed, response cookies will be available at resp.cookies. + */ + parse_cookies?: boolean; + /** + * Dump response output to file. + * This occurs after parsing and charset decoding is done. + */ + output?: string; + } + + interface RedirectOptions { + /** + * Sends the cookies received in the set-cookie header + * as part of the following request. + * false by default. + */ + follow_set_cookie?: boolean; + /** + * Sets the 'Referer' header to the requested URI + * when following a redirect. + * false by default. + */ + follow_set_referer?: boolean; + /** + * If enabled, resends the request using the original verb + * instead of being rewritten to get with no data. + * false by default. + */ + follow_keep_method?: boolean; + /** + * When true, Needle will only follow redirects that point to the same host + * as the original request. + * false by default. + */ + follow_if_same_host?: boolean; + /** + * When true, Needle will only follow redirects that point to the same protocol + * as the original request. + * false by default. + */ + follow_if_same_protocol?: boolean; + } + + interface KeyValue { + [key: string]: any; + } + + type BodyData = Buffer | KeyValue | NodeJS.ReadableStream | string | null; } + +/** + * Calling needle() directly returns a Promise. + * + * Since needle 2.0 + * @param method Designates an HTTP verb for the request. + */ +declare function needle(method: core.NeedleReadonlyHttpVerbs, url: string, options?: core.NeedleOptions): Promise; +/** + * Calling needle() directly returns a Promise. + * + * Since needle 2.0 + * @param method Designates an HTTP verb for the request. + * @param data May be null when issuing an HTTP DELETE request, but you need to explicity pass it. + */ +declare function needle(method: core.NeedleHttpVerbs, url: string, data: core.BodyData, options?: core.NeedleOptions): Promise; + +declare namespace needle { + type BodyData = core.BodyData; + type NeedleCallback = core.NeedleCallback; + type NeedleHttpVerbs = core.NeedleHttpVerbs; + export type NeedleOptions = core.NeedleOptions; + type ReadableStream = core.ReadableStream; + + /** + * Lets override the defaults for all future requests. + */ + export function defaults(options: NeedleOptions): NeedleOptions; + + /** + * Issues an HTTP HEAD request. + */ + export function head(url: string, callback?: NeedleCallback): ReadableStream; + /** + * Issues an HTTP HEAD request. + */ + export function head(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + /** + * Issues an HTTP GET request. + */ + export function get(url: string, callback?: NeedleCallback): ReadableStream; + /** + * Issues an HTTP GET request. + */ + export function get(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + /** + * Issues an HTTP POST request. + */ + export function post(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + /** + * Issues an HTTP POST request. + */ + export function post(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + /** + * Issues an HTTP PUT request. + */ + export function put(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + /** + * Issues an HTTP PUT request. + */ + export function put(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + /** + * Same behaviour as PUT. + */ + export function patch(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + /** + * Same behaviour as PUT. + */ + export function patch(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + /** + * Issues an HTTP DELETE request. + */ + function deleteFunc(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + /** + * Issues an HTTP DELETE request. + */ + function deleteFunc(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + // See https://github.com/Microsoft/TypeScript/issues/1784#issuecomment-258720219 + export { deleteFunc as delete }; + + /** + * Generic request. + * This not only allows for flexibility, but also lets you perform a GET request with data, + * in which case will be appended to the request as a query string, + * unless you pass a json: true option. + * @param method Designates an HTTP verb for the request. + */ + export function request(method: NeedleHttpVerbs, url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + /** + * Generic request. + * This not only allows for flexibility, but also lets you perform a GET request with data, + * in which case will be appended to the request as a query string, + * unless you pass a json: true option. + * @param method Designates an HTTP verb for the request. + */ + export function request(method: NeedleHttpVerbs, url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; +} + +export = needle; diff --git a/types/needle/needle-tests.ts b/types/needle/needle-tests.ts index 1c5090027a..e8f2efee20 100644 --- a/types/needle/needle-tests.ts +++ b/types/needle/needle-tests.ts @@ -2,24 +2,37 @@ import * as needle from "needle"; import * as fs from "fs"; function Usage() { + // using promises + needle('get', 'http://ifconfig.me/all.json') + .then((resp) => console.log(resp.body.ip_addr)); + // using callback - needle.get('http://ifconfig.me/all.json', function (error, response) { + needle.get('http://ifconfig.me/all.json', (error, response) => { if (!error) console.log(response.body.ip_addr); // JSON decoding magic. :) }); // using streams - var out: any; // = fs.createWriteStream('logo.png'); + const out = fs.createWriteStream('file.txt'); needle.get('https://google.com/images/logo.png').pipe(out); } function ResponsePipeline() { - needle.get('http://stackoverflow.com/feeds', { compressed: true }, function (err, resp) { + // using promises + needle('get', 'http://stackoverflow.com/feeds', { compressed: true }) + .then((resp) => { + console.log(resp.body); // this little guy won't be a Gzipped binary blob + // but a nice object containing all the latest entries + }); + + // using callback + needle.get('http://stackoverflow.com/feeds', { compressed: true }, (err, resp) => { console.log(resp.body); // this little guy won't be a Gzipped binary blob // but a nice object containing all the latest entries }); - var options = { + // using streams + const options = { compressed: true, follow: 5, rejectUnauthorized: true @@ -27,54 +40,69 @@ function ResponsePipeline() { // in this case, we'll ask Needle to follow redirects (disabled by default), // but also to verify their SSL certificates when connecting. - var stream = needle.get('https://backend.server.com/everything.html', options); + const stream = needle.get('https://backend.server.com/everything.html', options); - stream.on('readable', function () { - var data: any; + stream.on('readable', () => { + let data: any; while (data = stream.read()) { console.log(data.toString()); } }); - stream.on('end', function(err: any) { + stream.on('end', (err: any) => { // if our request had an error, our 'end' event will tell us. if (!err) console.log('Great success!'); - }) + }); } function API_head() { - var options = { - open_timeout: 5000 // if we don't get a response in 5 seconds, boom. - }; + // using promises + needle('head', 'https://my.backend.server.com') + .then((resp) => console.log('Yup, still alive.')) + .catch((err: Error) => console.log('Shoot! Something is wrong: ' + err.message)); - needle.head('https://my.backend.server.com', function (err, resp) { + // using callback + needle.head('https://my.backend.server.com', (err, resp) => { if (err) { console.log('Shoot! Something is wrong: ' + err.message); - } - else { + } else { console.log('Yup, still alive.'); } }); } function API_get() { - needle.get('google.com/search?q=syd+barrett', function (err, resp) { + // using promises + needle('get', 'google.com/search?q=syd+barrett') + .then((resp) => { + // if no http:// is found, Needle will automagically prepend it. + }); + + // using callback + needle.get('google.com/search?q=syd+barrett', (err, resp) => { // if no http:// is found, Needle will automagically prepend it. }); } function API_post() { - var options = { + const options = { headers: { 'X-Custom-Header': 'Bumbaway atuna' } }; - needle.post('https://my.app.com/endpoint', 'foo=bar', options, function (err, resp) { + // using promises + needle('post', 'https://my.app.com/endpoint', 'foo=bar', options) + .then((resp) => { + // you can pass params as a string or as an object. + }); + + // using callback + needle.post('https://my.app.com/endpoint', 'foo=bar', options, (err, resp) => { // you can pass params as a string or as an object. }); } function API_put() { - var nested = { + const nested = { params: { are: { also: 'supported' @@ -82,87 +110,155 @@ function API_put() { } }; - needle.put('https://api.app.com/v2', nested, function (err, resp) { - console.log('Got ' + resp.bytes + ' bytes.') // another nice treat from this handsome fella. + // using promises + needle('put', 'https://api.app.com/v2', nested) + .then((resp) => { + console.log(`Got ${resp.bytes} bytes.`); // another nice treat from this handsome fella. + }); + + // using callback + needle.put('https://api.app.com/v2', nested, (err, resp) => { + console.log(`Got ${resp.bytes} bytes.`); // another nice treat from this handsome fella. }); } function API_delete() { - var options = { + const options = { username: 'fidelio', password: 'x' }; - needle.delete('https://api.app.com/messages/123', null, options, function (err, resp) { + // using promises + needle('delete', 'https://api.app.com/messages/123', null, options) + .then((resp) => { + // in this case, data may be null, but you need to explicity pass it. + }); + + // using callback + needle.delete('https://api.app.com/messages/123', null, options, (err, resp) => { // in this case, data may be null, but you need to explicity pass it. }); } function API_request() { - var params = { + const params = { q: 'a very smart query', page: 2, }; - needle.request('get', 'forum.com/search', params, function (err, resp) { - if (!err && resp.statusCode == 200) + // using promises + needle('get', 'forum.com/search', params) + .then((resp) => { + if (resp.statusCode === 200) + console.log(resp.body); // here you go, mister. + }); + + needle('get', 'forum.com/search', params, { json: true }) + .then((resp) => { + if (resp.statusCode === 200) console.log('It worked!'); + }); + + // using callback + needle.request('get', 'forum.com/search', params, (err, resp) => { + if (!err && resp.statusCode === 200) console.log(resp.body); // here you go, mister. }); - needle.request('get', 'forum.com/search', params, { json: true }, function(err, resp) { - if (resp.statusCode == 200) console.log('It worked!'); + needle.request('get', 'forum.com/search', params, { json: true }, (err, resp) => { + if (resp.statusCode === 200) console.log('It worked!'); }); } function HttpGetWithBasicAuth() { - needle.get('https://api.server.com', { username: 'you', password: 'secret' }, function(err, resp) { + // using promises + needle('get', 'https://api.server.com', { username: 'you', password: 'secret' }) + .then((resp) => { + // used HTTP auth + }); + needle('get', 'https://username:password@api.server.com') + .then((resp) => { + // used HTTP auth from URL + }); + + // using callback + needle.get('https://api.server.com', { username: 'you', password: 'secret' }, (err, resp) => { // used HTTP auth }); - needle.get('https://username:password@api.server.com', function(err, resp) { + needle.get('https://username:password@api.server.com', (err, resp) => { // used HTTP auth from URL }); } function DigestAuth() { - needle.get('other.server.com', { username: 'you', password: 'secret', auth: 'digest' }, function(err, resp, body) { + // using promises + needle('get', 'other.server.com', { username: 'you', password: 'secret', auth: 'digest' }) + .then((resp) => { + // needle prepends 'http://' to your URL, if missing + }); + + // using callback + needle.get('other.server.com', { username: 'you', password: 'secret', auth: 'digest' }, (err, resp, body) => { // needle prepends 'http://' to your URL, if missing }); } function CustomAcceptHeaderDeflate() { - var options = { + const options: needle.NeedleOptions = { compressed: true, follow: 10, accept: 'application/vnd.github.full+json' - } + }; - needle.get('api.github.com/users/tomas', options, function(err, resp, body) { + // using promises + needle('get', 'api.github.com/users/tomas', options) + .then((resp) => { + // body will contain a JSON.parse(d) object + // if parsing fails, you'll simply get the original body + }); + + // using callback + needle.get('api.github.com/users/tomas', options, (err, resp, body) => { // body will contain a JSON.parse(d) object // if parsing fails, you'll simply get the original body }); - } function Various() { + // using promises + needle('get', 'https://news.ycombinator.com/rss') + .then((resp) => { + // if xml2js is installed, you'll get a nice object containing the nodes in the RSS + }); + needle('get', 'http://upload.server.com/tux.png', { output: '/tmp/tux.png' }) + .then((resp) => { + // you can dump any response to a file, not only binaries. + }); + needle('get', 'http://search.npmjs.org', { proxy: 'http://localhost:1234' }) + .then((resp) => { + // request passed through proxy + }); - needle.get('https://news.ycombinator.com/rss', function(err, resp, body) { + // using callback + needle.get('https://news.ycombinator.com/rss', (err, resp, body) => { // if xml2js is installed, you'll get a nice object containing the nodes in the RSS }); - needle.get('http://upload.server.com/tux.png', { output: '/tmp/tux.png' }, function(err, resp, body) { + needle.get('http://upload.server.com/tux.png', { output: '/tmp/tux.png' }, (err, resp, body) => { // you can dump any response to a file, not only binaries. }); - needle.get('http://search.npmjs.org', { proxy: 'http://localhost:1234' }, function(err, resp, body) { + needle.get('http://search.npmjs.org', { proxy: 'http://localhost:1234' }, (err, resp, body) => { // request passed through proxy }); + + // using streams const stream1 = needle.get('http://www.as35662.net/100.log'); - stream1.on('readable', function() { + stream1.on('readable', () => { let chunk: any; while (chunk = stream1.read()) { console.log('got data: ', chunk); } }); const stream2 = needle.get('http://jsonplaceholder.typicode.com/db', { parse: true }); - stream2.on('readable', function() { + stream2.on('readable', () => { let node: any; // our stream2 will only emit a single JSON root node. @@ -170,43 +266,54 @@ function Various() { console.log('got data: ', node); } }); - - /* - // Sample omitted, no JSONStream - needle.get('http://jsonplaceholder.typicode.com/db', { parse: true }) - .pipe(new JSONStream.parse('posts.*.title')) - .on('data', function (obj) { - console.log('got post title: %s', obj); - }); - */ } function FileUpload() { - var data = { + const data = { foo: 'bar', image: { file: '/home/tomas/linux.png', content_type: 'image/png' } }; - needle.post('http://my.other.app.com', data, { multipart: true }, function(err, resp, body) { + // using promises + needle('post', 'http://my.other.app.com', data, { multipart: true }) + .then((resp) => { + // needle will read the file and include it in the form-data as binary + }); + needle('put', 'https://api.app.com/v2', fs.createReadStream('myfile.txt')) + .then((resp) => { + // stream content is uploaded verbatim + }); + + // using callback + needle.post('http://my.other.app.com', data, { multipart: true }, (err, resp, body) => { // needle will read the file and include it in the form-data as binary }); - needle.put('https://api.app.com/v2', fs.createReadStream('myfile.txt'), function(err, resp, body) { + needle.put('https://api.app.com/v2', fs.createReadStream('myfile.txt'), (err, resp, body) => { // stream content is uploaded verbatim }); } function Multipart() { - var buffer = fs.readFileSync('/path/to/package.zip'); + const buffer = fs.readFileSync('/path/to/package.zip'); - var data = { + const data = { zip_file: { - buffer: buffer, + buffer, filename: 'mypackage.zip', content_type: 'application/octet-stream' } - } + }; - needle.post('http://somewhere.com/over/the/rainbow', data, { multipart: true }, function(err, resp, body) { + // using promises + needle('post', 'http://somewhere.com/over/the/rainbow', data, { multipart: true }) + .then((resp) => { + // if you see, when using buffers we need to pass the filename for the multipart body. + // you can also pass a filename when using the file path method, in case you want to override + // the default filename to be received on the other end. + }); + + // using callback + needle.post('http://somewhere.com/over/the/rainbow', data, { multipart: true }, (err, resp, body) => { // if you see, when using buffers we need to pass the filename for the multipart body. // you can also pass a filename when using the file path method, in case you want to override // the default filename to be received on the other end. @@ -214,15 +321,23 @@ function Multipart() { } function MultipartContentType() { - var data = { + const data = { token: 'verysecret', payload: { value: JSON.stringify({ title: 'test', version: 1 }), content_type: 'application/json' } - } + }; - needle.post('http://test.com/', data, { timeout: 5000, multipart: true }, function(err, resp, body) { + // using promises + needle('post', 'http://test.com/', data, { timeout: 5000, multipart: true }) + .then((resp) => { + // in this case, if the request takes more than 5 seconds + // the callback will return a [Socket closed] error + }); + + // using callback + needle.post('http://test.com/', data, { timeout: 5000, multipart: true }, (err, resp, body) => { // in this case, if the request takes more than 5 seconds // the callback will return a [Socket closed] error }); diff --git a/types/needle/tslint.json b/types/needle/tslint.json new file mode 100644 index 0000000000..70429c253f --- /dev/null +++ b/types/needle/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": "dtslint/dt.json", + "rules": { + "no-conditional-assignment": false, + "strict-export-declare-modifiers": false + } +} \ No newline at end of file diff --git a/types/needle/v1/index.d.ts b/types/needle/v1/index.d.ts new file mode 100644 index 0000000000..8cc61f2a2f --- /dev/null +++ b/types/needle/v1/index.d.ts @@ -0,0 +1,118 @@ +// Type definitions for needle 1.4 +// Project: https://github.com/tomas/needle +// Definitions by: San Chen , Niklas Mollenhauer +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/// + +declare module "needle" { + import * as http from 'http'; + import * as Buffer from 'buffer'; + import * as https from 'https'; + namespace Needle { + interface NeedleResponse extends http.IncomingMessage { + body: any; + raw: Buffer; + bytes: number; + } + + type ReadableStream = NodeJS.ReadableStream; + + type NeedleCallback = (error: Error, response: NeedleResponse, body: any) => void; + + interface Cookies { + [name: string]: any; + } + + type NeedleOptions = RequestOptions & ResponseOptions & RedirectOptions & https.RequestOptions; + + interface RequestOptions { + open_timeout?: number; + read_timeout?: number; + /** + * Alias for open_timeout + */ + timeout?: number; + + follow_max?: number; + /** + * Alias for follow_max + */ + follow?: number; + + multipart?: boolean; + agent?: http.Agent | boolean; + proxy?: string; + headers?: {}; + auth?: "auto" | "digest" | "basic"; + json?: boolean; + + // These properties are overwritten by those in the 'headers' field + cookies?: Cookies; + compressed?: boolean; + // Overwritten if present in the URI + username?: string; + password?: string; + accept?: string; + connection?: string; + user_agent?: string; + } + + interface ResponseOptions { + decode_response?: boolean; + /** + * Alias for decode_response + */ + decode?: boolean; + parse_response?: boolean; + /** + * Alias for parse_response + */ + parse?: boolean; + + parse_cookies?: boolean; + output?: string; + } + + interface RedirectOptions { + follow_set_cookie?: boolean; + follow_set_referer?: boolean; + follow_keep_method?: boolean; + follow_if_same_host?: boolean; + follow_if_same_protocol?: boolean; + } + + interface KeyValue { + [key: string]: any; + } + + type BodyData = Buffer | KeyValue | NodeJS.ReadableStream | string | null; + + interface NeedleStatic { + defaults(options: NeedleOptions): void; + + head(url: string, callback?: NeedleCallback): ReadableStream; + head(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + get(url: string, callback?: NeedleCallback): ReadableStream; + get(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + post(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + post(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + put(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + put(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + patch(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + patch(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + + delete(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + delete(url: string, data: BodyData, options?: NeedleOptions, callback ?: NeedleCallback): ReadableStream; + + request(method: string, url: string, data: BodyData, callback?: NeedleCallback): ReadableStream; + request(method: string, url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream; + } + } + const needle: Needle.NeedleStatic; + export = needle; +} diff --git a/types/needle/v1/needle-tests.ts b/types/needle/v1/needle-tests.ts new file mode 100644 index 0000000000..1c5090027a --- /dev/null +++ b/types/needle/v1/needle-tests.ts @@ -0,0 +1,229 @@ +import * as needle from "needle"; +import * as fs from "fs"; + +function Usage() { + // using callback + needle.get('http://ifconfig.me/all.json', function (error, response) { + if (!error) + console.log(response.body.ip_addr); // JSON decoding magic. :) + }); + + // using streams + var out: any; // = fs.createWriteStream('logo.png'); + needle.get('https://google.com/images/logo.png').pipe(out); +} + +function ResponsePipeline() { + needle.get('http://stackoverflow.com/feeds', { compressed: true }, function (err, resp) { + console.log(resp.body); // this little guy won't be a Gzipped binary blob + // but a nice object containing all the latest entries + }); + + var options = { + compressed: true, + follow: 5, + rejectUnauthorized: true + }; + + // in this case, we'll ask Needle to follow redirects (disabled by default), + // but also to verify their SSL certificates when connecting. + var stream = needle.get('https://backend.server.com/everything.html', options); + + stream.on('readable', function () { + var data: any; + while (data = stream.read()) { + console.log(data.toString()); + } + }); + + stream.on('end', function(err: any) { + // if our request had an error, our 'end' event will tell us. + if (!err) console.log('Great success!'); + }) +} + +function API_head() { + var options = { + open_timeout: 5000 // if we don't get a response in 5 seconds, boom. + }; + + needle.head('https://my.backend.server.com', function (err, resp) { + if (err) { + console.log('Shoot! Something is wrong: ' + err.message); + } + else { + console.log('Yup, still alive.'); + } + }); +} + +function API_get() { + needle.get('google.com/search?q=syd+barrett', function (err, resp) { + // if no http:// is found, Needle will automagically prepend it. + }); +} + +function API_post() { + var options = { + headers: { 'X-Custom-Header': 'Bumbaway atuna' } + }; + + needle.post('https://my.app.com/endpoint', 'foo=bar', options, function (err, resp) { + // you can pass params as a string or as an object. + }); +} + +function API_put() { + var nested = { + params: { + are: { + also: 'supported' + } + } + }; + + needle.put('https://api.app.com/v2', nested, function (err, resp) { + console.log('Got ' + resp.bytes + ' bytes.') // another nice treat from this handsome fella. + }); +} + +function API_delete() { + var options = { + username: 'fidelio', + password: 'x' + }; + + needle.delete('https://api.app.com/messages/123', null, options, function (err, resp) { + // in this case, data may be null, but you need to explicity pass it. + }); +} + +function API_request() { + var params = { + q: 'a very smart query', + page: 2, + }; + + needle.request('get', 'forum.com/search', params, function (err, resp) { + if (!err && resp.statusCode == 200) + console.log(resp.body); // here you go, mister. + }); + + needle.request('get', 'forum.com/search', params, { json: true }, function(err, resp) { + if (resp.statusCode == 200) console.log('It worked!'); + }); +} + +function HttpGetWithBasicAuth() { + needle.get('https://api.server.com', { username: 'you', password: 'secret' }, function(err, resp) { + // used HTTP auth + }); + needle.get('https://username:password@api.server.com', function(err, resp) { + // used HTTP auth from URL + }); +} + +function DigestAuth() { + needle.get('other.server.com', { username: 'you', password: 'secret', auth: 'digest' }, function(err, resp, body) { + // needle prepends 'http://' to your URL, if missing + }); +} + +function CustomAcceptHeaderDeflate() { + var options = { + compressed: true, + follow: 10, + accept: 'application/vnd.github.full+json' + } + + needle.get('api.github.com/users/tomas', options, function(err, resp, body) { + // body will contain a JSON.parse(d) object + // if parsing fails, you'll simply get the original body + }); + +} + +function Various() { + + needle.get('https://news.ycombinator.com/rss', function(err, resp, body) { + // if xml2js is installed, you'll get a nice object containing the nodes in the RSS + }); + needle.get('http://upload.server.com/tux.png', { output: '/tmp/tux.png' }, function(err, resp, body) { + // you can dump any response to a file, not only binaries. + }); + needle.get('http://search.npmjs.org', { proxy: 'http://localhost:1234' }, function(err, resp, body) { + // request passed through proxy + }); + const stream1 = needle.get('http://www.as35662.net/100.log'); + stream1.on('readable', function() { + let chunk: any; + while (chunk = stream1.read()) { + console.log('got data: ', chunk); + } + }); + const stream2 = needle.get('http://jsonplaceholder.typicode.com/db', { parse: true }); + stream2.on('readable', function() { + let node: any; + + // our stream2 will only emit a single JSON root node. + while (node = stream2.read()) { + console.log('got data: ', node); + } + }); + + /* + // Sample omitted, no JSONStream + needle.get('http://jsonplaceholder.typicode.com/db', { parse: true }) + .pipe(new JSONStream.parse('posts.*.title')) + .on('data', function (obj) { + console.log('got post title: %s', obj); + }); + */ +} + +function FileUpload() { + var data = { + foo: 'bar', + image: { file: '/home/tomas/linux.png', content_type: 'image/png' } + }; + + needle.post('http://my.other.app.com', data, { multipart: true }, function(err, resp, body) { + // needle will read the file and include it in the form-data as binary + }); + needle.put('https://api.app.com/v2', fs.createReadStream('myfile.txt'), function(err, resp, body) { + // stream content is uploaded verbatim + }); +} + +function Multipart() { + var buffer = fs.readFileSync('/path/to/package.zip'); + + var data = { + zip_file: { + buffer: buffer, + filename: 'mypackage.zip', + content_type: 'application/octet-stream' + } + } + + needle.post('http://somewhere.com/over/the/rainbow', data, { multipart: true }, function(err, resp, body) { + // if you see, when using buffers we need to pass the filename for the multipart body. + // you can also pass a filename when using the file path method, in case you want to override + // the default filename to be received on the other end. + }); +} + +function MultipartContentType() { + var data = { + token: 'verysecret', + payload: { + value: JSON.stringify({ title: 'test', version: 1 }), + content_type: 'application/json' + } + } + + needle.post('http://test.com/', data, { timeout: 5000, multipart: true }, function(err, resp, body) { + // in this case, if the request takes more than 5 seconds + // the callback will return a [Socket closed] error + }); +} diff --git a/types/needle/v1/tsconfig.json b/types/needle/v1/tsconfig.json new file mode 100644 index 0000000000..f6b8b1e810 --- /dev/null +++ b/types/needle/v1/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "baseUrl": "../../", + "typeRoots": [ + "../../" + ], + "paths": { + "needle": ["needle/v1"] + }, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "needle-tests.ts" + ] +}