n8n-nodes-prestashop8 2.5.0 → 2.6.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.
|
@@ -670,6 +670,38 @@ exports.PrestaShop8Description = {
|
|
|
670
670
|
},
|
|
671
671
|
],
|
|
672
672
|
},
|
|
673
|
+
{
|
|
674
|
+
displayName: 'Retry On Error',
|
|
675
|
+
name: 'retry',
|
|
676
|
+
type: 'collection',
|
|
677
|
+
placeholder: 'Add Retry Option',
|
|
678
|
+
default: {},
|
|
679
|
+
options: [
|
|
680
|
+
{
|
|
681
|
+
displayName: 'Enabled',
|
|
682
|
+
name: 'retryEnabled',
|
|
683
|
+
type: 'boolean',
|
|
684
|
+
default: false,
|
|
685
|
+
description: 'Whether to retry a call that fails on a transient error. Retries on: network timeout, connection drop (ECONNRESET / ECONNREFUSED / socket hang up), 5xx server errors and 429 rate-limit. Never retries client errors (4xx — invalid API key, 404, invalid XML). The retry budget is reset for each failing call.',
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
displayName: 'Max Retries',
|
|
689
|
+
name: 'maxRetries',
|
|
690
|
+
type: 'number',
|
|
691
|
+
typeOptions: { minValue: 0 },
|
|
692
|
+
default: 3,
|
|
693
|
+
description: 'Maximum number of retry attempts per failing call. This budget is reset for each call, it is not a global limit for the whole node execution.',
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
displayName: 'Retry Delay (ms)',
|
|
697
|
+
name: 'retryDelay',
|
|
698
|
+
type: 'number',
|
|
699
|
+
typeOptions: { minValue: 0 },
|
|
700
|
+
default: 1000,
|
|
701
|
+
description: 'Pause in milliseconds between two successive retry attempts of the same call',
|
|
702
|
+
},
|
|
703
|
+
],
|
|
704
|
+
},
|
|
673
705
|
{
|
|
674
706
|
displayName: 'Timeout (ms)',
|
|
675
707
|
name: 'timeout',
|
|
@@ -100,6 +100,12 @@ class PrestaShop8 {
|
|
|
100
100
|
let requestDebugInfo = {};
|
|
101
101
|
const opts = (0, http_1.getOperationOptions)(this, i);
|
|
102
102
|
const { rawMode, timeout, neverError, includeResponseHeaders, showRequestInfo, showRequestUrl } = opts;
|
|
103
|
+
// Trace each retry attempt to the n8n server logs (Option A).
|
|
104
|
+
if (opts.retry.enabled) {
|
|
105
|
+
opts.retry.onRetry = (attempt, error) => {
|
|
106
|
+
this.logger.warn(`PrestaShop8: retry ${attempt}/${opts.retry.maxRetries} on ${resource}/${operation} after ${(0, http_1.describeError)(error)} — waiting ${opts.retry.retryDelay}ms`);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
103
109
|
// Throttle: pause before each PrestaShop call except the first.
|
|
104
110
|
if (i > 0 && opts.delayBetweenCalls > 0) {
|
|
105
111
|
await (0, http_1.sleep)(opts.delayBetweenCalls);
|
|
@@ -124,13 +130,13 @@ class PrestaShop8 {
|
|
|
124
130
|
}
|
|
125
131
|
requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, urlParams, rawMode);
|
|
126
132
|
if (rawMode) {
|
|
127
|
-
const rawResult = await (0, http_1.executeRawModeRequest)(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource);
|
|
133
|
+
const rawResult = await (0, http_1.executeRawModeRequest)(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource, opts.retry);
|
|
128
134
|
responseData = rawResult.responseData;
|
|
129
135
|
requestDebugInfo = rawResult.requestDebugInfo;
|
|
130
136
|
}
|
|
131
137
|
else {
|
|
132
138
|
const options = (0, http_1.buildHttpOptions)('GET', requestUrl, credentials, rawMode, timeout);
|
|
133
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError);
|
|
139
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
|
|
134
140
|
requestUrl = url;
|
|
135
141
|
requestDebugInfo = debugInfo;
|
|
136
142
|
const processedResponse = (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -153,7 +159,7 @@ class PrestaShop8 {
|
|
|
153
159
|
}
|
|
154
160
|
requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}/${id}`, urlParams, rawMode);
|
|
155
161
|
const options = (0, http_1.buildHttpOptions)('GET', requestUrl, credentials, rawMode, timeout);
|
|
156
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError);
|
|
162
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
|
|
157
163
|
requestUrl = url;
|
|
158
164
|
requestDebugInfo = debugInfo;
|
|
159
165
|
const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -190,7 +196,7 @@ class PrestaShop8 {
|
|
|
190
196
|
// Build XML using new format
|
|
191
197
|
body = (0, utils_1.buildCreateXml)(resource, fieldsToCreate);
|
|
192
198
|
const options = (0, http_1.buildHttpOptions)('POST', `${credentials.baseUrl}/${resource}`, credentials, rawMode, timeout, body);
|
|
193
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body);
|
|
199
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body, opts.retry);
|
|
194
200
|
requestUrl = url;
|
|
195
201
|
requestDebugInfo = debugInfo;
|
|
196
202
|
const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -223,7 +229,7 @@ class PrestaShop8 {
|
|
|
223
229
|
// Build XML using new format
|
|
224
230
|
body = (0, utils_1.buildUpdateXml)(resource, id, fieldsToUpdate);
|
|
225
231
|
const options = (0, http_1.buildHttpOptions)('PATCH', `${credentials.baseUrl}/${resource}/${id}`, credentials, rawMode, timeout, body);
|
|
226
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body);
|
|
232
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body, opts.retry);
|
|
227
233
|
requestUrl = url;
|
|
228
234
|
requestDebugInfo = debugInfo;
|
|
229
235
|
const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -295,13 +301,13 @@ class PrestaShop8 {
|
|
|
295
301
|
}
|
|
296
302
|
requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, urlParams, rawMode);
|
|
297
303
|
if (rawMode) {
|
|
298
|
-
const rawResult = await (0, http_1.executeRawModeRequest)(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource);
|
|
304
|
+
const rawResult = await (0, http_1.executeRawModeRequest)(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource, opts.retry);
|
|
299
305
|
responseData = rawResult.responseData;
|
|
300
306
|
requestDebugInfo = rawResult.requestDebugInfo;
|
|
301
307
|
}
|
|
302
308
|
else {
|
|
303
309
|
const options = (0, http_1.buildHttpOptions)('GET', requestUrl, credentials, rawMode, timeout);
|
|
304
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError);
|
|
310
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
|
|
305
311
|
requestUrl = url;
|
|
306
312
|
requestDebugInfo = debugInfo;
|
|
307
313
|
const processedResponse = (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -315,7 +321,7 @@ class PrestaShop8 {
|
|
|
315
321
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'ID required for this operation');
|
|
316
322
|
}
|
|
317
323
|
const options = (0, http_1.buildHttpOptions)('DELETE', `${credentials.baseUrl}/${resource}/${id}`, credentials, rawMode, timeout);
|
|
318
|
-
const { debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError);
|
|
324
|
+
const { debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
|
|
319
325
|
requestUrl = url;
|
|
320
326
|
requestDebugInfo = debugInfo;
|
|
321
327
|
const deleteResponse = {
|
|
@@ -19,11 +19,40 @@ export interface OperationOptions {
|
|
|
19
19
|
showRequestInfo: boolean;
|
|
20
20
|
showRequestUrl: boolean;
|
|
21
21
|
delayBetweenCalls: number;
|
|
22
|
+
retry: RetryOptions;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Retry configuration applied to each individual HTTP call.
|
|
26
|
+
* The retry budget is per call: every failing call gets a fresh set of attempts.
|
|
27
|
+
*/
|
|
28
|
+
export interface RetryOptions {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
maxRetries: number;
|
|
31
|
+
retryDelay: number;
|
|
32
|
+
/** Optional hook called before each retry wait, with the upcoming retry number (1-based) and the error. */
|
|
33
|
+
onRetry?: (attempt: number, error: any) => void;
|
|
22
34
|
}
|
|
23
35
|
/**
|
|
24
36
|
* Pause execution for the given number of milliseconds (used to throttle calls).
|
|
25
37
|
*/
|
|
26
38
|
export declare function sleep(ms: number): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Determine whether an error is transient and worth retrying.
|
|
41
|
+
* Retries on network/timeout errors, 5xx server errors and 429 rate-limit.
|
|
42
|
+
* Never retries on 4xx (invalid key, not found, invalid XML, etc.).
|
|
43
|
+
*/
|
|
44
|
+
export declare function isRetryableError(error: any): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Build a short human-readable reason from an error, for retry logging.
|
|
47
|
+
* Prefers the network code, then the HTTP status, then the message.
|
|
48
|
+
*/
|
|
49
|
+
export declare function describeError(error: any): string;
|
|
50
|
+
/**
|
|
51
|
+
* Run an async HTTP operation with per-call retry on transient errors.
|
|
52
|
+
* Each call gets its own retry budget; on the last attempt the error is rethrown
|
|
53
|
+
* so existing neverError / continueOnFail handling applies unchanged.
|
|
54
|
+
*/
|
|
55
|
+
export declare function withRetry<T>(retry: RetryOptions, fn: () => Promise<T>): Promise<T>;
|
|
27
56
|
/**
|
|
28
57
|
* Extract common operation options from node parameters
|
|
29
58
|
*/
|
|
@@ -43,7 +72,7 @@ export declare function wrapResponse(processedResponse: any, includeHeaders: boo
|
|
|
43
72
|
/**
|
|
44
73
|
* Execute HTTP request with debug capture and response metadata
|
|
45
74
|
*/
|
|
46
|
-
export declare function executeHttpRequest(helpers: any, options: IHttpRequestOptions, credentials: any, rawMode: boolean, operation: string, resource: string, neverError?: boolean, body?: string): Promise<{
|
|
75
|
+
export declare function executeHttpRequest(helpers: any, options: IHttpRequestOptions, credentials: any, rawMode: boolean, operation: string, resource: string, neverError?: boolean, body?: string, retry?: RetryOptions): Promise<{
|
|
47
76
|
response: any;
|
|
48
77
|
debugInfo: any;
|
|
49
78
|
url: string;
|
|
@@ -53,7 +82,7 @@ export declare function executeHttpRequest(helpers: any, options: IHttpRequestOp
|
|
|
53
82
|
/**
|
|
54
83
|
* Execute a raw mode request using axios directly (bypasses n8n parsing)
|
|
55
84
|
*/
|
|
56
|
-
export declare function executeRawModeRequest(requestUrl: string, credentials: IPrestaShopCredentials, timeout: number, neverError: boolean, includeResponseHeaders: boolean, operation: string, resource: string): Promise<{
|
|
85
|
+
export declare function executeRawModeRequest(requestUrl: string, credentials: IPrestaShopCredentials, timeout: number, neverError: boolean, includeResponseHeaders: boolean, operation: string, resource: string, retry?: RetryOptions): Promise<{
|
|
57
86
|
responseData: any;
|
|
58
87
|
requestDebugInfo: any;
|
|
59
88
|
requestHeaders: any;
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FILTER_OPERATOR_FORMATS = void 0;
|
|
4
4
|
exports.sleep = sleep;
|
|
5
|
+
exports.isRetryableError = isRetryableError;
|
|
6
|
+
exports.describeError = describeError;
|
|
7
|
+
exports.withRetry = withRetry;
|
|
5
8
|
exports.getOperationOptions = getOperationOptions;
|
|
6
9
|
exports.buildHttpOptions = buildHttpOptions;
|
|
7
10
|
exports.captureRequestDebugInfo = captureRequestDebugInfo;
|
|
@@ -34,6 +37,80 @@ exports.FILTER_OPERATOR_FORMATS = {
|
|
|
34
37
|
function sleep(ms) {
|
|
35
38
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Determine whether an error is transient and worth retrying.
|
|
42
|
+
* Retries on network/timeout errors, 5xx server errors and 429 rate-limit.
|
|
43
|
+
* Never retries on 4xx (invalid key, not found, invalid XML, etc.).
|
|
44
|
+
*/
|
|
45
|
+
function isRetryableError(error) {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
// Network / timeout errors expose a code on the error or its cause
|
|
48
|
+
const code = (error === null || error === void 0 ? void 0 : error.code) || ((_a = error === null || error === void 0 ? void 0 : error.cause) === null || _a === void 0 ? void 0 : _a.code);
|
|
49
|
+
const retryableCodes = [
|
|
50
|
+
'ETIMEDOUT',
|
|
51
|
+
'ECONNRESET',
|
|
52
|
+
'ECONNREFUSED',
|
|
53
|
+
'ECONNABORTED',
|
|
54
|
+
'ENOTFOUND',
|
|
55
|
+
'EAI_AGAIN',
|
|
56
|
+
'EPIPE',
|
|
57
|
+
];
|
|
58
|
+
if (code && retryableCodes.includes(code)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// "socket hang up" and timeout messages have no stable code
|
|
62
|
+
const message = String((error === null || error === void 0 ? void 0 : error.message) || '').toLowerCase();
|
|
63
|
+
if (message.includes('socket hang up') || message.includes('timeout')) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
// HTTP status: retry on 429 (rate-limit) and 5xx (server errors) only
|
|
67
|
+
const status = (error === null || error === void 0 ? void 0 : error.httpCode) || ((_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status);
|
|
68
|
+
if (status === 429 || (status >= 500 && status <= 599)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build a short human-readable reason from an error, for retry logging.
|
|
75
|
+
* Prefers the network code, then the HTTP status, then the message.
|
|
76
|
+
*/
|
|
77
|
+
function describeError(error) {
|
|
78
|
+
var _a, _b;
|
|
79
|
+
const code = (error === null || error === void 0 ? void 0 : error.code) || ((_a = error === null || error === void 0 ? void 0 : error.cause) === null || _a === void 0 ? void 0 : _a.code);
|
|
80
|
+
if (code) {
|
|
81
|
+
return code;
|
|
82
|
+
}
|
|
83
|
+
const status = (error === null || error === void 0 ? void 0 : error.httpCode) || ((_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status);
|
|
84
|
+
if (status) {
|
|
85
|
+
return `HTTP ${status}`;
|
|
86
|
+
}
|
|
87
|
+
const message = error === null || error === void 0 ? void 0 : error.message;
|
|
88
|
+
return message ? String(message) : 'unknown error';
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Run an async HTTP operation with per-call retry on transient errors.
|
|
92
|
+
* Each call gets its own retry budget; on the last attempt the error is rethrown
|
|
93
|
+
* so existing neverError / continueOnFail handling applies unchanged.
|
|
94
|
+
*/
|
|
95
|
+
async function withRetry(retry, fn) {
|
|
96
|
+
var _a;
|
|
97
|
+
const maxRetries = retry.enabled ? Math.max(0, retry.maxRetries) : 0;
|
|
98
|
+
for (let attempt = 0;; attempt++) {
|
|
99
|
+
try {
|
|
100
|
+
return await fn();
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (attempt >= maxRetries || !isRetryableError(error)) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
// attempt is 0-based; report the upcoming retry number (1-based)
|
|
107
|
+
(_a = retry.onRetry) === null || _a === void 0 ? void 0 : _a.call(retry, attempt + 1, error);
|
|
108
|
+
if (retry.retryDelay > 0) {
|
|
109
|
+
await sleep(retry.retryDelay);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
37
114
|
/**
|
|
38
115
|
* Extract common operation options from node parameters
|
|
39
116
|
*/
|
|
@@ -46,6 +123,11 @@ function getOperationOptions(executeFunctions, itemIndex) {
|
|
|
46
123
|
showRequestInfo: executeFunctions.getNodeParameter('options.request.showRequestInfo', itemIndex, false),
|
|
47
124
|
showRequestUrl: executeFunctions.getNodeParameter('options.request.showRequestUrl', itemIndex, false),
|
|
48
125
|
delayBetweenCalls: executeFunctions.getNodeParameter('options.delayBetweenCalls', itemIndex, 0),
|
|
126
|
+
retry: {
|
|
127
|
+
enabled: executeFunctions.getNodeParameter('options.retry.retryEnabled', itemIndex, false),
|
|
128
|
+
maxRetries: executeFunctions.getNodeParameter('options.retry.maxRetries', itemIndex, 3),
|
|
129
|
+
retryDelay: executeFunctions.getNodeParameter('options.retry.retryDelay', itemIndex, 1000),
|
|
130
|
+
},
|
|
49
131
|
};
|
|
50
132
|
}
|
|
51
133
|
/**
|
|
@@ -115,12 +197,12 @@ function wrapResponse(processedResponse, includeHeaders, headers, statusCode) {
|
|
|
115
197
|
/**
|
|
116
198
|
* Execute HTTP request with debug capture and response metadata
|
|
117
199
|
*/
|
|
118
|
-
async function executeHttpRequest(helpers, options, credentials, rawMode, operation, resource, neverError = false, body) {
|
|
200
|
+
async function executeHttpRequest(helpers, options, credentials, rawMode, operation, resource, neverError = false, body, retry = { enabled: false, maxRetries: 0, retryDelay: 0 }) {
|
|
119
201
|
var _a, _b, _c, _d;
|
|
120
202
|
const requestUrl = options.url;
|
|
121
203
|
const debugInfo = captureRequestDebugInfo(options, credentials, rawMode, operation, resource, body);
|
|
122
204
|
try {
|
|
123
|
-
const response = await helpers.httpRequest(options);
|
|
205
|
+
const response = await withRetry(retry, () => helpers.httpRequest(options));
|
|
124
206
|
return {
|
|
125
207
|
response,
|
|
126
208
|
debugInfo,
|
|
@@ -149,11 +231,11 @@ async function executeHttpRequest(helpers, options, credentials, rawMode, operat
|
|
|
149
231
|
/**
|
|
150
232
|
* Execute a raw mode request using axios directly (bypasses n8n parsing)
|
|
151
233
|
*/
|
|
152
|
-
async function executeRawModeRequest(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource) {
|
|
234
|
+
async function executeRawModeRequest(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource, retry = { enabled: false, maxRetries: 0, retryDelay: 0 }) {
|
|
153
235
|
var _a, _b;
|
|
154
236
|
let responseData;
|
|
155
237
|
try {
|
|
156
|
-
const axiosResponse = await axios({
|
|
238
|
+
const axiosResponse = await withRetry(retry, () => axios({
|
|
157
239
|
method: 'GET',
|
|
158
240
|
url: requestUrl,
|
|
159
241
|
auth: {
|
|
@@ -164,7 +246,7 @@ async function executeRawModeRequest(requestUrl, credentials, timeout, neverErro
|
|
|
164
246
|
timeout: timeout || 30000,
|
|
165
247
|
transformResponse: [(data) => data],
|
|
166
248
|
validateStatus: neverError ? () => true : undefined,
|
|
167
|
-
});
|
|
249
|
+
}));
|
|
168
250
|
const requestDebugInfo = captureRequestDebugInfo({
|
|
169
251
|
method: 'GET',
|
|
170
252
|
url: requestUrl,
|
package/package.json
CHANGED