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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-prestashop8",
3
- "version": "2.4.2",
3
+ "version": "2.6.0",
4
4
  "description": "Nœud n8n personnalisé pour PrestaShop 8 avec support CRUD complet et conversion XML/JSON automatique",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",