Skip to content

Commit ddf9e7b

Browse files
fix merge conflicts
2 parents 01b9997 + 865d3c7 commit ddf9e7b

File tree

7 files changed

+2138
-1752
lines changed

7 files changed

+2138
-1752
lines changed

.talismanrc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
fileignoreconfig:
2+
- filename: lib/contentstackClient.js
3+
checksum: f564f6eee5c17dc73abdeab4be226a3b37942893e149d907d2a4ef415c485c5e
24
- filename: test/unit/globalField-test.js
35
checksum: 25185e3400a12e10a043dc47502d8f30b7e1c4f2b6b4d3b8b55cdc19850c48bf
46
- filename: lib/stack/index.js
@@ -9,7 +11,9 @@ fileignoreconfig:
911
ignore_detectors:
1012
- filecontent
1113
- filename: package-lock.json
12-
checksum: 47d7cb6b4cd8701aa289aa2e3975162b1291d4c8a60725d21c103a1ba59df201
14+
checksum: 4a58eb4ee1f54d68387bd005fb76e83a02461441c647d94017743d3442c0f476
15+
- filename: test/unit/ContentstackClient-test.js
16+
checksum: 5d8519b5b93c715e911a62b4033614cc4fb3596eabf31c7216ecb4cc08604a73
1317
- filename: .husky/pre-commit
1418
checksum: 52a664f536cf5d1be0bea19cb6031ca6e8107b45b6314fe7d47b7fad7d800632
1519
- filename: test/sanity-check/api/user-test.js
@@ -26,10 +30,6 @@ fileignoreconfig:
2630
checksum: e8a32ffbbbdba2a15f3d327273f0a5b4eb33cf84cd346562596ab697125bbbc6
2731
- filename: test/sanity-check/api/bulkOperation-test.js
2832
checksum: f40a14c84ab9a194aaf830ca68e14afde2ef83496a07d4a6393d7e0bed15fb0e
29-
- filename: lib/contentstackClient.js
30-
checksum: b76ca091caa3a1b2658cd422a2d8ef3ac9996aea0aff3f982d56bb309a3d9fde
31-
- filename: test/unit/ContentstackClient-test.js
32-
checksum: 974a4f335aef025b657d139bb290233a69bed1976b947c3c674e97baffe4ce2f
3333
- filename: test/unit/ContentstackHTTPClient-test.js
3434
checksum: 4043efd843e24da9afd0272c55ef4b0432e3374b2ca12b913f1a6654df3f62be
3535
- filename: test/unit/contentstack-test.js

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# Changelog
22

3-
## [v1.27.5](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-16)
3+
## [v1.27.6](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-23)
44
- Fix
55
- Skip token refresh on 401 when API returns error_code 161 (environment/permission) so the actual API error is returned instead of triggering refresh and a generic "Unable to refresh token" message
66
- When token refresh fails after a 401, return the original API error (error_message, error_code) instead of the generic "Unable to refresh token" message
77

8+
## [v1.27.5](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-11)
9+
- Fix
10+
- Concurrency queue: when response errors have no `config` (e.g. after network retries exhaust in some environments, or when plugins return a new error object), the SDK now rejects with a catchable Error instead of throwing an unhandled TypeError and crashing the process
11+
- Hardened `responseHandler` to safely handle errors without `config` (e.g. plugin-replaced errors) by guarding `config.onComplete` and still running queue `shift()` so rejections remain catchable
12+
- Added optional chaining for `error.config` reads in the retry path and unit tests for missing-config scenarios
13+
814
## [v1.27.4](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.4) (2026-02-02)
915
- Fix
1016
- Removed content-type header from the release delete method

lib/core/concurrency-queue.js

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
172172
logFinalFailure(errorInfo, this.config.maxNetworkRetries)
173173
// Final error message
174174
const finalError = new Error(`Network request failed after ${this.config.maxNetworkRetries} retries: ${errorInfo.reason}`)
175-
finalError.code = error.code
175+
finalError.code = error && error.code
176176
finalError.originalError = error
177177
finalError.retryAttempts = attempt - 1
178178
return Promise.reject(finalError)
@@ -181,6 +181,16 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
181181
const delay = calculateNetworkRetryDelay(attempt)
182182
logRetryAttempt(errorInfo, attempt, delay)
183183

