Skip to content

Commit 9ce212a

Browse files
committed
Add types
1 parent 65e3d9b commit 9ce212a

8 files changed

+214
-24
lines changed

.github/workflows/ci.yml

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ jobs:
2626
- name: Install
2727
run: npm ci
2828
- name: Lint
29+
if: matrix.node-version == '20.x'
2930
run: npm run lint
31+
- name: Types
32+
if: matrix.node-version == '20.x'
33+
run: node_modules/.bin/tsc
3034
- name: Test
3135
run: npm test

lib/request.js

+76-17
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ const https = require('https');
1111
const logger = require('@studio/log');
1212
const { failure, E_FAILED } = require('@studio/fail');
1313

14+
/**
15+
* @typedef {import('http').RequestOptions} RequestOptions
16+
* @typedef {import('http').ClientRequest} ClientRequest
17+
* @typedef {import('http').IncomingMessage} IncomingMessage
18+
* @typedef {import('stream').Readable} Readable
19+
* @typedef {import('@studio/fail').Failure} Failure
20+
* @typedef {import('@studio/log').Logger} Logger
21+
*/
22+
23+
/**
24+
* @typedef {Object} JsonRequestOptions
25+
* @property {number} [timeout]
26+
* @property {number | number[]} [expect]
27+
* @property {boolean} [stream]
28+
* @property {Logger} [log]
29+
*/
30+
31+
/**
32+
* @typedef {undefined | null | boolean | number | string | JsonArray | JsonObject} JsonValue
33+
* @typedef {JsonValue[]} JsonArray
34+
* @typedef {{ [k: string]: JsonValue }} JsonObject
35+
*/
36+
1437
const logger_name = 'Request';
1538
const default_log = logger(logger_name);
1639

@@ -19,6 +42,11 @@ const PROTOCOLS = {
1942
'https:': https
2043
};
2144

45+
/**
46+
* @param {string | number} expect
47+
* @param {number} status
48+
* @returns {Failure}
49+
*/
2250
function expectError(expect, status) {
2351
return failure(
2452
`Expected response statusCode to be ${expect}, but was ${status}`,
@@ -27,11 +55,32 @@ function expectError(expect, status) {
2755
);
2856
}
2957

58+
/**
59+
* @template T
60+
* @param {T} obj
61+
* @returns {T}
62+
*/
3063
function copy(obj) {
3164
return { ...obj };
3265
}
3366

34-
module.exports = function fetch(options, data, callback) {
67+
module.exports = fetch;
68+
69+
/**
70+
* @callback FetchCallback
71+
* @param {Error | null} err
72+
* @param {Object | null} [json]
73+
* @param {IncomingMessage} [res]
74+
*/
75+
76+
/**
77+
* @param {RequestOptions & JsonRequestOptions} options
78+
* @param {null | string | JsonObject | Readable | FetchCallback} data
79+
* @param {FetchCallback} [callback]
80+
* @returns {ClientRequest}
81+
*/
82+
// eslint-disable-next-line complexity
83+
function fetch(options, data, callback) {
3584
if (typeof data === 'function') {
3685
callback = data;
3786
data = null;
@@ -63,7 +112,7 @@ module.exports = function fetch(options, data, callback) {
63112
delete opts.log;
64113
}
65114

66-
if (data && !data.pipe) {
115+
if (data && typeof data !== 'string' && !data.pipe) {
67116
data = JSON.stringify(data);
68117
if (opts.headers) {
69118
opts.headers = copy(opts.headers);
@@ -86,7 +135,7 @@ module.exports = function fetch(options, data, callback) {
86135
if (options.port) {
87136
request.port = options.port;
88137
}
89-
if (data && !data.pipe) {
138+
if (typeof data === 'string') {
90139
request.body = data;
91140
}
92141

@@ -123,7 +172,9 @@ module.exports = function fetch(options, data, callback) {
123172
response.body = body;
124173
const ms_body = Date.now() - ts_head;
125174
log.warn({ request, ms_head, ms_body, response }, err.message);
126-
callback(err, null, res);
175+
if (callback) {
176+
callback(err, null, res);
177+
}
127178
});
128179
return;
129180
}
@@ -154,15 +205,19 @@ module.exports = function fetch(options, data, callback) {
154205
const e = failure('Response failure', res_err, E_FAILED);
155206
const ms_body = Date.now() - ts_head;
156207
log.error({ request, ms_head, ms_body, response }, res_err);
157-
callback(e);
208+
if (callback) {
209+
callback(e);
210+
}
158211
});
159212
if (opts.stream) {
160213
log.fetch({ request, ms_head, response });
161214
res.on('end', () => {
162215
const ms_body = Date.now() - ts_head;
163216
log.finish({ ms_body });
164217
});
165-
callback(null, res);
218+
if (callback) {
219+
callback(null, res);
220+
}
166221
return;
167222
}
168223

@@ -186,13 +241,17 @@ module.exports = function fetch(options, data, callback) {
186241
);
187242
response.body = body;
188243
log.error({ request, ms_head, ms_body, response });
189-
callback(json_err, body, res);
244+
if (callback) {
245+
callback(json_err, body, res);
246+
}
190247
return;
191248
}
192249
response.json = json;
193250
}
194251
log.fetch({ request, response, ms_head, ms_body });
195-
callback(null, json, res);
252+
if (callback) {
253+
callback(null, json, res);
254+
}
196255
});
197256
});
198257

