Skip to content

Commit 021e840

Browse files
authored
Merge pull request #7 from contentstack/development
New feature and Request Concurrency support added
2 parents bb184fd + ca44e57 commit 021e840

File tree

20 files changed

+980
-257
lines changed

20 files changed

+980
-257
lines changed

.jsdoc.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
"lib/stack/environment/index.js",
2525
"lib/stack/deliveryToken/index.js",
2626
"lib/stack/roles/index.js",
27-
"lib/stack/webhook/index.js"
27+
"lib/stack/webhook/index.js",
28+
"lib/stack/workflow/index.js",
29+
"lib/stack/workflow/publishRules/index.js"
30+
2831
],
2932
"excludePattern": "(node_modules/|jsdocs)"
3033
},

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [v1.2.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.2.0) (.....)
4+
- Bug Fix
5+
- Release Items issue for API key resolved
6+
- Enhanchment
7+
- Request concurrency added in SDK
8+
- New Feature
9+
- Workflow module support added
310
## [v1.1.2](https://github.com/contentstack/contentstack-management-javascript/tree/v1.1.2) (2021-01-07)
411
- Bug Fix
512
- Retry count on multiple request failuer

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ contentstackClient.stack({ api_key: 'API_KEY' }).asset().create({ asset })
104104
- [Content Management API Docs](https://www.contentstack.com/docs/developers/apis/content-management-api)
105105

106106
### The MIT License (MIT)
107-
Copyright © 2012-2020 [Contentstack](https://www.contentstack.com/). All Rights Reserved
107+
Copyright © 2012-2021 [Contentstack](https://www.contentstack.com/). All Rights Reserved
108108

109109
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
110110

lib/contentstack.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ import httpClient from './core/contentstackHTTPClient.js'
3939
* import * as contentstack from '@contentstack/management'
4040
* const client = contentstack.client({ timeout: 50000 })
4141
*
42+
* @prop {number=} params.maxRequests - Optional maximum number of requests SDK should send concurrently. Default is 5 concurrent request.
43+
* @example //Set the `maxRequests` to 5
44+
* import * as contentstack from '@contentstack/management'
45+
* const client = contentstack.client({ maxRequests: 5 })
46+
*
4247
* @prop {boolean=} params.retryOnError - Optional boolean for retry on failuer. Default is true
4348
* @example //Set the `retryOnError` to false
4449
* import * as contentstack from '@contentstack/management'
@@ -113,7 +118,7 @@ export function client (params = {}) {
113118
defaultHostName: 'api.contentstack.io'
114119
}
115120

116-
const sdkAgent = `${packages.name}-javascript/${packages.version}`
121+
const sdkAgent = `contentstack-management-javascript/${packages.version}`
117122
const userAgentHeader = getUserAgent(sdkAgent,
118123
params.application,
119124
params.integration,

lib/contentstackClient.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export default function contentstackClient ({ http }) {
8787
*
8888
*/
8989
function stack (params = {}) {
90-
var stack = { ...cloneDeep(params) }
90+
const stack = { ...cloneDeep(params) }
9191
return new Stack(http, { stack })
9292
}
9393

@@ -164,6 +164,7 @@ export default function contentstackClient ({ http }) {
164164
logout: logout,
165165
getUser: getUser,
166166
stack: stack,
167-
organization: organization
167+
organization: organization,
168+
axiosInstance: http
168169
}
169170
}

lib/core/concurrency-queue.js

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import Axios from 'axios'
2+
const defaultConfig = {
3+
maxRequests: 5,
4+
retryLimit: 5,
5+
retryDelay: 300
6+
}
7+
8+
export function ConcurrencyQueue ({ axios, config }) {
9+
if (!axios) {
10+
throw Error('Axios instance is not present')
11+
}
12+
13+
if (config) {
14+
if (config.maxRequests && config.maxRequests <= 0) {
15+
throw Error('Concurrency Manager Error: minimum concurrent requests is 1')
16+
} else if (config.retryLimit && config.retryLimit <= 0) {
17+
throw Error('Retry Policy Error: minimum retry limit is 1')
18+
} else if (config.retryDelay && config.retryDelay < 300) {
19+
throw Error('Retry Policy Error: minimum retry delay for requests is 300')
20+
}
21+
}
22+
23+
this.config = Object.assign({}, defaultConfig, config)
24+
this.queue = []
25+
this.running = []
26+
this.paused = false
27+
28+
// Initial shift will check running request,
29+
// and adds request to running queue if max requests are not running
30+
this.initialShift = () => {
31+
if (this.running.length < this.config.maxRequests && !this.paused) {
32+
shift()
33+
}
34+
}
35+
36+
// INTERNAL: Shift the queued item to running queue
37+
const shift = () => {
38+
if (this.queue.length && !this.paused) {
39+
const queueItem = this.queue.shift()
40+
queueItem.resolve(queueItem.request)
41+
this.running.push(queueItem)
42+
}
43+
}
44+
45+
// Append the request at start of queue
46+
this.unshift = requestPromise => {
47+
this.queue.unshift(requestPromise)
48+
}
49+
50+
this.push = requestPromise => {
51+
this.queue.push(requestPromise)
52+
this.initialShift()
53+
}
54+
55+
this.clear = () => {
56+
const requests = this.queue.splice(0, this.queue.length)
57+
requests.forEach((element) => {
58+
element.request.source.cancel()
59+
})
60+
}
61+
62+
// Detach the interceptors
63+
this.detach = () => {
64+
axios.interceptors.request.eject(this.interceptors.request)
65+
axios.interceptors.response.eject(this.interceptors.response)
66+
this.interceptors = {
67+
request: null,
68+
response: null
69+
}
70+
}
71+
72+
// Request interseptor to queue the request
73+
const requestHandler = request => {
74+
request.retryCount = request.retryCount || 0
75+
if (request.headers.authorization && request.headers.authorization !== undefined) {
76+
delete request.headers.authtoken
77+
}
78+
79+
if (request.cancelToken === undefined) {
80+
const source = Axios.CancelToken.source()
81+
request.cancelToken = source.token
82+
request.source = source
83+
}
84+
85+
if (this.paused && request.retryCount > 0) {
86+
return new Promise(resolve => {
87+
this.unshift({ request, resolve })
88+
})
89+
} else if (request.retryCount > 0) {
90+
return request
91+
}
92+
93+
return new Promise(resolve => {
94+
this.push({ request, resolve })
95+
})
96+
}
97+
98+
const delay = (time) => {
99+
if (!this.paused) {
100+
this.paused = true
101+
// Check for current running request.
102+
// Wait for running queue to complete.
103+
// Wait and prosed the Queued request.
104+
if (this.running.length > 0) {
105+
setTimeout(() => {
106+
delay(time)
107+
}, time)
108+
}
109+
return new Promise(resolve => setTimeout(() => {
110+
this.paused = false
111+
for (let i = 0; i < this.config.maxRequests; i++) {
112+
this.initialShift()
113+
}
114+
}, time))
115+
}
116+
}
117+
118+
// Response interceptor used for
119+
const responseHandler = (response) => {
120+
this.running.shift()
121+
shift()
122+
return response
123+
}
124+
125+
const responseErrorHandler = error => {
126+
let networkError = error.config.retryCount
127+
let retryErrorType = null
128+
if (!this.config.retryOnError || networkError > this.config.retryLimit) {
129+
return Promise.reject(responseHandler(error))
130+
}
131+
132+
// Error handling
133+
let wait = this.config.retryDelay
134+
const response = error.response
135+
if (!response) {
136+
if (error.code === 'ECONNABORTED') {
137+
error.response = {
138+
...error.response,
139+
status: 408,
140+
statusText: `timeout of ${this.config.timeout}ms exceeded`
141+
}
142+
} else {
143+
return Promise.reject(responseHandler(error))
144+
}
145+
} else if (response.status === 429) {
146+
retryErrorType = `Error with status: ${response.status}`
147+
networkError++
148+
149+
if (networkError > this.config.retryLimit) {
150+
return Promise.reject(responseHandler(error))
151+
}
152+
this.running.shift()
153+
wait = 1000
154+
// Cooldown the running requests
155+
delay(wait)
156+
error.config.retryCount = networkError
157+
158+
return axios(updateRequestConfig(error, retryErrorType, wait))
159+
} else if (this.config.retryCondition && this.config.retryCondition(error)) {
160+
retryErrorType = `Error with status: ${response.status}`
161+
networkError++
162+
if (networkError > this.config.retryLimit) {
163+
return Promise.reject(responseHandler(error))
164+
}
165+
if (this.config.retryDelayOptions) {
166+
if (this.config.retryDelayOptions.customBackoff) {
167+
wait = this.config.retryDelayOptions.customBackoff(networkError, error)
168+
if (wait && wait <= 0) {
169+
return Promise.reject(responseHandler(error))
170+
}
171+
} else if (this.config.retryDelayOptions.base) {
172+
wait = this.config.retryDelayOptions.base * networkError
173+
}
174+
} else {
175+
wait = this.config.retryDelay
176+
}
177+
error.config.retryCount = networkError
178+
return new Promise(function (resolve) {
179+
return setTimeout(function () {
180+
return resolve(axios(updateRequestConfig(error, retryErrorType, wait)))
181+
}, wait)
182+
})
183+
}
184+
return Promise.reject(responseHandler(error))
185+
}
186+
187+
this.interceptors = {
188+
request: null,
189+
response: null
190+
}
191+
192+
const updateRequestConfig = (error, retryErrorType, wait) => {
193+
const requestConfig = error.config
194+
this.config.logHandler('warning', `${retryErrorType} error occurred. Waiting for ${wait} ms before retrying...`)
195+
if (axios !== undefined && axios.defaults !== undefined) {
196+
if (axios.defaults.agent === requestConfig.agent) {
197+
delete requestConfig.agent
198+
}
199+
if (axios.defaults.httpAgent === requestConfig.httpAgent) {
200+
delete requestConfig.httpAgent
201+
}
202+
if (axios.defaults.httpsAgent === requestConfig.httpsAgent) {
203+
delete requestConfig.httpsAgent
204+
}
205+
}
206+
207+
requestConfig.transformRequest = [function (data) {
208+
return data
209+
}]
210+
return requestConfig
211+
}
212+
213+
// Adds interseptors in axios to queue request
214+
this.interceptors.request = axios.interceptors.request.use(requestHandler)
215+
this.interceptors.response = axios.interceptors.response.use(responseHandler, responseErrorHandler)
216+
}

lib/core/contentstack-retry.js

Lines changed: 0 additions & 86 deletions
This file was deleted.

0 commit comments

Comments
 (0)