@middy/util 3.0.0-alpha.2 → 3.0.0-alpha.3
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/index.js +169 -146
- package/package.json +5 -4
package/index.js
CHANGED
|
@@ -1,224 +1,247 @@
|
|
|
1
|
-
import { Agent } from 'https'
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Agent } from 'https'
|
|
2
|
+
|
|
3
|
+
// smaller version of `http-errors`
|
|
4
|
+
import statuses from './codes.js'
|
|
5
|
+
import { inherits } from 'util'
|
|
6
|
+
// import { NodeHttpHandler } from '@aws-sdk/node-http-handler' // aws-sdk v3
|
|
7
|
+
|
|
4
8
|
export const awsClientDefaultOptions = {
|
|
9
|
+
// AWS SDK v3
|
|
10
|
+
// Docs: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/enforcing-tls.html
|
|
11
|
+
/* requestHandler: new NodeHttpHandler({
|
|
12
|
+
httpsAgent: new Agent(
|
|
13
|
+
{
|
|
14
|
+
secureProtocol: 'TLSv1_2_method'
|
|
15
|
+
}
|
|
16
|
+
)
|
|
17
|
+
}) */
|
|
18
|
+
// Docs: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/enforcing-tls.html
|
|
5
19
|
httpOptions: {
|
|
6
20
|
agent: new Agent({
|
|
7
21
|
secureProtocol: 'TLSv1_2_method'
|
|
8
22
|
})
|
|
9
23
|
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const createPrefetchClient = (options) => {
|
|
27
|
+
const awsClientOptions = {
|
|
28
|
+
...awsClientDefaultOptions,
|
|
13
29
|
...options.awsClientOptions
|
|
14
|
-
}
|
|
15
|
-
const client = new options.AwsClient(awsClientOptions)
|
|
30
|
+
}
|
|
31
|
+
const client = new options.AwsClient(awsClientOptions)
|
|
16
32
|
|
|
33
|
+
// AWS XRay
|
|
17
34
|
if (options.awsClientCapture && options.disablePrefetch) {
|
|
18
|
-
return options.awsClientCapture(client)
|
|
35
|
+
return options.awsClientCapture(client)
|
|
19
36
|
} else if (options.awsClientCapture) {
|
|
20
|
-
console.warn('Unable to apply X-Ray outside of handler invocation scope.')
|
|
37
|
+
console.warn('Unable to apply X-Ray outside of handler invocation scope.')
|
|
21
38
|
}
|
|
22
39
|
|
|
23
|
-
return client
|
|
24
|
-
}
|
|
40
|
+
return client
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
export const createClient = async (options, request) => {
|
|
26
|
-
let awsClientCredentials = {}
|
|
44
|
+
let awsClientCredentials = {}
|
|
27
45
|
|
|
46
|
+
// Role Credentials
|
|
28
47
|
if (options.awsClientAssumeRole) {
|
|
29
|
-
if (!request) throw new Error('Request required when assuming role')
|
|
30
|
-
awsClientCredentials = await getInternal(
|
|
31
|
-
credentials: options.awsClientAssumeRole
|
|
32
|
-
|
|
48
|
+
if (!request) throw new Error('Request required when assuming role')
|
|
49
|
+
awsClientCredentials = await getInternal(
|
|
50
|
+
{ credentials: options.awsClientAssumeRole },
|
|
51
|
+
request
|
|
52
|
+
)
|
|
33
53
|
}
|
|
34
54
|
|
|
35
|
-
awsClientCredentials = {
|
|
55
|
+
awsClientCredentials = {
|
|
56
|
+
...awsClientCredentials,
|
|
36
57
|
...options.awsClientOptions
|
|
37
|
-
}
|
|
38
|
-
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return createPrefetchClient({
|
|
61
|
+
...options,
|
|
39
62
|
awsClientOptions: awsClientCredentials
|
|
40
|
-
})
|
|
41
|
-
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
42
66
|
export const canPrefetch = (options = {}) => {
|
|
43
|
-
return !options.awsClientAssumeRole && !options.disablePrefetch
|
|
44
|
-
}
|
|
45
|
-
export const getInternal = async (variables, request) => {
|
|
46
|
-
if (!variables || !request) return {};
|
|
47
|
-
let keys = [];
|
|
48
|
-
let values = [];
|
|
67
|
+
return !options.awsClientAssumeRole && !options.disablePrefetch
|
|
68
|
+
}
|
|
49
69
|
|
|
70
|
+
// Internal Context
|
|
71
|
+
export const getInternal = async (variables, request) => {
|
|
72
|
+
if (!variables || !request) return {}
|
|
73
|
+
let keys = []
|
|
74
|
+
let values = []
|
|
50
75
|
if (variables === true) {
|
|
51
|
-
keys = values = Object.keys(request.internal)
|
|
76
|
+
keys = values = Object.keys(request.internal)
|
|
52
77
|
} else if (typeof variables === 'string') {
|
|
53
|
-
keys = values = [variables]
|
|
78
|
+
keys = values = [variables]
|
|
54
79
|
} else if (Array.isArray(variables)) {
|
|
55
|
-
keys = values = variables
|
|
80
|
+
keys = values = variables
|
|
56
81
|
} else if (typeof variables === 'object') {
|
|
57
|
-
keys = Object.keys(variables)
|
|
58
|
-
values = Object.values(variables)
|
|
82
|
+
keys = Object.keys(variables)
|
|
83
|
+
values = Object.values(variables)
|
|
59
84
|
}
|
|
60
|
-
|
|
61
|
-
const promises = [];
|
|
62
|
-
|
|
85
|
+
const promises = []
|
|
63
86
|
for (const internalKey of values) {
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
87
|
+
// 'internal.key.sub_value' -> { [key]: internal.key.sub_value }
|
|
88
|
+
const pathOptionKey = internalKey.split('.')
|
|
89
|
+
const rootOptionKey = pathOptionKey.shift()
|
|
90
|
+
let valuePromise = request.internal[rootOptionKey]
|
|
68
91
|
if (typeof valuePromise.then !== 'function') {
|
|
69
|
-
valuePromise = Promise.resolve(valuePromise)
|
|
92
|
+
valuePromise = Promise.resolve(valuePromise)
|
|
70
93
|
}
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
promises.push(
|
|
95
|
+
valuePromise.then((value) =>
|
|
96
|
+
pathOptionKey.reduce((p, c) => p?.[c], value)
|
|
97
|
+
)
|
|
98
|
+
)
|
|
73
99
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
// ensure promise has resolved by the time it's needed
|
|
101
|
+
// If one of the promises throws it will bubble up to @middy/core
|
|
102
|
+
values = await Promise.allSettled(promises)
|
|
103
|
+
const errors = values
|
|
104
|
+
.filter((res) => res.status === 'rejected')
|
|
105
|
+
.map((res) => res.reason)
|
|
78
106
|
if (errors.length) {
|
|
79
|
-
|
|
80
|
-
error
|
|
81
|
-
|
|
107
|
+
// throw new Error('Failed to resolve internal values', { cause: errors })
|
|
108
|
+
const error = new Error('Failed to resolve internal values')
|
|
109
|
+
error.cause = errors
|
|
110
|
+
throw error
|
|
82
111
|
}
|
|
112
|
+
return keys.reduce(
|
|
113
|
+
(obj, key, index) => ({ ...obj, [sanitizeKey(key)]: values[index].value }),
|
|
114
|
+
{}
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
const sanitizeKeyPrefixLeadingNumber = /^([0-9])/
|
|
118
|
+
const sanitizeKeyRemoveDisallowedChar = /[^a-zA-Z0-9]+/g
|
|
119
|
+
export const sanitizeKey = (key) => {
|
|
120
|
+
return key
|
|
121
|
+
.replace(sanitizeKeyPrefixLeadingNumber, '_$1')
|
|
122
|
+
.replace(sanitizeKeyRemoveDisallowedChar, '_')
|
|
123
|
+
}
|
|
83
124
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}), {});
|
|
87
|
-
};
|
|
88
|
-
const sanitizeKeyPrefixLeadingNumber = /^([0-9])/;
|
|
89
|
-
const sanitizeKeyRemoveDisallowedChar = /[^a-zA-Z0-9]+/g;
|
|
90
|
-
export const sanitizeKey = key => {
|
|
91
|
-
return key.replace(sanitizeKeyPrefixLeadingNumber, '_$1').replace(sanitizeKeyRemoveDisallowedChar, '_');
|
|
92
|
-
};
|
|
93
|
-
const cache = {};
|
|
125
|
+
// fetch Cache
|
|
126
|
+
const cache = {} // key: { value:{fetchKey:Promise}, expiry }
|
|
94
127
|
export const processCache = (options, fetch = () => undefined, request) => {
|
|
95
|
-
const {
|
|
96
|
-
cacheExpiry,
|
|
97
|
-
cacheKey
|
|
98
|
-
} = options;
|
|
99
|
-
|
|
128
|
+
const { cacheExpiry, cacheKey } = options
|
|
100
129
|
if (cacheExpiry) {
|
|
101
|
-
const cached = getCache(cacheKey)
|
|
102
|
-
const unexpired = cached.expiry && (cacheExpiry < 0 || cached.expiry > Date.now())
|
|
130
|
+
const cached = getCache(cacheKey)
|
|
131
|
+
const unexpired = cached.expiry && (cacheExpiry < 0 || cached.expiry > Date.now())
|
|
103
132
|
|
|
104
133
|
if (unexpired && cached.modified) {
|
|
105
|
-
const value = fetch(request, cached.value)
|
|
134
|
+
const value = fetch(request, cached.value)
|
|
106
135
|
cache[cacheKey] = {
|
|
107
|
-
value: { ...cached.value,
|
|
108
|
-
...value
|
|
109
|
-
},
|
|
136
|
+
value: { ...cached.value, ...value },
|
|
110
137
|
expiry: cached.expiry
|
|
111
|
-
}
|
|
112
|
-
return cache[cacheKey]
|
|
138
|
+
}
|
|
139
|
+
return cache[cacheKey]
|
|
113
140
|
}
|
|
114
|
-
|
|
115
141
|
if (unexpired) {
|
|
116
|
-
return { ...cached,
|
|
117
|
-
cache: true
|
|
118
|
-
};
|
|
142
|
+
return { ...cached, cache: true }
|
|
119
143
|
}
|
|
120
144
|
}
|
|
145
|
+
const value = fetch(request)
|
|
121
146
|
|
|
122
|
-
const
|
|
123
|
-
const expiry = Date.now() + cacheExpiry;
|
|
124
|
-
|
|
147
|
+
const expiry = Date.now() + cacheExpiry
|
|
125
148
|
if (cacheExpiry) {
|
|
126
|
-
cache[cacheKey] = {
|
|
127
|
-
value,
|
|
128
|
-
expiry
|
|
129
|
-
};
|
|
149
|
+
cache[cacheKey] = { value, expiry }
|
|
130
150
|
}
|
|
151
|
+
return { value, expiry }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const getCache = (key) => {
|
|
155
|
+
if (!cache[key]) return {}
|
|
156
|
+
return cache[key]
|
|
157
|
+
}
|
|
131
158
|
|
|
132
|
-
|
|
133
|
-
value,
|
|
134
|
-
expiry
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
export const getCache = key => {
|
|
138
|
-
if (!cache[key]) return {};
|
|
139
|
-
return cache[key];
|
|
140
|
-
};
|
|
159
|
+
// Used to remove parts of a cache
|
|
141
160
|
export const modifyCache = (cacheKey, value) => {
|
|
142
|
-
if (!cache[cacheKey]) return
|
|
143
|
-
cache[cacheKey] = { ...cache[cacheKey],
|
|
144
|
-
|
|
145
|
-
modified: true
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
export const clearCache = (keys = null) => {
|
|
149
|
-
keys = keys ?? Object.keys(cache);
|
|
150
|
-
if (!Array.isArray(keys)) keys = [keys];
|
|
161
|
+
if (!cache[cacheKey]) return
|
|
162
|
+
cache[cacheKey] = { ...cache[cacheKey], value, modified: true }
|
|
163
|
+
}
|
|
151
164
|
|
|
165
|
+
export const clearCache = (keys = null) => {
|
|
166
|
+
keys = keys ?? Object.keys(cache)
|
|
167
|
+
if (!Array.isArray(keys)) keys = [keys]
|
|
152
168
|
for (const cacheKey of keys) {
|
|
153
|
-
cache[cacheKey] = undefined
|
|
169
|
+
cache[cacheKey] = undefined
|
|
154
170
|
}
|
|
155
|
-
}
|
|
156
|
-
export const jsonSafeParse = (string, reviver) => {
|
|
157
|
-
if (typeof string !== 'string') return string;
|
|
158
|
-
const firstChar = string[0];
|
|
159
|
-
if (firstChar !== '{' && firstChar !== '[' && firstChar !== '"') return string;
|
|
171
|
+
}
|
|
160
172
|
|
|
173
|
+
export const jsonSafeParse = (string, reviver) => {
|
|
174
|
+
if (typeof string !== 'string') return string
|
|
175
|
+
const firstChar = string[0]
|
|
176
|
+
if (firstChar !== '{' && firstChar !== '[' && firstChar !== '"') return string
|
|
161
177
|
try {
|
|
162
|
-
return JSON.parse(string, reviver)
|
|
178
|
+
return JSON.parse(string, reviver)
|
|
163
179
|
} catch (e) {}
|
|
164
180
|
|
|
165
|
-
return string
|
|
166
|
-
}
|
|
167
|
-
export const normalizeHttpResponse = request => {
|
|
168
|
-
var _response, _response2, _response3, _response4;
|
|
169
|
-
|
|
170
|
-
let {
|
|
171
|
-
response
|
|
172
|
-
} = request;
|
|
181
|
+
return string
|
|
182
|
+
}
|
|
173
183
|
|
|
184
|
+
export const normalizeHttpResponse = (request) => {
|
|
185
|
+
let { response } = request
|
|
174
186
|
if (response === undefined) {
|
|
175
|
-
response = {}
|
|
176
|
-
} else if (
|
|
177
|
-
response = {
|
|
178
|
-
body: response
|
|
179
|
-
};
|
|
187
|
+
response = {}
|
|
188
|
+
} else if (response?.statusCode === undefined && response?.body === undefined && response?.headers === undefined) {
|
|
189
|
+
response = { body: response }
|
|
180
190
|
}
|
|
191
|
+
response.headers ??= {}
|
|
192
|
+
request.response = response
|
|
193
|
+
return response
|
|
194
|
+
}
|
|
181
195
|
|
|
182
|
-
|
|
183
|
-
request.response = response;
|
|
184
|
-
return response;
|
|
185
|
-
};
|
|
186
|
-
const createErrorRegexp = /[^a-zA-Z]/g;
|
|
196
|
+
const createErrorRegexp = /[^a-zA-Z]/g
|
|
187
197
|
export const createError = (code, message, properties = {}) => {
|
|
188
|
-
const name = statuses[code].replace(createErrorRegexp, '')
|
|
189
|
-
const className = name.substr(-5) !== 'Error' ? name + 'Error' : name
|
|
190
|
-
|
|
191
|
-
function HttpError(message) {
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
Error
|
|
195
|
-
|
|
198
|
+
const name = statuses[code].replace(createErrorRegexp, '')
|
|
199
|
+
const className = name.substr(-5) !== 'Error' ? name + 'Error' : name
|
|
200
|
+
|
|
201
|
+
function HttpError (message) {
|
|
202
|
+
// create the error object
|
|
203
|
+
const msg = message ?? statuses[code]
|
|
204
|
+
const err = new Error(msg)
|
|
205
|
+
|
|
206
|
+
// capture a stack trace to the construction point
|
|
207
|
+
Error.captureStackTrace(err, HttpError)
|
|
208
|
+
|
|
209
|
+
// adjust the [[Prototype]]
|
|
210
|
+
Object.setPrototypeOf(err, HttpError.prototype)
|
|
211
|
+
|
|
212
|
+
// redefine the error message
|
|
196
213
|
Object.defineProperty(err, 'message', {
|
|
197
214
|
enumerable: true,
|
|
198
215
|
configurable: true,
|
|
199
216
|
value: msg,
|
|
200
217
|
writable: true
|
|
201
|
-
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
// redefine the error name
|
|
202
221
|
Object.defineProperty(err, 'name', {
|
|
203
222
|
enumerable: false,
|
|
204
223
|
configurable: true,
|
|
205
224
|
value: className,
|
|
206
225
|
writable: true
|
|
207
|
-
})
|
|
208
|
-
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
return err
|
|
209
229
|
}
|
|
210
230
|
|
|
211
|
-
inherits(HttpError, Error)
|
|
212
|
-
const desc = Object.getOwnPropertyDescriptor(HttpError, 'name')
|
|
213
|
-
desc.value = className
|
|
214
|
-
Object.defineProperty(HttpError, 'name', desc)
|
|
231
|
+
inherits(HttpError, Error)
|
|
232
|
+
const desc = Object.getOwnPropertyDescriptor(HttpError, 'name')
|
|
233
|
+
desc.value = className
|
|
234
|
+
Object.defineProperty(HttpError, 'name', desc)
|
|
235
|
+
|
|
215
236
|
Object.assign(HttpError.prototype, {
|
|
216
237
|
status: code,
|
|
217
238
|
statusCode: code,
|
|
218
239
|
expose: code < 500
|
|
219
|
-
}, properties)
|
|
220
|
-
|
|
221
|
-
|
|
240
|
+
}, properties)
|
|
241
|
+
|
|
242
|
+
return new HttpError(message)
|
|
243
|
+
}
|
|
244
|
+
|
|
222
245
|
export default {
|
|
223
246
|
createPrefetchClient,
|
|
224
247
|
createClient,
|
|
@@ -232,4 +255,4 @@ export default {
|
|
|
232
255
|
jsonSafeParse,
|
|
233
256
|
normalizeHttpResponse,
|
|
234
257
|
createError
|
|
235
|
-
}
|
|
258
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@middy/util",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.3",
|
|
4
4
|
"description": "🛵 The stylish Node.js middleware engine for AWS Lambda (util package)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"test": "npm run test:unit",
|
|
22
|
-
"test:unit": "ava"
|
|
22
|
+
"test:unit": "ava",
|
|
23
|
+
"test:benchmark": "node __benchmarks__/index.js"
|
|
23
24
|
},
|
|
24
25
|
"license": "MIT",
|
|
25
26
|
"keywords": [
|
|
@@ -45,12 +46,12 @@
|
|
|
45
46
|
"url": "https://github.com/middyjs/middy/issues"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
|
-
"@middy/core": "^3.0.0-alpha.
|
|
49
|
+
"@middy/core": "^3.0.0-alpha.3",
|
|
49
50
|
"@types/aws-lambda": "^8.10.76",
|
|
50
51
|
"@types/node": "^17.0.0",
|
|
51
52
|
"aws-sdk": "^2.939.0",
|
|
52
53
|
"aws-xray-sdk": "^3.3.3"
|
|
53
54
|
},
|
|
54
55
|
"homepage": "https://github.com/middyjs/middy#readme",
|
|
55
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "1441158711580313765e6d156046ef0fade0d156"
|
|
56
57
|
}
|