@@ -201,17 +260,17 @@ module.exports = function fetch(options, data, callback) {
201260
req.destroy();
202261
const err = failure('Request timeout', 'E_TIMEOUT');
203262
log.warn({ ms: Date.now() - ts_start, request });
204-
callback(err);
205-
callback = null;
263+
if (callback) {
264+
callback(err);
265+
}
266+
callback = undefined;
206267
}, timeout);
207268
}
208269

209-
if (data) {
210-
if (data.pipe) {
211-
data.pipe(req);
212-
} else {
213-
req.end(data);
214-
}
270+
if (typeof data === 'string') {
271+
req.end(data);
272+
} else if (data && typeof data.pipe === 'function') {
273+
data.pipe(req);
215274
} else {
216275
req.end();
217276
}
@@ -227,4 +286,4 @@ module.exports = function fetch(options, data, callback) {
227286
}
228287
});
229288
return req;
230-
};
289+
}

package-lock.json

+65-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+15-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
"lint": "eslint .",
1111
"test": "mocha",
1212
"watch": "mocha --watch",
13-
"preversion": "npm run lint && npm test",
13+
"build": "tsc --project tsconfig.pack.json",
14+
"clean": "rimraf --glob 'lib/*.d.ts'",
15+
"prepack": "npm run build",
16+
"postpack": "npm run clean",
17+
"preversion": "npm run lint && tsc && npm test",
1418
"version": "changes --commits --footer",
1519
"postversion": "git push --follow-tags && npm publish"
1620
},
@@ -29,19 +33,27 @@
2933
},
3034
"dependencies": {
3135
"@studio/fail": "^1.8.0",
32-
"@studio/log": "^2.1.2"
36+
"@studio/log": "^2.1.2",
37+
"@types/node": "^16.18.76"
3338
},
3439
"devDependencies": {
3540
"@sinonjs/referee-sinon": "^12.0.0",
3641
"@studio/changes": "^3.0.0",
3742
"@studio/eslint-config": "^6.0.0",
3843
"@studio/log-x": "^1.3.1",
44+
"@studio/tsconfig": "^1.3.0",
3945
"eslint": "^8.56.0",
40-
"mocha": "^10.2.0"
46+
"mocha": "^10.2.0",
47+
"typescript": "^5.3.3"
4148
},
4249
"repository": {
4350
"type": "git",
4451
"url": "https://github.com/javascript-studio/studio-json-request.git"
4552
},
53+
"files": [
54+
"lib",
55+
"LICENSE",
56+
"README.md"
57+
],
4658
"license": "MIT"
4759
}

test/integration-test.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const { assert, refute, match, sinon } = require('@sinonjs/referee-sinon');
55
const http = require('http');
66
const request = require('..');
77

8+
/**
9+
* @typedef {import('http').IncomingMessage} IncomingMessage
10+
*/
811

912
describe('integration', () => {
1013
let server;
@@ -24,7 +27,7 @@ describe('integration', () => {
2427
timeout: 10
2528
}, (err) => {
2629
refute.isNull(err);
27-
assert.equals(err.message, 'Request timeout');
30+
assert.equals(/** @type {Error} */ (err).message, 'Request timeout');
2831
setTimeout(() => {
2932
assert.calledOnceWith(onError, match({ message: 'socket hang up' }));
3033
done();
@@ -57,7 +60,7 @@ describe('integration', () => {
5760
expect: [200, 302]
5861
}, (err, json, res) => {
5962
assert.isNull(err);
60-
assert.equals(res.statusCode, 200);
63+
assert.equals(/** @type {IncomingMessage} */ (res).statusCode, 200);
6164
assert.equals(json, { hello: 'redirect' });
6265
done();
6366
});

0 commit comments

Comments
 (0)