Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [v1.30.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.30.0) (2026-04-13)

- Enh
- Per-module CMA headers: `addHeader`, `addHeaderDict`, and `removeHeader` on stack resources and query builders (stack shares one map; other modules copy-on-write). Backward compatible if unused.
- Test
- Unit + sanity coverage for header APIs; minor sanity robustness (taxonomy 412, nested GF plan skip, entry update timeout).

## [v1.29.2](https://github.com/contentstack/contentstack-management-javascript/tree/v1.29.2) (2026-04-06)

- Fix
Expand Down
124 changes: 124 additions & 0 deletions lib/core/moduleHeaderSupport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import cloneDeep from 'lodash/cloneDeep'

const moduleHeadersOwn = Symbol.for('contentstack.management.moduleHeadersOwn')

/**
* Attaches `addHeader`, `addHeaderDict`, and `removeHeader` to a stack resource instance.
* Per-module headers merge into request stack headers and override parent keys for that
* instance only (copy-on-write). Pass `ownsHeadersInline: true` on Stack so the canonical
* stack header map stays shared across child modules until a child calls addHeader.
*
* @param {Object} instance - Resource instance that uses `stackHeaders` for CMA headers.
* @param {Object} [options]
* @param {boolean} [options.ownsHeadersInline=false] - Mutate `stackHeaders` in place (stack root).
* @returns {Object} The same instance for chaining.
*/
export function bindModuleHeaders (instance, { ownsHeadersInline = false } = {}) {
instance.addHeader = function (key, value) {
if (key === undefined || key === null) {
return this
}
prepareStackHeaders(this, ownsHeadersInline)
this.stackHeaders[key] = value
return this
}

instance.addHeaderDict = function (headerDict) {
if (!headerDict || typeof headerDict !== 'object') {
return this
}
prepareStackHeaders(this, ownsHeadersInline)
Object.assign(this.stackHeaders, headerDict)
return this
}

instance.removeHeader = function (key) {
if (key === undefined || key === null) {
return this
}
if (ownsHeadersInline) {
if (this.stackHeaders && Object.prototype.hasOwnProperty.call(this.stackHeaders, key)) {
delete this.stackHeaders[key]
}
return this
}
if (!this[moduleHeadersOwn]) {
if (!this.stackHeaders || !Object.prototype.hasOwnProperty.call(this.stackHeaders, key)) {
return this
}
prepareStackHeaders(this, ownsHeadersInline)
delete this.stackHeaders[key]
return this
}
if (this.stackHeaders && Object.prototype.hasOwnProperty.call(this.stackHeaders, key)) {
delete this.stackHeaders[key]
}
return this
}

return instance
}

function prepareStackHeaders (instance, ownsHeadersInline) {
if (ownsHeadersInline) {
if (!instance.stackHeaders) {
instance.stackHeaders = {}
}
return
}
if (!instance[moduleHeadersOwn]) {
instance.stackHeaders = cloneDeep(instance.stackHeaders || {})
instance[moduleHeadersOwn] = true
} else if (!instance.stackHeaders) {
instance.stackHeaders = {}
}
}

/**
* Attaches `addHeader`, `addHeaderDict`, and `removeHeader` for a mutable header map (e.g. query builder).
*
* @param {Object} target - Object to receive methods.
* @param {function(): Object} getHeaderMap - Returns the header key/value object used for requests.
* @returns {Object} The same target for chaining.
*/
export function bindHeaderTarget (target, getHeaderMap) {
target.addHeader = function (key, value) {
if (key === undefined || key === null) {
return this
}
const headers = getHeaderMap()
if (!headers) {
return this
}
headers[key] = value
return this
}

target.addHeaderDict = function (headerDict) {
if (!headerDict || typeof headerDict !== 'object') {
return this
}
const headers = getHeaderMap()
if (!headers) {
return this
}
Object.assign(headers, headerDict)
return this
}

target.removeHeader = function (key) {
if (key === undefined || key === null) {
return this
}
const headers = getHeaderMap()
if (!headers) {
return this
}
if (Object.prototype.hasOwnProperty.call(headers, key)) {
delete headers[key]
}
return this
}

return target
}
14 changes: 8 additions & 6 deletions lib/query/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import error from '../core/contentstackError'
import cloneDeep from 'lodash/cloneDeep'
import ContentstackCollection from '../contentstackCollection'
import { bindHeaderTarget } from '../core/moduleHeaderSupport.js'

