@questwork/q-utilities 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ Copyright 2019 Questwork Consulting Limited
2
+
3
+ This project is free software released under the MIT/X11 license:
4
+
5
+ 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:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // 'use strict'
2
+
3
+ export * from './lib'
@@ -0,0 +1,56 @@
1
+ const axios = require('axios')
2
+ const { ClientApp } = require('./clientApp')
3
+
4
+ async function getAuthToken({ id, appSecret, name, service, authServerHost, authServerPath, authPath, additionalData }) {
5
+ try {
6
+ const clientApp = _initClientApp({ id, appSecret, name })
7
+ return _getAuthToken({
8
+ clientApp, service, authServerHost, authServerPath, authPath, additionalData
9
+ })
10
+ } catch (err) {
11
+ throw err
12
+ }
13
+ }
14
+
15
+ function _getAuthCredentials({ clientApp, service, additionalData }) {
16
+ const jwt = clientApp.getJwtToken()
17
+ const data = {
18
+ service,
19
+ ...additionalData
20
+ }
21
+ const headers = { Authorization: `Bearer ${jwt}` }
22
+ return {
23
+ data,
24
+ headers
25
+ }
26
+ }
27
+
28
+ async function _getAuthToken({ clientApp, service, authServerHost, authServerPath, authPath, additionalData }) {
29
+ try {
30
+ const path = authServerPath || authPath
31
+ if (!!path && !!authServerHost) {
32
+ const { data, headers } = _getAuthCredentials({ clientApp, service, additionalData })
33
+ const response = await axios.post(`${authServerHost}${path}`, data, { headers })
34
+ return response.data
35
+ }
36
+ throw new Error('missing auth server')
37
+ } catch (err) {
38
+ const message = err.response && err.response.data ? err.response.data.message : err.message
39
+ throw new Error(`failed to get ServiceApp token: ${message}`)
40
+ }
41
+ }
42
+
43
+ function _initClientApp({ id, appSecret, name } = {}) {
44
+ try {
45
+ const clientApp = ClientApp.init({
46
+ id, appSecret, name
47
+ })
48
+ return clientApp
49
+ } catch (err) {
50
+ throw new Error(`invalid ClientApp: ${err.message}`)
51
+ }
52
+ }
53
+
54
+ module.exports = {
55
+ getAuthToken
56
+ }
@@ -0,0 +1,46 @@
1
+ const { jwtHelper } = require('../jwtHelper')
2
+
3
+ const TOKEN_EXPIRY = 300
4
+
5
+ class ClientApp {
6
+ constructor(options) {
7
+ options = options || {}
8
+ this.id = options.id
9
+ this.appSecret = options.appSecret
10
+ this.name = options.name
11
+ }
12
+
13
+ // Class method
14
+ static init(options = {}) {
15
+ const clientApp = new ClientApp(options)
16
+ return clientApp.isValid ? clientApp : null
17
+ }
18
+
19
+ // Getter
20
+ get isValid() {
21
+ return !!this.name && !!this.appSecret && !!this.id
22
+ }
23
+
24
+ // instance methods
25
+ getJwtPayload() {
26
+ return {
27
+ clientApp: {
28
+ id: this.id,
29
+ name: this.name
30
+ }
31
+ }
32
+ }
33
+ getJwtToken({ tokenExpiry } = {}) {
34
+ const iExpiry = tokenExpiry || Math.floor(Date.now() / 1000) + TOKEN_EXPIRY // must be second
35
+ return jwtHelper.create(
36
+ this.getJwtPayload(),
37
+ {
38
+ secret: this.appSecret,
39
+ expiry: iExpiry
40
+ })
41
+ }
42
+ }
43
+
44
+ module.exports = {
45
+ ClientApp
46
+ }
@@ -0,0 +1,5 @@
1
+ const authenticator = require('./authenticator')
2
+
3
+ module.exports = {
4
+ authenticator
5
+ }
@@ -0,0 +1,20 @@
1
+ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByKeys) {
2
+ if (!string || typeof getValueByKeys !== 'function') {
3
+ return ''
4
+ }
5
+ const reg = new RegExp(patternMatch, 'g')
6
+ return string.replace(reg, (match, key) => {
7
+ const result = getValueByKeys({ keys: key.split('.'), obj: value })
8
+ if (result === null || result === undefined) {
9
+ return ''
10
+ }
11
+ return typeof result === 'object' ? JSON.stringify(result) : result
12
+ })
13
+ }
14
+
15
+ export default {
16
+ convertString
17
+ }
18
+ export {
19
+ convertString
20
+ }
@@ -0,0 +1 @@
1
+ export { convertString } from './convertString'
@@ -0,0 +1,93 @@
1
+ // const crypto = require('crypto-browserify')
2
+ const crypto = require('crypto')
3
+ // const { subtle } = globalThis.crypto
4
+
5
+ const cryptoHelper = {
6
+ createHash(algorithm, options) {
7
+ return crypto.createHash(algorithm, options)
8
+ },
9
+
10
+ createHmac(algorithm, key, options) {
11
+ return crypto.createHmac(algorithm, key, options)
12
+ },
13
+
14
+ createSign(algorithm, options) {
15
+ return crypto.createSign(algorithm, options)
16
+ },
17
+
18
+ createVerify(algorithm, options) {
19
+ return crypto.createVerify(algorithm, options)
20
+ },
21
+
22
+ digest(instance, outputEncoding) {
23
+ if (!(typeof instance.digest === 'function')) {
24
+ throw new Error('no digest method')
25
+ }
26
+ return instance.digest(outputEncoding)
27
+ },
28
+
29
+ sign({ instance, privateKey, outputEncoding }) {
30
+ if (!(typeof instance.sign === 'function')) {
31
+ throw new Error('no sign method')
32
+ }
33
+ return instance.sign(privateKey, outputEncoding)
34
+ },
35
+
36
+ verify({ instance, object, signature, signatureEncoding }) {
37
+ if (!(typeof instance.verify === 'function')) {
38
+ throw new Error('no verify method')
39
+ }
40
+ return instance.verify(object, signature, signatureEncoding)
41
+ },
42
+
43
+ update({ instance, data, inputEncoding }) {
44
+ if (!(typeof instance.update === 'function')) {
45
+ throw new Error('no update method')
46
+ }
47
+ return instance.update(data, inputEncoding)
48
+ },
49
+
50
+ createStringHmac({ algorithm, options, key, data, inputEncoding, outputEncoding }) {
51
+ try {
52
+ const hmac = this.createHmac(algorithm, key, options)
53
+ const instance = this.update({ instance: hmac, data, inputEncoding })
54
+ return this.digest(instance, outputEncoding)
55
+ } catch (err) {
56
+ throw err
57
+ }
58
+ },
59
+
60
+ createStringVerify({ algorithm, options, data, object, signature, inputEncoding, signatureEncoding }) {
61
+ try {
62
+ const verify = this.createVerify(algorithm, options)
63
+ const instance = this.update({ instance: verify, data, inputEncoding })
64
+ return this.verify({ instance, object, signature, signatureEncoding })
65
+ } catch (err) {
66
+ throw err
67
+ }
68
+ },
69
+
70
+ createSignature({ algorithm, options, data, privateKey, inputEncoding, outputEncoding }) {
71
+ try {
72
+ const sign = this.createSign(algorithm, options)
73
+ const instance = this.update({ instance: sign, data, inputEncoding })
74
+ return this.sign({ instance, privateKey, outputEncoding })
75
+ } catch (err) {
76
+ throw err
77
+ }
78
+ },
79
+
80
+ createStringHash({ algorithm, options, data, inputEncoding, outputEncoding }) {
81
+ try {
82
+ const hash = this.createHash(algorithm, options)
83
+ const instance = this.update({ instance: hash, data, inputEncoding })
84
+ return this.digest(instance, outputEncoding)
85
+ } catch (err) {
86
+ throw err
87
+ }
88
+ }
89
+ }
90
+
91
+ module.exports = {
92
+ cryptoHelper
93
+ }
@@ -0,0 +1,5 @@
1
+ const { cryptoHelper } = require('./cryptoHelper')
2
+
3
+ module.exports = {
4
+ cryptoHelper
5
+ }
@@ -0,0 +1,117 @@
1
+ function getValidation(rule, data, getDataByKey, KeyValueObject) {
2
+ if (!rule) {
3
+ return true
4
+ }
5
+ if (typeof getDataByKey !== 'function' || typeof KeyValueObject !== 'function') {
6
+ return false
7
+ }
8
+ const { key = '', value, keyValuePath = '' } = rule
9
+ const [valueAttribute] = Object.keys(value)
10
+
11
+ if (!key) {
12
+ switch (valueAttribute) {
13
+ case '$and': {
14
+ const arr = value['$and']
15
+ return arr.reduce((acc, item) => (acc && getValidation(item, data, getDataByKey)), true)
16
+ }
17
+ case '$or': {
18
+ const arr = value['$or']
19
+ return arr.reduce((acc, item) => (acc || getValidation(item, data, getDataByKey)), false)
20
+ }
21
+ default:
22
+ return false
23
+ }
24
+ }
25
+
26
+ let rowValue = getDataByKey(key, data)
27
+
28
+ // debugger
29
+
30
+ // if KeyValue object
31
+ if (keyValuePath) {
32
+ console.log('keyValuePath', keyValuePath)
33
+ const rowValueData = KeyValueObject.toObject(rowValue)
34
+ rowValue = getDataByKey(keyValuePath, rowValueData)
35
+ }
36
+
37
+ switch (valueAttribute) {
38
+ case '$empty': {
39
+ const isEmpty = rowValue === null || rowValue === undefined
40
+ return isEmpty === value['$empty']
41
+ }
42
+ case '$eq': {
43
+ return rowValue === value['$eq']
44
+ }
45
+ case '$gt': {
46
+ return rowValue > value['$gt']
47
+ }
48
+ case '$gte': {
49
+ return rowValue >= value['$gte']
50
+ }
51
+ case '$lt': {
52
+ return rowValue < value['$lt']
53
+ }
54
+ case '$lte': {
55
+ return rowValue <= value['$lte']
56
+ }
57
+ case '$in': {
58
+ if (Array.isArray(rowValue)) {
59
+ return !!rowValue.find((e) => (value['$in'].includes(e)))
60
+ }
61
+ if (typeof rowValue !== 'object') {
62
+ return !!value['$in'].includes(rowValue)
63
+ }
64
+ return false
65
+ }
66
+ case '$inValue': {
67
+ const result = getDataByKey(value['$inValue'], data)
68
+ const _value = Array.isArray(result) ? result : []
69
+ if (Array.isArray(rowValue)) {
70
+ return !!rowValue.find((e) => (_value.includes(e)))
71
+ }
72
+ if (typeof rowValue === 'string') {
73
+ return !!_value.includes(rowValue)
74
+ }
75
+ return false
76
+ }
77
+ case '$ne': {
78
+ return rowValue !== value['$ne']
79
+ }
80
+ case '$notIn': {
81
+ if (Array.isArray(rowValue)) {
82
+ return !rowValue.find((e) => (value['$notIn'].includes(e)))
83
+ }
84
+ if (typeof rowValue !== 'object') {
85
+ return !value['$notIn'].includes(rowValue)
86
+ }
87
+ return false
88
+ }
89
+ case '$notInValue': {
90
+ const result = getDataByKey(value['$notInValue'], data)
91
+ const _value = Array.isArray(result) ? result : []
92
+ if (Array.isArray(rowValue)) {
93
+ return !rowValue.find((e) => (_value.includes(e)))
94
+ }
95
+ if (typeof rowValue !== 'object') {
96
+ return !_value.includes(rowValue)
97
+ }
98
+ return false
99
+ }
100
+ case '$range': {
101
+ const [min, max] = value['$range']
102
+ if (typeof min === 'number' && typeof max === 'number' && rowValue >= min && rowValue <= max) {
103
+ return true
104
+ }
105
+ return false
106
+ }
107
+ default:
108
+ return false
109
+ }
110
+ }
111
+
112
+ export default {
113
+ getValidation
114
+ }
115
+ export {
116
+ getValidation
117
+ }
@@ -0,0 +1 @@
1
+ export { getValidation } from './getValidation'
@@ -0,0 +1,20 @@
1
+ function getValueByKeys(keys, data) {
2
+ if (keys.length === 0) {
3
+ return data
4
+ }
5
+ const firstKey = keys.shift()
6
+ if (data && Object.prototype.hasOwnProperty.call(data, firstKey)) {
7
+ return getValueByKeys(keys, data[firstKey])
8
+ }
9
+ if (data && firstKey) {
10
+ return data[firstKey]
11
+ }
12
+ return data
13
+ }
14
+ export default {
15
+ getValueByKeys
16
+ }
17
+ export {
18
+ getValueByKeys
19
+ }
20
+
@@ -0,0 +1 @@
1
+ export { getValueByKeys } from './getValueByKeys'
package/lib/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // const { authenticator } = require('./authenticator')
2
+ // const { cryptoHelper } = require('./cryptoHelper')
3
+ // const { jwtHelper } = require('./jwtHelper')
4
+
5
+ // module.exports = {
6
+ // authenticator,
7
+ // cryptoHelper,
8
+ // jwtHelper
9
+ // }
10
+
11
+ export * from './convertString'
12
+ export * from './getValidation'
13
+ export * from './getValueByKeys'
14
+ export * from './keyValueObject'
@@ -0,0 +1,5 @@
1
+ const { jwtHelper } = require('./jwtHelper')
2
+
3
+ module.exports = {
4
+ jwtHelper
5
+ }
@@ -0,0 +1,254 @@
1
+ // 'use strict'
2
+
3
+ /* eslint-disable new-cap */
4
+ /* eslint-disable camelcase */
5
+ /* eslint-disable no-mixed-operators */
6
+ /* eslint-disable no-useless-escape */
7
+ /* eslint-disable no-param-reassign */
8
+
9
+ /* eslint func-names: 0 */
10
+
11
+ const Buffer = require('buffer/').Buffer
12
+
13
+ const EXPIRY = 3600 // in second
14
+ const ALGORITHM = 'HS256'
15
+ const SECRET = 'ab1234cd'
16
+
17
+ const jwtHelper = {
18
+ create(obj, { secret, algorithm, expiry } = {}) {
19
+ const sAlgorithm = algorithm || ALGORITHM
20
+ const sSecret = secret || SECRET
21
+ const exp = expiry || getTimeInSecond() + EXPIRY
22
+ const payload = {
23
+ ...obj,
24
+ exp
25
+ }
26
+ return encode(payload, sSecret, sAlgorithm)
27
+ },
28
+ createByUser(loginAccount) {
29
+ const exp = getTimeInSecond() + EXPIRY
30
+ const payload = {
31
+ loginAccount,
32
+ exp
33
+ }
34
+ return this.encode(payload)
35
+ },
36
+ encode(payload, algorithm) {
37
+ return encode(payload, SECRET, algorithm)
38
+ },
39
+ decode(token, algorithm) {
40
+ const noVerify = !this.verify(token)
41
+ return decode(token, SECRET, noVerify, algorithm) // if noVerify = true, may skip verification
42
+ },
43
+ getPayload(token) {
44
+ const payload = getPayload(token)
45
+ return {
46
+ payload
47
+ }
48
+ },
49
+ getPayloadIdByKey(token, key) {
50
+ const payload = getPayload(token)
51
+ const id = payload[key] ? payload[key].id : null
52
+ return {
53
+ id,
54
+ jwtToken: token
55
+ }
56
+ },
57
+ resolve(token, secret, algorithm) {
58
+ const sSecret = secret || SECRET
59
+ return decode(token, sSecret, false, algorithm) // need verification
60
+ },
61
+ verify(token) {
62
+ const payload = getPayload(token)
63
+ const today = getTimeInSecond()
64
+ return (payload.exp && (today <= payload.exp)) || false
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Private functions
70
+ */
71
+
72
+ const getPayload = (token) => decode(token, SECRET, true)
73
+
74
+ const getTimeInSecond = () => Math.floor(Date.now() / 1000)
75
+
76
+ /**
77
+ * Private functions, based on jwt-simple 0.2.0
78
+ */
79
+
80
+ const { cryptoHelper } = require('../cryptoHelper')
81
+
82
+ const algorithmMap = {
83
+ HS256: 'sha256',
84
+ HS384: 'sha384',
85
+ HS512: 'sha512',
86
+ RS256: 'RSA-SHA256'
87
+ }
88
+
89
+ const typeMap = {
90
+ HS256: 'hmac',
91
+ HS384: 'hmac',
92
+ HS512: 'hmac',
93
+ RS256: 'sign'
94
+ }
95
+
96
+
97
+ /**
98
+ * Decode jwt
99
+ *
100
+ * @param {Object} token
101
+ * @param {String} key
102
+ * @param {Boolean} noVerify
103
+ * @param {String} algorithm
104
+ * @return {Object} payload
105
+ * @api public
106
+ */
107
+ const decode = function jwt_decode(token, key, noVerify, algorithm) {
108
+ // check token
109
+ if (!token) {
110
+ throw new Error('No token supplied')
111
+ }
112
+ // check segments
113
+ const segments = token.split('.')
114
+ if (segments.length !== 3) {
115
+ throw new Error('Not enough or too many segments')
116
+ }
117
+
118
+ // All segment should be base64
119
+ const headerSeg = segments[0]
120
+ const payloadSeg = segments[1]
121
+ const signatureSeg = segments[2]
122
+
123
+ // base64 decode and parse JSON
124
+ const header = JSON.parse(base64urlDecode(headerSeg))
125
+
126
+ const payload = JSON.parse(base64urlDecode(payloadSeg))
127
+
128
+ if (!noVerify) {
129
+ const signingMethod = algorithmMap[algorithm || header.alg]
130
+ const signingType = typeMap[algorithm || header.alg]
131
+ if (!signingMethod || !signingType) {
132
+ throw new Error('Algorithm not supported')
133
+ }
134
+
135
+ // verify signature. `sign` will return base64 string.
136
+ const signingInput = [headerSeg, payloadSeg].join('.')
137
+ if (!verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
138
+ throw new Error('Signature verification failed')
139
+ }
140
+ }
141
+
142
+ return payload
143
+ }
144
+
145
+
146
+ /**
147
+ * Encode jwt
148
+ *
149
+ * @param {Object} payload
150
+ * @param {String} key
151
+ * @param {String} algorithm
152
+ * @return {String} token
153
+ * @api public
154
+ */
155
+ const encode = function jwt_encode(payload, key, algorithm) {
156
+ // Check key
157
+ if (!key) {
158
+ throw new Error('Require key')
159
+ }
160
+
161
+ // Check algorithm, default is HS256
162
+ if (!algorithm) {
163
+ algorithm = ALGORITHM
164
+ }
165
+
166
+ const signingMethod = algorithmMap[algorithm]
167
+ const signingType = typeMap[algorithm]
168
+ if (!signingMethod || !signingType) {
169
+ throw new Error('Algorithm not supported')
170
+ }
171
+
172
+ // header, typ is fixed value.
173
+ const header = { typ: 'JWT', alg: algorithm }
174
+
175
+ // create segments, all segments should be base64 string
176
+ const segments = []
177
+ segments.push(base64urlEncode(JSON.stringify(header)))
178
+ segments.push(base64urlEncode(JSON.stringify(payload)))
179
+ segments.push(sign(segments.join('.'), key, signingMethod, signingType))
180
+
181
+ return segments.join('.')
182
+ }
183
+
184
+
185
+ /**
186
+ * private util functions
187
+ */
188
+
189
+ function verify(input, key, method, type, signature) {
190
+ if (type === 'hmac') {
191
+ return (signature === sign(input, key, method, type))
192
+ } else if (type === 'sign') {
193
+ try {
194
+ return cryptoHelper.createStringVerify({
195
+ algorithm: method,
196
+ data: input,
197
+ object: key,
198
+ signature: base64urlUnescape(signature),
199
+ signatureEncoding: 'base64'
200
+ })
201
+ } catch (error) {
202
+ throw new Error('createStringVerify failed')
203
+ }
204
+ }
205
+ throw new Error('Algorithm type not recognized')
206
+ }
207
+
208
+ function sign(input, key, method, type) {
209
+ let base64str
210
+ if (type === 'hmac') {
211
+ base64str = cryptoHelper.createStringHmac({
212
+ algorithm: method,
213
+ key,
214
+ data: input,
215
+ outputEncoding: 'base64'
216
+ })
217
+ } else if (type === 'sign') {
218
+ try {
219
+ base64str = cryptoHelper.createSignature({
220
+ algorithm: method,
221
+ data: input,
222
+ privateKey: key,
223
+ outputEncoding: 'base64'
224
+ })
225
+ } catch (error) {
226
+ throw new Error('createSignature failed')
227
+ }
228
+ } else {
229
+ throw new Error('Algorithm type not recognized')
230
+ }
231
+ return base64urlEscape(base64str)
232
+ }
233
+
234
+ function base64urlDecode(str) {
235
+ // fixed bug if decode string is incorrect
236
+ return (new Buffer.from(base64urlUnescape(str), 'base64')).toString() || '{}'
237
+ }
238
+
239
+ function base64urlUnescape(str) {
240
+ str += new Array(5 - str.length % 4).join('=')
241
+ return str.replace(/\-/g, '+').replace(/_/g, '/')
242
+ }
243
+
244
+ function base64urlEncode(str) {
245
+ return base64urlEscape(new Buffer.from((str)).toString('base64'))
246
+ }
247
+
248
+ function base64urlEscape(str) {
249
+ return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
250
+ }
251
+
252
+ module.exports = {
253
+ jwtHelper
254
+ }
@@ -0,0 +1 @@
1
+ export { KeyValueObject } from './keyValueObject'
@@ -0,0 +1,163 @@
1
+ 'use strict'
2
+
3
+ class KeyValueObject {
4
+ constructor(options = {}) {
5
+ options = options || {}
6
+ this.key = options.key || null
7
+ this.value = (typeof options.value !== 'undefined') ? options.value : ''
8
+ }
9
+
10
+ // Class methods
11
+
12
+ static addItem(arr, key, value) {
13
+ arr.push(
14
+ { key, value }
15
+ )
16
+ }
17
+ static addRecord(arr = [], key, value) {
18
+ const self = this
19
+ if (!KeyValueObject.hasKeyValue(arr, key, value)) {
20
+ arr.push(self.init({ key, value }))
21
+ }
22
+ return arr
23
+ }
24
+
25
+ static fromObject(options = {}) {
26
+ const self = this
27
+ return Object.keys(options).reduce((acc, key) => {
28
+ acc.push(self.init({ key, value: options[key] }))
29
+ return acc
30
+ }, [])
31
+ }
32
+
33
+ static removeByKey(arr, key) {
34
+ return arr.reduce((acc, item) => {
35
+ if (item.key !== key) {
36
+ acc.push(item)
37
+ }
38
+ return acc
39
+ }, [])
40
+ }
41
+
42
+ static getValuesByKey(arr = [], key) {
43
+ return arr.reduce((acc, item) => {
44
+ if (item.key === key) {
45
+ acc.push(item.value)
46
+ }
47
+ return acc
48
+ }, [])
49
+ }
50
+
51
+ static hasKeyValue(arr = [], key, value) {
52
+ if (typeof value === 'undefined') {
53
+ return arr.filter((item) => item.key === key).length > 0
54
+ }
55
+ return arr.filter((item) => (item.key === key && item.value === value)).length > 0
56
+ }
57
+
58
+ static init(options = {}) {
59
+ if (options instanceof KeyValueObject) {
60
+ return options
61
+ }
62
+ const kvObject = new KeyValueObject(options)
63
+ return kvObject.isValid ? kvObject : null
64
+ }
65
+
66
+ static initFromArray(arr = []) {
67
+ const self = this
68
+ if (Array.isArray(arr)) {
69
+ return arr.map(self.init)
70
+ }
71
+ return []
72
+ }
73
+
74
+ static initOnlyValidFromArray(arr = []) {
75
+ return this.initFromArray(arr).filter((i) => i)
76
+ }
77
+
78
+ static keys(arr = []) {
79
+ if (Array.isArray(arr)) {
80
+ return arr.reduce((acc, item) => {
81
+ acc.push(item.key)
82
+ return acc
83
+ }, [])
84
+ }
85
+ return []
86
+ }
87
+
88
+ static toObject(arr = []) {
89
+ if (Array.isArray(arr)) {
90
+ return arr.reduce((acc, item) => {
91
+ acc[item.key] = item.value
92
+ return acc
93
+ }, {})
94
+ }
95
+ return {}
96
+ }
97
+
98
+ static toString(arr = [], delimiter = '; ') {
99
+ if (Array.isArray(arr)) {
100
+ return arr.reduce((acc, item) => {
101
+ acc.push(`${item.key}: ${item.value}`)
102
+ return acc
103
+ }, []).join(delimiter)
104
+ }
105
+ return ''
106
+ }
107
+
108
+ static updateRecord(arr = [], key, value) {
109
+ return arr.map((item) => {
110
+ if (item.key === key) {
111
+ item.value = value
112
+ }
113
+ return item
114
+ })
115
+ }
116
+
117
+ static updateOrInsertRecord(arr = [], key, value) {
118
+ const self = this
119
+ let copy = [...arr]
120
+ if (!self.hasKeyValue(arr, key)) {
121
+ copy.push(self.init({ key, value }))
122
+ } else {
123
+ copy = self.updateRecord(arr, key, value)
124
+ }
125
+ return copy
126
+ }
127
+
128
+ static updateRecordsFromArray(arr = [], updateArr = []) {
129
+ if (Array.isArray(arr) && Array.isArray(updateArr)) {
130
+ const obj1 = KeyValueObject.toObject(arr)
131
+ const obj2 = KeyValueObject.toObject(updateArr)
132
+ return KeyValueObject.fromObject(Object.assign({}, obj1, obj2))
133
+ }
134
+ return []
135
+ }
136
+
137
+ static values(arr = []) {
138
+ if (Array.isArray(arr)) {
139
+ return arr.reduce((acc, item) => {
140
+ acc.push(item.value)
141
+ return acc
142
+ }, [])
143
+ }
144
+ return []
145
+ }
146
+
147
+ // getters
148
+ get isValid() {
149
+ return !!this.key
150
+ }
151
+
152
+ get toObject() {
153
+ const obj = {}
154
+ if (this.isValid) {
155
+ obj[this.key] = this.value
156
+ }
157
+ return obj
158
+ }
159
+ }
160
+
161
+ export {
162
+ KeyValueObject
163
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@questwork/q-utilities",
3
+ "version": "0.1.0",
4
+ "description": "Questwork QUtilities",
5
+ "main": "index.js",
6
+ "author": {
7
+ "name": "Questwork Consulting Limited",
8
+ "email": "info@questwork.com",
9
+ "url": "https://questwork.com/"
10
+ },
11
+ "license": "MIT",
12
+ "dependencies": {
13
+ "axios": "^0.28.1",
14
+ "buffer": "^6.0.3"
15
+ },
16
+ "devDependencies": {
17
+ "chai": "^4.2.0",
18
+ "eslint": "4.0.0",
19
+ "eslint-config-airbnb-base": "11.2.0",
20
+ "eslint-plugin-import": "2.3.0",
21
+ "eslint-plugin-mocha": "^5.3.0",
22
+ "mocha": "^6.2.2",
23
+ "nodemon": "^2.0.2"
24
+ },
25
+ "engines": {
26
+ "node": ">=10.0.0"
27
+ },
28
+ "scripts": {
29
+ "lint": "eslint .",
30
+ "test": "mocha --exit 'test/test.setup.js' 'test/**/*.spec.js'",
31
+ "test-models": "NODE_ENV=test mocha --exit 'test/test.setup.js' 'test/**/*.spec.js'"
32
+ }
33
+ }