@clickup/rest-client 2.10.296 → 2.11.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.
Files changed (111) hide show
  1. package/.eslintrc.base.js +10 -1
  2. package/.eslintrc.js +4 -4
  3. package/.github/workflows/ci.yml +26 -0
  4. package/.github/workflows/semgrep.yml +36 -0
  5. package/.prettierrc +8 -0
  6. package/.vscode/extensions.json +8 -0
  7. package/.vscode/tasks.json +20 -0
  8. package/babel.config.js +5 -0
  9. package/dist/.eslintcache +1 -1
  10. package/dist/RestClient.d.ts +1 -3
  11. package/dist/RestClient.d.ts.map +1 -1
  12. package/dist/RestClient.js +3 -4
  13. package/dist/RestClient.js.map +1 -1
  14. package/dist/RestOptions.d.ts +2 -2
  15. package/dist/RestOptions.d.ts.map +1 -1
  16. package/dist/RestOptions.js +1 -0
  17. package/dist/RestOptions.js.map +1 -1
  18. package/dist/RestRequest.d.ts +0 -2
  19. package/dist/RestRequest.d.ts.map +1 -1
  20. package/dist/RestRequest.js +12 -5
  21. package/dist/RestRequest.js.map +1 -1
  22. package/dist/RestResponse.d.ts +0 -1
  23. package/dist/RestResponse.d.ts.map +1 -1
  24. package/dist/helpers/depaginate.js +1 -1
  25. package/dist/helpers/depaginate.js.map +1 -1
  26. package/dist/index.d.ts +2 -5
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -5
  29. package/dist/index.js.map +1 -1
  30. package/dist/internal/RestFetchReader.d.ts +3 -3
  31. package/dist/internal/RestFetchReader.d.ts.map +1 -1
  32. package/dist/internal/RestFetchReader.js +21 -11
  33. package/dist/internal/RestFetchReader.js.map +1 -1
  34. package/dist/internal/RestRangeUploader.d.ts +0 -1
  35. package/dist/internal/RestRangeUploader.d.ts.map +1 -1
  36. package/dist/internal/calcRetryDelay.js +1 -1
  37. package/dist/internal/calcRetryDelay.js.map +1 -1
  38. package/dist/internal/ellipsis.d.ts +6 -0
  39. package/dist/internal/ellipsis.d.ts.map +1 -0
  40. package/dist/internal/ellipsis.js +17 -0
  41. package/dist/internal/ellipsis.js.map +1 -0
  42. package/dist/internal/inferResBodyEncoding.d.ts +0 -1
  43. package/dist/internal/inferResBodyEncoding.d.ts.map +1 -1
  44. package/dist/internal/inferResBodyEncoding.js +1 -1
  45. package/dist/internal/inferResBodyEncoding.js.map +1 -1
  46. package/dist/internal/inspectPossibleJSON.d.ts +0 -2
  47. package/dist/internal/inspectPossibleJSON.d.ts.map +1 -1
  48. package/dist/internal/inspectPossibleJSON.js +6 -9
  49. package/dist/internal/inspectPossibleJSON.js.map +1 -1
  50. package/dist/internal/prependNewlineIfMultiline.js +1 -1
  51. package/dist/internal/prependNewlineIfMultiline.js.map +1 -1
  52. package/dist/internal/substituteParams.js +1 -1
  53. package/dist/internal/substituteParams.js.map +1 -1
  54. package/dist/internal/throwIfErrorResponse.js +2 -2
  55. package/dist/internal/throwIfErrorResponse.js.map +1 -1
  56. package/dist/internal/toFloatMs.js +1 -1
  57. package/dist/internal/toFloatMs.js.map +1 -1
  58. package/dist/middlewares/paceRequests.d.ts +21 -2
  59. package/dist/middlewares/paceRequests.d.ts.map +1 -1
  60. package/dist/middlewares/paceRequests.js +4 -3
  61. package/dist/middlewares/paceRequests.js.map +1 -1
  62. package/docs/interfaces/Pacer.md +7 -12
  63. package/docs/interfaces/PacerOutcome.md +25 -0
  64. package/docs/interfaces/RestOptions.md +22 -47
  65. package/docs/modules.md +4 -7
  66. package/internal/clean.sh +4 -0
  67. package/internal/deploy.sh +7 -0
  68. package/internal/docs.sh +6 -0
  69. package/internal/lint.sh +4 -0
  70. package/jest.config.base.js +18 -0
  71. package/jest.config.js +1 -10
  72. package/package.json +15 -10
  73. package/src/RestClient.ts +2 -2
  74. package/src/RestOptions.ts +3 -0
  75. package/src/RestRequest.ts +32 -5
  76. package/src/__tests__/RestClient.test.ts +53 -0
  77. package/src/__tests__/RestFetchReader.test.ts +154 -0
  78. package/src/__tests__/RestRequest.test.ts +262 -0
  79. package/src/__tests__/RestRequestCacheableLookup.test.ts +88 -0
  80. package/src/__tests__/RestStream.test.ts +67 -0
  81. package/src/__tests__/helpers.ts +173 -0
  82. package/src/index.ts +2 -7
  83. package/src/internal/RestFetchReader.ts +4 -1
  84. package/src/internal/ellipsis.ts +16 -0
  85. package/src/internal/inspectPossibleJSON.ts +1 -5
  86. package/src/internal/throwIfErrorResponse.ts +1 -1
  87. package/src/middlewares/paceRequests.ts +27 -2
  88. package/tsconfig.base.json +31 -0
  89. package/tsconfig.json +1 -31
  90. package/typedoc.config.js +20 -0
  91. package/dist/pacers/Pacer.d.ts +0 -21
  92. package/dist/pacers/Pacer.d.ts.map +0 -1
  93. package/dist/pacers/Pacer.js +0 -3
  94. package/dist/pacers/Pacer.js.map +0 -1
  95. package/dist/pacers/PacerComposite.d.ts +0 -14
  96. package/dist/pacers/PacerComposite.d.ts.map +0 -1
  97. package/dist/pacers/PacerComposite.js +0 -32
  98. package/dist/pacers/PacerComposite.js.map +0 -1
  99. package/dist/pacers/PacerQPS.d.ts +0 -53
  100. package/dist/pacers/PacerQPS.d.ts.map +0 -1
  101. package/dist/pacers/PacerQPS.js +0 -105
  102. package/dist/pacers/PacerQPS.js.map +0 -1
  103. package/docs/classes/PacerComposite.md +0 -66
  104. package/docs/classes/PacerQPS.md +0 -79
  105. package/docs/interfaces/PacerDelay.md +0 -25
  106. package/docs/interfaces/PacerQPSBackend.md +0 -44
  107. package/docs/interfaces/PacerQPSOptions.md +0 -40
  108. package/src/pacers/Pacer.ts +0 -22
  109. package/src/pacers/PacerComposite.ts +0 -29
  110. package/src/pacers/PacerQPS.ts +0 -147
  111. package/typedoc.json +0 -22
