@faasjs/request 8.0.0-beta.6 → 8.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,224 +1,238 @@
1
- 'use strict';
2
-
3
- var crypto = require('crypto');
4
- var fs = require('fs');
5
- var http = require('http');
6
- var https = require('https');
7
- var path = require('path');
8
- var url = require('url');
9
- var zlib = require('zlib');
10
- var logger = require('@faasjs/logger');
11
-
12
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) {
14
+ __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ }
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
26
+ }) : target, mod));
13
27
 
14
- var http__default = /*#__PURE__*/_interopDefault(http);
15
- var https__default = /*#__PURE__*/_interopDefault(https);
28
+ //#endregion
29
+ let node_crypto = require("node:crypto");
30
+ let node_fs = require("node:fs");
31
+ let node_http = require("node:http");
32
+ node_http = __toESM(node_http);
33
+ let node_https = require("node:https");
34
+ node_https = __toESM(node_https);
35
+ let node_path = require("node:path");
36
+ let node_url = require("node:url");
37
+ let node_zlib = require("node:zlib");
38
+ let _faasjs_logger = require("@faasjs/logger");
16
39
 
17
- // src/index.ts
18
- var mock = null;
40
+ //#region src/index.ts
41
+ /**
42
+ * FaasJS's request module.
43
+ *
44
+ * [![License: MIT](https://img.shields.io/npm/l/@faasjs/request.svg)](https://github.com/faasjs/faasjs/blob/main/packages/request/LICENSE)
45
+ * [![NPM Version](https://img.shields.io/npm/v/@faasjs/request.svg)](https://www.npmjs.com/package/@faasjs/request)
46
+ *
47
+ * ## Install
48
+ *
49
+ * ```sh
50
+ * npm install @faasjs/request
51
+ * ```
52
+ * @packageDocumentation
53
+ */
54
+ let mock = null;
55
+ /**
56
+ * Mock requests
57
+ * @param handler {function | null} null to disable mock
58
+ * @example setMock(async (url, options) => Promise.resolve({ headers: {}, statusCode: 200, body: { data: 'ok' } }))
59
+ */
19
60
  function setMock(handler) {
20
- mock = handler;
61
+ mock = handler;
21
62
  }
22
63
  function querystringify(obj) {
23
- const pairs = [];
24
- let value;
25
- let key;
26
- for (key in obj) {
27
- if (Object.hasOwn(obj, key)) {
28
- value = obj[key];
29
- if (!value && (value === null || value === void 0 || Number.isNaN(value))) {
30
- value = "";
31
- }
32
- key = encodeURIComponent(key);
33
- value = encodeURIComponent(value);
34
- if (key === null || value === null) continue;
35
- pairs.push(`${key}=${value}`);
36
- }
37
- }
38
- return pairs.length ? pairs.join("&") : "";
64
+ const pairs = [];
65
+ let value;
66
+ let key;
67
+ for (key in obj) if (Object.hasOwn(obj, key)) {
68
+ value = obj[key];
69
+ if (!value && (value === null || value === void 0 || Number.isNaN(value))) value = "";
70
+ key = encodeURIComponent(key);
71
+ value = encodeURIComponent(value);
72
+ if (key === null || value === null) continue;
73
+ pairs.push(`${key}=${value}`);
74
+ }
75
+ return pairs.length ? pairs.join("&") : "";
39
76
  }
77
+ /**
78
+ * ResponseError class
79
+ */
40
80
  var ResponseError = class extends Error {
41
- response;
42
- request;
43
- statusCode;
44
- statusMessage;
45
- headers;
46
- body;
47
- constructor(message, response) {
48
- super(message);
49
- this.response = response;
50
- this.request = response.request;
51
- this.statusCode = response.statusCode;
52
- this.statusMessage = response.statusMessage;
53
- this.headers = response.headers;
54
- this.body = response.body;
55
- }
81
+ response;
82
+ request;
83
+ statusCode;
84
+ statusMessage;
85
+ headers;
86
+ body;
87
+ constructor(message, response) {
88
+ super(message);
89
+ this.response = response;
90
+ this.request = response.request;
91
+ this.statusCode = response.statusCode || 500;
92
+ this.statusMessage = response.statusMessage || "Error";
93
+ this.headers = response.headers;
94
+ this.body = response.body;
95
+ }
56
96
  };
