n8n-nodes-prestashop8 2.5.0 → 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',
@@ -124,13 +124,13 @@ class PrestaShop8 {
124
124
  }
125
125
  requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, urlParams, rawMode);
126
126
  if (rawMode) {
127
- 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);
128
128
  responseData = rawResult.responseData;
129
129
  requestDebugInfo = rawResult.requestDebugInfo;
130
130
  }
131
131
  else {
132
132
  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);
133
+ const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
134
134
  requestUrl = url;
135
135
  requestDebugInfo = debugInfo;
136
136
  const processedResponse = (0, utils_1.processResponseForMode)(response, resource);
@@ -153,7 +153,7 @@ class PrestaShop8 {
153
153
  }
154
154
  requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}/${id}`, urlParams, rawMode);
155
155
  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);
156
+ const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
157
157
  requestUrl = url;
158
158
  requestDebugInfo = debugInfo;
159
159
  const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
@@ -190,7 +190,7 @@ class PrestaShop8 {
190
190
  // Build XML using new format
191
191
  body = (0, utils_1.buildCreateXml)(resource, fieldsToCreate);
192
192
  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);
193
+ const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body, opts.retry);
194
194
  requestUrl = url;
195
195
  requestDebugInfo = debugInfo;
196
196
  const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
@@ -223,7 +223,7 @@ class PrestaShop8 {
223
223
  // Build XML using new format
224
224
  body = (0, utils_1.buildUpdateXml)(resource, id, fieldsToUpdate);
225
225
  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);
226
+ const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, body, opts.retry);
227
227
  requestUrl = url;
228
228
  requestDebugInfo = debugInfo;
229
229
  const processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource);
@@ -295,13 +295,13 @@ class PrestaShop8 {
295
295
  }
296
296
  requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, urlParams, rawMode);
297
297
  if (rawMode) {
298
- 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);
299
299
  responseData = rawResult.responseData;
300
300
  requestDebugInfo = rawResult.requestDebugInfo;
301
301
  }
302
302
  else {
303
303
  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);
304
+ const { response, debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
305
305
  requestUrl = url;
306
306
  requestDebugInfo = debugInfo;
307
307
  const processedResponse = (0, utils_1.processResponseForMode)(response, resource);
@@ -315,7 +315,7 @@ class PrestaShop8 {
315
315
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'ID required for this operation');
316
316
  }
317
317
  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);
318
+ const { debugInfo, url, responseHeaders, statusCode } = await (0, http_1.executeHttpRequest)(this.helpers, options, credentials, rawMode, operation, resource, neverError, undefined, opts.retry);
319
319
  requestUrl = url;
320
320
  requestDebugInfo = debugInfo;
321
321
  const deleteResponse = {
@@ -19,11 +19,33 @@ 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;
22
32
  }
23
33
  /**
24
34
  * Pause execution for the given number of milliseconds (used to throttle calls).
25
35
  */
26
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>;
27
49
  /**
28
50
  * Extract common operation options from node parameters
29
51
  */
@@ -43,7 +65,7 @@ export declare function wrapResponse(processedResponse: any, includeHeaders: boo
43
65
  /**
44
66
  * Execute HTTP request with debug capture and response metadata
45
67
  */
46
- 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<{
47
69
  response: any;
48
70
  debugInfo: any;
49
71
  url: string;
@@ -53,7 +75,7 @@ export declare function executeHttpRequest(helpers: any, options: IHttpRequestOp
53
75
  /**
54
76
  * Execute a raw mode request using axios directly (bypasses n8n parsing)
55
77
  */
56
- 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<{
57
79
  responseData: any;
58
80
  requestDebugInfo: any;
59
81
  requestHeaders: any;
@@ -2,6 +2,8 @@
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.withRetry = withRetry;
5
7
  exports.getOperationOptions = getOperationOptions;
6
8
  exports.buildHttpOptions = buildHttpOptions;
7
9
  exports.captureRequestDebugInfo = captureRequestDebugInfo;
@@ -34,6 +36,60 @@ exports.FILTER_OPERATOR_FORMATS = {
34
36
  function sleep(ms) {
35
37
  return new Promise((resolve) => setTimeout(resolve, ms));
36
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
+ }
37
93
  /**
38
94
  * Extract common operation options from node parameters
39
95
  */
@@ -46,6 +102,11 @@ function getOperationOptions(executeFunctions, itemIndex) {
46
102
  showRequestInfo: executeFunctions.getNodeParameter('options.request.showRequestInfo', itemIndex, false),
47
103
  showRequestUrl: executeFunctions.getNodeParameter('options.request.showRequestUrl', itemIndex, false),
48
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
+ },
49
110
  };
50
111
  }
51
112
  /**
@@ -115,12 +176,12 @@ function wrapResponse(processedResponse, includeHeaders, headers, statusCode) {
115
176
  /**
116
177
  * Execute HTTP request with debug capture and response metadata
117
178
  */
118
- 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 }) {
119
180
  var _a, _b, _c, _d;
120
181
  const requestUrl = options.url;
121
182
  const debugInfo = captureRequestDebugInfo(options, credentials, rawMode, operation, resource, body);
122
183
  try {
123
- const response = await helpers.httpRequest(options);
184
+ const response = await withRetry(retry, () => helpers.httpRequest(options));
124
185
  return {
125
186
  response,
126
187
  debugInfo,
@@ -149,11 +210,11 @@ async function executeHttpRequest(helpers, options, credentials, rawMode, operat
149
210
  /**
150
211
  * Execute a raw mode request using axios directly (bypasses n8n parsing)
151
212
  */
152
- 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 }) {
153
214
  var _a, _b;
154
215
  let responseData;
155
216
  try {
156
- const axiosResponse = await axios({
217
+ const axiosResponse = await withRetry(retry, () => axios({
157
218
  method: 'GET',
158
219
  url: requestUrl,
159
220
  auth: {
@@ -164,7 +225,7 @@ async function executeRawModeRequest(requestUrl, credentials, timeout, neverErro
164
225
  timeout: timeout || 30000,
165
226
  transformResponse: [(data) => data],
166
227
  validateStatus: neverError ? () => true : undefined,
167
- });
228
+ }));
168
229
  const requestDebugInfo = captureRequestDebugInfo({
169
230
  method: 'GET',
170
231
  url: requestUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-prestashop8",
3
- "version": "2.5.0",
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",