@@ -126,6 +126,18 @@ addresses are allowed.
126
126
 
127
127
  ___
128
128
 
129
+ ### responseEncoding
130
+
131
+ • **responseEncoding**: `undefined` \| `BufferEncoding`
132
+
133
+ Overrides the default encoding heuristics for responses.
134
+
135
+ #### Defined in
136
+
137
+ [src/RestOptions.ts:100](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L100)
138
+
139
+ ___
140
+
129
141
  ### isDebug
130
142
 
131
143
  • **isDebug**: `boolean`
@@ -134,7 +146,7 @@ If true, logs request-response pairs to console.
134
146
 
135
147
  #### Defined in
136
148
 
137
- [src/RestOptions.ts:100](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L100)
149
+ [src/RestOptions.ts:102](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L102)
138
150
 
139
151
  ___
140
152
 
@@ -153,7 +165,7 @@ Sets Keep-Alive parameters (persistent connections).
153
165
 
154
166
  #### Defined in
155
167
 
156
- [src/RestOptions.ts:104](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L104)
168
+ [src/RestOptions.ts:106](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L106)
157
169
 
158
170
  ___
159
171
 
@@ -165,7 +177,7 @@ When resolving DNS, use IPv4, IPv6 or both (see dns.lookup() docs).
165
177
 
166
178
  #### Defined in
167
179
 
168
- [src/RestOptions.ts:112](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L112)
180
+ [src/RestOptions.ts:114](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L114)
169
181
 
170
182
  ___
171
183
 
@@ -177,7 +189,7 @@ Max timeout to wait for a response.
177
189
 
178
190
  #### Defined in
179
191
 
180
- [src/RestOptions.ts:114](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L114)
192
+ [src/RestOptions.ts:116](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L116)
181
193
 
182
194
  ___
183
195
 
