rekwest 6.2.0 → 7.0.0

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.
Files changed (45) hide show
  1. package/README.md +11 -10
  2. package/dist/codecs.cjs +55 -0
  3. package/dist/{config.js → config.cjs} +15 -19
  4. package/dist/{cookies.js → cookies.cjs} +2 -2
  5. package/dist/{formdata.js → formdata.cjs} +8 -11
  6. package/dist/{index.js → index.cjs} +27 -15
  7. package/dist/{mixin.js → mixin.cjs} +22 -17
  8. package/dist/postflight.cjs +60 -0
  9. package/dist/{preflight.js → preflight.cjs} +11 -9
  10. package/dist/redirects.cjs +63 -0
  11. package/dist/retries.cjs +57 -0
  12. package/dist/{transfer.js → transfer.cjs} +13 -40
  13. package/dist/transform.cjs +105 -0
  14. package/dist/utils.cjs +140 -0
  15. package/dist/{validation.js → validation.cjs} +1 -1
  16. package/package.json +15 -14
  17. package/src/{ackn.mjs → ackn.js} +33 -33
  18. package/src/codecs.js +55 -0
  19. package/src/{config.mjs → config.js} +88 -93
  20. package/src/{constants.mjs → constants.js} +29 -29
  21. package/src/{cookies.mjs → cookies.js} +100 -100
  22. package/src/{formdata.mjs → formdata.js} +8 -14
  23. package/src/{index.mjs → index.js} +22 -22
  24. package/src/{mediatypes.mjs → mediatypes.js} +6 -6
  25. package/src/{mixin.mjs → mixin.js} +25 -26
  26. package/src/postflight.js +56 -0
  27. package/src/{preflight.mjs → preflight.js} +100 -91
  28. package/src/redirects.js +79 -0
  29. package/src/retries.js +51 -0
  30. package/src/transfer.js +92 -0
  31. package/src/transform.js +109 -0
  32. package/src/utils.js +152 -0
  33. package/src/{validation.mjs → validation.js} +33 -33
  34. package/dist/postflight.js +0 -117
  35. package/dist/transform.js +0 -79
  36. package/dist/utils.js +0 -188
  37. package/src/postflight.mjs +0 -136
  38. package/src/transfer.mjs +0 -121
  39. package/src/transform.mjs +0 -82
  40. package/src/utils.mjs +0 -205
  41. /package/dist/{ackn.js → ackn.cjs} +0 -0
  42. /package/dist/{constants.js → constants.cjs} +0 -0
  43. /package/dist/{errors.js → errors.cjs} +0 -0
  44. /package/dist/{mediatypes.js → mediatypes.cjs} +0 -0
  45. /package/src/{errors.mjs → errors.js} +0 -0
