@jsforce/jsforce-node
Advanced tools
Comparing version 3.1.0 to 3.2.0
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -8,3 +31,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
const stream_1 = require("stream"); | ||
const node_fetch_1 = __importDefault(require("node-fetch")); | ||
const node_fetch_1 = __importStar(require("node-fetch")); | ||
const abort_controller_1 = __importDefault(require("abort-controller")); | ||
@@ -37,3 +60,6 @@ const https_proxy_agent_1 = __importDefault(require("https-proxy-agent")); | ||
const retryOpts = { | ||
statusCodes: options.retry?.statusCodes ?? [429, 500, 502, 503, 504], | ||
maxRetries: options.retry?.maxRetries ?? 5, | ||
minTimeout: options.retry?.minTimeout ?? 500, | ||
timeoutFactor: options.retry?.timeoutFactor ?? 2, | ||
errorCodes: options.retry?.errorCodes ?? [ | ||
@@ -58,2 +84,36 @@ 'ECONNRESET', | ||
}; | ||
const shouldRetryRequest = (maxRetry, resOrErr) => { | ||
if (!retryOpts.methods.includes(request.method)) | ||
return false; | ||
if (resOrErr instanceof node_fetch_1.Response) { | ||
if (retryOpts.statusCodes.includes(resOrErr.status)) { | ||
if (maxRetry === retryCount) { | ||
const err = new Error('Request failed'); | ||
err.name = 'RequestRetryError'; | ||
throw err; | ||
} | ||
else { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
else { | ||
if (maxRetry === retryCount) | ||
return false; | ||
// only retry on operational errors | ||
// https://github.com/node-fetch/node-fetch/blob/2.x/ERROR-HANDLING.md#error-handling-with-node-fetch | ||
if (resOrErr.name != 'FetchError') | ||
return false; | ||
if (is_1.default.nodeStream(body) && stream_1.Readable.isDisturbed(body)) { | ||
logger.debug('Body of type stream was read, unable to retry request.'); | ||
return false; | ||
} | ||
if ('code' in resOrErr && | ||
resOrErr.code && | ||
retryOpts?.errorCodes?.includes(resOrErr.code)) | ||
return true; | ||
return false; | ||
} | ||
}; | ||
const fetchWithRetries = async (maxRetry = retryOpts?.maxRetries) => { | ||
@@ -70,3 +130,17 @@ const fetchOpts = { | ||
try { | ||
return await (0, node_fetch_1.default)(url, fetchOpts); | ||
const res = await (0, node_fetch_1.default)(url, fetchOpts); | ||
if (shouldRetryRequest(retryOpts.maxRetries, res)) { | ||
logger.debug(`retrying for the ${retryCount + 1} time`); | ||
logger.debug(`reason: statusCode match`); | ||
await sleep(retryCount === 0 | ||
? retryOpts.minTimeout | ||
: retryOpts.minTimeout * retryOpts.timeoutFactor ** retryCount); | ||
// NOTE: this event is only used by tests and will be removed at any time. | ||
// jsforce may switch to node's fetch which doesn't emit this event on retries. | ||
emitter.emit('retry', retryCount); | ||
retryCount++; | ||
return await fetchWithRetries(maxRetry); | ||
} | ||
// should we throw here if the maxRetry already happened and still got the same statusCode? | ||
return res; | ||
} | ||
@@ -80,23 +154,8 @@ catch (err) { | ||
} | ||
const shouldRetry = () => { | ||
// only retry on operational errors | ||
if (error.name != 'FetchError') | ||
return false; | ||
if (retryCount === maxRetry) | ||
return false; | ||
if (!retryOpts?.methods?.includes(request.method)) | ||
return false; | ||
if (is_1.default.nodeStream(body) && stream_1.Readable.isDisturbed(body)) { | ||
logger.debug('Body of type stream was read, unable to retry request.'); | ||
return false; | ||
} | ||
if ('code' in error && | ||
error.code && | ||
retryOpts?.errorCodes?.includes(error.code)) | ||
return true; | ||
return false; | ||
}; | ||
if (shouldRetry()) { | ||
if (shouldRetryRequest(retryOpts.maxRetries, error)) { | ||
logger.debug(`retrying for the ${retryCount + 1} time`); | ||
logger.debug(`Error: ${error}`); | ||
await sleep(retryCount === 0 | ||
? retryOpts.minTimeout | ||
: retryOpts.minTimeout * retryOpts.timeoutFactor ** retryCount); | ||
// NOTE: this event is only used by tests and will be removed at any time. | ||
@@ -109,3 +168,10 @@ // jsforce may switch to node's fetch which doesn't emit this event on retries. | ||
logger.debug('Skipping retry...'); | ||
throw err; | ||
if (maxRetry === retryCount) { | ||
const error = new Error('Request failed', { cause: err }); | ||
error.name = 'RequestRetryError'; | ||
throw error; | ||
} | ||
else { | ||
throw err; | ||
} | ||
} | ||
@@ -151,1 +217,2 @@ }; | ||
exports.default = request; | ||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); |
@@ -24,4 +24,7 @@ /// <reference types="node" /> | ||
maxRetries?: number; | ||
minTimeout?: number; | ||
timeoutFactor?: number; | ||
errorCodes?: string[]; | ||
methods?: HttpMethods[]; | ||
statusCodes?: number[]; | ||
}; | ||
@@ -28,0 +31,0 @@ httpProxy?: string; |
@@ -13,3 +13,3 @@ { | ||
"homepage": "http://github.com/jsforce/jsforce", | ||
"version": "3.1.0", | ||
"version": "3.2.0", | ||
"repository": { | ||
@@ -16,0 +16,0 @@ "type": "git", |
2161625
61599