@@ -192,9 +204,6 @@ delay events logging.
192
204
 
193
205
  ▸ (`event`): `void`
194
206
 
195
- Logger to be used for each responses (including retried) plus for backoff
196
- delay events logging.
197
-
198
207
  ##### Parameters
199
208
 
200
209
  | Name | Type |
@@ -207,7 +216,7 @@ delay events logging.
207
216
 
208
217
  #### Defined in
209
218
 
210
- [src/RestOptions.ts:117](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L117)
219
+ [src/RestOptions.ts:119](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L119)
211
220
 
212
221
  ___
213
222
 
@@ -219,7 +228,7 @@ Middlewares to wrap requests. May alter both request and response.
219
228
 
220
229
  #### Defined in
221
230
 
222
- [src/RestOptions.ts:119](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L119)
231
+ [src/RestOptions.ts:121](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L121)
223
232
 
224
233
  ___
225
234
 
@@ -244,19 +253,6 @@ remote API is that weird. Return values:
244
253
 
245
254
  ▸ (`res`): ``"SUCCESS"`` \| ``"THROW"`` \| ``"BEST_EFFORT"``
246
255
 
247
- If set, makes decision whether the response is successful or not. The
248
- response will either be returned to the client, or an error will be thrown.
249
- This allows to treat some non-successful HTTP statuses as success if the
250
- remote API is that weird. Return values:
251
- * "SUCCESS" - the request will be considered successful, no further checks
252
- will be performed;
253
- * "BEST_EFFORT" - inconclusive, the request may be either successful or
254
- unsuccessful, additional tests (e.g. will check HTTP status code) will be
255
- performed;
256
- * "THROW" - the request resulted in error. Additional tests will be
257
- performed to determine is the error is retriable, is OAuth token good,
258
- and etc.
259
-
260
256
  ##### Parameters
261
257
 
262
258
  | Name | Type |
@@ -269,7 +265,7 @@ remote API is that weird. Return values:
269
265
 
270
266
  #### Defined in
271
267
 
272
- [src/RestOptions.ts:132](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L132)
268
+ [src/RestOptions.ts:134](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L134)
273
269
 
274
270
  ___
275
271
 
@@ -291,16 +287,6 @@ contradictory information; then isRateLimitError wins.
291
287
 
292
288
  ▸ (`res`): `number` \| ``"BEST_EFFORT"`` \| ``"SOMETHING_ELSE"`` \| ``"RATE_LIMIT"``
293
289
 
294
- Decides whether the response is a rate-limit error or not. Returning
295
- non-zero value is treated as retry delay (if retries are set up). In case
296
- the returned value is "SOMETHING_ELSE", the response ought to be either
297
- success or some other error. Returning "BEST_EFFORT" turns on built-in
298
- heuristic (e.g. relying on HTTP status code and Retry-After header). In
299
- case we've made a decision that it's a rate limited error, the request is
300
- always retried; this covers a very common case when we have both
301
- isRateLimitError and isRetriableError handlers set up, and they return
302
- contradictory information; then isRateLimitError wins.
303
-
304
290
  ##### Parameters
305
291
 
306
292
  | Name | Type |
@@ -313,7 +299,7 @@ contradictory information; then isRateLimitError wins.
313
299
 
314
300
  #### Defined in
315
301
 
316
- [src/RestOptions.ts:142](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L142)
302
+ [src/RestOptions.ts:144](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L144)
317
303
 
318
304
  ___
319
305
 
@@ -328,9 +314,6 @@ not, the response ought to be either success or some other error.
328
314
 
329
315
  ▸ (`res`): `boolean`
330
316
 
331
- Decides whether the response is a token-invalid error or not. In case it's
332
- not, the response ought to be either success or some other error.
333
-
334
317
  ##### Parameters
335
318
 
336
319
  | Name | Type |
@@ -343,7 +326,7 @@ not, the response ought to be either success or some other error.
343
326
 
344
327
  #### Defined in
345
328
 
346
- [src/RestOptions.ts:147](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L147)
329
+ [src/RestOptions.ts:149](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L149)
347
330
 
348
331
  ___
349
332
 
@@ -363,14 +346,6 @@ retry will happen in not less than this number of milliseconds.
363
346
 
364
347
  ▸ (`res`, `_error`): `number` \| ``"BEST_EFFORT"`` \| ``"NEVER_RETRY"`` \| ``"RETRY"``
365
348
 