57
- async function request(url$1, options = { headers: {} }) {
58
- const requestId = crypto.randomUUID();
59
- const logger$1 = options.logger || new logger.Logger(`request][${requestId}`);
60
- if (mock) {
61
- logger$1.debug("mock %s %j", url$1, options);
62
- return mock(url$1, options);
63
- }
64
- if (options.query) {
65
- if (!url$1.includes("?")) url$1 += "?";
66
- else if (!url$1.endsWith("?")) url$1 += "&";
67
- url$1 += querystringify(options.query);
68
- }
69
- if (!options.headers) options.headers = {};
70
- const uri = new url.URL(url$1);
71
- const protocol = uri.protocol === "https:" ? https__default.default : http__default.default;
72
- if (!uri.protocol) throw Error("Unknown protocol");
73
- const requestOptions = {
74
- headers: {},
75
- host: uri.host ? uri.host.replace(/:[0-9]+$/, "") : uri.host,
76
- method: options.method ? options.method.toUpperCase() : "GET",
77
- path: uri.pathname + uri.search,
78
- port: uri.port || (uri.protocol === "https:" ? "443" : "80"),
79
- timeout: options.timeout || 5e3,
80
- auth: options.auth,
81
- pfx: options.pfx,
82
- passphrase: options.passphrase,
83
- agent: options.agent
84
- };
85
- if (!options.headers["Accept-Encoding"] && !options.downloadFile && !options.downloadStream)
86
- options.headers["Accept-Encoding"] = "br,gzip";
87
- for (const key in options.headers)
88
- if (typeof options.headers[key] !== "undefined" && options.headers[key] !== null)
89
- requestOptions.headers[key] = options.headers[key];
90
- let body = options.body;
91
- if (body && typeof body !== "string")
92
- if (options.headers["Content-Type"]?.toString().includes("application/x-www-form-urlencoded"))
93
- body = querystringify(body);
94
- else body = JSON.stringify(body);
95
- if (body && !options.headers["Content-Length"])
96
- requestOptions.headers["Content-Length"] = Buffer.byteLength(body);
97
- return await new Promise((resolve, reject) => {
98
- logger$1.debug("request %j", {
99
- ...options,
100
- body
101
- });
102
- const req = protocol.request(requestOptions, (res) => {
103
- if (options.downloadStream) {
104
- options.downloadStream.on("error", (error) => {
105
- logger$1.timeEnd(requestId, "response.error %j", error);
106
- reject(error);
107
- }).on("finish", () => {
108
- logger$1.timeEnd(
109
- requestId,
110
- "response %s %s",
111
- res.statusCode,
112
- res.headers["content-type"]
113
- );
114
- options.downloadStream?.end();
115
- resolve(void 0);
116
- });
117
- res.pipe(options.downloadStream, { end: true });
118
- return;
119
- }
120
- if (options.downloadFile) {
121
- const stream2 = fs.createWriteStream(options.downloadFile).on("error", (error) => {
122
- logger$1.timeEnd(requestId, "response.error %j", error);
123
- stream2.destroy();
124
- reject(error);
125
- }).on("finish", () => {
126
- logger$1.timeEnd(
127
- requestId,
128
- "response %s %s %s",
129
- res.statusCode,
130
- res.headers["content-type"],
131
- stream2.bytesWritten
132
- );
133
- resolve(void 0);
134
- });
135
- res.pipe(stream2, { end: true });
136
- return;
137
- }
138
- let stream = res;
139
- switch (res.headers["content-encoding"]) {
140
- case "br":
141
- stream = res.pipe(zlib.createBrotliDecompress());
142
- break;
143
- case "gzip":
144
- stream = res.pipe(zlib.createGunzip());
145
- break;
146
- }
147
- let raw = "";
148
- stream.on("data", (chunk) => raw += chunk).on("error", (e) => {
149
- logger$1.timeEnd(requestId, "response.error %j", e);
150
- reject(e);
151
- }).on("end", () => {
152
- logger$1.timeEnd(
153
- requestId,
154
- "response %s %s %s %j",
155
- res.statusCode,
156
- res.headers["content-type"],
157
- res.headers["content-encoding"],
158
- raw
159
- );
160
- const response = /* @__PURE__ */ Object.create(null);
161
- response.request = requestOptions;
162
- response.request.body = body;
163
- response.statusCode = res.statusCode;
164
- response.statusMessage = res.statusMessage;
165
- response.headers = res.headers;
166
- response.body = raw;
167
- if (response.body && response.headers["content-type"] && response.headers["content-type"].includes("application/json") && typeof response.body === "string" && (response.body.startsWith("{") || response.body.startsWith("[")))
168
- try {
169
- response.body = (options.parse || JSON.parse)(response.body);
170
- logger$1.debug("response.parse JSON");
171
- } catch (error) {
172
- logger$1.warn("response plain body", response.body);
173
- logger$1.error(error);
174
- }
175
- if (response.statusCode >= 200 && response.statusCode < 400)
176
- resolve(response);
177
- else {
178
- logger$1.debug("response.error %j", response);
179
- reject(
180
- new ResponseError(
181
- `${res.statusMessage || res.statusCode} ${requestOptions.host}${requestOptions.path}`,
182
- response
183
- )
184
- );
185
- }
186
- });
187
- });
188
- if (body) req.write(body);
189
- if (options.file) {
190
- const crlf = "\r\n";
191
- const boundary = `--${Math.random().toString(16)}`;
192
- const delimiter = `${crlf}--${boundary}`;
193
- const headers = [
194
- `Content-Disposition: form-data; name="file"; filename="${path.basename(
195
- options.file
196
- )}"${crlf}`
197
- ];
198
- const multipartBody = Buffer.concat([
199
- Buffer.from(delimiter + crlf + headers.join("") + crlf),
200
- fs.readFileSync(options.file),
201
- Buffer.from(`${delimiter}--`)
202
- ]);
203
- req.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
204
- req.setHeader("Content-Length", multipartBody.length);
205
- req.write(multipartBody);
206
- }
207
- req.on("error", (e) => {
208
- logger$1.timeEnd(requestId, "response.error %j", e);
209
- reject(e);
210
- });
211
- req.on("timeout", () => {
212
- logger$1.timeEnd(requestId, "response.timeout");
213
- req.destroy();
214
- reject(Error(`Timeout ${url$1}`));
215
- });
216
- logger$1.time(requestId);
217
- req.end();
218
- });
97
+ /**
98
+ * Request
99
+ *
100
+ * @param url - Request target URL.
101
+ * @param options - Request options.
102
+ * @returns Request response.
103
+ * @see https://faasjs.com/doc/request.html
104
+ */
105
+ async function request(url, options = { headers: {} }) {
106
+ const requestId = (0, node_crypto.randomUUID)();
107
+ const logger = options.logger || new _faasjs_logger.Logger(`request][${requestId}`);
108
+ if (mock) {
109
+ logger.debug("mock %s %j", url, options);
110
+ return mock(url, options);
111
+ }
112
+ if (options.query) {
113
+ if (!url.includes("?")) url += "?";
114
+ else if (!url.endsWith("?")) url += "&";
115
+ url += querystringify(options.query);
116
+ }
117
+ if (!options.headers) options.headers = {};
118
+ const uri = new node_url.URL(url);
119
+ const protocol = uri.protocol === "https:" ? node_https.default : node_http.default;
120
+ if (!uri.protocol) throw Error("Unknown protocol");
121
+ const requestOptions = {
122
+ headers: {},
123
+ host: uri.host ? uri.host.replace(/:[0-9]+$/, "") : uri.host,
124
+ method: options.method ? options.method.toUpperCase() : "GET",
125
+ path: uri.pathname + uri.search,
126
+ port: uri.port || (uri.protocol === "https:" ? "443" : "80"),
127
+ timeout: options.timeout || 5e3,
128
+ auth: options.auth,
129
+ pfx: options.pfx,
130
+ passphrase: options.passphrase,
131
+ agent: options.agent
132
+ };
133
+ if (!options.headers["Accept-Encoding"] && !options.downloadFile && !options.downloadStream) options.headers["Accept-Encoding"] = "br,gzip";
134
+ for (const key in options.headers) if (typeof options.headers[key] !== "undefined" && options.headers[key] !== null) requestOptions.headers[key] = options.headers[key];
135
+ let body = options.body;
136
+ if (body && typeof body !== "string") if (options.headers["Content-Type"]?.toString().includes("application/x-www-form-urlencoded")) body = querystringify(body);
137
+ else body = JSON.stringify(body);
138
+ if (body && !options.headers["Content-Length"]) requestOptions.headers["Content-Length"] = Buffer.byteLength(body);
139
+ return await new Promise((resolve, reject) => {
140
+ logger.debug("request %j", {
141
+ ...options,
142
+ body
143
+ });
144
+ const req = protocol.request(requestOptions, (res) => {
145
+ if (options.downloadStream) {
146
+ options.downloadStream.on("error", (error) => {
147
+ logger.timeEnd(requestId, "response.error %j", error);
148
+ reject(error);
149
+ }).on("finish", () => {
150
+ logger.timeEnd(requestId, "response %s %s", res.statusCode, res.headers["content-type"]);
151
+ options.downloadStream?.end();
152
+ resolve(void 0);
153
+ });
154
+ res.pipe(options.downloadStream, { end: true });
155
+ return;
156
+ }
157
+ if (options.downloadFile) {
158
+ const stream = (0, node_fs.createWriteStream)(options.downloadFile).on("error", (error) => {
159
+ logger.timeEnd(requestId, "response.error %j", error);
160
+ stream.destroy();
161
+ reject(error);
162
+ }).on("finish", () => {
163
+ logger.timeEnd(requestId, "response %s %s %s", res.statusCode, res.headers["content-type"], stream.bytesWritten);
164
+ resolve(void 0);
165
+ });
166
+ res.pipe(stream, { end: true });
167
+ return;
168
+ }
169
+ let stream = res;
170
+ switch (res.headers["content-encoding"]) {
171
+ case "br":
172
+ stream = res.pipe((0, node_zlib.createBrotliDecompress)());
173
+ break;
174
+ case "gzip":
175
+ stream = res.pipe((0, node_zlib.createGunzip)());
176
+ break;
177
+ }
178
+ let raw = "";
179
+ stream.on("data", (chunk) => raw += chunk).on("error", (e) => {
180
+ logger.timeEnd(requestId, "response.error %j", e);
181
+ reject(e);
182
+ }).on("end", () => {
183
+ logger.timeEnd(requestId, "response %s %s %s %j", res.statusCode, res.headers["content-type"], res.headers["content-encoding"], raw);
184
+ const response = Object.create(null);
185
+ response.request = requestOptions;
186
+ response.request.body = body;
187
+ response.statusCode = res.statusCode;
188
+ response.statusMessage = res.statusMessage;
189
+ response.headers = res.headers;
190
+ response.body = raw;
191
+ if (response.body && response.headers["content-type"] && response.headers["content-type"].includes("application/json") && typeof response.body === "string" && (response.body.startsWith("{") || response.body.startsWith("["))) try {
192
+ response.body = (options.parse || JSON.parse)(response.body);
193
+ logger.debug("response.parse JSON");
194
+ } catch (error) {
195
+ logger.warn("response plain body", response.body);
196
+ logger.error(error);
197
+ }
198
+ if (response.statusCode >= 200 && response.statusCode < 400) resolve(response);
199
+ else {
200
+ logger.debug("response.error %j", response);
201
+ reject(new ResponseError(`${res.statusMessage || res.statusCode} ${requestOptions.host}${requestOptions.path}`, response));
202
+ }
203
+ });
204
+ });
205
+ if (body) req.write(body);
206
+ if (options.file) {
207
+ const crlf = "\r\n";
208
+ const boundary = `--${Math.random().toString(16)}`;
209
+ const delimiter = `${crlf}--${boundary}`;
210
+ const headers = [`Content-Disposition: form-data; name="file"; filename="${(0, node_path.basename)(options.file)}"${crlf}`];
211
+ const multipartBody = Buffer.concat([
212
+ Buffer.from(delimiter + crlf + headers.join("") + crlf),
213
+ (0, node_fs.readFileSync)(options.file),
214
+ Buffer.from(`${delimiter}--`)
215
+ ]);
216
+ req.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
217
+ req.setHeader("Content-Length", multipartBody.length);
218
+ req.write(multipartBody);
219
+ }
220
+ req.on("error", (e) => {
221
+ logger.timeEnd(requestId, "response.error %j", e);
222
+ reject(e);
223
+ });
224
+ req.on("timeout", () => {
225
+ logger.timeEnd(requestId, "response.timeout");
226
+ req.destroy();
227
+ reject(Error(`Timeout ${url}`));
228
+ });
229
+ logger.time(requestId);
230
+ req.end();
231
+ });
219
232
  }
220
233
 
234
+ //#endregion
221
235
  exports.ResponseError = ResponseError;
222
236
  exports.querystringify = querystringify;
223
237
  exports.request = request;
224
- exports.setMock = setMock;
238
+ exports.setMock = setMock;
package/dist/index.d.ts CHANGED
@@ -1,90 +1,76 @@
1
- import { OutgoingHttpHeaders } from 'node:http';
2
- import https from 'node:https';
3
- import { Logger } from '@faasjs/logger';
4
-
5
- /**
6
- * FaasJS's request module.
7
- *
8
- * [![License: MIT](https://img.shields.io/npm/l/@faasjs/request.svg)](https://github.com/faasjs/faasjs/blob/main/packages/request/LICENSE)
9
- * [![NPM Version](https://img.shields.io/npm/v/@faasjs/request.svg)](https://www.npmjs.com/package/@faasjs/request)
10
- *
11
- * ## Install
12
- *
13
- * ```sh
14
- * npm install @faasjs/request
15
- * ```
16
- * @packageDocumentation
17
- */
1
+ import { OutgoingHttpHeaders } from "node:http";
2
+ import https from "node:https";
3
+ import { Logger } from "@faasjs/logger";
18
4
 
5
+ //#region src/index.d.ts
19
6
  type Request = {
20
- headers?: OutgoingHttpHeaders;
21
- method?: string;
22
- host?: string;
23
- path?: string;
24
- query?: OutgoingHttpHeaders;
25
- body?: {
26
- [key: string]: any;
27
- };
7
+ headers?: OutgoingHttpHeaders;
8
+ method?: string;
9
+ host?: string;
10
+ path?: string;
11
+ query?: OutgoingHttpHeaders;
12
+ body?: {
13
+ [key: string]: any;
14
+ };
28
15
  };
29
16
  type Response<T = any> = {
30
- request?: Request;
31
- statusCode?: number;
32
- statusMessage?: string;
33
- headers: OutgoingHttpHeaders;
34
- body: T;
17
+ request?: Request;
18
+ statusCode?: number;
19
+ statusMessage?: string;
20
+ headers: OutgoingHttpHeaders;
21
+ body: T;
35
22
  };
36
23
  type RequestOptions = {
37
- headers?: OutgoingHttpHeaders;
38
- /**
39
- * The HTTP method to use when making the request. Defaults to GET.
40
- */
41
- method?: string;
42
- query?: {
43
- [key: string]: any;
44
- };
45
- body?: {
46
- [key: string]: any;
47
- } | string;
48
- /** Timeout in milliseconds, @default 5000 */
49
- timeout?: number;
50
- /**
51
- * The authentication credentials to use for the request.
52
- *
53
- * Format: `username:password`
54
- */
55
- auth?: string;
56
- /**
57
- * Path of uploading a file to the server.
58
- *
59
- * ```ts
60
- * await request('https://example.com', { file: 'filepath' })
61
- * ```
62
- */
63
- file?: string;
64
- /**
65
- * Create a write stream to download a file.
66
- *
67
- * ```ts
68
- * import { createWriteStream } from 'fs'
69
- *
70
- * const stream = createWriteStream('filepath')
71
- * await request('https://example.com', { downloadStream: stream })
72
- * ```
73
- */
74
- downloadStream?: NodeJS.WritableStream;
75
- /**
76
- * Path of downloading a file from the server.
77
- *
78
- * ```ts
79
- * await request('https://example.com', { downloadFile: 'filepath' })
80
- * ```
81
- */
82
- downloadFile?: string;
83
- /**
84
- * Body parser. Defaults to `JSON.parse`.
85
- */
86
- parse?: (body: string) => any;
87
- logger?: Logger;
24
+ headers?: OutgoingHttpHeaders;
25
+ /**
26
+ * The HTTP method to use when making the request. Defaults to GET.
27
+ */
28
+ method?: string;
29
+ query?: {
30
+ [key: string]: any;
31
+ };
32
+ body?: {
33
+ [key: string]: any;
34
+ } | string; /** Timeout in milliseconds, @default 5000 */
35
+ timeout?: number;
36
+ /**
37
+ * The authentication credentials to use for the request.
38
+ *
39
+ * Format: `username:password`
40
+ */
41
+ auth?: string;
42
+ /**
43
+ * Path of uploading a file to the server.
44
+ *
45
+ * ```ts
46
+ * await request('https://example.com', { file: 'filepath' })
47
+ * ```
48
+ */
49
+ file?: string;
50
+ /**
51
+ * Create a write stream to download a file.
52
+ *
53
+ * ```ts
54
+ * import { createWriteStream } from 'fs'
55
+ *
56
+ * const stream = createWriteStream('filepath')
57
+ * await request('https://example.com', { downloadStream: stream })
58
+ * ```
59
+ */
60
+ downloadStream?: NodeJS.WritableStream;
61
+ /**
62
+ * Path of downloading a file from the server.
63
+ *
64
+ * ```ts
65
+ * await request('https://example.com', { downloadFile: 'filepath' })
66
+ * ```
67
+ */
68
+ downloadFile?: string;
69
+ /**
70
+ * Body parser. Defaults to `JSON.parse`.
71
+ */
72
+ parse?: (body: string) => any;
73
+ logger?: Logger;
88
74
  } & Pick<https.RequestOptions, 'pfx' | 'passphrase' | 'agent'>;
89
75
  type Mock = (url: string, options: RequestOptions) => Promise<Response>;
90
76
  /**
@@ -98,13 +84,13 @@ declare function querystringify(obj: any): string;
98
84
  * ResponseError class
99
85
  */
100
86
  declare class ResponseError extends Error {
101
- response: Response;
102
- request?: Request;
103
- statusCode?: number;
104
- statusMessage?: string;
105
- headers: OutgoingHttpHeaders;
106
- body: any;
107
- constructor(message: string, response: Response<any>);
87
+ response: Response;
88
+ request: Request | undefined;
89
+ statusCode: number;
90
+ statusMessage: string;
91
+ headers: OutgoingHttpHeaders;
92
+ body: any;
93
+ constructor(message: string, response: Response<any>);
108
94
  }
109
95
  /**
110
96
  * Request
@@ -115,5 +101,5 @@ declare class ResponseError extends Error {
115
101
  * @see https://faasjs.com/doc/request.html
116
102
  */
117
103
  declare function request<T = any>(url: string, options?: RequestOptions): Promise<Response<T>>;
118
-
119
- export { type Mock, type Request, type RequestOptions, type Response, ResponseError, querystringify, request, setMock };
104
+ //#endregion
105
+ export { Mock, Request, RequestOptions, Response, ResponseError, querystringify, request, setMock };
package/dist/index.mjs CHANGED
@@ -1,214 +1,205 @@
1
- import { randomUUID } from 'crypto';
2
- import { createWriteStream, readFileSync } from 'fs';
3
- import http from 'http';
4
- import https from 'https';
5
- import { basename } from 'path';
6
- import { URL } from 'url';
7
- import { createGunzip, createBrotliDecompress } from 'zlib';
8
- import { Logger } from '@faasjs/logger';
1
+ import { randomUUID } from "node:crypto";
2
+ import { createWriteStream, readFileSync } from "node:fs";
3
+ import http from "node:http";
4
+ import https from "node:https";
5
+ import { basename } from "node:path";
6
+ import { URL } from "node:url";
7
+ import { createBrotliDecompress, createGunzip } from "node:zlib";
8
+ import { Logger } from "@faasjs/logger";
9
9
 
10
- // src/index.ts
11
- var mock = null;
10
+ //#region src/index.ts
11
+ /**
12
+ * FaasJS's request module.
13
+ *
14
+ * [![License: MIT](https://img.shields.io/npm/l/@faasjs/request.svg)](https://github.com/faasjs/faasjs/blob/main/packages/request/LICENSE)
15
+ * [![NPM Version](https://img.shields.io/npm/v/@faasjs/request.svg)](https://www.npmjs.com/package/@faasjs/request)
16
+ *
17
+ * ## Install
18
+ *
19
+ * ```sh
20
+ * npm install @faasjs/request
21
+ * ```
22
+ * @packageDocumentation
23
+ */
24
+ let mock = null;
25
+ /**
26
+ * Mock requests
27
+ * @param handler {function | null} null to disable mock
28
+ * @example setMock(async (url, options) => Promise.resolve({ headers: {}, statusCode: 200, body: { data: 'ok' } }))
29
+ */
12
30
  function setMock(handler) {
13
- mock = handler;
31
+ mock = handler;
14
32
  }
15
33
  function querystringify(obj) {
16
- const pairs = [];
17
- let value;
18
- let key;
19
- for (key in obj) {
20
- if (Object.hasOwn(obj, key)) {
21
- value = obj[key];
22
- if (!value && (value === null || value === void 0 || Number.isNaN(value))) {
23
- value = "";
24
- }
25
- key = encodeURIComponent(key);
26
- value = encodeURIComponent(value);
27
- if (key === null || value === null) continue;
28
- pairs.push(`${key}=${value}`);
29
- }
30
- }
31
- return pairs.length ? pairs.join("&") : "";
34
+ const pairs = [];
35
+ let value;
36
+ let key;
37
+ for (key in obj) if (Object.hasOwn(obj, key)) {
38
+ value = obj[key];
39
+ if (!value && (value === null || value === void 0 || Number.isNaN(value))) value = "";
40
+ key = encodeURIComponent(key);
41
+ value = encodeURIComponent(value);
42
+ if (key === null || value === null) continue;
43
+ pairs.push(`${key}=${value}`);
44
+ }
45
+ return pairs.length ? pairs.join("&") : "";
32
46
  }
47
+ /**
48
+ * ResponseError class
49
+ */
33
50
  var ResponseError = class extends Error {
34
- response;
35
- request;
36
- statusCode;
37
- statusMessage;
38
- headers;
39
- body;
40
- constructor(message, response) {
41
- super(message);
42
- this.response = response;
43
- this.request = response.request;
44
- this.statusCode = response.statusCode;
45
- this.statusMessage = response.statusMessage;
46
- this.headers = response.headers;
47
- this.body = response.body;
48
- }
51
+ response;
52
+ request;
53
+ statusCode;
54
+ statusMessage;
55
+ headers;
56
+ body;
57
+ constructor(message, response) {
58
+ super(message);
59
+ this.response = response;
60
+ this.request = response.request;
61
+ this.statusCode = response.statusCode || 500;
62
+ this.statusMessage = response.statusMessage || "Error";
63
+ this.headers = response.headers;
64
+ this.body = response.body;
65
+ }
49
66
  };
67
+ /**
68
+ * Request
69
+ *
70
+ * @param url - Request target URL.
71
+ * @param options - Request options.
72
+ * @returns Request response.
73
+ * @see https://faasjs.com/doc/request.html
74
+ */
50
75
  async function request(url, options = { headers: {} }) {
51
- const requestId = randomUUID();
52
- const logger = options.logger || new Logger(`request][${requestId}`);
53
- if (mock) {
54
- logger.debug("mock %s %j", url, options);
55
- return mock(url, options);
56
- }
57
- if (options.query) {
58
- if (!url.includes("?")) url += "?";
59
- else if (!url.endsWith("?")) url += "&";
60
- url += querystringify(options.query);
61
- }
62
- if (!options.headers) options.headers = {};
63
- const uri = new URL(url);
64
- const protocol = uri.protocol === "https:" ? https : http;
65
- if (!uri.protocol) throw Error("Unknown protocol");
66
- const requestOptions = {
67
- headers: {},
68
- host: uri.host ? uri.host.replace(/:[0-9]+$/, "") : uri.host,
69
- method: options.method ? options.method.toUpperCase() : "GET",
70
- path: uri.pathname + uri.search,
71
- port: uri.port || (uri.protocol === "https:" ? "443" : "80"),
72
- timeout: options.timeout || 5e3,
73
- auth: options.auth,
74
- pfx: options.pfx,
75
- passphrase: options.passphrase,
76
- agent: options.agent
77
- };
78
- if (!options.headers["Accept-Encoding"] && !options.downloadFile && !options.downloadStream)
79
- options.headers["Accept-Encoding"] = "br,gzip";
80
- for (const key in options.headers)
81
- if (typeof options.headers[key] !== "undefined" && options.headers[key] !== null)
82
- requestOptions.headers[key] = options.headers[key];
83
- let body = options.body;
84
- if (body && typeof body !== "string")
85
- if (options.headers["Content-Type"]?.toString().includes("application/x-www-form-urlencoded"))
86
- body = querystringify(body);
87
- else body = JSON.stringify(body);
88
- if (body && !options.headers["Content-Length"])
89
- requestOptions.headers["Content-Length"] = Buffer.byteLength(body);
90
- return await new Promise((resolve, reject) => {
91
- logger.debug("request %j", {
92
- ...options,
93
- body
94
- });
95
- const req = protocol.request(requestOptions, (res) => {
96
- if (options.downloadStream) {
97
- options.downloadStream.on("error", (error) => {
98
- logger.timeEnd(requestId, "response.error %j", error);
99
- reject(error);
100
- }).on("finish", () => {
101
- logger.timeEnd(
102
- requestId,
103
- "response %s %s",
104
- res.statusCode,
105
- res.headers["content-type"]
106
- );
107
- options.downloadStream?.end();
108
- resolve(void 0);
109
- });
110
- res.pipe(options.downloadStream, { end: true });
111
- return;
112
- }
113
- if (options.downloadFile) {
114
- const stream2 = createWriteStream(options.downloadFile).on("error", (error) => {
115
- logger.timeEnd(requestId, "response.error %j", error);
116
- stream2.destroy();
117
- reject(error);
118
- }).on("finish", () => {
119
- logger.timeEnd(
120
- requestId,
121
- "response %s %s %s",
122
- res.statusCode,
123
- res.headers["content-type"],
124
- stream2.bytesWritten
125
- );
126
- resolve(void 0);
127
- });
128
- res.pipe(stream2, { end: true });
129
- return;
130
- }
131
- let stream = res;
132
- switch (res.headers["content-encoding"]) {
133
- case "br":
134
- stream = res.pipe(createBrotliDecompress());
135
- break;
136
- case "gzip":
137
- stream = res.pipe(createGunzip());
138
- break;
139
- }
140
- let raw = "";
141
- stream.on("data", (chunk) => raw += chunk).on("error", (e) => {
142
- logger.timeEnd(requestId, "response.error %j", e);
143
- reject(e);
144
- }).on("end", () => {
145
- logger.timeEnd(
146
- requestId,
147
- "response %s %s %s %j",
148
- res.statusCode,
149
- res.headers["content-type"],
150
- res.headers["content-encoding"],
151
- raw
152
- );
153
- const response = /* @__PURE__ */ Object.create(null);
154
- response.request = requestOptions;
155
- response.request.body = body;
156
- response.statusCode = res.statusCode;
157
- response.statusMessage = res.statusMessage;
158
- response.headers = res.headers;
159
- response.body = raw;
160
- if (response.body && response.headers["content-type"] && response.headers["content-type"].includes("application/json") && typeof response.body === "string" && (response.body.startsWith("{") || response.body.startsWith("[")))
161
- try {
162
- response.body = (options.parse || JSON.parse)(response.body);
163
- logger.debug("response.parse JSON");
164
- } catch (error) {
165
- logger.warn("response plain body", response.body);
166
- logger.error(error);
167
- }
168
- if (response.statusCode >= 200 && response.statusCode < 400)
169
- resolve(response);
170
- else {
171
- logger.debug("response.error %j", response);
172
- reject(
173
- new ResponseError(
174
- `${res.statusMessage || res.statusCode} ${requestOptions.host}${requestOptions.path}`,
175
- response
176
- )
177
- );
178
- }
179
- });
180
- });
181
- if (body) req.write(body);
182
- if (options.file) {
183
- const crlf = "\r\n";
184
- const boundary = `--${Math.random().toString(16)}`;
185
- const delimiter = `${crlf}--${boundary}`;
186
- const headers = [
187
- `Content-Disposition: form-data; name="file"; filename="${basename(
188
- options.file
189
- )}"${crlf}`
190
- ];
191
- const multipartBody = Buffer.concat([
192
- Buffer.from(delimiter + crlf + headers.join("") + crlf),
193
- readFileSync(options.file),
194
- Buffer.from(`${delimiter}--`)
195
- ]);
196
- req.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
197
- req.setHeader("Content-Length", multipartBody.length);
198
- req.write(multipartBody);
199
- }
200
- req.on("error", (e) => {
201
- logger.timeEnd(requestId, "response.error %j", e);
202
- reject(e);
203
- });
204
- req.on("timeout", () => {
205
- logger.timeEnd(requestId, "response.timeout");
206
- req.destroy();
207
- reject(Error(`Timeout ${url}`));
208
- });
209
- logger.time(requestId);
210
- req.end();
211
- });
76
+ const requestId = randomUUID();
77
+ const logger = options.logger || new Logger(`request][${requestId}`);
78
+ if (mock) {
79
+ logger.debug("mock %s %j", url, options);
80
+ return mock(url, options);
81
+ }
82
+ if (options.query) {
83
+ if (!url.includes("?")) url += "?";
84
+ else if (!url.endsWith("?")) url += "&";
85
+ url += querystringify(options.query);
86
+ }
87
+ if (!options.headers) options.headers = {};
88
+ const uri = new URL(url);
89
+ const protocol = uri.protocol === "https:" ? https : http;
90
+ if (!uri.protocol) throw Error("Unknown protocol");
91
+ const requestOptions = {
92
+ headers: {},
93
+ host: uri.host ? uri.host.replace(/:[0-9]+$/, "") : uri.host,
94
+ method: options.method ? options.method.toUpperCase() : "GET",
95
+ path: uri.pathname + uri.search,
96
+ port: uri.port || (uri.protocol === "https:" ? "443" : "80"),
97
+ timeout: options.timeout || 5e3,
98
+ auth: options.auth,
99
+ pfx: options.pfx,
100
+ passphrase: options.passphrase,
101
+ agent: options.agent
102
+ };
103
+ if (!options.headers["Accept-Encoding"] && !options.downloadFile && !options.downloadStream) options.headers["Accept-Encoding"] = "br,gzip";
104
+ for (const key in options.headers) if (typeof options.headers[key] !== "undefined" && options.headers[key] !== null) requestOptions.headers[key] = options.headers[key];
105
+ let body = options.body;
106
+ if (body && typeof body !== "string") if (options.headers["Content-Type"]?.toString().includes("application/x-www-form-urlencoded")) body = querystringify(body);
107
+ else body = JSON.stringify(body);
108
+ if (body && !options.headers["Content-Length"]) requestOptions.headers["Content-Length"] = Buffer.byteLength(body);
109
+ return await new Promise((resolve, reject) => {
110
+ logger.debug("request %j", {
111
+ ...options,
112
+ body
113
+ });
114
+ const req = protocol.request(requestOptions, (res) => {
115
+ if (options.downloadStream) {
116
+ options.downloadStream.on("error", (error) => {
117
+ logger.timeEnd(requestId, "response.error %j", error);
118
+ reject(error);
119
+ }).on("finish", () => {
120
+ logger.timeEnd(requestId, "response %s %s", res.statusCode, res.headers["content-type"]);
121
+ options.downloadStream?.end();
122
+ resolve(void 0);
123
+ });
124
+ res.pipe(options.downloadStream, { end: true });
125
+ return;
126
+ }
127
+ if (options.downloadFile) {
128
+ const stream = createWriteStream(options.downloadFile).on("error", (error) => {
129
+ logger.timeEnd(requestId, "response.error %j", error);
130
+ stream.destroy();
131
+ reject(error);
132
+ }).on("finish", () => {
133
+ logger.timeEnd(requestId, "response %s %s %s", res.statusCode, res.headers["content-type"], stream.bytesWritten);
134
+ resolve(void 0);
135
+ });
136
+ res.pipe(stream, { end: true });
137
+ return;
138
+ }
139
+ let stream = res;
140
+ switch (res.headers["content-encoding"]) {
141
+ case "br":
142
+ stream = res.pipe(createBrotliDecompress());
143
+ break;
144
+ case "gzip":
145
+ stream = res.pipe(createGunzip());
146
+ break;
147
+ }
148
+ let raw = "";
149
+ stream.on("data", (chunk) => raw += chunk).on("error", (e) => {
150
+ logger.timeEnd(requestId, "response.error %j", e);
151
+ reject(e);
152
+ }).on("end", () => {
153
+ logger.timeEnd(requestId, "response %s %s %s %j", res.statusCode, res.headers["content-type"], res.headers["content-encoding"], raw);
154
+ const response = Object.create(null);
155
+ response.request = requestOptions;
156
+ response.request.body = body;
157
+ response.statusCode = res.statusCode;
158
+ response.statusMessage = res.statusMessage;
159
+ response.headers = res.headers;
160
+ response.body = raw;
161
+ if (response.body && response.headers["content-type"] && response.headers["content-type"].includes("application/json") && typeof response.body === "string" && (response.body.startsWith("{") || response.body.startsWith("["))) try {
162
+ response.body = (options.parse || JSON.parse)(response.body);
163
+ logger.debug("response.parse JSON");
164
+ } catch (error) {
165
+ logger.warn("response plain body", response.body);
166
+ logger.error(error);
167
+ }
168
+ if (response.statusCode >= 200 && response.statusCode < 400) resolve(response);
169
+ else {
170
+ logger.debug("response.error %j", response);
171
+ reject(new ResponseError(`${res.statusMessage || res.statusCode} ${requestOptions.host}${requestOptions.path}`, response));
172
+ }
173
+ });
174
+ });
175
+ if (body) req.write(body);
176
+ if (options.file) {
177
+ const crlf = "\r\n";
178
+ const boundary = `--${Math.random().toString(16)}`;
179
+ const delimiter = `${crlf}--${boundary}`;
180
+ const headers = [`Content-Disposition: form-data; name="file"; filename="${basename(options.file)}"${crlf}`];
181
+ const multipartBody = Buffer.concat([
182
+ Buffer.from(delimiter + crlf + headers.join("") + crlf),
183
+ readFileSync(options.file),
184
+ Buffer.from(`${delimiter}--`)
185
+ ]);
186
+ req.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
187
+ req.setHeader("Content-Length", multipartBody.length);
188
+ req.write(multipartBody);
189
+ }
190
+ req.on("error", (e) => {
191
+ logger.timeEnd(requestId, "response.error %j", e);
192
+ reject(e);
193
+ });
194
+ req.on("timeout", () => {
195
+ logger.timeEnd(requestId, "response.timeout");
196
+ req.destroy();
197
+ reject(Error(`Timeout ${url}`));
198
+ });
199
+ logger.time(requestId);
200
+ req.end();
201
+ });
212
202
  }
213
203
 
214
- export { ResponseError, querystringify, request, setMock };
204
+ //#endregion
205
+ export { ResponseError, querystringify, request, setMock };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/request",
3
- "version": "8.0.0-beta.6",
3
+ "version": "8.0.0-beta.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -24,16 +24,16 @@
24
24
  },
25
25
  "funding": "https://github.com/sponsors/faasjs",
26
26
  "scripts": {
27
- "build": "tsup-node src/index.ts --config ../../tsup.config.ts"
27
+ "build": "tsdown src/index.ts --config ../../tsdown.config.ts"
28
28
  },
29
29
  "files": [
30
30
  "dist"
31
31
  ],
32
32
  "peerDependencies": {
33
- "@faasjs/logger": ">=8.0.0-beta.6"
33
+ "@faasjs/logger": ">=8.0.0-beta.8"
34
34
  },
35
35
  "devDependencies": {
36
- "@faasjs/logger": ">=8.0.0-beta.6"
36
+ "@faasjs/logger": ">=8.0.0-beta.8"
37
37
  },
38
38
  "engines": {
39
39
  "node": ">=24.0.0",