@clickup/rest-client 2.10.294 → 2.11.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 (120) hide show
  1. package/.eslintrc.base.js +12 -2
  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/README.md +2 -0
  9. package/dist/.eslintcache +1 -1
  10. package/dist/RestClient.js +2 -2
  11. package/dist/RestClient.js.map +1 -1
  12. package/dist/RestOptions.d.ts +3 -0
  13. package/dist/RestOptions.d.ts.map +1 -1
  14. package/dist/RestOptions.js +1 -0
  15. package/dist/RestOptions.js.map +1 -1
  16. package/dist/RestRequest.d.ts.map +1 -1
  17. package/dist/RestRequest.js +1 -0
  18. package/dist/RestRequest.js.map +1 -1
  19. package/dist/errors/RestRateLimitError.d.ts.map +1 -1
  20. package/dist/errors/RestRateLimitError.js.map +1 -1
  21. package/dist/errors/RestRetriableError.d.ts.map +1 -1
  22. package/dist/errors/RestRetriableError.js.map +1 -1
  23. package/dist/errors/RestTokenInvalidError.d.ts.map +1 -1
  24. package/dist/errors/RestTokenInvalidError.js.map +1 -1
  25. package/dist/index.d.ts +2 -5
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/internal/RestFetchReader.d.ts +4 -2
  30. package/dist/internal/RestFetchReader.d.ts.map +1 -1
  31. package/dist/internal/RestFetchReader.js +4 -4
  32. package/dist/internal/RestFetchReader.js.map +1 -1
  33. package/dist/internal/ellipsis.d.ts +6 -0
  34. package/dist/internal/ellipsis.d.ts.map +1 -0
  35. package/dist/internal/ellipsis.js +17 -0
  36. package/dist/internal/ellipsis.js.map +1 -0
  37. package/dist/internal/inferResBodyEncoding.js.map +1 -1
  38. package/dist/internal/inspectPossibleJSON.js +5 -8
  39. package/dist/internal/inspectPossibleJSON.js.map +1 -1
  40. package/dist/internal/throwIfErrorResponse.js +1 -1
  41. package/dist/internal/throwIfErrorResponse.js.map +1 -1
  42. package/dist/middlewares/paceRequests.d.ts +21 -2
  43. package/dist/middlewares/paceRequests.d.ts.map +1 -1
  44. package/dist/middlewares/paceRequests.js +3 -2
  45. package/dist/middlewares/paceRequests.js.map +1 -1
  46. package/docs/README.md +2 -0
  47. package/docs/classes/RestClient.md +32 -28
  48. package/docs/classes/RestContentSizeOverLimitError.md +5 -1
  49. package/docs/classes/RestError.md +5 -1
  50. package/docs/classes/RestRateLimitError.md +6 -2
  51. package/docs/classes/RestRequest.md +22 -18
  52. package/docs/classes/RestResponse.md +7 -3
  53. package/docs/classes/RestResponseError.md +5 -1
  54. package/docs/classes/RestRetriableError.md +6 -2
  55. package/docs/classes/RestStream.md +12 -8
  56. package/docs/classes/RestTimeoutError.md +5 -1
  57. package/docs/classes/RestTokenInvalidError.md +6 -2
  58. package/docs/interfaces/Middleware.md +4 -4
  59. package/docs/interfaces/Pacer.md +7 -12
  60. package/docs/interfaces/PacerOutcome.md +25 -0
  61. package/docs/interfaces/RestLogEvent.md +1 -1
  62. package/docs/interfaces/RestOptions.md +42 -30
  63. package/docs/interfaces/TokenGetter.md +3 -3
  64. package/docs/modules.md +8 -11
  65. package/internal/clean.sh +4 -0
  66. package/internal/deploy.sh +7 -0
  67. package/internal/docs.sh +6 -0
  68. package/internal/lint.sh +4 -0
  69. package/jest.config.base.js +13 -0
  70. package/jest.config.js +1 -10
  71. package/package.json +10 -6
  72. package/src/RestClient.ts +21 -21
  73. package/src/RestOptions.ts +6 -3
  74. package/src/RestRequest.ts +12 -11
  75. package/src/RestResponse.ts +1 -1
  76. package/src/RestStream.ts +1 -1
  77. package/src/__tests__/RestClient.test.ts +53 -0
  78. package/src/__tests__/RestFetchReader.test.ts +150 -0
  79. package/src/__tests__/RestRequest.test.ts +262 -0
  80. package/src/__tests__/RestStream.test.ts +63 -0
  81. package/src/__tests__/helpers.ts +173 -0
  82. package/src/errors/RestRateLimitError.ts +5 -1
  83. package/src/errors/RestResponseError.ts +3 -3
  84. package/src/errors/RestRetriableError.ts +5 -1
  85. package/src/errors/RestTokenInvalidError.ts +4 -1
  86. package/src/helpers/depaginate.ts +3 -3
  87. package/src/index.ts +2 -7
  88. package/src/internal/RestFetchReader.ts +6 -3
  89. package/src/internal/RestRangeUploader.ts +2 -2
  90. package/src/internal/calcRetryDelay.ts +2 -2
  91. package/src/internal/ellipsis.ts +16 -0
  92. package/src/internal/inferResBodyEncoding.ts +13 -13
  93. package/src/internal/inspectPossibleJSON.ts +6 -10
  94. package/src/internal/substituteParams.ts +1 -1
  95. package/src/internal/throwIfErrorResponse.ts +8 -8
  96. package/src/middlewares/paceRequests.ts +28 -3
  97. package/tsconfig.base.json +31 -0
  98. package/tsconfig.json +1 -32
  99. package/typedoc.config.js +20 -0
  100. package/dist/pacers/Pacer.d.ts +0 -21
  101. package/dist/pacers/Pacer.d.ts.map +0 -1
  102. package/dist/pacers/Pacer.js +0 -3
  103. package/dist/pacers/Pacer.js.map +0 -1
  104. package/dist/pacers/PacerComposite.d.ts +0 -14
  105. package/dist/pacers/PacerComposite.d.ts.map +0 -1
  106. package/dist/pacers/PacerComposite.js +0 -32
  107. package/dist/pacers/PacerComposite.js.map +0 -1
  108. package/dist/pacers/PacerQPS.d.ts +0 -53
  109. package/dist/pacers/PacerQPS.d.ts.map +0 -1
  110. package/dist/pacers/PacerQPS.js +0 -105
  111. package/dist/pacers/PacerQPS.js.map +0 -1
  112. package/docs/classes/PacerComposite.md +0 -62
  113. package/docs/classes/PacerQPS.md +0 -75
  114. package/docs/interfaces/PacerDelay.md +0 -25
  115. package/docs/interfaces/PacerQPSBackend.md +0 -44
  116. package/docs/interfaces/PacerQPSOptions.md +0 -40
  117. package/src/pacers/Pacer.ts +0 -22
  118. package/src/pacers/PacerComposite.ts +0 -29
  119. package/src/pacers/PacerQPS.ts +0 -147
  120. package/typedoc.json +0 -22
