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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-prestashop8",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
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",