184+
// Guard: retry failures (e.g. from nested retries) may not have config in some
185+
// environments. Reject with a catchable error instead of throwing TypeError.
186+
if (!error || !error.config) {
187+
const finalError = new Error(`Network request failed after retries: ${errorInfo.reason}`)
188+
finalError.code = error && error.code
189+
finalError.originalError = error
190+
finalError.retryAttempts = attempt - 1
191+
return Promise.reject(finalError)
192+
}
193+
184194
// Initialize retry count if not present
185195
if (!error.config.networkRetryCount) {
186196
error.config.networkRetryCount = 0
@@ -200,9 +210,7 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
200210
safeAxiosRequest(requestConfig)
201211
.then((response) => {
202212
// On successful retry, call the original onComplete to properly clean up
203-
if (error.config.onComplete) {
204-
error.config.onComplete()
205-
}
213+
error?.config?.onComplete?.()
206214
shift() // Process next queued request
207215
resolve(response)
208216
})
@@ -214,17 +222,13 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
214222
.then(resolve)
215223
.catch((finalError) => {
216224
// On final failure, clean up the running queue
217-
if (error.config.onComplete) {
218-
error.config.onComplete()
219-
}
225+
error?.config?.onComplete?.()
220226
shift() // Process next queued request
221227
reject(finalError)
222228
})
223229
} else {
224230
// On non-retryable error, clean up the running queue
225-
if (error.config.onComplete) {
226-
error.config.onComplete()
227-
}
231+
error?.config?.onComplete?.()
228232
shift() // Process next queued request
229233
reject(retryError)
230234
}
@@ -446,9 +450,12 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
446450
}
447451
})
448452
}
449-
// Response interceptor used for
453+
// Response interceptor used for success and for error path (Promise.reject(responseHandler(err))).
454+
// When used with an error, err may lack config (e.g. plugin returns new error). Guard so we don't throw.
450455
const responseHandler = (response) => {
451-
response.config.onComplete()
456+
if (response?.config?.onComplete) {
457+
response.config.onComplete()
458+
}
452459
shift()
453460
return response
454461
}
@@ -478,13 +485,27 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
478485
}
479486

480487
const responseErrorHandler = error => {
481-
let networkError = error.config.retryCount
488+
// Guard: Axios errors normally have config; missing config can occur when a retry
489+
// fails in certain environments or when non-Axios errors propagate (e.g. timeouts).
490+
// Reject with a catchable error instead of throwing TypeError and crashing the process.
491+
if (!error || !error.config) {
492+
const fallbackError = new Error(
493+
error && typeof error.message === 'string'
494+
? error.message
495+
: 'Network request failed: error object missing request config'
496+
)
497+
fallbackError.code = error?.code
498+
fallbackError.originalError = error
499+
return Promise.reject(runPluginOnResponseForError(fallbackError))
500+
}
501+
502+
let networkError = error?.config?.retryCount ?? 0
482503
let retryErrorType = null
483504

484505
// First, check for transient network errors
485506
const networkErrorInfo = isTransientNetworkError(error)
486507
if (networkErrorInfo && this.config.retryOnNetworkFailure) {
487-
const networkRetryCount = error.config.networkRetryCount || 0
508+
const networkRetryCount = error?.config?.networkRetryCount || 0
488509
return retryNetworkError(error, networkErrorInfo, networkRetryCount + 1)
489510
}
490511

@@ -499,7 +520,7 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
499520
var response = error.response
500521
if (!response) {
501522
if (error.code === 'ECONNABORTED') {
502-
const timeoutMs = error.config.timeout || this.config.timeout || 'unknown'
523+
const timeoutMs = error?.config?.timeout || this.config.timeout || 'unknown'
503524
error.response = {
504525
...error.response,
505526
status: 408,

0 commit comments

Comments
 (0)