@jsforce/jsforce-node 3.1.0 → 3.2.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.
package/lib/request.js CHANGED
@@ -1,11 +1,34 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
29
  exports.setDefaults = void 0;
7
30
  const stream_1 = require("stream");
8
- const node_fetch_1 = __importDefault(require("node-fetch"));
31
+ const node_fetch_1 = __importStar(require("node-fetch"));
9
32
  const abort_controller_1 = __importDefault(require("abort-controller"));
10
33
  const https_proxy_agent_1 = __importDefault(require("https-proxy-agent"));
11
34
  const request_helper_1 = require("./request-helper");
@@ -33,7 +56,10 @@ async function startFetchRequest(request, options, input, output, emitter, count
33
56
  const controller = new abort_controller_1.default();
34
57
  let retryCount = 0;
35
58
  const retryOpts = {
59
+ statusCodes: options.retry?.statusCodes ?? [429, 500, 502, 503, 504],
36
60
  maxRetries: options.retry?.maxRetries ?? 5,
61
+ minTimeout: options.retry?.minTimeout ?? 500,
62
+ timeoutFactor: options.retry?.timeoutFactor ?? 2,
37
63
  errorCodes: options.retry?.errorCodes ?? [
38
64
  'ECONNRESET',
39
65
  'ECONNREFUSED',
@@ -53,6 +79,40 @@ async function startFetchRequest(request, options, input, output, emitter, count
53
79
  'DELETE',
54
80
  ],
55
81
  };
82
+ const shouldRetryRequest = (maxRetry, resOrErr) => {
83
+ if (!retryOpts.methods.includes(request.method))
84
+ return false;
85
+ if (resOrErr instanceof node_fetch_1.Response) {
86
+ if (retryOpts.statusCodes.includes(resOrErr.status)) {
87
+ if (maxRetry === retryCount) {
88
+ const err = new Error('Request failed');
89
+ err.name = 'RequestRetryError';
90
+ throw err;
91
+ }
92
+ else {
93
+ return true;
94
+ }
95
+ }
96
+ return false;
97
+ }
98
+ else {
99
+ if (maxRetry === retryCount)
100
+ return false;
101
+ // only retry on operational errors
102
+ // https://github.com/node-fetch/node-fetch/blob/2.x/ERROR-HANDLING.md#error-handling-with-node-fetch
103
+ if (resOrErr.name != 'FetchError')
104
+ return false;
105
+ if (is_1.default.nodeStream(body) && stream_1.Readable.isDisturbed(body)) {
106
+ logger.debug('Body of type stream was read, unable to retry request.');
107
+ return false;
108
+ }
109
+ if ('code' in resOrErr &&
110
+ resOrErr.code &&
111
+ retryOpts?.errorCodes?.includes(resOrErr.code))
112
+ return true;
113
+ return false;
114
+ }
115
+ };
56
116
  const fetchWithRetries = async (maxRetry = retryOpts?.maxRetries) => {
57
117
  const fetchOpts = {
58
118
  ...rrequest,
@@ -64,7 +124,21 @@ async function startFetchRequest(request, options, input, output, emitter, count
64
124
  agent,
65
125
  };
66
126
  try {
67
- return await (0, node_fetch_1.default)(url, fetchOpts);
127
+ const res = await (0, node_fetch_1.default)(url, fetchOpts);
128
+ if (shouldRetryRequest(retryOpts.maxRetries, res)) {
129
+ logger.debug(`retrying for the ${retryCount + 1} time`);
130
+ logger.debug(`reason: statusCode match`);
131
+ await sleep(retryCount === 0
132
+ ? retryOpts.minTimeout
133
+ : retryOpts.minTimeout * retryOpts.timeoutFactor ** retryCount);
134
+ // NOTE: this event is only used by tests and will be removed at any time.
135
+ // jsforce may switch to node's fetch which doesn't emit this event on retries.
136
+ emitter.emit('retry', retryCount);
137
+ retryCount++;
138
+ return await fetchWithRetries(maxRetry);
139
+ }
140
+ // should we throw here if the maxRetry already happened and still got the same statusCode?
141
+ return res;
68
142
  }
69
143
  catch (err) {
70
144
  logger.debug(`Request failed`);
@@ -73,27 +147,12 @@ async function startFetchRequest(request, options, input, output, emitter, count
73
147
  if (error.name === 'AbortError') {
74
148
  throw error;
75
149
  }
76
- const shouldRetry = () => {
77
- // only retry on operational errors
78
- if (error.name != 'FetchError')
79
- return false;
80
- if (retryCount === maxRetry)
81
- return false;
82
- if (!retryOpts?.methods?.includes(request.method))
83
- return false;
84
- if (is_1.default.nodeStream(body) && stream_1.Readable.isDisturbed(body)) {
85
- logger.debug('Body of type stream was read, unable to retry request.');
86
- return false;
87
- }
88
- if ('code' in error &&
89
- error.code &&
90
- retryOpts?.errorCodes?.includes(error.code))
91
- return true;
92
- return false;
93
- };
94
- if (shouldRetry()) {
150
+ if (shouldRetryRequest(retryOpts.maxRetries, error)) {
95
151
  logger.debug(`retrying for the ${retryCount + 1} time`);
96
152
  logger.debug(`Error: ${error}`);
153
+ await sleep(retryCount === 0
154
+ ? retryOpts.minTimeout
155
+ : retryOpts.minTimeout * retryOpts.timeoutFactor ** retryCount);
97
156
  // NOTE: this event is only used by tests and will be removed at any time.
98
157
  // jsforce may switch to node's fetch which doesn't emit this event on retries.
99
158
  emitter.emit('retry', retryCount);
@@ -101,7 +160,14 @@ async function startFetchRequest(request, options, input, output, emitter, count
101
160
  return await fetchWithRetries(maxRetry);
102
161
  }
103
162
  logger.debug('Skipping retry...');
104
- throw err;
163
+ if (maxRetry === retryCount) {
164
+ const error = new Error('Request failed', { cause: err });
165
+ error.name = 'RequestRetryError';
166
+ throw error;
167
+ }
168
+ else {
169
+ throw err;
170
+ }
105
171
  }
106
172
  };
107
173
  let res;
@@ -142,3 +208,4 @@ function request(req, options_ = {}) {
142
208
  return stream;
143
209
  }
144
210
  exports.default = request;
211
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -21,8 +21,11 @@ export type HttpRequest = {
21
21
  export type HttpRequestOptions = {
22
22
  retry?: {
23
23
  maxRetries?: number;
24
+ minTimeout?: number;
25
+ timeoutFactor?: number;
24
26
  errorCodes?: string[];
25
27
  methods?: HttpMethods[];
28
+ statusCodes?: number[];
26
29
  };
27
30
  httpProxy?: string;
28
31
  timeout?: number;
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "database.com"
11
11
  ],
12
12
  "homepage": "http://github.com/jsforce/jsforce",
13
- "version": "3.1.0",
13
+ "version": "3.2.0",
14
14
  "repository": {
15
15
  "type": "git",
16
16
  "url": "git://github.com/jsforce/jsforce.git"