n8n-nodes-prestashop8 2.4.2 → 2.6.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.
|
@@ -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 (network timeout, connection drop, 5xx server error or 429 rate-limit). Never retries client errors (4xx).',
|
|
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',
|
|
@@ -677,6 +709,14 @@ exports.PrestaShop8Description = {
|
|
|
677
709
|
default: 30000,
|
|
678
710
|
description: 'Request timeout in milliseconds',
|
|
679
711
|
},
|
|
712
|
+
{
|
|
713
|
+
displayName: 'Delay Between Calls (ms)',
|
|
714
|
+
name: 'delayBetweenCalls',
|
|
715
|
+
type: 'number',
|
|
716
|
+
typeOptions: { minValue: 0 },
|
|
717
|
+
default: 0,
|
|
718
|
+
description: 'Pause in milliseconds applied before each PrestaShop call except the first, to throttle requests when processing multiple input items',
|
|
719
|
+
},
|
|
680
720
|
],
|
|
681
721
|
},
|
|
682
722
|
],
|
|
@@ -100,6 +100,10 @@ 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
|
+
// Throttle: pause before each PrestaShop call except the first.
|
|
104
|
+
if (i > 0 && opts.delayBetweenCalls > 0) {
|
|
105
|
+
await (0, http_1.sleep)(opts.delayBetweenCalls);
|
|
106
|
+
}
|
|
103
107
|
switch (operation) {
|
|
104
108
|
case 'list': {
|
|
105
109
|
const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
|
|
@@ -120,13 +124,13 @@ class PrestaShop8 {
|
|
|
120
124
|
}
|
|
121
125
|
requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, urlParams, rawMode);
|
|
122
126
|
if (rawMode) {
|
|
123
|
-
const rawResult = await (0, http_1.executeRawModeRequest)(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource);
|
|
127
|
+
const rawResult = await (0, http_1.executeRawModeRequest)(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource, opts.retry);
|
|
124
128
|
responseData = rawResult.responseData;
|
|
125
129
|
requestDebugInfo = rawResult.requestDebugInfo;
|
|
126
130
|
}
|
|
127
131
|
else {
|
|
128
132
|
const options = (0, http_1.buildHttpOptions)('GET', requestUrl, credentials, rawMode, timeout);
|
|
129
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError);
|
|
133
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
|
|
130
134
|
requestUrl = url;
|
|
131
135
|
requestDebugInfo = debugInfo;
|
|
132
136
|
const processedResponse = (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -149,7 +153,7 @@ class PrestaShop8 {
|
|
|
149
153
|
}
|
|
150
154
|
requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}/${id}`, urlParams, rawMode);
|
|
151
155
|
const options = (0, http_1.buildHttpOptions)('GET', requestUrl, credentials, rawMode, timeout);
|
|
152
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError);
|
|
156
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
|
|
153
157
|
requestUrl = url;
|
|
154
158
|
requestDebugInfo = debugInfo;
|
|
155
159
|
const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -186,7 +190,7 @@ class PrestaShop8 {
|
|
|
186
190
|
// Build XML using new format
|
|
187
191
|
body = (0, utils_1.buildCreateXml)(resource, fieldsToCreate);
|
|
188
192
|
const options = (0, http_1.buildHttpOptions)('POST', `${credentials.baseUrl}/${resource}`, credentials, rawMode, timeout, body);
|
|
189
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body);
|
|
193
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body, opts.retry);
|
|
190
194
|
requestUrl = url;
|
|
191
195
|
requestDebugInfo = debugInfo;
|
|
192
196
|
const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -219,7 +223,7 @@ class PrestaShop8 {
|
|
|
219
223
|
// Build XML using new format
|
|
220
224
|
body = (0, utils_1.buildUpdateXml)(resource, id, fieldsToUpdate);
|
|
221
225
|
const options = (0, http_1.buildHttpOptions)('PATCH', `${credentials.baseUrl}/${resource}/${id}`, credentials, rawMode, timeout, body);
|
|
222
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body);
|
|
226
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body, opts.retry);
|
|
223
227
|
requestUrl = url;
|
|
224
228
|
requestDebugInfo = debugInfo;
|
|
225
229
|
const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -291,13 +295,13 @@ class PrestaShop8 {
|
|
|
291
295
|
}
|
|
292
296
|
requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, urlParams, rawMode);
|
|
293
297
|
if (rawMode) {
|
|
294
|
-
const rawResult = await (0, http_1.executeRawModeRequest)(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource);
|
|
298
|
+
const rawResult = await (0, http_1.executeRawModeRequest)(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource, opts.retry);
|
|
295
299
|
responseData = rawResult.responseData;
|
|
296
300
|
requestDebugInfo = rawResult.requestDebugInfo;
|
|
297
301
|
}
|
|
298
302
|
else {
|
|
299
303
|
const options = (0, http_1.buildHttpOptions)('GET', requestUrl, credentials, rawMode, timeout);
|
|
300
|
-
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError);
|
|
304
|
+
const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
|
|
301
305
|
requestUrl = url;
|
|
302
306
|
requestDebugInfo = debugInfo;
|
|
303
307
|
const processedResponse = (0, utils_1.processResponseForMode)(response, resource);
|
|
@@ -311,7 +315,7 @@ class PrestaShop8 {
|
|
|
311
315
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'ID required for this operation');
|
|
312
316
|
}
|
|
313
317
|
const options = (0, http_1.buildHttpOptions)('DELETE', `${credentials.baseUrl}/${resource}/${id}`, credentials, rawMode, timeout);
|
|
314
|
-
const { debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError);
|
|
318
|
+
const { debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
|
|
315
319
|
requestUrl = url;
|
|
316
320
|
requestDebugInfo = debugInfo;
|
|
317
321
|
const deleteResponse = {
|
|
@@ -18,7 +18,34 @@ export interface OperationOptions {
|
|
|
18
18
|
rawMode: boolean;
|
|
19
19
|
showRequestInfo: boolean;
|
|
20
20
|
showRequestUrl: boolean;
|
|
21
|
+
delayBetweenCalls: number;
|
|
22
|
+
retry: RetryOptions;
|
|
21
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
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Pause execution for the given number of milliseconds (used to throttle calls).
|
|
35
|
+
*/
|
|
36
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Determine whether an error is transient and worth retrying.
|
|
39
|
+
* Retries on network/timeout errors, 5xx server errors and 429 rate-limit.
|
|
40
|
+
* Never retries on 4xx (invalid key, not found, invalid XML, etc.).
|
|
41
|
+
*/
|
|
42
|
+
export declare function isRetryableError(error: any): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Run an async HTTP operation with per-call retry on transient errors.
|
|
45
|
+
* Each call gets its own retry budget; on the last attempt the error is rethrown
|
|
46
|
+
* so existing neverError / continueOnFail handling applies unchanged.
|
|
47
|
+
*/
|
|
48
|
+
export declare function withRetry<T>(retry: RetryOptions, fn: () => Promise<T>): Promise<T>;
|
|
22
49
|
/**
|
|
23
50
|
* Extract common operation options from node parameters
|
|
24
51
|
*/
|
|
@@ -38,7 +65,7 @@ export declare function wrapResponse(processedResponse: any, includeHeaders: boo
|
|
|
38
65
|
/**
|
|
39
66
|
* Execute HTTP request with debug capture and response metadata
|
|
40
67
|
*/
|
|
41
|
-
export declare function executeHttpRequest(helpers: any, options: IHttpRequestOptions, credentials: any, rawMode: boolean, operation: string, resource: string, neverError?: boolean, body?: string): Promise<{
|
|
68
|
+
export declare function executeHttpRequest(helpers: any, options: IHttpRequestOptions, credentials: any, rawMode: boolean, operation: string, resource: string, neverError?: boolean, body?: string, retry?: RetryOptions): Promise<{
|
|
42
69
|
response: any;
|
|
43
70
|
debugInfo: any;
|
|
44
71
|
url: string;
|
|
@@ -48,7 +75,7 @@ export declare function executeHttpRequest(helpers: any, options: IHttpRequestOp
|
|
|
48
75
|
/**
|
|
49
76
|
* Execute a raw mode request using axios directly (bypasses n8n parsing)
|
|
50
77
|
*/
|
|
51
|
-
export declare function executeRawModeRequest(requestUrl: string, credentials: IPrestaShopCredentials, timeout: number, neverError: boolean, includeResponseHeaders: boolean, operation: string, resource: string): Promise<{
|
|
78
|
+
export declare function executeRawModeRequest(requestUrl: string, credentials: IPrestaShopCredentials, timeout: number, neverError: boolean, includeResponseHeaders: boolean, operation: string, resource: string, retry?: RetryOptions): Promise<{
|
|
52
79
|
responseData: any;
|
|
53
80
|
requestDebugInfo: any;
|
|
54
81
|
requestHeaders: any;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FILTER_OPERATOR_FORMATS = void 0;
|
|
4
|
+
exports.sleep = sleep;
|
|
5
|
+
exports.isRetryableError = isRetryableError;
|
|
6
|
+
exports.withRetry = withRetry;
|
|
4
7
|
exports.getOperationOptions = getOperationOptions;
|
|
5
8
|
exports.buildHttpOptions = buildHttpOptions;
|
|
6
9
|
exports.captureRequestDebugInfo = captureRequestDebugInfo;
|
|
@@ -27,6 +30,66 @@ exports.FILTER_OPERATOR_FORMATS = {
|
|
|
27
30
|
'IS_EMPTY': { template: '[]', requiresValue: false },
|
|
28
31
|
'IS_NOT_EMPTY': { template: '![]', requiresValue: false },
|
|
29
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Pause execution for the given number of milliseconds (used to throttle calls).
|
|
35
|
+
*/
|
|
36
|
+
function sleep(ms) {
|
|
37
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
38
|
+
}
|
|
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
|
+
function isRetryableError(error) {
|
|
45
|
+
var _a, _b;
|
|
46
|
+
// Network / timeout errors expose a code on the error or its cause
|
|
47
|
+
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);
|
|
48
|
+
const retryableCodes = [
|
|
49
|
+
'ETIMEDOUT',
|
|
50
|
+
'ECONNRESET',
|
|
51
|
+
'ECONNREFUSED',
|
|
52
|
+
'ECONNABORTED',
|
|
53
|
+
'ENOTFOUND',
|
|
54
|
+
'EAI_AGAIN',
|
|
55
|
+
'EPIPE',
|
|
56
|
+
];
|
|
57
|
+
if (code && retryableCodes.includes(code)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// "socket hang up" and timeout messages have no stable code
|
|
61
|
+
const message = String((error === null || error === void 0 ? void 0 : error.message) || '').toLowerCase();
|
|
62
|
+
if (message.includes('socket hang up') || message.includes('timeout')) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// HTTP status: retry on 429 (rate-limit) and 5xx (server errors) only
|
|
66
|
+
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);
|
|
67
|
+
if (status === 429 || (status >= 500 && status <= 599)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Run an async HTTP operation with per-call retry on transient errors.
|
|
74
|
+
* Each call gets its own retry budget; on the last attempt the error is rethrown
|
|
75
|
+
* so existing neverError / continueOnFail handling applies unchanged.
|
|
76
|
+
*/
|
|
77
|
+
async function withRetry(retry, fn) {
|
|
78
|
+
const maxRetries = retry.enabled ? Math.max(0, retry.maxRetries) : 0;
|
|
79
|
+
for (let attempt = 0;; attempt++) {
|
|
80
|
+
try {
|
|
81
|
+
return await fn();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (attempt >= maxRetries || !isRetryableError(error)) {
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
if (retry.retryDelay > 0) {
|
|
88
|
+
await sleep(retry.retryDelay);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
30
93
|
/**
|
|
31
94
|
* Extract common operation options from node parameters
|
|
32
95
|
*/
|
|
@@ -38,6 +101,12 @@ function getOperationOptions(executeFunctions, itemIndex) {
|
|
|
38
101
|
rawMode: executeFunctions.getNodeParameter('options.response.rawMode', itemIndex, false),
|
|
39
102
|
showRequestInfo: executeFunctions.getNodeParameter('options.request.showRequestInfo', itemIndex, false),
|
|
40
103
|
showRequestUrl: executeFunctions.getNodeParameter('options.request.showRequestUrl', itemIndex, false),
|
|
104
|
+
delayBetweenCalls: executeFunctions.getNodeParameter('options.delayBetweenCalls', itemIndex, 0),
|
|
105
|
+
retry: {
|
|
106
|
+
enabled: executeFunctions.getNodeParameter('options.retry.retryEnabled', itemIndex, false),
|
|
107
|
+
maxRetries: executeFunctions.getNodeParameter('options.retry.maxRetries', itemIndex, 3),
|
|
108
|
+
retryDelay: executeFunctions.getNodeParameter('options.retry.retryDelay', itemIndex, 1000),
|
|
109
|
+
},
|
|
41
110
|
};
|
|
42
111
|
}
|
|
43
112
|
/**
|
|
@@ -107,12 +176,12 @@ function wrapResponse(processedResponse, includeHeaders, headers, statusCode) {
|
|
|
107
176
|
/**
|
|
108
177
|
* Execute HTTP request with debug capture and response metadata
|
|
109
178
|
*/
|
|
110
|
-
async function executeHttpRequest(helpers, options, credentials, rawMode, operation, resource, neverError = false, body) {
|
|
179
|
+
async function executeHttpRequest(helpers, options, credentials, rawMode, operation, resource, neverError = false, body, retry = { enabled: false, maxRetries: 0, retryDelay: 0 }) {
|
|
111
180
|
var _a, _b, _c, _d;
|
|
112
181
|
const requestUrl = options.url;
|
|
113
182
|
const debugInfo = captureRequestDebugInfo(options, credentials, rawMode, operation, resource, body);
|
|
114
183
|
try {
|
|
115
|
-
const response = await helpers.httpRequest(options);
|
|
184
|
+
const response = await withRetry(retry, () => helpers.httpRequest(options));
|
|
116
185
|
return {
|
|
117
186
|
response,
|
|
118
187
|
debugInfo,
|
|
@@ -141,11 +210,11 @@ async function executeHttpRequest(helpers, options, credentials, rawMode, operat
|
|
|
141
210
|
/**
|
|
142
211
|
* Execute a raw mode request using axios directly (bypasses n8n parsing)
|
|
143
212
|
*/
|
|
144
|
-
async function executeRawModeRequest(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource) {
|
|
213
|
+
async function executeRawModeRequest(requestUrl, credentials, timeout, neverError, includeResponseHeaders, operation, resource, retry = { enabled: false, maxRetries: 0, retryDelay: 0 }) {
|
|
145
214
|
var _a, _b;
|
|
146
215
|
let responseData;
|
|
147
216
|
try {
|
|
148
|
-
const axiosResponse = await axios({
|
|
217
|
+
const axiosResponse = await withRetry(retry, () => axios({
|
|
149
218
|
method: 'GET',
|
|
150
219
|
url: requestUrl,
|
|
151
220
|
auth: {
|
|
@@ -156,7 +225,7 @@ async function executeRawModeRequest(requestUrl, credentials, timeout, neverErro
|
|
|
156
225
|
timeout: timeout || 30000,
|
|
157
226
|
transformResponse: [(data) => data],
|
|
158
227
|
validateStatus: neverError ? () => true : undefined,
|
|
159
|
-
});
|
|
228
|
+
}));
|
|
160
229
|
const requestDebugInfo = captureRequestDebugInfo({
|
|
161
230
|
method: 'GET',
|
|
162
231
|
url: requestUrl,
|
package/package.json
CHANGED