@@ -1,6 +1,6 @@
1
1
  [@clickup/rest-client](../README.md) / [Exports](../modules.md) / TokenGetter
2
2
 
3
- # Interface: TokenGetter<TData\>
3
+ # Interface: TokenGetter\<TData\>
4
4
 
5
5
  A callback which returns access token, possibly after refreshing it, and also
6
6
  possibly before a retry on "invalid token" condition. I.e. it can be called
@@ -17,7 +17,7 @@ will be passed as a parameter).
17
17
 
18
18
  ### TokenGetter
19
19
 
20
- ▸ **TokenGetter**(`prevError`): `Promise`<`TData`\>
20
+ ▸ **TokenGetter**(`prevError`): `Promise`\<`TData`\>
21
21
 
22
22
  #### Parameters
23
23
 
@@ -27,7 +27,7 @@ will be passed as a parameter).
27
27
 
28
28
  #### Returns
29
29
 
30
- `Promise`<`TData`\>
30
+ `Promise`\<`TData`\>
31
31
 
32
32
  #### Defined in
33
33
 
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,16 +22,14 @@
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
 
34
30
  ### depaginate
35
31
 
36
- ▸ **depaginate**<`TItem`, `TCursor`\>(`readFunc`): `AsyncGenerator`<`TItem`, `void`, `undefined`\>
32
+ ▸ **depaginate**\<`TItem`, `TCursor`\>(`readFunc`): `AsyncGenerator`\<`TItem`, `void`, `undefined`\>
37
33
 