export default function Query (http, urlPath, param, stackHeaders = null, wrapperCollection) {
const headers = {}
if (stackHeaders) {
headers.headers = stackHeaders
}
const headerMap = stackHeaders && typeof stackHeaders === 'object' ? stackHeaders : {}
headers.headers = headerMap
var contentTypeUid = null
if (param) {
if (param.content_type_uid) {
Expand Down Expand Up @@ -43,7 +43,7 @@ export default function Query (http, urlPath, param, stackHeaders = null, wrappe
if (contentTypeUid) {
response.data.content_type_uid = contentTypeUid
}
return new ContentstackCollection(response, http, stackHeaders, wrapperCollection)
return new ContentstackCollection(response, http, headerMap, wrapperCollection)
} else {
throw error(response)
}
Expand Down Expand Up @@ -114,7 +114,7 @@ export default function Query (http, urlPath, param, stackHeaders = null, wrappe
if (contentTypeUid) {
response.data.content_type_uid = contentTypeUid
}
return new ContentstackCollection(response, http, stackHeaders, wrapperCollection)
return new ContentstackCollection(response, http, headerMap, wrapperCollection)
} else {
throw error(response)
}
Expand All @@ -123,9 +123,11 @@ export default function Query (http, urlPath, param, stackHeaders = null, wrappe
}
}

return {
const api = {
count: count,
find: find,
findOne: findOne
}
bindHeaderTarget(api, () => headers.headers)
return api
}
2 changes: 2 additions & 0 deletions lib/stack/asset/folders/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
fetch,
create
} from '../../../entity'
import { bindModuleHeaders } from '../../../core/moduleHeaderSupport.js'

/**
* Folders refer to Asset Folders.
Expand Down Expand Up @@ -84,6 +85,7 @@ export function Folder (http, data = {}) {
*/
this.create = create({ http: http })
}
bindModuleHeaders(this)
}

export function FolderCollection (http, data) {
Expand Down
2 changes: 2 additions & 0 deletions lib/stack/asset/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
unpublish } from '../../entity'
import { Folder } from './folders'
import error from '../../core/contentstackError'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'
import { ERROR_MESSAGES } from '../../core/errorMessages'
import FormData from 'form-data'
import { createReadStream } from 'fs'
Expand Down Expand Up @@ -297,6 +298,7 @@ export function Asset (http, data = {}) {
throw error(err)
}
}
bindModuleHeaders(this)
return this
}

Expand Down
2 changes: 2 additions & 0 deletions lib/stack/auditlog/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import cloneDeep from 'lodash/cloneDeep'
import error from '../../core/contentstackError'
import { fetchAll } from '../../entity'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

/**
*
Expand Down Expand Up @@ -63,6 +64,7 @@ export function AuditLog (http, data = {}) {
*/
this.fetchAll = fetchAll(http, LogCollection)
}
bindModuleHeaders(this)
return this
}

Expand Down
2 changes: 2 additions & 0 deletions lib/stack/branch/compare.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { get } from '../../entity'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

/**
*
Expand Down Expand Up @@ -74,5 +75,6 @@ export function Compare (http, data = {}) {
return get(http, url, params, data)
}

bindModuleHeaders(this)
return this
}
2 changes: 2 additions & 0 deletions lib/stack/branch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { create, query, fetch, deleteEntity } from '../../entity'
import { Compare } from './compare'
import { MergeQueue } from './mergeQueue'
import error from '../../core/contentstackError'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

/**
*
Expand Down Expand Up @@ -205,6 +206,7 @@ export function Branch (http, data = {}) {
return new MergeQueue(http, mergeData)
}
}
bindModuleHeaders(this)
return this
}

Expand Down
2 changes: 2 additions & 0 deletions lib/stack/branch/mergeQueue.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { get } from '../../entity'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

export function MergeQueue (http, data = {}) {
this.stackHeaders = data.stackHeaders
Expand Down Expand Up @@ -43,5 +44,6 @@ export function MergeQueue (http, data = {}) {
}
}

bindModuleHeaders(this)
return this
}
2 changes: 2 additions & 0 deletions lib/stack/branchAlias/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cloneDeep from 'lodash/cloneDeep'
import error from '../../core/contentstackError'
import { deleteEntity, fetchAll, parseData } from '../../entity'
import { Branch, BranchCollection } from '../branch'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

/**
*
Expand Down Expand Up @@ -110,5 +111,6 @@ export function BranchAlias (http, data = {}) {
*/
this.fetchAll = fetchAll(http, BranchCollection)
}
bindModuleHeaders(this)
return this
}
2 changes: 2 additions & 0 deletions lib/stack/bulkOperation/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cloneDeep from 'lodash/cloneDeep'
import { publishUnpublish } from '../../entity'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

