@contentstack/datasync-manager 2.3.0 → 2.4.0-beta.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.
package/dist/api.js CHANGED
@@ -56,7 +56,7 @@ const messages_1 = require("./util/messages");
56
56
  const debug = (0, debug_1.default)('api');
57
57
  let MAX_RETRY_LIMIT;
58
58
  let RETRY_DELAY_BASE = 200; // Default base delay in milliseconds
59
- let TIMEOUT = 30000; // Default timeout in milliseconds
59
+ let TIMEOUT = 60000; // Increased from 30000 to 60000 (60 seconds) for large stack syncs
60
60
  let Contentstack;
61
61
  /**
62
62
  * @description Initialize sync utilities API requests
@@ -158,6 +158,45 @@ const get = (req, RETRY = 1) => {
158
158
  }, timeDelay);
159
159
  }
160
160
  else {
161
+ // Enhanced error handling for Error 141 (Invalid sync_token)
162
+ try {
163
+ const errorBody = JSON.parse(body);
164
+ // Validate error response structure and check for Error 141
165
+ if (errorBody && typeof errorBody === 'object' && errorBody.error_code === 141 && errorBody.errors && typeof errorBody.errors === 'object' && errorBody.errors.sync_token) {
166
+ debug('Error 141 detected: Invalid sync_token. Triggering auto-recovery with init=true');
167
+ // Ensure req.qs exists before modifying
168
+ if (!req.qs) {
169
+ req.qs = {};
170
+ }
171
+ // Clear the invalid token parameters and reinitialize
172
+ delete req.qs.sync_token;
173
+ delete req.qs.pagination_token;
174
+ req.qs.init = true;
175
+ // Reset req.path so it gets rebuilt from Contentstack.apis.sync
176
+ // (req.path has the old query string baked in from line 109)
177
+ delete req.path;
178
+ // Mark this as a recovery attempt to prevent infinite loops
179
+ if (!req._error141Recovery) {
180
+ req._error141Recovery = true;
181
+ debug('Retrying with init=true after Error 141');
182
+ // Use delayed retry
183
+ timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE;
184
+ debug(`Error 141 recovery: waiting ${timeDelay}ms before retry`);
185
+ return setTimeout(() => {
186
+ return (0, exports.get)(req, RETRY)
187
+ .then(resolve)
188
+ .catch(reject);
189
+ }, timeDelay);
190
+ }
191
+ else {
192
+ debug('Error 141 recovery already attempted, failing to prevent infinite loop');
193
+ }
194
+ }
195
+ }
196
+ catch (parseError) {
197
+ // Body is not JSON or parsing failed, continue with normal error handling
198
+ debug('Error response parsing failed:', parseError);
199
+ }
161
200
  debug(messages_1.MESSAGES.API.REQUEST_FAILED(options));
162
201
  return reject(body);
163
202
  }
@@ -169,14 +208,28 @@ const get = (req, RETRY = 1) => {
169
208
  httpRequest.destroy();
170
209
  reject(new Error('Request timeout'));
171
210
  });
172
- // Enhanced error handling for socket hang ups and connection resets
211
+ // Enhanced error handling for network and connection errors
173
212
  httpRequest.on('error', (error) => {
174
- var _a;
213
+ var _a, _b;
175
214
  debug(messages_1.MESSAGES.API.REQUEST_ERROR(options.path, error === null || error === void 0 ? void 0 : error.message, error === null || error === void 0 ? void 0 : error.code));
176
- // Handle socket hang up and connection reset errors with retry
177
- if (((error === null || error === void 0 ? void 0 : error.code) === 'ECONNRESET' || ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes('socket hang up'))) && RETRY <= MAX_RETRY_LIMIT) {
215
+ // List of retryable network error codes
216
+ const retryableErrors = [
217
+ 'ECONNRESET',
218
+ 'ETIMEDOUT',
219
+ 'ECONNREFUSED',
220
+ 'ENOTFOUND',
221
+ 'ENETUNREACH',
222
+ 'EAI_AGAIN',
223
+ 'EPIPE',
224
+ 'EHOSTUNREACH', // Host unreachable
225
+ ];
226
+ // Check if error is retryable
227
+ const isRetryable = retryableErrors.includes(error === null || error === void 0 ? void 0 : error.code) ||
228
+ ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes('socket hang up')) ||
229
+ ((_b = error === null || error === void 0 ? void 0 : error.message) === null || _b === void 0 ? void 0 : _b.includes('ETIMEDOUT'));
230
+ if (isRetryable && RETRY <= MAX_RETRY_LIMIT) {
178
231
  timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE;
179
- debug(messages_1.MESSAGES.API.SOCKET_HANGUP_RETRY(options.path, timeDelay, RETRY, MAX_RETRY_LIMIT));
232
+ debug(`Network error ${(error === null || error === void 0 ? void 0 : error.code) || (error === null || error === void 0 ? void 0 : error.message)}: waiting ${timeDelay}ms before retry ${RETRY}/${MAX_RETRY_LIMIT}`);
180
233
  RETRY++;
181
234
  return setTimeout(() => {
182
235
  return (0, exports.get)(req, RETRY)
@@ -42,7 +42,6 @@ exports.unlock = exports.lock = exports.poke = exports.pop = exports.unshift = e
42
42
  * MIT Licensed
43
43
  */
44
44
  const fs = __importStar(require("fs"));
45
- const path = __importStar(require("path"));
46
45
  const debug_1 = __importDefault(require("debug"));
47
46
  const events_1 = require("events");
48
47
  const lodash_1 = require("lodash");