38
34
  Keeps calling a function with an updating cursor, and depaginates all the
39
35
  results until the cursor returned is null or undefined.
@@ -53,11 +49,11 @@ On each call, the inner function needs to return an array with two elements:
53
49
 
54
50
  | Name | Type |
55
51
  | :------ | :------ |
56
- | `readFunc` | (`cursor`: `undefined` \| `TCursor`) => `Promise`<readonly [`TItem`[], `undefined` \| ``null`` \| `TCursor`]\> |
52
+ | `readFunc` | (`cursor`: `undefined` \| `TCursor`) => `Promise`\<readonly [`TItem`[], `undefined` \| ``null`` \| `TCursor`]\> |
57
53
 
58
54
  #### Returns
59
55
 
60
- `AsyncGenerator`<`TItem`, `void`, `undefined`\>
56
+ `AsyncGenerator`\<`TItem`, `void`, `undefined`\>
61
57
 
62
58
  #### Defined in
63
59
 
@@ -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.
@@ -76,7 +72,8 @@ Pacer implementations.
76
72
 
77
73
  | Name | Type |
78
74
  | :------ | :------ |
79
- | `pacer` | ``null`` \| [`Pacer`](interfaces/Pacer.md) \| (`req`: [`RestRequest`](classes/RestRequest.md)<`any`\>) => `Promise`<``null`` \| [`Pacer`](interfaces/Pacer.md)\> |
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 pnpm-lock.yaml 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,13 @@
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
+ : { forceExit: true, testTimeout: 30000 }),
10
+ transform: {
11
+ "\\.ts$": "ts-jest",
12
+ },
13
+ };
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.294",
4
+ "version": "2.11.0",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "rest-client",
@@ -24,18 +24,18 @@
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",
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
37
  "delay": "^4.4.1",
38
- "fast-typescript-memoize": "^1.0.2",
38
+ "fast-typescript-memoize": "^1.1.1",
39
39
  "ipaddr.js": "^1.9.1",
40
40
  "lodash": "^4.17.21",
41
41
  "node-fetch": "^2.6.11",
@@ -45,11 +45,13 @@
45
45
  "@types/jest": "^29.5.5",
46
46
  "@types/lodash": "^4.14.175",
47
47
  "@types/node-fetch": "^2.6.4",
48
+ "@types/node": "^20.4.1",
48
49
  "@typescript-eslint/eslint-plugin": "^5.59.6",
49
50
  "@typescript-eslint/parser": "^5.59.6",
50
51
  "eslint-import-resolver-typescript": "^3.5.5",
51
52
  "eslint-plugin-import": "^2.27.5",
52
53
  "eslint-plugin-lodash": "^7.4.0",
54
+ "eslint-plugin-no-only-tests": "^3.1.0",
53
55
  "eslint-plugin-node": "^11.1.0",
54
56
  "eslint-plugin-react-hooks": "^4.6.0",
55
57
  "eslint-plugin-react": "^7.32.2",
@@ -57,6 +59,8 @@
57
59
  "eslint-plugin-typescript-sort-keys": "^2.3.0",
58
60
  "eslint-plugin-unused-imports": "^2.0.0",
59
61
  "eslint": "^8.40.0",
62
+ "jest": "^29.7.0",
63
+ "prettier": "3.2.1",
60
64
  "superstruct": "^1.0.3",
61
65
  "ts-jest": "^29.1.1",
62
66
  "typedoc-plugin-markdown": "^3.16.0",