/**
* Bulk operations such as Publish, Unpublish, and Delete on multiple entries or assets.
Expand Down Expand Up @@ -454,4 +455,5 @@ export function BulkOperation (http, data = {}) {
}
return publishUnpublish(http, '/bulk/workflow', updateBody, headers)
}
bindModuleHeaders(this)
}
2 changes: 2 additions & 0 deletions lib/stack/contentType/entry/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { create,
import FormData from 'form-data'
import { createReadStream } from 'fs'
import error from '../../../core/contentstackError'
import { bindModuleHeaders } from '../../../core/moduleHeaderSupport.js'
import { Variants } from './variants/index'

/**
Expand Down Expand Up @@ -486,6 +487,7 @@ export function Entry (http, data) {
throw error(err)
}
}
bindModuleHeaders(this)
return this
}

Expand Down
2 changes: 2 additions & 0 deletions lib/stack/contentType/entry/variants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
}
from '../../../../entity'
import error from '../../../../core/contentstackError'
import { bindModuleHeaders } from '../../../../core/moduleHeaderSupport.js'
/**
* Variants allow you to create variant versions of entries within a content type. Read more about <a href='https://www.contentstack.com/docs/developers/create-content-types/manage-variants'>Variants</a>.
* @namespace Variants
Expand Down Expand Up @@ -139,6 +140,7 @@ export function Variants (http, data) {
*/
this.query = query({ http: http, wrapperCollection: VariantsCollection })
}
bindModuleHeaders(this)
}
export function VariantsCollection (http, data) {
const obj = cloneDeep(data.entries) || []
Expand Down
2 changes: 2 additions & 0 deletions lib/stack/contentType/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { Entry } from './entry/index'
import error from '../../core/contentstackError'
import { ERROR_MESSAGES } from '../../core/errorMessages'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

import FormData from 'form-data'
import { createReadStream } from 'fs'
Expand Down Expand Up @@ -290,6 +291,7 @@ export function ContentType (http, data = {}) {
}
}
}
bindModuleHeaders(this)
return this
}

Expand Down
2 changes: 2 additions & 0 deletions lib/stack/deliveryToken/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import cloneDeep from 'lodash/cloneDeep'
import { create, update, deleteEntity, fetch, query } from '../../entity'
import { PreviewToken } from './previewToken'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

/**
* Delivery tokens provide read-only access to the associated environments. Read more about <a href='https://www.contentstack.com/docs/developers/create-tokens/about-delivery-tokens'>DeliveryToken</a>.
Expand Down Expand Up @@ -117,6 +118,7 @@ export function DeliveryToken (http, data = {}) {
*/
this.query = query({ http: http, wrapperCollection: DeliveryTokenCollection })
}
bindModuleHeaders(this)
}

export function DeliveryTokenCollection (http, data) {
Expand Down
2 changes: 2 additions & 0 deletions lib/stack/deliveryToken/previewToken/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cloneDeep from 'lodash/cloneDeep'
import { create, deleteEntity } from '../../../entity'
import { bindModuleHeaders } from '../../../core/moduleHeaderSupport.js'

/**
* Preview tokens provide read-only access to the associated environments. Read more about <a href='https://www.contentstack.com/docs/developers/create-tokens/about-preview-tokens'>PreviewToken</a>.
Expand Down Expand Up @@ -39,6 +40,7 @@ export function PreviewToken (http, data = {}) {
*/
this.create = create({ http: http })
}
bindModuleHeaders(this)
}

export function PreviewTokenCollection (http, data) {
Expand Down
2 changes: 2 additions & 0 deletions lib/stack/environment/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cloneDeep from 'lodash/cloneDeep'
import { create, update, deleteEntity, fetch, query } from '../../entity'
import { bindModuleHeaders } from '../../core/moduleHeaderSupport.js'

/**
* A publishing environment corresponds to one or more deployment servers or a content delivery destination where the entries need to be published. Read more about <a href='https://www.contentstack.com/docs/developers/set-up-environments'>Environment</a>.
Expand Down Expand Up @@ -105,6 +106,7 @@ export function Environment (http, data = {}) {
*/
this.query = query({ http: http, wrapperCollection: EnvironmentCollection })
}
bindModuleHeaders(this)
}

export function EnvironmentCollection (http, data) {
Expand Down
Loading
Loading