Skip to content

Commit 25d8ff2

Browse files
Merge pull request #100 from contentstack/development
DX | 16-02-2026 | Beta Release
2 parents 3f7478f + 4ec76ce commit 25d8ff2

5 files changed

Lines changed: 52 additions & 37 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@contentstack/datasync-manager",
33
"author": "Contentstack LLC <support@contentstack.com>",
4-
"version": "2.4.0-beta.0",
4+
"version": "2.4.0-beta.1",
55
"description": "The primary module of Contentstack DataSync. Syncs Contentstack data with your server using Contentstack Sync API",
66
"main": "dist/index.js",
77
"dependencies": {

src/api.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import { MESSAGES } from './util/messages'
6060
const debug = Debug('api')
6161
let MAX_RETRY_LIMIT
6262
let RETRY_DELAY_BASE = 200 // Default base delay in milliseconds
63-
let TIMEOUT = 30000 // Default timeout in milliseconds
63+
let TIMEOUT = 60000 // Increased from 30000 to 60000 (60 seconds) for large stack syncs
6464
let Contentstack
6565

6666
/**
@@ -186,21 +186,26 @@ export const get = (req, RETRY = 1) => {
186186
}
187187

188188
// Clear the invalid token parameters and reinitialize
189-
if (req.qs.sync_token) {
190-
delete req.qs.sync_token
191-
}
192-
if (req.qs.pagination_token) {
193-
delete req.qs.pagination_token
194-
}
189+
delete req.qs.sync_token
190+
delete req.qs.pagination_token
195191
req.qs.init = true
196-
192+
// Reset req.path so it gets rebuilt from Contentstack.apis.sync
193+
// (req.path has the old query string baked in from line 109)
194+
delete req.path
195+
197196
// Mark this as a recovery attempt to prevent infinite loops
198197
if (!req._error141Recovery) {
199198
req._error141Recovery = true
200199
debug('Retrying with init=true after Error 141')
201-
return get(req, 1) // Reset retry counter for fresh start
202-
.then(resolve)
203-
.catch(reject)
200+
// Use delayed retry
201+
timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE
202+
debug(`Error 141 recovery: waiting ${timeDelay}ms before retry`)
203+
204+
return setTimeout(() => {
205+
return get(req, RETRY)
206+
.then(resolve)
207+
.catch(reject)
208+
}, timeDelay)
204209
} else {
205210
debug('Error 141 recovery already attempted, failing to prevent infinite loop')
206211
}
@@ -223,14 +228,30 @@ export const get = (req, RETRY = 1) => {
223228
reject(new Error('Request timeout'))
224229
})
225230

226-
// Enhanced error handling for socket hang ups and connection resets
231+
// Enhanced error handling for network and connection errors
227232
httpRequest.on('error', (error: any) => {
228233
debug(MESSAGES.API.REQUEST_ERROR(options.path, error?.message, error?.code))
229234

230-
// Handle socket hang up and connection reset errors with retry
231-
if ((error?.code === 'ECONNRESET' || error?.message?.includes('socket hang up')) && RETRY <= MAX_RETRY_LIMIT) {
235+
// List of retryable network error codes
236+
const retryableErrors = [
237+
'ECONNRESET', // Connection reset by peer
238+
'ETIMEDOUT', // Connection timeout
239+
'ECONNREFUSED', // Connection refused
240+
'ENOTFOUND', // DNS lookup failed
241+
'ENETUNREACH', // Network unreachable
242+
'EAI_AGAIN', // DNS lookup timeout
243+
'EPIPE', // Broken pipe
244+
'EHOSTUNREACH', // Host unreachable
245+
]
246+
247+
// Check if error is retryable
248+
const isRetryable = retryableErrors.includes(error?.code) ||
249+
error?.message?.includes('socket hang up') ||
250+
error?.message?.includes('ETIMEDOUT')
251+
252+
if (isRetryable && RETRY <= MAX_RETRY_LIMIT) {
232253
timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE
233-
debug(MESSAGES.API.SOCKET_HANGUP_RETRY(options.path, timeDelay, RETRY, MAX_RETRY_LIMIT))
254+
debug(`Network error ${error?.code || error?.message}: waiting ${timeDelay}ms before retry ${RETRY}/${MAX_RETRY_LIMIT}`)
234255
RETRY++
235256

236257
return setTimeout(() => {

src/core/index.ts

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
* MIT Licensed
55
*/
66
import * as fs from 'fs'
7-
import * as path from 'path'
87
import Debug from 'debug'
98
import { EventEmitter } from 'events'
109
import { cloneDeep, remove } from 'lodash'
@@ -18,7 +17,6 @@ import { map } from '../util/promise.map'
1817
import { netConnectivityIssues } from './inet'
1918
import { Q as Queue } from './q'
2019
import { getToken, saveCheckpoint } from './token-management'
21-
import { sanitizePath } from '../plugins/helper'
2220

2321
interface IQueryString {
2422
init?: true,
@@ -123,18 +121,9 @@ export const init = (contentStore, assetStore) => {
123121
const loadCheckpoint = (checkPointConfig: ICheckpoint, paths: any): void => {
124122
if (!checkPointConfig?.enabled) return;
125123

126-
// Try reading checkpoint from primary path
124+
// Read checkpoint from configured path only
127125
let checkpoint = readHiddenFile(paths.checkpoint);
128126

129-
// Fallback to filePath in config if not found
130-
if (!checkpoint) {
131-
const fallbackPath = path.join(
132-
sanitizePath(__dirname),
133-
sanitizePath(checkPointConfig.filePath || ".checkpoint")
134-
);
135-
checkpoint = readHiddenFile(fallbackPath);
136-
}
137-
138127
// Set sync token if checkpoint is found
139128
if (checkpoint) {
140129
debug(MESSAGES.SYNC_CORE.TOKEN_FOUND, checkpoint);
@@ -376,25 +365,26 @@ const fire = (req: IApiRequest) => {
376365
.catch(reject)
377366
}).catch((error) => {
378367
debug(MESSAGES.SYNC_CORE.ERROR_FIRE, error);
379-
380-
// Check if this is an Error 141 (invalid token) - enhanced handling
368+
369+
// Check if this is an Error 141 (outdated token)
370+
// Note: api.ts already handles recovery by retrying with init=true
381371
try {
382372
const parsedError = typeof error === 'string' ? JSON.parse(error) : error
383373
if (parsedError.error_code === 141) {
384-
logger.error('Error 141: Invalid sync_token detected. Token has been reset.')
385-
logger.info('System will automatically re-initialize with fresh token on next sync.')
386-
// The error has already been handled in api.ts with init=true
387-
// Just ensure we don't keep retrying with the bad token
374+
logger.error(MESSAGES.SYNC_CORE.OUTDATED_SYNC_TOKEN)
375+
logger.info(MESSAGES.SYNC_CORE.SYNC_TOKEN_RENEWAL)
376+
// Reset flag so next webhook notification can trigger a fresh sync
388377
flag.SQ = false
378+
// Reset sync_token so next sync starts fresh with init=true
379+
Contentstack.sync_token = undefined
389380
}
390381
} catch (parseError) {
391382
// Not a JSON error or not Error 141, continue with normal handling
392383
}
393-
384+
394385
if (netConnectivityIssues(error)) {
395386
flag.SQ = false
396387
}
397-
// do something
398388

399389
return reject(error)
400390
})

src/plugins/helper.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const { cloneDeep } = require('lodash')
2-
const { getConfig } = require('../index')
32

43

54
const fieldType = {
@@ -184,6 +183,9 @@ const checkReferences = (schema, key) => {
184183
}
185184

186185
exports.buildAssetObject = (asset, locale, entry_uid, content_type_uid) => {
186+
// Lazy-load getConfig at runtime to avoid circular dependency
187+
// (helper.js is loaded during plugin init before index.ts finishes exporting)
188+
const { getConfig } = require('../index')
187189
const { contentstack } = getConfig()
188190
// add locale key to inside of asset
189191
asset.locale = locale

src/util/messages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export const MESSAGES = {
8888
ERROR_FIRE: 'Error during fire operation.',
8989
REFIRE_CALLED: (req: any) => `Re-fire operation triggered with: ${JSON.stringify(req)}`,
9090
CHECKPOINT_LOCKDOWN: 'Checkpoint: lockdown has been invoked',
91+
OUTDATED_SYNC_TOKEN: 'Sync token is outdated and no longer accepted by the API. Recovering...',
92+
SYNC_TOKEN_RENEWAL: 'Renewing sync token. This typically happens after network interruptions or long inactivity.',
9193
},
9294

9395
// Main index messages (index.ts)

0 commit comments

Comments
 (0)