package/src/RestClient.ts CHANGED
@@ -54,7 +54,7 @@ export default class RestClient {
54
54
  */
55
55
  withMiddleware(
56
56
  middleware: RestOptions["middlewares"][0],
57
- method: "unshift" | "push" = "push"
57
+ method: "unshift" | "push" = "push",
58
58
  ) {
59
59
  const clone = new RestClient(this._options);
60
60
  clone._options.middlewares[method](middleware);
@@ -156,7 +156,7 @@ export default class RestClient {
156
156
  */
157
157
  withOAuth1(
158
158
  consumer: { consumerKey: string; consumerSecret: string },
159
- token: TokenGetter<{ token: string; tokenSecret: string }>
159
+ token: TokenGetter<{ token: string; tokenSecret: string }>,
160
160
  ) {
161
161
  const oauth = new OAuth1({
162
162
  consumer: { key: consumer.consumerKey, secret: consumer.consumerSecret },
@@ -176,7 +176,7 @@ export default class RestClient {
176
176
  oauth.authorize(requestData, {
177
177
  key: tokenData.token,
178
178
  secret: tokenData.tokenSecret,
179
- })
179
+ }),
180
180
  );
181
181
  for (const [name, value] of Object.entries(addHeaders)) {
182
182
  req.headers.set(name, value);
@@ -196,7 +196,7 @@ export default class RestClient {
196
196
  const unencodedHeader = name + ":" + password;
197
197
  req.headers.set(
198
198
  "Authorization",
199
- "Basic " + Buffer.from(unencodedHeader).toString("base64")
199
+ "Basic " + Buffer.from(unencodedHeader).toString("base64"),
200
200
  );
201
201
  return next(req);
202
202
  });
@@ -211,7 +211,7 @@ export default class RestClient {
211
211
  get(
212
212
  path: string,
213
213
  args: Partial<Record<string, string | number | string[]>> = {},
214
- accept: string = "application/json"
214
+ accept: string = "application/json",
215
215
  ) {
216
216
  return this._noBodyRequest(path, args, "GET", accept);
217
217
  }
@@ -224,7 +224,7 @@ export default class RestClient {
224
224
  body: string | Buffer | NodeJS.ReadableStream,
225
225
  contentType: string,
226
226
  method: "POST" | "PUT" | "PATCH" = "POST",
227
- accept?: string
227
+ accept?: string,
228
228
  ) {
229
229
  const origShape = simpleShape(path);
230
230
  return new RestRequest(
@@ -236,7 +236,7 @@ export default class RestClient {
236
236
  "Content-Type": contentType,
237
237
  }),
238
238
  body,
239
- origShape
239
+ origShape,
240
240
  );
241
241
  }
242
242
 
@@ -247,7 +247,7 @@ export default class RestClient {
247
247
  path: string,
248
248
  body: any,
249
249
  method: "POST" | "PUT" | "PATCH" | "DELETE" = "POST",
250
- accept: string = "application/json"
250
+ accept: string = "application/json",
251
251
  ) {
252
252
  const origShape = simpleShape(path, body);
253
253
  [path, body] = substituteParams(path, body);
@@ -260,7 +260,7 @@ export default class RestClient {
260
260
  "Content-Type": "application/json",
261
261
  }),
262
262
  JSON.stringify(body),
263
- origShape
263
+ origShape,
264
264
  );
265
265
  }
266
266
 
@@ -271,7 +271,7 @@ export default class RestClient {
271
271
  path: string,
272
272
  body: Partial<Record<string, string>> | string,
273
273
  method: "POST" | "PUT" | "PATCH" = "POST",
274
- accept: string = "application/json"
274
+ accept: string = "application/json",
275
275
  ) {
276
276
  const origShape = simpleShape(path, body);
277
277
  [path, body] = substituteParams(path, body);
@@ -286,9 +286,9 @@ export default class RestClient {
286
286
  typeof body === "string"
287
287
  ? body
288
288
  : new URLSearchParams(
289
- omitBy(body, isUndefined) as Record<string, string>
289
+ omitBy(body, isUndefined) as Record<string, string>,
290
290
  ).toString(),
291
- origShape
291
+ origShape,
292
292
  );
293
293
  }
294
294
 
@@ -298,7 +298,7 @@ export default class RestClient {
298
298
  writeDelete(
299
299
  path: string,
300
300
  args: Partial<Record<string, string>> = {},
301
- accept: string = "application/json"
301
+ accept: string = "application/json",
302
302
  ) {
303
303
  return this._noBodyRequest(path, args, "DELETE", accept);
304
304
  }
@@ -355,14 +355,14 @@ export default class RestClient {
355
355
  mimeType: string,
356
356
  stream: AsyncIterable<Buffer>,
357
357
  method: "POST" | "PUT" = "POST",
358
- chunkSize: number
358
+ chunkSize: number,
359
359
  ) {
360
360
  return new RestRangeUploader(
361
361
  this,
362
362
  chunkSize,
363
363
  method,
364
364
  path,
365
- mimeType
365
+ mimeType,
366
366
  ).upload(stream);
367
367
  }
368
368
 
@@ -381,7 +381,7 @@ export default class RestClient {
381
381
  "",
382
382
  new Headers({ "Content-Type": "application/json" }),
383
383
  JSON.stringify({ variables, query }), // variables first - for truncation in logs,
384
- origShape
384
+ origShape,
385
385
  );
386
386
  }
387
387
 
@@ -392,7 +392,7 @@ export default class RestClient {
392
392
  path: string,
393
393
  args: Partial<Record<string, string | number | string[]>> = {},
394
394
  method: "GET" | "DELETE",
395
- accept: string = "application/json"
395
+ accept: string = "application/json",
396
396
  ) {
397
397
  const origShape = simpleShape(path, args);
398
398
  [path, args] = substituteParams(path, args);
@@ -418,7 +418,7 @@ export default class RestClient {
418
418
  path,
419
419
  new Headers({ Accept: accept }),
420
420
  "",
421
- origShape
421
+ origShape,
422
422
  );
423
423
  }
424
424
  }
@@ -431,7 +431,7 @@ export default class RestClient {
431
431
  */
432
432
  export async function tokenRetryStrategy<TData>(
433
433
  token: TokenGetter<TData>,
434
- body: (tokenData: TData) => Promise<RestResponse>
434
+ body: (tokenData: TData) => Promise<RestResponse>,
435
435
  ) {
436
436
  let tokenData = await token(null);
437
437
  try {
@@ -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
  : "";
@@ -27,7 +27,7 @@ export interface RestLogEvent {
27
27
  export interface Middleware {
28
28
  (
29
29
  req: RestRequest,
30
- next: (req: RestRequest) => Promise<RestResponse>
30
+ next: (req: RestRequest) => Promise<RestResponse>,
31
31
  ): Promise<RestResponse>;
32
32
  }
33
33
 
@@ -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. */
@@ -140,7 +142,7 @@ export default interface RestOptions {
140
142
  * isRateLimitError and isRetriableError handlers set up, and they return
141
143
  * contradictory information; then isRateLimitError wins. */
142
144
  isRateLimitError: (
143
- res: RestResponse
145
+ res: RestResponse,
144
146
  ) => "SOMETHING_ELSE" | "RATE_LIMIT" | "BEST_EFFORT" | number;
145
147
  /** Decides whether the response is a token-invalid error or not. In case it's
146
148
  * not, the response ought to be either success or some other error. */
@@ -154,7 +156,7 @@ export default interface RestOptions {
154
156
  * retry will happen in not less than this number of milliseconds. */
155
157
  isRetriableError: (
156
158
  res: RestResponse,
157
- _error: any
159
+ _error: any,
158
160
  ) => "NEVER_RETRY" | "RETRY" | "BEST_EFFORT" | number;
159
161
  }
160
162
 
@@ -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 },
@@ -33,7 +33,7 @@ export default class RestRequest<TAssertShape = any> {
33
33
  public url: string,
34
34
  public readonly headers: Headers,
35
35
  public readonly body: string | Buffer | NodeJS.ReadableStream,
36
- public readonly shape?: string
36
+ public readonly shape?: string,
37
37
  ) {
38
38
  this.options = { ...options };
39
39
  }
@@ -120,7 +120,7 @@ export default class RestRequest<TAssertShape = any> {
120
120
  // By passing Number.MAX_SAFE_INTEGER to stream(), we ensure that the
121
121
  // entire data will be preloaded, or the loading will fail due to
122
122
  // throwIfResIsBigger limitation, whichever will happen faster.
123
- Number.MAX_SAFE_INTEGER
123
+ Number.MAX_SAFE_INTEGER,
124
124
  );
125
125
  await stream.close();
126
126
  return stream.res;
@@ -182,7 +182,7 @@ export default class RestRequest<TAssertShape = any> {
182
182
  await reader?.close();
183
183
  throw e;
184
184
  }
185
- }
185
+ },
186
186
  );
187
187
 
188
188
  // The only place where we return the response. Otherwise we retry or
@@ -215,7 +215,7 @@ export default class RestRequest<TAssertShape = any> {
215
215
  error,
216
216
  this.options,
217
217
  res,
218
- retryDelayMs
218
+ retryDelayMs,
219
219
  );
220
220
  if (newRetryDelay === "no_retry") {
221
221
  throw error;
@@ -228,7 +228,7 @@ export default class RestRequest<TAssertShape = any> {
228
228
  retryDelayMs *= random(
229
229
  1 - this.options.retryDelayJitter,
230
230
  1 + this.options.retryDelayJitter,
231
- true
231
+ true,
232
232
  );
233
233
  await this.options.heartbeater.delay(retryDelayMs);
234
234
 
@@ -279,7 +279,7 @@ export default class RestRequest<TAssertShape = any> {
279
279
  const isInternal = range !== "unicast";
280
280
  if (isInternal) {
281
281
  throw new RestError(
282
- `Domain ${hostname} resolves to a non-public (${range}) IP address ${addr.address}`
282
+ `Domain ${hostname} resolves to a non-public (${range}) IP address ${addr.address}`,
283
283
  );
284
284
  }
285
285
 
@@ -333,7 +333,7 @@ export default class RestRequest<TAssertShape = any> {
333
333
  onTimeout: (reader) => {
334
334
  throw new RestTimeoutError(
335
335
  `Timed out while reading response body (${this.options.timeoutMs} ms)`,
336
- this._createRestResponse(reader)!
336
+ this._createRestResponse(reader)!,
337
337
  );
338
338
  },
339
339
  onAfterRead: (reader) => {
@@ -343,10 +343,11 @@ export default class RestRequest<TAssertShape = any> {
343
343
  ) {
344
344
  throw new RestContentSizeOverLimitError(
345
345
  `Content size is over limit of ${this.options.throwIfResIsBigger} characters`,
346
- this._createRestResponse(reader)!
346
+ this._createRestResponse(reader)!,
347
347
  );
348
348
  }
349
349
  },
350
+ responseEncoding: this.options.responseEncoding,
350
351
  });
351
352
  }
352
353
 
@@ -361,7 +362,7 @@ export default class RestRequest<TAssertShape = any> {
361
362
  reader.status,
362
363
  reader.headers,
363
364
  reader.textFetched,
364
- reader.textIsPartial
365
+ reader.textIsPartial,
365
366
  );
366
367
  }
367
368
 
@@ -405,7 +406,7 @@ export default class RestRequest<TAssertShape = any> {
405
406
  inspectPossibleJSON(
406
407
  event.res.headers,
407
408
  event.res.text,
408
- MAX_DEBUG_LEN
409
+ MAX_DEBUG_LEN,
409
410
  );
410
411
  }
411
412
  } else if (event.exception) {
@@ -430,7 +431,7 @@ export default class RestRequest<TAssertShape = any> {
430
431
  async function runMiddlewares(
431
432
  req: RestRequest,
432
433
  middlewares: RestOptions["middlewares"],
433
- last: (req: RestRequest) => Promise<RestResponse>
434
+ last: (req: RestRequest) => Promise<RestResponse>,
434
435
  ): Promise<RestResponse> {
435
436
  if (middlewares.length > 0) {
436
437
  const [head, ...tail] = middlewares;
@@ -21,7 +21,7 @@ export default class RestResponse {
21
21
  public readonly status: number,
22
22
  public readonly headers: Headers,
23
23
  public readonly text: string,
24
- public readonly textIsPartial: boolean
24
+ public readonly textIsPartial: boolean,
25
25
  ) {}
26
26
 
27
27
  /**
package/src/RestStream.ts CHANGED
@@ -18,7 +18,7 @@ export default class RestStream {
18
18
  public readonly res: RestResponse,
19
19
  readerIterable: {
20
20
  [Symbol.asyncIterator]: () => AsyncGenerator<string, void>;
21
- }
21
+ },
22
22
  ) {
23
23
  this._generator = readerIterable[Symbol.asyncIterator]();
24
24
  }
@@ -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
+ });