diff --git a/.talismanrc b/.talismanrc
index e784c309..61cb65f1 100644
--- a/.talismanrc
+++ b/.talismanrc
@@ -17,3 +17,11 @@ fileignoreconfig:
- filename: test/sanity-check/api/stack-test.js
checksum: 198d5cf7ead33b079249dc3ecdee61a9c57453e93f1073ed0341400983e5aa53
version: "1.0"
+fileignoreconfig:
+- filename: test/sanity-check/api/previewToken-test.js
+ checksum: 9a42e079b7c71f76932896a0d2390d86ac626678ab20d36821dcf962820a886c
+- filename: lib/stack/deliveryToken/index.js
+ checksum: 51ae00f07f4cc75c1cd832b311c2e2482f04a8467a0139da6013ceb88fbdda2f
+- filename: lib/stack/deliveryToken/previewToken/index.js
+ checksum: b506f33bffdd20dfc701f964370707f5d7b28a2c05c70665f0edb7b3c53c165b
+version: "1.0"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b638938a..13e1c417 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,12 @@
# Changelog
+## [v1.21.5](https://github.com/contentstack/contentstack-management-javascript/tree/v1.21.5) (2025-06-09)
+ - Enhancement
+ - Preview token support added
+
+## [v1.21.4](https://github.com/contentstack/contentstack-management-javascript/tree/v1.21.4) (2025-06-02)
+ - Enhancement
+ - Retry Logic modification on x-ratelimit-remaining Header
+
## [v1.21.3](https://github.com/contentstack/contentstack-management-javascript/tree/v1.21.3) (2025-05-26)
- Enhancement
- Update addSettings Method to Support Generic Stack Settings Update
diff --git a/lib/core/concurrency-queue.js b/lib/core/concurrency-queue.js
index 868307e3..247184bd 100644
--- a/lib/core/concurrency-queue.js
+++ b/lib/core/concurrency-queue.js
@@ -229,6 +229,7 @@ export function ConcurrencyQueue ({ axios, config }) {
if (!this.config.retryOnError || networkError > this.config.retryLimit) {
return Promise.reject(responseHandler(error))
}
+ // Check rate limit remaining header before retrying
// Error handling
const wait = this.config.retryDelay
@@ -244,19 +245,26 @@ export function ConcurrencyQueue ({ axios, config }) {
} else {
return Promise.reject(responseHandler(error))
}
- } else if ((response.status === 401 && this.config.refreshToken)) {
- retryErrorType = `Error with status: ${response.status}`
- networkError++
-
- if (networkError > this.config.retryLimit) {
+ } else {
+ const rateLimitRemaining = response.headers['x-ratelimit-remaining']
+ if (rateLimitRemaining !== undefined && parseInt(rateLimitRemaining) <= 0) {
return Promise.reject(responseHandler(error))
}
- this.running.shift()
- // Cool down the running requests
- delay(wait, response.status === 401)
- error.config.retryCount = networkError
- // deepcode ignore Ssrf: URL is dynamic
- return axios(updateRequestConfig(error, retryErrorType, wait))
+
+ if ((response.status === 401 && this.config.refreshToken)) {
+ retryErrorType = `Error with status: ${response.status}`
+ networkError++
+
+ if (networkError > this.config.retryLimit) {
+ return Promise.reject(responseHandler(error))
+ }
+ this.running.shift()
+ // Cool down the running requests
+ delay(wait, response.status === 401)
+ error.config.retryCount = networkError
+ // deepcode ignore Ssrf: URL is dynamic
+ return axios(updateRequestConfig(error, retryErrorType, wait))
+ }
}
if (this.config.retryCondition && this.config.retryCondition(error)) {
retryErrorType = error.response ? `Error with status: ${response.status}` : `Error Code:${error.code}`
diff --git a/lib/entity.js b/lib/entity.js
index 266c10e2..8088f720 100644
--- a/lib/entity.js
+++ b/lib/entity.js
@@ -85,21 +85,20 @@ export const upload = async ({ http, urlPath, stackHeaders, formData, params, me
}
}
-export const create = ({ http, params = {}, createWithPreviewToken = false }) => {
+export const create = ({ http, params }) => {
return async function (data, param) {
this.stackHeaders = {
...this.stackHeaders
}
- const queryParams = {
- ...(createWithPreviewToken ? { create_with_preview_token: true } : {}),
- ...cloneDeep(param) // user param can override default
- }
+
const headers = {
headers: {
...cloneDeep(params),
...cloneDeep(this.stackHeaders)
},
- params: queryParams
+ params: {
+ ...cloneDeep(param)
+ }
} || {}
try {
diff --git a/lib/stack/deliveryToken/index.js b/lib/stack/deliveryToken/index.js
index 763423c9..12f8ac10 100644
--- a/lib/stack/deliveryToken/index.js
+++ b/lib/stack/deliveryToken/index.js
@@ -1,5 +1,6 @@
import cloneDeep from 'lodash/cloneDeep'
import { create, update, deleteEntity, fetch, query } from '../../entity'
+import { PreviewToken } from './previewToken'
/**
* Delivery tokens provide read-only access to the associated environments. Read more about DeliveryToken.
@@ -59,6 +60,22 @@ export function DeliveryToken (http, data = {}) {
*
*/
this.fetch = fetch(http, 'token')
+
+ /**
+ * @description The Create a PreviewToken call creates a new previewToken in a particular stack of your Contentstack account.
+ * @memberof DeliveryToken
+ * @func previewToken
+ * @returns {PreviewToken} Instance of PreviewToken.
+ * @example
+ * import * as contentstack from '@contentstack/management'
+ * const client = contentstack.client()
+ * const deliveryToken = client.stack({ api_key: 'api_key'}).deliveryToken('delivery_token_uid')
+ * const previewToken = deliveryToken.previewToken()
+ * console.log(previewToken)
+ */
+ this.previewToken = () => {
+ return new PreviewToken(http, { stackHeaders: this.stackHeaders, token: { uid: this.uid } })
+ }
} else {
/**
* @description The Create a DeliveryToken call creates a new deliveryToken in a particular stack of your Contentstack account.
@@ -84,7 +101,7 @@ export function DeliveryToken (http, data = {}) {
* client.stack().deliveryToken().create({ token })
* .then((deliveryToken) => console.log(deliveryToken))
*/
- this.create = create({ http: http, createWithPreviewToken: true })
+ this.create = create({ http: http })
/**
* @description The ‘Get all deliveryToken’ request returns comprehensive information about all deliveryToken created in a stack.
diff --git a/lib/stack/deliveryToken/previewToken/index.js b/lib/stack/deliveryToken/previewToken/index.js
new file mode 100644
index 00000000..f438a09e
--- /dev/null
+++ b/lib/stack/deliveryToken/previewToken/index.js
@@ -0,0 +1,50 @@
+import cloneDeep from 'lodash/cloneDeep'
+import { create, deleteEntity } from '../../../entity'
+
+/**
+ * Preview tokens provide read-only access to the associated environments. Read more about PreviewToken.
+ * @namespace PreviewToken
+ */
+export function PreviewToken (http, data = {}) {
+ this.stackHeaders = data.stackHeaders
+ if (data.token) {
+ Object.assign(this, cloneDeep(data.token))
+ this.urlPath = `/stacks/delivery_tokens/${this.uid}/preview_token`
+
+ /**
+ * @description The Delete PreviewToken call is used to delete an existing PreviewToken permanently from your Stack.
+ * @memberof PreviewToken
+ * @func delete
+ * @returns {Object} Response Object.
+ * @example
+ * import * as contentstack from '@contentstack/management'
+ * const client = contentstack.client()
+ *
+ * client.stack({ api_key: 'api_key'}).deliveryToken('delivery_token_uid').previewToken().delete()
+ * .then((response) => console.log(response.notice))
+ */
+ this.delete = deleteEntity(http)
+
+ /**
+ * @description The Create a PreviewToken call creates a new previewToken in a particular stack of your Contentstack account.
+ * @memberof PreviewToken
+ * @func create
+ * @returns {Promise} Promise for PreviewToken instance
+ *
+ * @example
+ * import * as contentstack from '@contentstack/management'
+ * const client = contentstack.client()
+ * client.stack().deliveryToken('delivery_token_uid').previewToken().create()
+ * .then((previewToken) => console.log(previewToken))
+ */
+ this.create = create({ http: http })
+ }
+}
+
+export function PreviewTokenCollection (http, data) {
+ const obj = cloneDeep(data.tokens) || []
+ const previewTokenCollection = obj.map((userdata) => {
+ return new PreviewToken(http, { token: userdata, stackHeaders: data.stackHeaders })
+ })
+ return previewTokenCollection
+}
diff --git a/package-lock.json b/package-lock.json
index 32aa93f5..15c34fe4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@contentstack/management",
- "version": "1.21.3",
+ "version": "1.21.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@contentstack/management",
- "version": "1.21.3",
+ "version": "1.21.5",
"license": "MIT",
"dependencies": {
"assert": "^2.1.0",
@@ -83,8 +83,6 @@
},
"node_modules/@babel/cli": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.27.0.tgz",
- "integrity": "sha512-bZfxn8DRxwiVzDO5CEeV+7IqXeCkzI4yYnrQbpwjT76CUyossQc6RYE7n+xfm0/2k40lPaCpW0FhxYs7EBAetw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -134,8 +132,6 @@
},
"node_modules/@babel/core": {
"version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
- "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -165,8 +161,6 @@
},
"node_modules/@babel/eslint-parser": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.27.0.tgz",
- "integrity": "sha512-dtnzmSjXfgL/HDgMcmsLSzyGbEosi4DrGWoCNfuI+W4IkVJw6izpTe7LtOdwAXnkDqw5yweboYCTkM2rQizCng==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -184,8 +178,6 @@
},
"node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
- "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -194,8 +186,6 @@
},
"node_modules/@babel/generator": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
- "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -428,8 +418,6 @@
},
"node_modules/@babel/helpers": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
- "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -442,8 +430,6 @@
},
"node_modules/@babel/parser": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
- "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1370,8 +1356,6 @@
},
"node_modules/@babel/plugin-transform-runtime": {
"version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz",
- "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1634,8 +1618,6 @@
},
"node_modules/@babel/runtime": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
- "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1647,8 +1629,6 @@
},
"node_modules/@babel/template": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
- "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1662,8 +1642,6 @@
},
"node_modules/@babel/traverse": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
- "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1681,8 +1659,6 @@
},
"node_modules/@babel/types": {
"version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
- "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2637,8 +2613,6 @@
},
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
- "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
- "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2647,8 +2621,6 @@
},
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": {
"version": "5.1.1",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
- "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -2661,8 +2633,6 @@
},
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": {
"version": "4.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -3087,8 +3057,6 @@
},
"node_modules/@types/mocha": {
"version": "8.2.3",
- "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz",
- "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==",
"dev": true,
"license": "MIT"
},
@@ -3667,8 +3635,6 @@
},
"node_modules/assert": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
- "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
@@ -3713,8 +3679,6 @@
},
"node_modules/axios": {
"version": "1.9.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
- "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -3981,8 +3945,6 @@
},
"node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.11.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz",
- "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4173,8 +4135,6 @@
},
"node_modules/base64-js": {
"version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
@@ -4311,8 +4271,6 @@
},
"node_modules/buffer": {
"version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
@@ -5045,8 +5003,6 @@
},
"node_modules/dotenv": {
"version": "16.5.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
- "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -6577,8 +6533,6 @@
},
"node_modules/husky": {
"version": "9.1.7",
- "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
- "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"license": "MIT",
"bin": {
"husky": "bin.js"
@@ -6603,8 +6557,6 @@
},
"node_modules/ieee754": {
"version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
@@ -7040,8 +6992,6 @@
},
"node_modules/is-nan": {
"version": "1.3.2",
- "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
- "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.0",
@@ -10724,8 +10674,6 @@
},
"node_modules/readable-stream": {
"version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
@@ -11616,8 +11564,6 @@
},
"node_modules/stream-browserify": {
"version": "3.0.0",
- "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
- "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.4",
@@ -11626,8 +11572,6 @@
},
"node_modules/string_decoder": {
"version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
@@ -12486,8 +12430,6 @@
},
"node_modules/util": {
"version": "0.12.5",
- "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
- "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
@@ -12499,8 +12441,6 @@
},
"node_modules/util-deprecate": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/utils-merge": {
diff --git a/package.json b/package.json
index d2e6a11a..2d302a11 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@contentstack/management",
- "version": "1.21.3",
+ "version": "1.21.5",
"description": "The Content Management API is used to manage the content of your Contentstack account",
"main": "./dist/node/contentstack-management.js",
"browser": "./dist/web/contentstack-management.js",
diff --git a/test/sanity-check/api/previewToken-test.js b/test/sanity-check/api/previewToken-test.js
new file mode 100644
index 00000000..a6a31047
--- /dev/null
+++ b/test/sanity-check/api/previewToken-test.js
@@ -0,0 +1,91 @@
+import { expect } from 'chai'
+import { describe, it, setup } from 'mocha'
+import { jsonReader } from '../utility/fileOperations/readwrite'
+import { createDeliveryToken3 } from '../mock/deliveryToken.js'
+import { contentstackClient } from '../utility/ContentstackClient.js'
+import dotenv from 'dotenv'
+
+dotenv.config()
+let client = {}
+
+let tokenUID = ''
+describe('Preview Token api Test', () => {
+ setup(() => {
+ const user = jsonReader('loggedinuser.json')
+ client = contentstackClient(user.authtoken)
+ })
+
+ it('should add a Delivery Token for development', (done) => {
+ makeDeliveryToken()
+ .create(createDeliveryToken3)
+ .then((token) => {
+ tokenUID = token.uid
+ expect(token.name).to.be.equal(createDeliveryToken3.token.name)
+ expect(token.description).to.be.equal(
+ createDeliveryToken3.token.description
+ )
+ expect(token.scope[0].environments[0].name).to.be.equal(
+ createDeliveryToken3.token.scope[0].environments[0]
+ )
+ expect(token.scope[0].module).to.be.equal(
+ createDeliveryToken3.token.scope[0].module
+ )
+ expect(token.uid).to.be.not.equal(null)
+ expect(token.preview_token).to.be.not.equal(null)
+ done()
+ })
+ .catch(done)
+ })
+
+ it('should add a Preview Token', (done) => {
+ makePreviewToken(tokenUID)
+ .create()
+ .then((token) => {
+ expect(token.name).to.be.equal(createDeliveryToken3.token.name)
+ expect(token.description).to.be.equal(
+ createDeliveryToken3.token.description
+ )
+ expect(token.scope[0].environments[0].name).to.be.equal(
+ createDeliveryToken3.token.scope[0].environments[0]
+ )
+ expect(token.scope[0].module).to.be.equal(
+ createDeliveryToken3.token.scope[0].module
+ )
+ expect(token.uid).to.be.not.equal(null)
+ expect(token.preview_token).to.be.not.equal(null)
+ done()
+ })
+ .catch(done)
+ })
+
+ it('should delete a Preview Token from uid', (done) => {
+ makePreviewToken(tokenUID)
+ .delete()
+ .then((data) => {
+ expect(data.notice).to.be.equal('Preview token deleted successfully.')
+ done()
+ })
+ .catch(done)
+ })
+
+ it('should delete a Delivery Token from uid', (done) => {
+ makeDeliveryToken(tokenUID)
+ .delete()
+ .then((data) => {
+ expect(data.notice).to.be.equal('Delivery Token deleted successfully.')
+ done()
+ })
+ .catch(done)
+ })
+})
+
+function makePreviewToken (uid = null) {
+ return client
+ .stack({ api_key: process.env.API_KEY })
+ .deliveryToken(uid)
+ .previewToken()
+}
+
+function makeDeliveryToken (uid = null) {
+ return client.stack({ api_key: process.env.API_KEY }).deliveryToken(uid)
+}
diff --git a/test/sanity-check/mock/deliveryToken.js b/test/sanity-check/mock/deliveryToken.js
index aaf386f6..29ebc770 100644
--- a/test/sanity-check/mock/deliveryToken.js
+++ b/test/sanity-check/mock/deliveryToken.js
@@ -70,5 +70,31 @@ const createDeliveryToken2 = {
]
}
}
+const createDeliveryToken3 = {
+ token: {
+ name: 'preview token test',
+ description: 'This is a demo token.',
+ scope: [
+ {
+ module: 'environment',
+ environments: [
+ 'development'
+ ],
+ acl: {
+ read: true
+ }
+ },
+ {
+ module: 'branch',
+ branches: [
+ 'main'
+ ],
+ acl: {
+ read: true
+ }
+ }
+ ]
+ }
+}
-export { createDeliveryToken, createDeliveryToken2 }
+export { createDeliveryToken, createDeliveryToken2, createDeliveryToken3 }
diff --git a/test/unit/concurrency-Queue-test.js b/test/unit/concurrency-Queue-test.js
index de3e2197..f90e85c2 100644
--- a/test/unit/concurrency-Queue-test.js
+++ b/test/unit/concurrency-Queue-test.js
@@ -506,6 +506,76 @@ describe('Concurrency queue test', () => {
})
.catch(done)
})
+
+ it('should not retry when rate limit remaining is 0', done => {
+ const mock = new MockAdapter(api)
+ mock.onGet('/ratelimit').reply(429, { errorCode: 429 }, {
+ 'x-ratelimit-remaining': '0'
+ })
+
+ reconfigureQueue({
+ retryOnError: true,
+ retryLimit: 3,
+ retryDelay: 300
+ })
+
+ api.get('/ratelimit')
+ .catch(error => {
+ expect(error.response.status).to.equal(429)
+ expect(error.config.retryCount).to.equal(0) // Should not have retried
+ expect(logHandlerStub.callCount).to.equal(0) // No retry logs
+ mock.restore()
+ done()
+ })
+ })
+
+ it('should retry when rate limit remaining is greater than 0', done => {
+ const mock = new MockAdapter(api)
+ mock.onGet('/ratelimit').reply(429, { errorCode: 429 }, {
+ 'x-ratelimit-remaining': '5'
+ })
+ mock.onGet('/ratelimit').reply(200, { success: true })
+
+ reconfigureQueue({
+ retryOnError: true,
+ retryLimit: 3,
+ retryDelay: 300
+ })
+
+ api.get('/ratelimit')
+ .then(response => {
+ /* eslint-disable no-unused-expressions */
+ expect(response.status).to.equal(200)
+ expect(response.data.success).to.be.true
+ /* eslint-enable no-unused-expressions */
+ mock.restore()
+ done()
+ })
+ .catch(done)
+ })
+
+ it('should retry when rate limit remaining header is not present', done => {
+ const mock = new MockAdapter(api)
+ mock.onGet('/ratelimit').reply(429, { errorCode: 429 })
+ mock.onGet('/ratelimit').reply(200, { success: true })
+
+ reconfigureQueue({
+ retryOnError: true,
+ retryLimit: 3,
+ retryDelay: 300
+ })
+
+ api.get('/ratelimit')
+ .then(response => {
+ /* eslint-disable no-unused-expressions */
+ expect(response.status).to.equal(200)
+ expect(response.data.success).to.be.true
+ /* eslint-enable no-unused-expressions */
+ mock.restore()
+ done()
+ })
+ .catch(done)
+ })
})
function makeConcurrencyQueue (config) {
diff --git a/types/stack/deliveryToken/previewToken.ts b/types/stack/deliveryToken/previewToken.ts
new file mode 100644
index 00000000..1f5e6efb
--- /dev/null
+++ b/types/stack/deliveryToken/previewToken.ts
@@ -0,0 +1,78 @@
+import { AnyProperty, SystemFields } from "../../utility/fields";
+import { Creatable, SystemFunction } from "../../utility/operations";
+
+// Main preview token interface
+export interface PreviewToken
+ extends SystemFields,
+ Creatable,
+ SystemFunction {
+ name: string;
+ description: string;
+ scope: Scope[];
+ uid: string;
+ created_by: string;
+ updated_by: string;
+ created_at: string;
+ updated_at: string;
+ token: string;
+ type: string;
+ preview_token: string;
+}
+
+// API response shape for creating a preview token
+export interface PreviewTokenResponse {
+ notice: string;
+ token: PreviewTokenData;
+}
+
+// Data inside the response `token`
+export interface PreviewTokenData extends AnyProperty {
+ name: string;
+ description: string;
+ scope: Scope[];
+ uid: string;
+ created_by: string;
+ updated_by: string;
+ created_at: string;
+ updated_at: string;
+ token: string;
+ type: string;
+ preview_token: string;
+}
+
+export interface Scope {
+ module: string;
+ environments?: Environment[];
+ branches?: string[];
+ locales?: string[];
+ acl: ACL;
+ _metadata?: {
+ uid: string;
+ };
+}
+
+export interface Environment extends AnyProperty {
+ name: string;
+ uid: string;
+ urls?: UrlLocale[];
+ _version?: number;
+ app_user_object_uid?: string;
+ created_by?: string;
+ updated_by?: string;
+ created_at?: string;
+ updated_at?: string;
+ ACL?: unknown[];
+ tags?: string[];
+}
+
+export interface UrlLocale {
+ url: string;
+ locale: string;
+}
+
+export interface ACL extends AnyProperty {
+ read?: boolean;
+ write?: boolean;
+ create?: boolean;
+ update?: boolean;
+}
diff --git a/types/stack/index.d.ts b/types/stack/index.d.ts
index 055ab895..0105d881 100644
--- a/types/stack/index.d.ts
+++ b/types/stack/index.d.ts
@@ -66,9 +66,6 @@ export interface Stack extends SystemFields {
globalField(options: object): GlobalFields;
globalField(uidOrOptions?: string | object, option?: object): GlobalFields | GlobalField;
-
-
-
asset(): Assets
asset(uid: string): Asset