@@ -56,7 +55,6 @@ const promise_map_1 = require("../util/promise.map");
56
55
  const inet_1 = require("./inet");
57
56
  const q_1 = require("./q");
58
57
  const token_management_1 = require("./token-management");
59
- const helper_1 = require("../plugins/helper");
60
58
  const debug = (0, debug_1.default)('sync-core');
61
59
  const emitter = new events_1.EventEmitter();
62
60
  const formattedAssetType = '_assets';
@@ -126,13 +124,8 @@ exports.init = init;
126
124
  const loadCheckpoint = (checkPointConfig, paths) => {
127
125
  if (!(checkPointConfig === null || checkPointConfig === void 0 ? void 0 : checkPointConfig.enabled))
128
126
  return;
129
- // Try reading checkpoint from primary path
127
+ // Read checkpoint from configured path only
130
128
  let checkpoint = readHiddenFile(paths.checkpoint);
131
- // Fallback to filePath in config if not found
132
- if (!checkpoint) {
133
- const fallbackPath = path.join((0, helper_1.sanitizePath)(__dirname), (0, helper_1.sanitizePath)(checkPointConfig.filePath || ".checkpoint"));
134
- checkpoint = readHiddenFile(fallbackPath);
135
- }
136
129
  // Set sync token if checkpoint is found
137
130
  if (checkpoint) {
138
131
  debug(messages_1.MESSAGES.SYNC_CORE.TOKEN_FOUND, checkpoint);
@@ -361,10 +354,25 @@ const fire = (req) => {
361
354
  .catch(reject);
362
355
  }).catch((error) => {
363
356
  debug(messages_1.MESSAGES.SYNC_CORE.ERROR_FIRE, error);
357
+ // Check if this is an Error 141 (outdated token)
358
+ // Note: api.ts already handles recovery by retrying with init=true
359
+ try {
360
+ const parsedError = typeof error === 'string' ? JSON.parse(error) : error;
361
+ if (parsedError.error_code === 141) {
362
+ logger_1.logger.error(messages_1.MESSAGES.SYNC_CORE.OUTDATED_SYNC_TOKEN);
363
+ logger_1.logger.info(messages_1.MESSAGES.SYNC_CORE.SYNC_TOKEN_RENEWAL);
364
+ // Reset flag so next webhook notification can trigger a fresh sync
365
+ flag.SQ = false;
366
+ // Reset sync_token so next sync starts fresh with init=true
367
+ Contentstack.sync_token = undefined;
368
+ }
369
+ }
370
+ catch (parseError) {
371
+ // Not a JSON error or not Error 141, continue with normal handling
372
+ }
364
373
  if ((0, inet_1.netConnectivityIssues)(error)) {
365
374
  flag.SQ = false;
366
375
  }
367
- // do something
368
376
  return reject(error);
369
377
  });
370
378
  });
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  const { cloneDeep } = require('lodash');
3
- const { getConfig } = require('../index');
4
3
  const fieldType = {
5
4
  REFERENCE: 'reference',
6
5
  GLOBAL_FIELD: 'global_field',
@@ -186,6 +185,9 @@ const checkReferences = (schema, key) => {
186
185
  }
187
186
  };
188
187
  exports.buildAssetObject = (asset, locale, entry_uid, content_type_uid) => {
188
+ // Lazy-load getConfig at runtime to avoid circular dependency
189
+ // (helper.js is loaded during plugin init before index.ts finishes exporting)
190
+ const { getConfig } = require('../index');
189
191
  const { contentstack } = getConfig();
190
192
  // add locale key to inside of asset
191
193
  asset.locale = locale;
@@ -38,6 +38,9 @@ exports.MESSAGES = {
38
38
  REQUEST_TIMEOUT: (path) => `Request timeout for ${path || 'unknown'}`,
39
39
  REQUEST_ERROR: (path, message, code) => `Request error for ${path || 'unknown'}: ${message || 'Unknown error'} (${code || 'NO_CODE'})`,
40
40
  SOCKET_HANGUP_RETRY: (path, delay, attempt, max) => `Socket hang up detected. Retrying ${path || 'unknown'} with ${delay} ms delay (attempt ${attempt}/${max})`,
41
+ ERROR_141_DETECTED: 'Error 141: Invalid sync_token detected. Token is no longer valid.',
42
+ ERROR_141_RECOVERY: 'Attempting automatic recovery with init=true to get fresh token.',
43
+ ERROR_141_RETRY: 'Retrying sync operation with fresh initialization after Error 141.',
41
44
  },
42
45
  // Plugin messages (plugins.ts)
43
46
  PLUGINS: {
@@ -78,6 +81,8 @@ exports.MESSAGES = {
78
81
  ERROR_FIRE: 'Error during fire operation.',
79
82
  REFIRE_CALLED: (req) => `Re-fire operation triggered with: ${JSON.stringify(req)}`,
80
83
  CHECKPOINT_LOCKDOWN: 'Checkpoint: lockdown has been invoked',
84
+ OUTDATED_SYNC_TOKEN: 'Sync token is outdated and no longer accepted by the API. Recovering...',
85
+ SYNC_TOKEN_RENEWAL: 'Renewing sync token. This typically happens after network interruptions or long inactivity.',
81
86
  },
82
87
  // Main index messages (index.ts)
83
88
  INDEX: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@contentstack/datasync-manager",
3
3
  "author": "Contentstack LLC <support@contentstack.com>",
4
- "version": "2.3.0",
4
+ "version": "2.4.0-beta.1",
5
5
  "description": "The primary module of Contentstack DataSync. Syncs Contentstack data with your server using Contentstack Sync API",
6
6
  "main": "dist/index.js",
7
7
  "dependencies": {