rekwest 5.1.0 → 5.2.1

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/src/transfer.mjs CHANGED
@@ -1,114 +1,117 @@
1
- import http from 'node:http';
2
- import http2 from 'node:http2';
3
- import https from 'node:https';
4
- import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
5
- import { ackn } from './ackn.mjs';
6
- import { RequestError } from './errors.mjs';
7
- import { postflight } from './postflight.mjs';
8
- import { preflight } from './preflight.mjs';
9
- import { transform } from './transform.mjs';
10
- import {
11
- affix,
12
- dispatch,
13
- maxRetryAfterError,
14
- } from './utils.mjs';
15
-
16
- const {
17
- HTTP2_HEADER_RETRY_AFTER,
18
- } = http2.constants;
19
-
20
- export const transfer = async (options, overact) => {
21
- const { digest, redirected, thenable, url } = options;
22
-
23
- if (options.follow === 0) {
24
- throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
25
- }
26
-
27
- if (url.protocol === 'https:') {
28
- options = !options.h2 ? await ackn(options) : {
29
- ...options,
30
- createConnection: null,
31
- protocol: url.protocol,
32
- };
33
- } else if (Reflect.has(options, 'alpnProtocol')) {
34
- for (const it of [
35
- 'alpnProtocol',
36
- 'createConnection',
37
- 'h2',
38
- 'protocol',
39
- ]) { Reflect.deleteProperty(options, it); }
40
- }
41
-
42
- try {
43
- options = await transform(preflight(options));
44
- } catch (ex) {
45
- options.createConnection?.().destroy();
46
- throw ex;
47
- }
48
-
49
- const promise = new Promise((resolve, reject) => {
50
- let client, req;
51
-
52
- if (options.h2) {
53
- client = http2.connect(url.origin, options);
54
- req = client.request(options.headers, options);
55
- } else {
56
- const { request } = url.protocol === 'http:' ? http : https;
57
-
58
- req = request(url, options);
59
- }
60
-
61
- affix(client, req, options);
62
-
63
- req.once('error', reject);
64
- req.once('frameError', reject);
65
- req.once('goaway', reject);
66
- req.once('response', (res) => postflight(req, res, options, {
67
- reject,
68
- resolve,
69
- }));
70
-
71
- dispatch(options, req);
72
- });
73
-
74
- try {
75
- const res = await promise;
76
-
77
- if (digest && !redirected) {
78
- res.body = await res.body();
79
- }
80
-
81
- return res;
82
- } catch (ex) {
83
- const { maxRetryAfter, retry } = options;
84
-
85
- if (retry?.attempts && retry?.statusCodes.includes(ex.statusCode)) {
86
- let { interval } = retry;
87
-
88
- if (retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
89
- interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
90
- interval = Number(interval) * 1000 || new Date(interval) - Date.now();
91
- if (interval > maxRetryAfter) {
92
- throw maxRetryAfterError(interval, { cause: ex });
93
- }
94
- } else {
95
- interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
96
- }
97
-
98
- retry.attempts--;
99
- retry.interval = interval;
100
-
101
- return setTimeoutPromise(interval).then(() => overact(url, options));
102
- }
103
-
104
- if (digest && !redirected && ex.body) {
105
- ex.body = await ex.body();
106
- }
107
-
108
- if (!thenable) {
109
- throw ex;
110
- } else {
111
- return ex;
112
- }
113
- }
114
- };
1
+ import http from 'node:http';
2
+ import http2 from 'node:http2';
3
+ import https from 'node:https';
4
+ import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
5
+ import { ackn } from './ackn.mjs';
6
+ import { RequestError } from './errors.mjs';
7
+ import { postflight } from './postflight.mjs';
8
+ import { preflight } from './preflight.mjs';
9
+ import { transform } from './transform.mjs';
10
+ import {
11
+ affix,
12
+ dispatch,
13
+ maxRetryAfterError,
14
+ } from './utils.mjs';
15
+
16
+ const {
17
+ HTTP2_HEADER_RETRY_AFTER,
18
+ } = http2.constants;
19
+
20
+ export const transfer = async (options, overact) => {
21
+ const { digest, redirected, thenable, url } = options;
22
+
23
+ if (options.follow === 0) {
24
+ throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
25
+ }
26
+
27
+ if (url.protocol === 'https:') {
28
+ options = !options.h2 ? await ackn(options) : {
29
+ ...options,
30
+ createConnection: null,
31
+ protocol: url.protocol,
32
+ };
33
+ } else if (Reflect.has(options, 'alpnProtocol')) {
34
+ for (const it of [
35
+ 'alpnProtocol',
36
+ 'createConnection',
37
+ 'h2',
38
+ 'protocol',
39
+ ]) { Reflect.deleteProperty(options, it); }
40
+ }
41
+
42
+ try {
43
+ options = await transform(preflight(options));
44
+ } catch (ex) {
45
+ options.createConnection?.().destroy();
46
+ throw ex;
47
+ }
48
+
49
+ const promise = new Promise((resolve, reject) => {
50
+ let client, req;
51
+
52
+ if (options.h2) {
53
+ client = http2.connect(url.origin, options);
54
+ req = client.request(options.headers, options);
55
+ } else {
56
+ const { request } = url.protocol === 'http:' ? http : https;
57
+
58
+ req = request(url, options);
59
+ }
60
+
61
+ affix(client, req, options);
62
+
63
+ req.once('aborted', reject);
64
+ req.once('error', reject);
65
+ req.once('frameError', reject);
66
+ req.once('goaway', reject);
67
+ req.once('response', (res) => postflight(req, res, options, {
68
+ reject,
69
+ resolve,
70
+ }));
71
+
72
+ dispatch(options, req);
73
+ });
74
+
75
+ try {
76
+ const res = await promise;
77
+
78
+ if (digest && !redirected) {
79
+ res.body = await res.body();
80
+ }
81
+
82
+ return res;
83
+ } catch (ex) {
84
+ const { maxRetryAfter, retry } = options;
85
+
86
+ if (retry?.attempts > 0) {
87
+ if (retry.errorCodes?.includes(ex.code) || retry.statusCodes?.includes(ex.statusCode)) {
88
+ let { interval } = retry;
89
+
90
+ if (retry.retryAfter && ex.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
91
+ interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
92
+ interval = Number(interval) * 1e3 || new Date(interval) - Date.now();
93
+ if (interval > maxRetryAfter) {
94
+ throw maxRetryAfterError(interval, { cause: ex });
95
+ }
96
+ } else {
97
+ interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
98
+ }
99
+
100
+ retry.attempts--;
101
+ retry.interval = interval;
102
+
103
+ return setTimeoutPromise(interval).then(() => overact(url, options));
104
+ }
105
+ }
106
+
107
+ if (digest && !redirected && ex.body) {
108
+ ex.body = await ex.body();
109
+ }
110
+
111
+ if (!thenable) {
112
+ throw ex;
113
+ } else {
114
+ return ex;
115
+ }
116
+ }
117
+ };
package/src/utils.mjs CHANGED
@@ -1,200 +1,201 @@
1
- import {
2
- Blob,
3
- File,
4
- } from 'node:buffer';
5
- import http2 from 'node:http2';
6
- import { pipeline } from 'node:stream';
7
- import zlib from 'node:zlib';
8
- import defaults from './defaults.mjs';
9
- import {
10
- RequestError,
11
- TimeoutError,
12
- } from './errors.mjs';
13
-
14
- const {
15
- HTTP2_HEADER_RETRY_AFTER,
16
- HTTP2_HEADER_STATUS,
17
- } = http2.constants;
18
-
19
- export const admix = (res, headers, options) => {
20
- const { h2 } = options;
21
-
22
- if (h2) {
23
- Reflect.defineProperty(res, 'headers', {
24
- enumerable: true,
25
- value: headers,
26
- });
27
-
28
- Reflect.defineProperty(res, 'httpVersion', {
29
- enumerable: true,
30
- value: `${ h2 + 1 }.0`,
31
- });
32
-
33
- Reflect.defineProperty(res, 'statusCode', {
34
- enumerable: true,
35
- value: headers[HTTP2_HEADER_STATUS],
36
- });
37
- }
38
-
39
- Reflect.defineProperty(res, 'ok', {
40
- enumerable: true,
41
- value: /^2\d{2}$/.test(res.statusCode),
42
- });
43
-
44
- Reflect.defineProperty(res, 'redirected', {
45
- enumerable: true,
46
- value: !!options.redirected,
47
- });
48
- };
49
-
50
- export const affix = (client, req, options) => {
51
- req.once('end', () => client?.close());
52
- req.once('timeout', () => req.destroy(new TimeoutError(`Timed out after ${ options.timeout } ms.`)));
53
- req.once('trailers', (trailers) => {
54
- Reflect.defineProperty(req, 'trailers', {
55
- enumerable: true,
56
- value: trailers,
57
- });
58
- });
59
- };
60
-
61
- export const brandCheck = (value, ctor) => {
62
- if (!(value instanceof ctor)) {
63
- throw new TypeError('Illegal invocation');
64
- }
65
- };
66
-
67
- export const compress = (readable, encodings = '') => {
68
- const encoders = [];
69
-
70
- encodings = unwind(encodings);
71
-
72
- for (const encoding of encodings) {
73
- if (/\bbr\b/i.test(encoding)) {
74
- encoders.push(zlib.createBrotliCompress());
75
- } else if (/\bdeflate(?!-(?:\w+)?)\b/i.test(encoding)) {
76
- encoders.push(zlib.createDeflate());
77
- } else if (/\bdeflate-raw\b/i.test(encoding)) {
78
- encoders.push(zlib.createDeflateRaw());
79
- } else if (/\bgzip\b/i.test(encoding)) {
80
- encoders.push(zlib.createGzip());
81
- } else {
82
- return readable;
83
- }
84
- }
85
-
86
- return pipeline(readable, ...encoders, () => void 0);
87
- };
88
-
89
- export const copyWithMerge = (target, ...rest) => {
90
- target = structuredClone(target);
91
- if (!rest.length) {
92
- return target;
93
- }
94
-
95
- return merge(target, ...rest);
96
- };
97
-
98
- export const decompress = (readable, encodings = '') => {
99
- const decoders = [];
100
-
101
- encodings = unwind(encodings);
102
-
103
- for (const encoding of encodings) {
104
- if (/\bbr\b/i.test(encoding)) {
105
- decoders.push(zlib.createBrotliDecompress());
106
- } else if (/\bdeflate(?!-(?:\w+)?)\b/i.test(encoding)) {
107
- decoders.push(zlib.createInflate());
108
- } else if (/\bdeflate-raw\b/i.test(encoding)) {
109
- decoders.push(zlib.createInflateRaw());
110
- } else if (/\bgzip\b/i.test(encoding)) {
111
- decoders.push(zlib.createGunzip());
112
- } else {
113
- return readable;
114
- }
115
- }
116
-
117
- return pipeline(readable, ...decoders, () => void 0);
118
- };
119
-
120
- export const dispatch = ({ body }, req) => {
121
- if (body?.pipe?.constructor === Function) {
122
- body.pipe(req);
123
- } else {
124
- req.end(body);
125
- }
126
- };
127
-
128
- export const isFileLike = (instance) => {
129
- return [
130
- Blob.name,
131
- File.name,
132
- ].includes(instance?.[Symbol.toStringTag]);
133
- };
134
-
135
- export const isReadableStream = (instance) => {
136
- return ReadableStream.name === instance?.[Symbol.toStringTag];
137
- };
138
-
139
- export const maxRetryAfter = Symbol('maxRetryAfter');
140
-
141
- export const maxRetryAfterError = (
142
- interval,
143
- options,
144
- ) => new RequestError(`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms.`, options);
145
-
146
- export const merge = (target, ...rest) => {
147
- rest = rest.filter((it) => it === Object(it));
148
- for (const source of rest) {
149
- for (const key of Object.getOwnPropertyNames(source)) {
150
- const sv = source[key];
151
- const tv = target[key];
152
-
153
- if (Object(sv) === sv && Object(tv) === tv) {
154
- target[key] = merge(tv, sv);
155
- continue;
156
- }
157
-
158
- target[key] = source[key];
159
- }
160
- }
161
-
162
- return target;
163
- };
164
-
165
- export const normalize = (url, options = {}) => {
166
- if (!options.redirected) {
167
- options = copyWithMerge(defaults.stash, options);
168
- }
169
-
170
- if (options.trimTrailingSlashes) {
171
- url = `${ url }`.replace(/(?<!:)\/+/g, '/');
172
- }
173
-
174
- if (options.stripTrailingSlash) {
175
- url = `${ url }`.replace(/\/$|\/(?=#)|\/(?=\?)/g, '');
176
- }
177
-
178
- url = new URL(url, options.baseURL);
179
-
180
- return Object.assign(options, { url });
181
- };
182
-
183
- export const sameOrigin = (a, b) => a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;
184
-
185
- export async function* tap(value) {
186
- if (Reflect.has(value, Symbol.asyncIterator)) {
187
- yield* value;
188
- } else if (value.stream) {
189
- yield* value.stream();
190
- } else {
191
- yield await value.arrayBuffer();
192
- }
193
- }
194
-
195
- export const toCamelCase = (str) => str?.toLowerCase().replace(
196
- /\p{Punctuation}.|\p{White_Space}./gu,
197
- (val) => val.replace(/\p{Punctuation}+|\p{White_Space}+/gu, '').toUpperCase(),
198
- );
199
-
200
- export const unwind = (encodings) => encodings.split(',').map((it) => it.trim());
1
+ import {
2
+ Blob,
3
+ File,
4
+ } from 'node:buffer';
5
+ import http2 from 'node:http2';
6
+ import { pipeline } from 'node:stream';
7
+ import zlib from 'node:zlib';
8
+ import defaults from './defaults.mjs';
9
+ import {
10
+ RequestError,
11
+ TimeoutError,
12
+ } from './errors.mjs';
13
+
14
+ const {
15
+ HTTP2_HEADER_RETRY_AFTER,
16
+ HTTP2_HEADER_STATUS,
17
+ } = http2.constants;
18
+
19
+ export const admix = (res, headers, options) => {
20
+ const { h2 } = options;
21
+
22
+ if (h2) {
23
+ Reflect.defineProperty(res, 'headers', {
24
+ enumerable: true,
25
+ value: headers,
26
+ });
27
+
28
+ Reflect.defineProperty(res, 'httpVersion', {
29
+ enumerable: true,
30
+ value: `${ h2 + 1 }.0`,
31
+ });
32
+
33
+ Reflect.defineProperty(res, 'statusCode', {
34
+ enumerable: true,
35
+ value: headers[HTTP2_HEADER_STATUS],
36
+ });
37
+ }
38
+
39
+ Reflect.defineProperty(res, 'ok', {
40
+ enumerable: true,
41
+ value: /^2\d{2}$/.test(res.statusCode),
42
+ });
43
+
44
+ Reflect.defineProperty(res, 'redirected', {
45
+ enumerable: true,
46
+ value: !!options.redirected,
47
+ });
48
+ };
49
+
50
+ export const affix = (client, req, options) => {
51
+ req.once('close', () => client?.close());
52
+ req.once('end', () => client?.close());
53
+ req.once('timeout', () => req.destroy(new TimeoutError(`Timed out after ${ options.timeout } ms.`)));
54
+ req.once('trailers', (trailers) => {
55
+ Reflect.defineProperty(req, 'trailers', {
56
+ enumerable: true,
57
+ value: trailers,
58
+ });
59
+ });
60
+ };
61
+
62
+ export const brandCheck = (value, ctor) => {
63
+ if (!(value instanceof ctor)) {
64
+ throw new TypeError('Illegal invocation');
65
+ }
66
+ };
67
+
68
+ export const compress = (readable, encodings = '') => {
69
+ const encoders = [];
70
+
71
+ encodings = unwind(encodings);
72
+
73
+ for (const encoding of encodings) {
74
+ if (/\bbr\b/i.test(encoding)) {
75
+ encoders.push(zlib.createBrotliCompress());
76
+ } else if (/\bdeflate(?!-(?:\w+)?)\b/i.test(encoding)) {
77
+ encoders.push(zlib.createDeflate());
78
+ } else if (/\bdeflate-raw\b/i.test(encoding)) {
79
+ encoders.push(zlib.createDeflateRaw());
80
+ } else if (/\bgzip\b/i.test(encoding)) {
81
+ encoders.push(zlib.createGzip());
82
+ } else {
83
+ return readable;
84
+ }
85
+ }
86
+
87
+ return pipeline(readable, ...encoders, () => void 0);
88
+ };
89
+
90
+ export const copyWithMerge = (target, ...rest) => {
91
+ target = structuredClone(target);
92
+ if (!rest.length) {
93
+ return target;
94
+ }
95
+
96
+ return merge(target, ...rest);
97
+ };
98
+
99
+ export const decompress = (readable, encodings = '') => {
100
+ const decoders = [];
101
+
102
+ encodings = unwind(encodings);
103
+
104
+ for (const encoding of encodings) {
105
+ if (/\bbr\b/i.test(encoding)) {
106
+ decoders.push(zlib.createBrotliDecompress());
107
+ } else if (/\bdeflate(?!-(?:\w+)?)\b/i.test(encoding)) {
108
+ decoders.push(zlib.createInflate());
109
+ } else if (/\bdeflate-raw\b/i.test(encoding)) {
110
+ decoders.push(zlib.createInflateRaw());
111
+ } else if (/\bgzip\b/i.test(encoding)) {
112
+ decoders.push(zlib.createGunzip());
113
+ } else {
114
+ return readable;
115
+ }
116
+ }
117
+
118
+ return pipeline(readable, ...decoders, () => void 0);
119
+ };
120
+
121
+ export const dispatch = ({ body }, req) => {
122
+ if (body?.pipe?.constructor === Function) {
123
+ body.pipe(req);
124
+ } else {
125
+ req.end(body);
126
+ }
127
+ };
128
+
129
+ export const isFileLike = (instance) => {
130
+ return [
131
+ Blob.name,
132
+ File.name,
133
+ ].includes(instance?.[Symbol.toStringTag]);
134
+ };
135
+
136
+ export const isReadableStream = (instance) => {
137
+ return ReadableStream.name === instance?.[Symbol.toStringTag];
138
+ };
139
+
140
+ export const maxRetryAfter = Symbol('maxRetryAfter');
141
+
142
+ export const maxRetryAfterError = (
143
+ interval,
144
+ options,
145
+ ) => new RequestError(`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms.`, options);
146
+
147
+ export const merge = (target, ...rest) => {
148
+ rest = rest.filter((it) => it === Object(it));
149
+ for (const source of rest) {
150
+ for (const key of Object.getOwnPropertyNames(source)) {
151
+ const sv = source[key];
152
+ const tv = target[key];
153
+
154
+ if (Object(sv) === sv && Object(tv) === tv) {
155
+ target[key] = merge(tv, sv);
156
+ continue;
157
+ }
158
+
159
+ target[key] = source[key];
160
+ }
161
+ }
162
+
163
+ return target;
164
+ };
165
+
166
+ export const normalize = (url, options = {}) => {
167
+ if (!options.redirected) {
168
+ options = copyWithMerge(defaults.stash, options);
169
+ }
170
+
171
+ if (options.trimTrailingSlashes) {
172
+ url = `${ url }`.replace(/(?<!:)\/+/g, '/');
173
+ }
174
+
175
+ if (options.stripTrailingSlash) {
176
+ url = `${ url }`.replace(/\/$|\/(?=#)|\/(?=\?)/g, '');
177
+ }
178
+
179
+ url = new URL(url, options.baseURL);
180
+
181
+ return Object.assign(options, { url });
182
+ };
183
+
184
+ export const sameOrigin = (a, b) => a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;
185
+
186
+ export async function* tap(value) {
187
+ if (Reflect.has(value, Symbol.asyncIterator)) {
188
+ yield* value;
189
+ } else if (value.stream) {
190
+ yield* value.stream();
191
+ } else {
192
+ yield await value.arrayBuffer();
193
+ }
194
+ }
195
+
196
+ export const toCamelCase = (str) => str?.toLowerCase().replace(
197
+ /\p{Punctuation}.|\p{White_Space}./gu,
198
+ (val) => val.replace(/\p{Punctuation}+|\p{White_Space}+/gu, '').toUpperCase(),
199
+ );
200
+
201
+ export const unwind = (encodings) => encodings.split(',').map((it) => it.trim());