366
- Called only if we haven't decided earlier that it's a rate limit error.
367
- Decides whether the response is a retriable error or not. In case the
368
- returned value is "NEVER_RETRY", the response ought to be either success or
369
- some other error, but it's guaranteed that the request won't be retried.
370
- Returning "BEST_EFFORT" turns on built-in heuristics (e.g. never retry "not
371
- found" errors). Returning a number is treated as "RETRY", and the next
372
- retry will happen in not less than this number of milliseconds.
373
-
374
349
  ##### Parameters
375
350
 
376
351
  | Name | Type |
@@ -384,4 +359,4 @@ retry will happen in not less than this number of milliseconds.
384
359
 
385
360
  #### Defined in
386
361
 
387
- [src/RestOptions.ts:155](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L155)
362
+ [src/RestOptions.ts:157](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L157)
package/docs/modules.md CHANGED
@@ -15,8 +15,6 @@
15
15
  - [RestRetriableError](classes/RestRetriableError.md)
16
16
  - [RestTimeoutError](classes/RestTimeoutError.md)
17
17
  - [RestTokenInvalidError](classes/RestTokenInvalidError.md)
18
- - [PacerComposite](classes/PacerComposite.md)
19
- - [PacerQPS](classes/PacerQPS.md)
20
18
 
21
19
  ## Interfaces
22
20
 
@@ -24,10 +22,8 @@
24
22
  - [RestLogEvent](interfaces/RestLogEvent.md)
25
23
  - [Middleware](interfaces/Middleware.md)
26
24
  - [RestOptions](interfaces/RestOptions.md)
27
- - [PacerDelay](interfaces/PacerDelay.md)
28
25
  - [Pacer](interfaces/Pacer.md)
29
- - [PacerQPSBackend](interfaces/PacerQPSBackend.md)
30
- - [PacerQPSOptions](interfaces/PacerQPSOptions.md)
26
+ - [PacerOutcome](interfaces/PacerOutcome.md)
31
27
 
32
28
  ## Functions
33
29
 
@@ -67,7 +63,7 @@ ___
67
63
 
68
64
  ### paceRequests
69
65
 
70
- ▸ **paceRequests**(`pacer`): [`Middleware`](interfaces/Middleware.md)
66
+ ▸ **paceRequests**(`pacer`, `delayMetric?`): [`Middleware`](interfaces/Middleware.md)
71
67
 
72
68
  Rest Client middleware that adds some delay between requests using one of
73
69
  Pacer implementations.
@@ -77,6 +73,7 @@ Pacer implementations.
77
73
  | Name | Type |
78
74
  | :------ | :------ |
79
75
  | `pacer` | ``null`` \| [`Pacer`](interfaces/Pacer.md) \| (`req`: [`RestRequest`](classes/RestRequest.md)\<`any`\>) => `Promise`\<``null`` \| [`Pacer`](interfaces/Pacer.md)\> |
76
+ | `delayMetric?` | (`delay`: `number`, `reason`: `string`) => `void` |
80
77
 
81
78
  #### Returns
82
79
 
@@ -84,4 +81,4 @@ Pacer implementations.
84
81
 
85
82
  #### Defined in
86
83
 
87
- [src/middlewares/paceRequests.ts:11](https://github.com/clickup/rest-client/blob/master/src/middlewares/paceRequests.ts#L11)
84
+ [src/middlewares/paceRequests.ts:33](https://github.com/clickup/rest-client/blob/master/src/middlewares/paceRequests.ts#L33)
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ rm -rf dist yarn.lock package-lock.json node_modules ./*.log
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ npm run build
5
+ npm run lint
6
+ npm run test
7
+ npm publish --access=public
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ rm -rf docs
5
+ typedoc --plugin typedoc-plugin-markdown --plugin typedoc-plugin-merge-modules
6
+ sed -i '' -E 's#packages/[^/]+/##g' $(find docs -type f -name '*.md')
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ eslint . --ext .ts --cache --cache-location dist/.eslintcache
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ module.exports = {
3
+ roots: ["<rootDir>/src"],
4
+ testMatch: ["**/*.test.ts"],
5
+ clearMocks: true,
6
+ restoreMocks: true,
7
+ ...(process.env.IN_JEST_PROJECT
8
+ ? {}
9
+ : { testTimeout: 30000 }),
10
+ transform: {
11
+ "\\.ts$": "ts-jest",
12
+ "\\.m?js$": "babel-jest",
13
+ },
14
+ transformIgnorePatterns: [
15
+ "<rootDir>/node_modules/.pnpm/(?!(cacheable-lookup)@)",
16
+ "node_modules/(?!.pnpm|cacheable-lookup)",
17
+ ],
18
+ };
package/jest.config.js CHANGED
@@ -1,11 +1,2 @@
1
1
  "use strict";
2
- module.exports = {
3
- roots: ["<rootDir>/src"],
4
- testMatch: ["**/*.test.ts"],
5
- clearMocks: true,
6
- restoreMocks: true,
7
- ...(process.env.IN_JEST_PROJECT ? {} : { forceExit: true }),
8
- transform: {
9
- "\\.ts$": "ts-jest",
10
- },
11
- };
2
+ module.exports = { ...require("./jest.config.base") };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clickup/rest-client",
3
3
  "description": "A syntax sugar tool around Node fetch() API, tailored to work with TypeScript and response validators",
4
- "version": "2.10.296",
4
+ "version": "2.11.1",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "rest-client",
@@ -24,47 +24,52 @@
24
24
  "scripts": {
25
25
  "build": "tsc",
26
26
  "dev": "tsc --watch --preserveWatchOutput",
27
- "lint": "eslint . --ext .ts --cache --cache-location dist/.eslintcache",
27
+ "lint": "bash internal/lint.sh",
28
28
  "test": "jest",
29
- "docs": "rm -rf docs && typedoc --plugin typedoc-plugin-markdown --plugin typedoc-plugin-merge-modules && sed -i '' -E 's#packages/[^/]+/##g' $(find docs -type f -name '*.md')",
30
- "clean": "rm -rf dist node_modules yarn.lock package-lock.json pnpm-lock.yaml *.log",
29
+ "docs": "bash internal/docs.sh",
30
+ "clean": "bash internal/clean.sh",
31
31
  "copy-package-to-public-dir": "copy-package-to-public-dir.sh",
32
32
  "backport-package-from-public-dir": "backport-package-from-public-dir.sh",
33
- "deploy": "npm run build && npm run lint && npm run test && npm publish --access=public"
33
+ "deploy": "bash internal/deploy.sh"
34
34
  },
35
35
  "dependencies": {
36
36
  "abort-controller": "^3.0.0",
37
+ "cacheable-lookup": "^7.0.0",
37
38
  "delay": "^4.4.1",
38
- "fast-typescript-memoize": "^1.0.2",
39
+ "fast-typescript-memoize": "^1.1.1",
39
40
  "ipaddr.js": "^1.9.1",
40
41
  "lodash": "^4.17.21",
41
42
  "node-fetch": "^2.6.11",
42
43
  "oauth-1.0a": "^2.2.6"
43
44
  },
44
45
  "devDependencies": {
46
+ "@babel/core": "^7.29.0",
47
+ "@babel/preset-env": "^7.29.0",
45
48
  "@types/jest": "^29.5.5",
46
49
  "@types/lodash": "^4.14.175",
47
- "@types/node-fetch": "^2.6.4",
48
50
  "@types/node": "^20.4.1",
51
+ "@types/node-fetch": "^2.6.4",
49
52
  "@typescript-eslint/eslint-plugin": "^5.59.6",
50
53
  "@typescript-eslint/parser": "^5.59.6",
54
+ "babel-jest": "^29.7.0",
55
+ "eslint": "^8.40.0",
51
56
  "eslint-import-resolver-typescript": "^3.5.5",
52
57
  "eslint-plugin-import": "^2.27.5",
53
58
  "eslint-plugin-lodash": "^7.4.0",
59
+ "eslint-plugin-no-only-tests": "^3.1.0",
54
60
  "eslint-plugin-node": "^11.1.0",
55
- "eslint-plugin-react-hooks": "^4.6.0",
56
61
  "eslint-plugin-react": "^7.32.2",
62
+ "eslint-plugin-react-hooks": "^4.6.0",
57
63
  "eslint-plugin-typescript-enum": "^2.1.0",
58
64
  "eslint-plugin-typescript-sort-keys": "^2.3.0",
59
65
  "eslint-plugin-unused-imports": "^2.0.0",
60
- "eslint": "^8.40.0",
61
66
  "jest": "^29.7.0",
62
67
  "prettier": "3.2.1",
63
68
  "superstruct": "^1.0.3",
64
69
  "ts-jest": "^29.1.1",
70
+ "typedoc": "^0.25.2",
65
71
  "typedoc-plugin-markdown": "^3.16.0",
66
72
  "typedoc-plugin-merge-modules": "^5.1.0",
67
- "typedoc": "^0.25.2",
68
73
  "typescript": "^5.2.2"
69
74
  },
70
75
  "repository": {
package/src/RestClient.ts CHANGED
@@ -480,8 +480,8 @@ function simpleShape(path: string, args?: any) {
480
480
  args && typeof args === "object"
481
481
  ? Object.keys(args)
482
482
  // Filter out args that are already mentioned in the path, e.g.
483
- // /pages/:pageID/blocks.
484
- .filter((arg) => !argsInPath.includes(arg))
483
+ // /pages/:pageID/blocks. Also filter out undefined args.
484
+ .filter((arg) => !argsInPath.includes(arg) && args[arg] !== undefined)
485
485
  .sort()
486
486
  .join(",")
487
487
  : "";
@@ -96,6 +96,8 @@ export default interface RestOptions {
96
96
  /** If true, non-public IP addresses are allowed too; otherwise, only unicast
97
97
  * addresses are allowed. */
98
98
  allowInternalIPs: boolean;
99
+ /** Overrides the default encoding heuristics for responses. */
100
+ responseEncoding: NodeJS.BufferEncoding | undefined;
99
101
  /** If true, logs request-response pairs to console. */
100
102
  isDebug: boolean;
101
103
  /** @ignore Holds HttpsAgent/HttpAgent instances; used internally only. */
@@ -172,6 +174,7 @@ export const DEFAULT_OPTIONS: RestOptions = {
172
174
  throwIfResIsBigger: undefined,
173
175
  privateDataInResponse: false,
174
176
  allowInternalIPs: false,
177
+ responseEncoding: undefined,
175
178
  isDebug: false,
176
179
  agents: new Agents(),
177
180
  keepAlive: { timeoutMs: 10000 },
@@ -1,5 +1,3 @@
1
- import dns from "dns";
2
- import { promisify } from "util";
3
1
  import { Memoize } from "fast-typescript-memoize";
4
2
  import { parse } from "ipaddr.js";
5
3
  import random from "lodash/random";
@@ -20,6 +18,36 @@ import RestStream from "./RestStream";
20
18
 
21
19
  const MAX_DEBUG_LEN = 1024 * 100;
22
20
 
21
+ interface CacheableLookupEntry {
22
+ address: string;
23
+ family: 4 | 6;
24
+ }
25
+
26
+ interface CacheableLookupLike {
27
+ lookupAsync(hostname: string): Promise<CacheableLookupEntry>;
28
+ lookupAsync(
29
+ hostname: string,
30
+ options: { family: 4 | 6 },
31
+ ): Promise<CacheableLookupEntry>;
32
+ }
33
+
34
+ // cacheable-lookup v7 is ESM-only, while this library currently builds/tests in
35
+ // a CommonJS flow. A cached dynamic import keeps both TypeScript node16 build
36
+ // and runtime compatibility, and shares one lookup cache instance per process.
37
+ const cacheableLookupPromise: Promise<CacheableLookupLike> = import(
38
+ "cacheable-lookup"
39
+ ).then(({ default: CacheableLookupClass }) => new CacheableLookupClass());
40
+
41
+ const lookupHostname = async (
42
+ hostname: string,
43
+ family: RestOptions["family"],
44
+ ) => {
45
+ const cacheableLookup = await cacheableLookupPromise;
46
+ return family === 0
47
+ ? cacheableLookup.lookupAsync(hostname)
48
+ : cacheableLookup.lookupAsync(hostname, { family });
49
+ };
50
+
23
51
  /**
24
52
  * Type TAssertShape allows to limit json()'s assert callbacks to only those
25
53
  * which return an object compatible with TAssertShape.
@@ -269,9 +297,7 @@ export default class RestRequest<TAssertShape = any> {
269
297
  // it in the URL; the hostname will be passed separately via Host header.
270
298
  const hostname = url.hostname;
271
299
  const headers = new Headers(this.headers);
272
- const addr = await promisify(dns.lookup)(hostname, {
273
- family: this.options.family,
274
- });
300
+ const addr = await lookupHostname(hostname, this.options.family);
275
301
  let redirectMode: RequestRedirect = "follow";
276
302
  if (!this.options.allowInternalIPs) {
277
303
  const range = parse(addr.address).range();
@@ -347,6 +373,7 @@ export default class RestRequest<TAssertShape = any> {
347
373
  );
348
374
  }
349
375
  },
376
+ responseEncoding: this.options.responseEncoding,
350
377
  });
351
378
  }
352
379
 
@@ -0,0 +1,53 @@
1
+ import { RestClient } from "..";
2
+ import substituteParams from "../internal/substituteParams";
3
+
4
+ test("fetch_plain_url", async () => {
5
+ const client = new RestClient();
6
+ const res = await client.get("http://example.com", undefined, "*/*").text();
7
+ expect(res.length).toBeGreaterThan(10);
8
+ });
9
+
10
+ test("fetch_loopback_url_fails", async () => {
11
+ const client = new RestClient();
12
+ await expect(client.get("http://localhost/abc").response()).rejects.toThrow(
13
+ new RegExp(
14
+ "^Domain localhost resolves to a non-public \\(loopback\\) IP address (127\\.0\\.0\\.1)|(\\:\\:1)",
15
+ ),
16
+ );
17
+ });
18
+
19
+ test("fetch_private_url_fails", async () => {
20
+ const client = new RestClient();
21
+ await expect(client.get("http://10.0.0.1/abc").response()).rejects.toThrow(
22
+ "Domain 10.0.0.1 resolves to a non-public (private) IP address 10.0.0.1",
23
+ );
24
+ });
25
+
26
+ test("fetch_url_with_redirect_fails", async () => {
27
+ const client = new RestClient();
28
+ await expect(client.get("http://google.com").response()).rejects.toThrow(
29
+ /redirect mode is set to error:/,
30
+ );
31
+ });
32
+
33
+ test("fetch_relative_url_fails", async () => {
34
+ const client = new RestClient();
35
+ await expect(client.get("/test").text()).rejects.toThrow(/Invalid URL/);
36
+ await expect(client.get("zzz").text()).rejects.toThrow(/Invalid URL/);
37
+ });
38
+
39
+ test("substituteParams", async () => {
40
+ const body = { aa: "aaa", b: 42, x: "y" };
41
+ expect(substituteParams("/some/:aa/other/:b/ccc/:ddd/eee", body)).toEqual([
42
+ "/some/aaa/other/42/ccc/:ddd/eee",
43
+ { x: "y" },
44
+ ]);
45
+ expect(body).toEqual({ aa: "aaa", b: 42, x: "y" });
46
+ });
47
+
48
+ test("withOptions ignores undefined", async () => {
49
+ const request = new RestClient({ timeoutMs: 1234 })
50
+ .withOptions({ timeoutMs: undefined })
51
+ .writeJson("/", "");
52
+ expect(request.options.timeoutMs).toEqual(1234);
53
+ });
@@ -0,0 +1,154 @@
1
+ import delay from "delay";
2
+ import range from "lodash/range";
3
+ import {
4
+ BINARY_BUF,
5
+ consumeIterable,
6
+ createFetchReader,
7
+ server,
8
+ serverAssertConnectionsCount,
9
+ TimeoutError,
10
+ UTF8_BUF,
11
+ } from "./helpers";
12
+
13
+ let ORIGIN: string;
14
+
15
+ beforeAll(async () => {
16
+ ORIGIN = await new Promise((resolve) => {
17
+ server.listen(0, () =>
18
+ resolve("http://127.0.0.1:" + (server.address() as any).port),
19
+ );
20
+ });
21
+ });
22
+
23
+ afterAll(async () => {
24
+ await new Promise((resolve) => server.close(resolve));
25
+ });
26
+
27
+ beforeEach(async () => {
28
+ await serverAssertConnectionsCount(0);
29
+ });
30
+
31
+ test("small_response", async () => {
32
+ const reader = createFetchReader(`${ORIGIN}/small`);
33
+ await reader.preload(1024);
34
+
35
+ expect(reader.textFetched).toEqual("ok");
36
+ expect(reader.textIsPartial).toBeFalsy();
37
+ expect(reader.charsRead).toEqual(reader.textFetched.length);
38
+
39
+ for await (const _ of reader) {
40
+ throw "Must not return any more data";
41
+ }
42
+
43
+ await serverAssertConnectionsCount(0);
44
+ });
45
+
46
+ test("large_response", async () => {
47
+ const reader = createFetchReader(`${ORIGIN}/large`);
48
+ await reader.preload(42);
49
+ await serverAssertConnectionsCount(1);
50
+
51
+ expect(reader.textFetched.length).toBeGreaterThan(1);
52
+ expect(reader.textFetched.length).toBeLessThan(1024 * 10);
53
+ expect(reader.textIsPartial).toBeTruthy();
54
+ expect(reader.charsRead).toEqual(reader.textFetched.length);
55
+
56
+ const rest = await consumeIterable(reader);
57
+ await serverAssertConnectionsCount(0);
58
+ expect(rest.length).toBeGreaterThan(1);
59
+ expect(reader.textIsPartial).toBeTruthy();
60
+ expect(reader.charsRead).toEqual(reader.textFetched.length + rest.length);
61
+ });
62
+
63
+ test("slow_response", async () => {
64
+ const reader = createFetchReader(`${ORIGIN}/slow`);
65
+ await reader.preload(42);
66
+ await delay(300);
67
+ await serverAssertConnectionsCount(1);
68
+ await reader[Symbol.asyncIterator]().return();
69
+ await serverAssertConnectionsCount(0);
70
+ });
71
+
72
+ test("small_slow_utf8_response", async () => {
73
+ const reader = createFetchReader(`${ORIGIN}/small_slow_utf8`);
74
+ await reader.preload(1);
75
+ await serverAssertConnectionsCount(1);
76
+ expect(reader.textFetched.length).toEqual(1);
77
+ expect(reader.charsRead).toEqual(1);
78
+ const rest = await consumeIterable(reader);
79
+ expect(reader.textFetched + rest).toEqual(UTF8_BUF.toString());
80
+ await serverAssertConnectionsCount(0);
81
+ });
82
+
83
+ test("too_big_response", async () => {
84
+ const reader = createFetchReader(`${ORIGIN}/large`, {
85
+ onAfterRead: (reader) => {
86
+ if (reader.charsRead > 1024 * 64) {
87
+ throw Error("too large");
88
+ }
89
+ },
90
+ });
91
+ await reader.preload(42);
92
+ await serverAssertConnectionsCount(1);
93
+ await expect(async () => consumeIterable(reader, 10)).rejects.toThrow(
94
+ "too large",
95
+ );
96
+ await serverAssertConnectionsCount(0);
97
+ });
98
+
99
+ test("timeout_response_no_preload_long_read_delay", async () => {
100
+ const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
101
+ await expect(async () => consumeIterable(reader, 500)).rejects.toThrow(
102
+ TimeoutError,
103
+ );
104
+ await serverAssertConnectionsCount(0);
105
+ });
106
+
107
+ test("timeout_response_no_preload_short_read_delay", async () => {
108
+ const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
109
+ await expect(async () => consumeIterable(reader, 10)).rejects.toThrow(
110
+ TimeoutError,
111
+ );
112
+ await serverAssertConnectionsCount(0);
113
+ });
114
+
115
+ test("timeout_in_preload", async () => {
116
+ const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
117
+ await expect(async () => reader.preload(10000000)).rejects.toThrow(
118
+ TimeoutError,
119
+ );
120
+ await serverAssertConnectionsCount(0);
121
+ });
122
+
123
+ test("timeout_in_stream_after_preload_succeeded", async () => {
124
+ const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
125
+ await reader.preload(42);
126
+ await serverAssertConnectionsCount(1);
127
+ await expect(async () => consumeIterable(reader, 10)).rejects.toThrow(
128
+ TimeoutError,
129
+ );
130
+ await serverAssertConnectionsCount(0);
131
+ });
132
+
133
+ test("timeout_after_reader_waited_for_too_long", async () => {
134
+ const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
135
+ await reader.preload(42);
136
+ await serverAssertConnectionsCount(1);
137
+ await delay(500);
138
+ await expect(async () => consumeIterable(reader, 10)).rejects.toThrow(
139
+ TimeoutError,
140
+ );
141
+ await serverAssertConnectionsCount(0);
142
+ });
143
+
144
+ test("read_binary_data", async () => {
145
+ const reader = createFetchReader(`${ORIGIN}/binary`, { timeoutMs: 2000 });
146
+ await reader.preload(42);
147
+ const preload = reader.textFetched;
148
+ expect(reader.textIsPartial).toBeTruthy();
149
+ const data = await consumeIterable(reader, 10);
150
+ expect(Buffer.from(preload + data, "binary")).toEqual(
151
+ Buffer.concat(range(10).map(() => BINARY_BUF)),
152
+ );
153
+ await serverAssertConnectionsCount(0);
154
+ });