@@ -1,93 +1,88 @@
1
- import http2 from 'node:http2';
2
- import zlib from 'node:zlib';
3
- import {
4
- requestCredentials,
5
- requestRedirect,
6
- } from './constants.mjs';
7
- import {
8
- APPLICATION_JSON,
9
- TEXT_PLAIN,
10
- WILDCARD,
11
- } from './mediatypes.mjs';
12
- import { maxRetryAfter } from './utils.mjs';
13
-
14
- const {
15
- HTTP2_HEADER_ACCEPT,
16
- HTTP2_HEADER_ACCEPT_ENCODING,
17
- HTTP2_METHOD_GET,
18
- HTTP_STATUS_BAD_GATEWAY,
19
- HTTP_STATUS_GATEWAY_TIMEOUT,
20
- HTTP_STATUS_INTERNAL_SERVER_ERROR,
21
- HTTP_STATUS_SERVICE_UNAVAILABLE,
22
- HTTP_STATUS_TOO_MANY_REQUESTS,
23
- } = http2.constants;
24
-
25
- export const isZstdSupported = !!zlib.constants.ZSTD_CLEVEL_DEFAULT;
26
-
27
- const defaults = {
28
- compression: {
29
- brotliOptions: {
30
- params: {
31
- [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
32
- },
33
- },
34
- zstdOptions: {
35
- params: {
36
- [zlib.constants.ZSTD_c_compressionLevel]: 6,
37
- },
38
- },
39
- },
40
- cookiesTTL: false,
41
- credentials: requestCredentials.sameOrigin,
42
- decompression: {},
43
- digest: true,
44
- follow: 20,
45
- h2: false,
46
- headers: {
47
- [HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
48
- [HTTP2_HEADER_ACCEPT_ENCODING]: `br,${ isZstdSupported ? ' zstd, ' : ' ' }gzip, deflate, deflate-raw`,
49
- },
50
- get maxRetryAfter() {
51
- return this[maxRetryAfter] ?? this.timeout;
52
- },
53
- set maxRetryAfter(value) {
54
- this[maxRetryAfter] = value;
55
- },
56
- method: HTTP2_METHOD_GET,
57
- parse: true,
58
- redirect: requestRedirect.follow,
59
- redirected: false,
60
- retry: {
61
- attempts: 0,
62
- backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
63
- errorCodes: [
64
- 'EAI_AGAIN',
65
- 'ECONNREFUSED',
66
- 'ECONNRESET',
67
- 'EHOSTDOWN',
68
- 'EHOSTUNREACH',
69
- 'ENETDOWN',
70
- 'ENETUNREACH',
71
- 'ENOTFOUND',
72
- 'EPIPE',
73
- 'ERR_HTTP2_STREAM_ERROR',
74
- ],
75
- interval: 1e3,
76
- retryAfter: true,
77
- statusCodes: [
78
- HTTP_STATUS_TOO_MANY_REQUESTS,
79
- HTTP_STATUS_INTERNAL_SERVER_ERROR,
80
- HTTP_STATUS_BAD_GATEWAY,
81
- HTTP_STATUS_SERVICE_UNAVAILABLE,
82
- HTTP_STATUS_GATEWAY_TIMEOUT,
83
- ],
84
- },
85
- stripTrailingSlash: false,
86
- thenable: false,
87
- timeout: 3e5,
88
- trimTrailingSlashes: false,
89
- };
90
-
91
- export default {
92
- defaults,
93
- };
1
+ import http2 from 'node:http2';
2
+ import zlib from 'node:zlib';
3
+ import {
4
+ requestCredentials,
5
+ requestRedirect,
6
+ } from './constants.js';
7
+ import {
8
+ APPLICATION_JSON,
9
+ TEXT_PLAIN,
10
+ WILDCARD,
11
+ } from './mediatypes.js';
12
+
13
+ export const isZstdSupported = !!zlib.constants.ZSTD_CLEVEL_DEFAULT;
14
+
15
+ const {
16
+ HTTP2_HEADER_ACCEPT,
17
+ HTTP2_HEADER_ACCEPT_ENCODING,
18
+ HTTP2_METHOD_GET,
19
+ HTTP_STATUS_BAD_GATEWAY,
20
+ HTTP_STATUS_GATEWAY_TIMEOUT,
21
+ HTTP_STATUS_INTERNAL_SERVER_ERROR,
22
+ HTTP_STATUS_SERVICE_UNAVAILABLE,
23
+ HTTP_STATUS_TOO_MANY_REQUESTS,
24
+ } = http2.constants;
25
+
26
+ const timeout = 3e5;
27
+
28
+ const defaults = {
29
+ bufferBody: false,
30
+ cookiesTTL: false,
31
+ credentials: requestCredentials.sameOrigin,
32
+ decodersOptions: {},
33
+ digest: true,
34
+ encodersOptions: {
35
+ brotli: {
36
+ params: {
37
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
38
+ },
39
+ },
40
+ zstd: {
41
+ params: {
42
+ [zlib.constants.ZSTD_c_compressionLevel]: 6,
43
+ },
44
+ },
45
+ },
46
+ follow: 20,
47
+ h2: false,
48
+ headers: {
49
+ [HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
50
+ [HTTP2_HEADER_ACCEPT_ENCODING]: `br,${ isZstdSupported ? ' zstd, ' : ' ' }gzip, deflate, deflate-raw`,
51
+ },
52
+ maxRetryAfter: timeout,
53
+ method: HTTP2_METHOD_GET,
54
+ parse: true,
55
+ redirect: requestRedirect.follow,
56
+ redirected: false,
57
+ retry: {
58
+ attempts: 0,
59
+ backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
60
+ errorCodes: [
61
+ 'ECONNREFUSED',
62
+ 'ECONNRESET',
63
+ 'EHOSTDOWN',
64
+ 'EHOSTUNREACH',
65
+ 'ENETDOWN',
66
+ 'ENETUNREACH',
67
+ 'ENOTFOUND',
68
+ 'ERR_HTTP2_STREAM_ERROR',
69
+ ],
70
+ interval: 1e3,
71
+ retryAfter: true,
72
+ statusCodes: [
73
+ HTTP_STATUS_TOO_MANY_REQUESTS,
74
+ HTTP_STATUS_INTERNAL_SERVER_ERROR,
75
+ HTTP_STATUS_BAD_GATEWAY,
76
+ HTTP_STATUS_SERVICE_UNAVAILABLE,
77
+ HTTP_STATUS_GATEWAY_TIMEOUT,
78
+ ],
79
+ },
80
+ stripTrailingSlash: false,
81
+ thenable: false,
82
+ timeout,
83
+ trimTrailingSlashes: false,
84
+ };
85
+
86
+ export default {
87
+ defaults,
88
+ };
@@ -1,29 +1,29 @@
1
- import http2 from 'node:http2';
2
-
3
- const {
4
- HTTP_STATUS_FOUND,
5
- HTTP_STATUS_MOVED_PERMANENTLY,
6
- HTTP_STATUS_PERMANENT_REDIRECT,
7
- HTTP_STATUS_SEE_OTHER,
8
- HTTP_STATUS_TEMPORARY_REDIRECT,
9
- } = http2.constants;
10
-
11
- export const requestCredentials = {
12
- include: 'include',
13
- omit: 'omit',
14
- sameOrigin: 'same-origin',
15
- };
16
-
17
- export const requestRedirect = {
18
- error: 'error',
19
- follow: 'follow',
20
- manual: 'manual',
21
- };
22
-
23
- export const requestRedirectCodes = [
24
- HTTP_STATUS_MOVED_PERMANENTLY,
25
- HTTP_STATUS_FOUND,
26
- HTTP_STATUS_SEE_OTHER,
27
- HTTP_STATUS_TEMPORARY_REDIRECT,
28
- HTTP_STATUS_PERMANENT_REDIRECT,
29
- ];
1
+ import http2 from 'node:http2';
2
+
3
+ const {
4
+ HTTP_STATUS_FOUND,
5
+ HTTP_STATUS_MOVED_PERMANENTLY,
6
+ HTTP_STATUS_PERMANENT_REDIRECT,
7
+ HTTP_STATUS_SEE_OTHER,
8
+ HTTP_STATUS_TEMPORARY_REDIRECT,
9
+ } = http2.constants;
10
+
11
+ export const requestCredentials = {
12
+ include: 'include',
13
+ omit: 'omit',
14
+ sameOrigin: 'same-origin',
15
+ };
16
+
17
+ export const requestRedirect = {
18
+ error: 'error',
19
+ follow: 'follow',
20
+ manual: 'manual',
21
+ };
22
+
23
+ export const requestRedirectCodes = [
24
+ HTTP_STATUS_MOVED_PERMANENTLY,
25
+ HTTP_STATUS_FOUND,
26
+ HTTP_STATUS_SEE_OTHER,
27
+ HTTP_STATUS_TEMPORARY_REDIRECT,
28
+ HTTP_STATUS_PERMANENT_REDIRECT,
29
+ ];
@@ -1,100 +1,100 @@
1
- import {
2
- brandCheck,
3
- toCamelCase,
4
- } from './utils.mjs';
5
-
6
- const lifetimeCap = 3456e7; // pragma: 400 days
7
-
8
- export class Cookies extends URLSearchParams {
9
-
10
- static #finalizers = new Set();
11
- static jar = new Map();
12
-
13
- static #register(target, value) {
14
- const finalizer = new FinalizationRegistry((heldValue) => {
15
- clearTimeout(heldValue);
16
- this.#finalizers.delete(finalizer);
17
- });
18
-
19
- finalizer.register(target, value);
20
- this.#finalizers.add(finalizer);
21
- }
22
-
23
- #chronometry = new Map();
24
-
25
- get [Symbol.toStringTag]() {
26
- return this.constructor.name;
27
- }
28
-
29
- constructor(input, { cookiesTTL } = { cookiesTTL: false }) {
30
- if (Array.isArray(input) && input.every((it) => !Array.isArray(it))) {
31
- input = input.map((it) => {
32
- if (!cookiesTTL) {
33
- return [it.split(';').at(0).trim()];
34
- }
35
-
36
- const [cookie, ...attrs] = it.split(';').map((it) => it.trim());
37
- const ttl = {};
38
-
39
- for (const val of attrs) {
40
- if (/(?:Expires|Max-Age)=/i.test(val)) {
41
- const [key, value] = val.toLowerCase().split('=');
42
- const ms = Number.isFinite(Number(value)) ? value * 1e3 : Date.parse(value) - Date.now();
43
-
44
- ttl[toCamelCase(key)] = Math.min(ms, lifetimeCap);
45
- }
46
- }
47
-
48
- return [
49
- cookie.replace(/\u0022/g, ''),
50
- Object.keys(ttl).length ? ttl : null,
51
- ];
52
- });
53
- }
54
-
55
- super(Array.isArray(input) ? input.map((it) => it.at(0)).join('&') : input);
56
-
57
- if (Array.isArray(input) && cookiesTTL) {
58
- for (const [cookie, ttl] of input.filter((it) => it.at(1))) {
59
- const key = cookie.split('=').at(0);
60
-
61
- if (this.#chronometry.has(key)) {
62
- clearTimeout(this.#chronometry.get(key));
63
- this.#chronometry.delete(key);
64
- }
65
-
66
- const { expires, maxAge } = ttl;
67
-
68
- for (const ms of [
69
- maxAge,
70
- expires,
71
- ]) {
72
- if (!Number.isInteger(ms)) {
73
- continue;
74
- }
75
-
76
- const ref = new WeakRef(this);
77
- const tid = setTimeout(() => {
78
- const ctx = ref.deref();
79
-
80
- if (ctx) {
81
- ctx.#chronometry.delete(key);
82
- ctx.delete(key);
83
- }
84
- }, Math.max(ms, 0));
85
-
86
- this.constructor.#register(this, tid);
87
- this.#chronometry.set(key, tid);
88
- break;
89
- }
90
- }
91
- }
92
- }
93
-
94
- toString() {
95
- brandCheck(this, Cookies);
96
-
97
- return super.toString().split('&').join('; ').trim();
98
- }
99
-
100
- }
1
+ import {
2
+ brandCheck,
3
+ toCamelCase,
4
+ } from './utils.js';
5
+
6
+ const lifetimeCap = 3456e7; // pragma: 400 days
7
+
8
+ export class Cookies extends URLSearchParams {
9
+
10
+ static #finalizers = new Set();
11
+ static jar = new Map();
12
+
13
+ static #register(target, value) {
14
+ const finalizer = new FinalizationRegistry((heldValue) => {
15
+ clearTimeout(heldValue);
16
+ this.#finalizers.delete(finalizer);
17
+ });
18
+
19
+ finalizer.register(target, value);
20
+ this.#finalizers.add(finalizer);
21
+ }
22
+
23
+ #chronometry = new Map();
24
+
25
+ get [Symbol.toStringTag]() {
26
+ return this.constructor.name;
27
+ }
28
+
29
+ constructor(input, { cookiesTTL } = { cookiesTTL: false }) {
30
+ if (Array.isArray(input) && input.every((it) => !Array.isArray(it))) {
31
+ input = input.map((it) => {
32
+ if (!cookiesTTL) {
33
+ return [it.split(';').at(0).trim()];
34
+ }
35
+
36
+ const [cookie, ...attrs] = it.split(';').map((it) => it.trim());
37
+ const ttl = {};
38
+
39
+ for (const val of attrs) {
40
+ if (/(?:expires|max-age)=/i.test(val)) {
41
+ const [key, value] = val.toLowerCase().split('=');
42
+ const ms = Number.isFinite(Number(value)) ? value * 1e3 : Date.parse(value) - Date.now();
43
+
44
+ ttl[toCamelCase(key)] = Math.min(ms, lifetimeCap);
45
+ }
46
+ }
47
+
48
+ return [
49
+ cookie.replace(/\u0022/g, ''),
50
+ Object.keys(ttl).length ? ttl : null,
51
+ ];
52
+ });
53
+ }
54
+
55
+ super(Array.isArray(input) ? input.map((it) => it.at(0)).join('&') : input);
56
+
57
+ if (Array.isArray(input) && cookiesTTL) {
58
+ for (const [cookie, ttl] of input.filter((it) => it.at(1))) {
59
+ const key = cookie.split('=').at(0);
60
+
61
+ if (this.#chronometry.has(key)) {
62
+ clearTimeout(this.#chronometry.get(key));
63
+ this.#chronometry.delete(key);
64
+ }
65
+
66
+ const { expires, maxAge } = ttl;
67
+
68
+ for (const ms of [
69
+ maxAge,
70
+ expires,
71
+ ]) {
72
+ if (!Number.isInteger(ms)) {
73
+ continue;
74
+ }
75
+
76
+ const ref = new WeakRef(this);
77
+ const tid = setTimeout(() => {
78
+ const ctx = ref.deref();
79
+
80
+ if (ctx) {
81
+ ctx.#chronometry.delete(key);
82
+ ctx.delete(key);
83
+ }
84
+ }, Math.max(ms, 0));
85
+
86
+ this.constructor.#register(this, tid);
87
+ this.#chronometry.set(key, tid);
88
+ break;
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ toString() {
95
+ brandCheck(this, Cookies);
96
+
97
+ return super.toString().split('&').join('; ').trim();
98
+ }
99
+
100
+ }
@@ -5,12 +5,12 @@ import { toUSVString } from 'node:util';
5
5
  import {
6
6
  APPLICATION_OCTET_STREAM,
7
7
  MULTIPART_FORM_DATA,
8
- } from './mediatypes.mjs';
8
+ } from './mediatypes.js';
9
9
  import {
10
10
  brandCheck,
11
11
  isFileLike,
12
12
  tap,
13
- } from './utils.mjs';
13
+ } from './utils.js';
14
14
 
15
15
  const CRLF = '\r\n';
16
16
  const {
@@ -59,8 +59,8 @@ export class FormData {
59
59
  };
60
60
  }
61
61
 
62
- static alike(instance) {
63
- return FormData.name === instance?.[Symbol.toStringTag];
62
+ static alike(value) {
63
+ return FormData.name === value?.[Symbol.toStringTag];
64
64
  }
65
65
 
66
66
  static #enfoldEntry(name, value, filename) {
@@ -83,7 +83,7 @@ export class FormData {
83
83
  }
84
84
 
85
85
  static #ensureInstance(value) {
86
- return isFileLike(value) || (value === Object(value) && Reflect.has(value, Symbol.asyncIterator));
86
+ return isFileLike(value) || (Object(value) === value && Reflect.has(value, Symbol.asyncIterator));
87
87
  }
88
88
 
89
89
  #entries = [];
@@ -93,13 +93,7 @@ export class FormData {
93
93
  }
94
94
 
95
95
  constructor(input) {
96
- if (input === Object(input)
97
- && (input?.constructor === Object || Reflect.has(input, Symbol.iterator))) {
98
-
99
- if (input.constructor !== Object) {
100
- input = Array.from(input);
101
- }
102
-
96
+ if (Object(input) === input) {
103
97
  if (Array.isArray(input)) {
104
98
  if (!input.every((it) => Array.isArray(it))) {
105
99
  throw new TypeError(`Failed to construct '${
@@ -110,9 +104,9 @@ export class FormData {
110
104
  this[Symbol.toStringTag]
111
105
  }': Sequence initializer must only contain pair elements.`);
112
106
  }
113
- }
114
107
 
115
- if (input.constructor === Object) {
108
+ input = Array.from(input);
109
+ } else if (!Reflect.has(input, Symbol.iterator)) {
116
110
  input = Object.entries(input);
117
111
  }
118
112
 
@@ -1,41 +1,41 @@
1
1
  import http from 'node:http';
2
2
  import http2 from 'node:http2';
3
3
  import https from 'node:https';
4
- import config from './config.mjs';
5
- import { requestRedirect } from './constants.mjs';
6
- import { APPLICATION_OCTET_STREAM } from './mediatypes.mjs';
7
- import { preflight } from './preflight.mjs';
8
- import { transfer } from './transfer.mjs';
4
+ import config from './config.js';
5
+ import { requestRedirect } from './constants.js';
6
+ import { APPLICATION_OCTET_STREAM } from './mediatypes.js';
7
+ import { preflight } from './preflight.js';
8
+ import { transfer } from './transfer.js';
9
9
  import {
10
- admix,
11
- affix,
10
+ augment,
12
11
  copyWithMerge,
13
12
  normalize,
14
- } from './utils.mjs';
15
- import { validation } from './validation.mjs';
13
+ snoop,
14
+ } from './utils.js';
15
+ import { validation } from './validation.js';
16
16
 
17
17
  export {
18
18
  Blob,
19
19
  File,
20
20
  } from 'node:buffer';
21
21
  export { constants } from 'node:http2';
22
-
23
- export * from './ackn.mjs';
24
- export * from './constants.mjs';
25
- export * from './cookies.mjs';
26
- export * from './errors.mjs';
27
- export * from './formdata.mjs';
28
- export * as mediatypes from './mediatypes.mjs';
29
- export * from './mixin.mjs';
30
- export * from './utils.mjs';
31
- export * from './validation.mjs';
22
+ export * from './ackn.js';
23
+ export * from './codecs.js';
24
+ export * from './constants.js';
25
+ export * from './cookies.js';
26
+ export * from './errors.js';
27
+ export * from './formdata.js';
28
+ export * as mediatypes from './mediatypes.js';
29
+ export * from './mixin.js';
30
+ export * from './utils.js';
31
+ export * from './validation.js';
32
32
 
33
33
  const {
34
34
  HTTP2_HEADER_CONTENT_TYPE,
35
35
  } = http2.constants;
36
36
 
37
37
  export default function rekwest(url, options) {
38
- return transfer(validation(normalize(url, options)), rekwest);
38
+ return transfer(validation(normalize(url, options)));
39
39
  }
40
40
 
41
41
  Reflect.defineProperty(rekwest, 'defaults', {
@@ -69,7 +69,7 @@ Reflect.defineProperty(rekwest, 'stream', {
69
69
  req = request(options.url, options);
70
70
  }
71
71
 
72
- affix(client, req, options);
72
+ snoop(client, req, options);
73
73
 
74
74
  req.once('response', (res) => {
75
75
  let headers;
@@ -79,7 +79,7 @@ Reflect.defineProperty(rekwest, 'stream', {
79
79
  res = req;
80
80
  }
81
81
 
82
- admix(res, headers, options);
82
+ augment(res, headers, options);
83
83
  });
84
84
 
85
85
  return req;
@@ -1,6 +1,6 @@
1
- export const APPLICATION_FORM_URLENCODED = 'application/x-www-form-urlencoded';
2
- export const APPLICATION_JSON = 'application/json';
3
- export const APPLICATION_OCTET_STREAM = 'application/octet-stream';
4
- export const MULTIPART_FORM_DATA = 'multipart/form-data';
5
- export const TEXT_PLAIN = 'text/plain';
6
- export const WILDCARD = '*/*';
1
+ export const APPLICATION_FORM_URLENCODED = 'application/x-www-form-urlencoded';
2
+ export const APPLICATION_JSON = 'application/json';
3
+ export const APPLICATION_OCTET_STREAM = 'application/octet-stream';
4
+ export const MULTIPART_FORM_DATA = 'multipart/form-data';
5
+ export const TEXT_PLAIN = 'text/plain';
6
+ export const WILDCARD = '*/*';
@@ -1,16 +1,16 @@
1
1
  import { Blob } from 'node:buffer';
2
2
  import http2 from 'node:http2';
3
- import {
4
- brandCheck,
5
- decompress,
6
- } from './utils.mjs';
3
+ import { buffer } from 'node:stream/consumers';
4
+ import { MIMEType } from 'node:util';
5
+ import { decode } from './codecs.js';
6
+ import { brandCheck } from './utils.js';
7
7
 
8
8
  const {
9
9
  HTTP2_HEADER_CONTENT_ENCODING,
10
10
  HTTP2_HEADER_CONTENT_TYPE,
11
11
  } = http2.constants;
12
12
 
13
- export const mixin = (res, { decompression, digest = false, parse = false } = {}) => {
13
+ export const mixin = (res, { decodersOptions, digest = false, parse = false } = {}) => {
14
14
  if (!digest) {
15
15
  Object.defineProperties(res, {
16
16
  arrayBuffer: {
@@ -68,16 +68,10 @@ export const mixin = (res, { decompression, digest = false, parse = false } = {}
68
68
  brandCheck(this, res?.constructor);
69
69
 
70
70
  if (this.bodyUsed) {
71
- throw new TypeError('Response stream already read');
71
+ throw new TypeError('Response stream already read.');
72
72
  }
73
73
 
74
- let body = [];
75
-
76
- for await (const chunk of decompress(this, this.headers[HTTP2_HEADER_CONTENT_ENCODING], { decompression })) {
77
- body.push(chunk);
78
- }
79
-
80
- body = Buffer.concat(body);
74
+ let body = await buffer(decode(this, this.headers[HTTP2_HEADER_CONTENT_ENCODING], { decodersOptions }));
81
75
 
82
76
  if (!body.length && parse) {
83
77
  return null;
@@ -85,20 +79,25 @@ export const mixin = (res, { decompression, digest = false, parse = false } = {}
85
79
 
86
80
  if (body.length && parse) {
87
81
  const contentType = this.headers[HTTP2_HEADER_CONTENT_TYPE] ?? '';
88
- const charset = contentType.split(';')
89
- .find((it) => /charset=/i.test(it))
90
- ?.toLowerCase()
91
- .replace('charset=', '')
92
- .replace('iso-8859-1', 'latin1')
93
- .trim() || 'utf-8';
94
-
95
- if (/\bjson\b/i.test(contentType)) {
96
- body = JSON.parse(body.toString(charset));
97
- } else if (/\b(?:text|xml)\b/i.test(contentType)) {
98
- if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b/i.test(charset)) {
99
- body = body.toString(charset);
82
+ let isTextual, mimeType;
83
+
84
+ try {
85
+ mimeType = contentType ? new MIMEType(contentType) : null;
86
+ } finally {
87
+ isTextual = mimeType && (
88
+ mimeType.type === 'text'
89
+ || mimeType.subtype.match(/\bcsv\b|\bjson\b|\bxml\b|\byaml\b/)
90
+ || mimeType.essence.match(/\becmascript\b|\bjavascript\b|\bx-www-form-urlencoded\b/)
91
+ );
92
+ }
93
+
94
+ if (isTextual) {
95
+ if (/\bjson\b/i.test(contentType)) {
96
+ body = JSON.parse(body.toString());
100
97
  } else {
101
- body = new TextDecoder(charset).decode(body);
98
+ const charset = mimeType.params.get('charset')?.toLowerCase() ?? 'utf-8';
99
+
100
+ body = new TextDecoder(charset, { fatal: true }).decode(body);
102
101
  }
103
102
  }
104
103
  }