@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 +59 -6
- package/dist/core/index.js +17 -9
- package/dist/plugins/helper.js +3 -1
- package/dist/util/messages.js +5 -0
- package/package.json +1 -1
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 =
|
|
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
|
|
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
|
-
//
|
|
177
|
-
|
|
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(
|
|
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)
|
package/dist/core/index.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
});
|
package/dist/plugins/helper.js
CHANGED
|
@@ -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;
|
package/dist/util/messages.js
CHANGED
|
@@ -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.
|
|
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": {
|