@allthings/cloud-toolkit 2.2.1 → 2.3.0-dev.1

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.
Files changed (61) hide show
  1. package/dist/lib.cjs.js +658 -2
  2. package/dist/lib.esm.js +654 -4
  3. package/dist/lib.umd.min.js +1 -1
  4. package/dist/src/alagarr/error-handler.d.ts +3 -0
  5. package/dist/src/alagarr/errors/clientError.d.ts +5 -0
  6. package/dist/src/alagarr/errors/serverError.d.ts +5 -0
  7. package/dist/src/alagarr/index.d.ts +6 -0
  8. package/dist/src/alagarr/index.test.d.ts +1 -0
  9. package/dist/src/alagarr/request/index.d.ts +4 -0
  10. package/dist/src/alagarr/request/index.test.d.ts +1 -0
  11. package/dist/src/alagarr/request/middleware/base64Body.d.ts +3 -0
  12. package/dist/src/alagarr/request/middleware/base64Body.test.d.ts +1 -0
  13. package/dist/src/alagarr/request/middleware/cookies.d.ts +6 -0
  14. package/dist/src/alagarr/request/middleware/cookies.test.d.ts +1 -0
  15. package/dist/src/alagarr/request/middleware/hostname.d.ts +3 -0
  16. package/dist/src/alagarr/request/middleware/hostname.test.d.ts +1 -0
  17. package/dist/src/alagarr/request/middleware/json-body.d.ts +3 -0
  18. package/dist/src/alagarr/request/middleware/json-body.test.d.ts +1 -0
  19. package/dist/src/alagarr/request/middleware/meta.d.ts +3 -0
  20. package/dist/src/alagarr/request/middleware/meta.test.d.ts +1 -0
  21. package/dist/src/alagarr/request/middleware/normalizeHeaders.d.ts +3 -0
  22. package/dist/src/alagarr/request/middleware/normalizeHeaders.test.d.ts +1 -0
  23. package/dist/src/alagarr/request/middleware/normalizeProgrammingModel.d.ts +3 -0
  24. package/dist/src/alagarr/request/middleware/normalizeProgrammingModel.test.d.ts +1 -0
  25. package/dist/src/alagarr/request/middleware/timestamp.d.ts +3 -0
  26. package/dist/src/alagarr/request/middleware/timestamp.test.d.ts +1 -0
  27. package/dist/src/alagarr/request/middleware/urlEncodedBody.d.ts +3 -0
  28. package/dist/src/alagarr/request/middleware/urlEncodedBody.test.d.ts +1 -0
  29. package/dist/src/alagarr/response/helpers/setHeader.d.ts +3 -0
  30. package/dist/src/alagarr/response/helpers/setHeader.test.d.ts +1 -0
  31. package/dist/src/alagarr/response/html.d.ts +3 -0
  32. package/dist/src/alagarr/response/html.test.d.ts +1 -0
  33. package/dist/src/alagarr/response/index.d.ts +3 -0
  34. package/dist/src/alagarr/response/index.test.d.ts +1 -0
  35. package/dist/src/alagarr/response/json.d.ts +3 -0
  36. package/dist/src/alagarr/response/json.test.d.ts +1 -0
  37. package/dist/src/alagarr/response/makeResponseObject.d.ts +3 -0
  38. package/dist/src/alagarr/response/makeResponseObject.test.d.ts +1 -0
  39. package/dist/src/alagarr/response/middleware/compress.d.ts +3 -0
  40. package/dist/src/alagarr/response/middleware/compress.test.d.ts +1 -0
  41. package/dist/src/alagarr/response/middleware/contentLength.d.ts +3 -0
  42. package/dist/src/alagarr/response/middleware/contentLength.test.d.ts +1 -0
  43. package/dist/src/alagarr/response/middleware/csp.d.ts +3 -0
  44. package/dist/src/alagarr/response/middleware/csp.test.d.ts +1 -0
  45. package/dist/src/alagarr/response/middleware/enforcedHeaders.d.ts +3 -0
  46. package/dist/src/alagarr/response/middleware/enforcedHeaders.test.d.ts +1 -0
  47. package/dist/src/alagarr/response/middleware/etag.d.ts +4 -0
  48. package/dist/src/alagarr/response/middleware/etag.test.d.ts +1 -0
  49. package/dist/src/alagarr/response/middleware/log.d.ts +4 -0
  50. package/dist/src/alagarr/response/redirect.d.ts +3 -0
  51. package/dist/src/alagarr/response/redirect.test.d.ts +1 -0
  52. package/dist/src/alagarr/response/respondTo.d.ts +3 -0
  53. package/dist/src/alagarr/response/respondTo.test.d.ts +1 -0
  54. package/dist/src/alagarr/response/text.d.ts +3 -0
  55. package/dist/src/alagarr/response/text.test.d.ts +1 -0
  56. package/dist/src/alagarr/types.d.ts +79 -0
  57. package/dist/src/alagarr/utils/applyMiddleware.d.ts +3 -0
  58. package/dist/src/alagarr/utils/applyMiddleware.test.d.ts +1 -0
  59. package/dist/src/index.d.ts +1 -0
  60. package/dist/test/fixtures/requests.d.ts +5 -0
  61. package/package.json +20 -13
package/dist/lib.esm.js CHANGED
@@ -1,14 +1,664 @@
1
+ import * as querystring from 'querystring';
2
+ import * as zlib from 'zlib';
3
+ import * as crypto from 'crypto';
1
4
  import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
5
  import { DynamoDBDocumentClient, UpdateCommand, ScanCommand, QueryCommand, PutCommand, GetCommand, DeleteCommand, BatchWriteCommand } from '@aws-sdk/lib-dynamodb';
3
6
  import Bottleneck from 'bottleneck';
4
7
  import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
5
8
  import { SSMClient, GetParametersCommand, GetParameterCommand } from '@aws-sdk/client-ssm';
6
- import * as aws from '@aws-sdk/client-ses';
9
+ import * as aws$1 from '@aws-sdk/client-ses';
7
10
  import { createTransport } from 'nodemailer';
8
11
  import { SQSClient, SendMessageCommand, GetQueueUrlCommand, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
9
12
  import { format, createLogger, transports } from 'winston';
10
13
  export { Logger } from 'winston';
11
14
 
15
+ const defaultErrorHandler = (request, response, error) => {
16
+ const { requestId } = request.requestContext;
17
+ const errorName = error.name && typeof error.name === 'string'
18
+ ? error.name.replace(/([A-Z])([A-Z])([a-z])|([a-z])([A-Z])/g, '$1$4 $2$3$5')
19
+ : 'Error';
20
+ const errorMessage = error.message || '';
21
+ const statusCode = error.statusCode ? ` ${error.statusCode}` : '';
22
+ return error.name === 'ClientError' ||
23
+ (error.statusCode >= 400 && error.statusCode < 500)
24
+ ? response.respondTo({
25
+ html: `<html><body><strong>${errorName}${statusCode}</strong>: ${errorMessage}<br/>Request ID: ${requestId}</body></html>`,
26
+ json: { error: error.name, message: errorMessage, requestId },
27
+ }, error.statusCode || 400)
28
+ : response.respondTo({
29
+ html: `<html><body>An internal server error occurred.<br/>Request ID: ${requestId}</body></html>`,
30
+ json: {
31
+ error: 'Internal server error',
32
+ message: 'An internal server error occurred',
33
+ requestId,
34
+ },
35
+ }, 500);
36
+ };
37
+
38
+ class ClientError extends Error {
39
+ name = 'ClientError';
40
+ statusCode = 400;
41
+ constructor(message, name, statusCode = 400) {
42
+ super(String(message));
43
+ this.name = name || this.name;
44
+ this.statusCode = statusCode;
45
+ }
46
+ }
47
+
48
+ class ServerError extends Error {
49
+ name = 'ServerError';
50
+ statusCode = 500;
51
+ constructor(message, name, statusCode = 500) {
52
+ super(String(message));
53
+ this.name = name || this.name;
54
+ this.statusCode = statusCode;
55
+ }
56
+ }
57
+
58
+ const applyMiddleware = async (middlewareList, initialData, ...arguments_) => middlewareList.reduce(async (applied, middleware) => middleware(await applied, ...arguments_), Promise.resolve(initialData));
59
+
60
+ const decodeBase64 = (encoded) => {
61
+ const bodyBuffer = Buffer.from(encoded, 'base64');
62
+ return bodyBuffer.toString('utf8');
63
+ };
64
+ const decodeBase64Body = (request) => request.isBase64Encoded && typeof request.body === 'string'
65
+ ? {
66
+ ...request,
67
+ body: decodeBase64(request.body),
68
+ }
69
+ : request;
70
+
71
+ const parseCookiePair = (pair) => {
72
+ const indexOfEqualCharacter = pair.indexOf('=');
73
+ const looksLikeKeyValue = indexOfEqualCharacter >= 0;
74
+ const key = looksLikeKeyValue &&
75
+ pair.slice(0, Math.max(0, indexOfEqualCharacter)).trim();
76
+ const value = looksLikeKeyValue &&
77
+ pair.substr(indexOfEqualCharacter + 1, pair.length).trim();
78
+ const cleanValue = value && '"' === value[0] ? value.slice(1, -1) : value;
79
+ return key ? { [key]: cleanValue ? decodeURIComponent(cleanValue) : '' } : {};
80
+ };
81
+ const parseCookieHeader = (cookieHeader) => {
82
+ const pairs = cookieHeader.split(/; */);
83
+ return pairs.reduce((parsedCookies, pair) => ({ ...parsedCookies, ...parseCookiePair(pair) }), {});
84
+ };
85
+ const cookies = (request) => ({
86
+ ...request,
87
+ cookies: parseCookieHeader((request.headers && request.headers.cookie) || ''),
88
+ });
89
+
90
+ const setHostname = (request) => ({
91
+ ...request,
92
+ hostname: (request.headers && request.headers.host) || undefined,
93
+ });
94
+
95
+ const parseJson = (stringified) => {
96
+ try {
97
+ return JSON.parse(stringified);
98
+ }
99
+ catch {
100
+ return {};
101
+ }
102
+ };
103
+ const parseJsonBody = (request) => {
104
+ const { headers, body } = request;
105
+ return headers['content-type'] &&
106
+ headers['content-type'].split(';').shift() === 'application/json' &&
107
+ typeof body === 'string'
108
+ ? { ...request, body: parseJson(body) }
109
+ : request;
110
+ };
111
+
112
+ const state = new Map([['invocationCount', 0]]);
113
+ const meta = (request) => {
114
+ const invocationCount = Number(state.get('invocationCount'));
115
+ return {
116
+ ...request,
117
+ meta: {
118
+ ...request.meta,
119
+ coldStart: !invocationCount,
120
+ invocationCount: state
121
+ .set('invocationCount', invocationCount + 1)
122
+ .get('invocationCount'),
123
+ },
124
+ };
125
+ };
126
+
127
+ const normalizeHeaders = ({ headers = {}, ...request }) => ({
128
+ ...request,
129
+ headers: Object.keys(headers).reduce((normalizedHeaders, key) => {
130
+ const normalizedHeaderKey = key.toLowerCase();
131
+ return request.provider === 'aws' &&
132
+ Reflect.has(normalizedHeaders, normalizedHeaderKey) &&
133
+ normalizedHeaderKey === 'content-type' &&
134
+ headers[key] === 'application/json'
135
+ ? normalizedHeaders
136
+ : {
137
+ ...normalizedHeaders,
138
+ [normalizedHeaderKey]: headers[key],
139
+ };
140
+ }, {}),
141
+ });
142
+
143
+ const normalizeProgrammingModel = (request) => ({
144
+ ...request,
145
+ method: request.httpMethod || '',
146
+ provider: 'aws',
147
+ query: request.queryStringParameters || {},
148
+ source: request.requestContext ? 'api-gateway' : request.source,
149
+ });
150
+
151
+ const addTimestamp = (request) => ({
152
+ ...request,
153
+ timestamp: Date.now(),
154
+ });
155
+
156
+ const parseUrlEncodedBody = (request) => {
157
+ const { headers, body } = request;
158
+ return headers['content-type'] &&
159
+ headers['content-type'].split(';').shift() ===
160
+ 'application/x-www-form-urlencoded' &&
161
+ typeof body === 'string'
162
+ ? { ...request, body: querystring.parse(body) }
163
+ : request;
164
+ };
165
+
166
+ var makeRequest = async (event, context, options = {}) => applyMiddleware([
167
+ addTimestamp,
168
+ normalizeProgrammingModel,
169
+ normalizeHeaders,
170
+ decodeBase64Body,
171
+ cookies,
172
+ parseUrlEncodedBody,
173
+ parseJsonBody,
174
+ setHostname,
175
+ meta,
176
+ ...(options.requestMiddleware || []),
177
+ ], Object.freeze({
178
+ ...event,
179
+ context,
180
+ }), options);
181
+
182
+ const setHeader = (response, responseData, key, value) => {
183
+ Object.assign(responseData.headers, { [key]: value });
184
+ return response;
185
+ };
186
+
187
+ const makeResponseObject = (responseData, body = '', statusCode = 200, { headers = {}, ...options } = {}, contentType) => Object.freeze({
188
+ ...responseData,
189
+ body: body || '',
190
+ headers: {
191
+ ...(responseData && responseData.headers),
192
+ 'content-type': contentType,
193
+ ...headers,
194
+ },
195
+ statusCode,
196
+ ...options,
197
+ });
198
+
199
+ const html = (responseData, _, body, statusCode, options) => makeResponseObject(responseData, body, statusCode, options, 'text/html');
200
+
201
+ const stringifyIfNotStringifiedJson = (data) => {
202
+ const maybeTrimmedDataString = typeof data === 'string' ? data.trim() : data;
203
+ const isStringifiedJson = typeof data === 'string' &&
204
+ ['{', '"'].includes(maybeTrimmedDataString[0]) &&
205
+ ['}', '"'].includes(maybeTrimmedDataString.at(-1));
206
+ return isStringifiedJson ? maybeTrimmedDataString : JSON.stringify(data);
207
+ };
208
+ const json = (responseData, _, body, statusCode, options) => makeResponseObject(responseData, stringifyIfNotStringifiedJson(body), statusCode, options, 'application/json');
209
+
210
+ const COMPRESSIBLE = [
211
+ 'application/activity+json',
212
+ 'application/alto-costmap+json',
213
+ 'application/alto-costmapfilter+json',
214
+ 'application/alto-directory+json',
215
+ 'application/alto-endpointcost+json',
216
+ 'application/alto-endpointcostparams+json',
217
+ 'application/alto-endpointprop+json',
218
+ 'application/alto-endpointpropparams+json',
219
+ 'application/alto-error+json',
220
+ 'application/alto-networkmap+json',
221
+ 'application/alto-networkmapfilter+json',
222
+ 'application/atom+xml',
223
+ 'application/calendar+json',
224
+ 'application/coap-group+json',
225
+ 'application/csvm+json',
226
+ 'application/dart',
227
+ 'application/dicom+json',
228
+ 'application/ecmascript',
229
+ 'application/fhir+json',
230
+ 'application/fido.trusted-apps+json',
231
+ 'application/geo+json',
232
+ 'application/javascript',
233
+ 'application/jf2feed+json',
234
+ 'application/jose+json',
235
+ 'application/jrd+json',
236
+ 'application/json',
237
+ 'application/json-patch+json',
238
+ 'application/jsonml+json',
239
+ 'application/jwk+json',
240
+ 'application/jwk-set+json',
241
+ 'application/ld+json',
242
+ 'application/manifest+json',
243
+ 'application/merge-patch+json',
244
+ 'application/mud+json',
245
+ 'application/postscript',
246
+ 'application/ppsp-tracker+json',
247
+ 'application/problem+json',
248
+ 'application/raml+yaml',
249
+ 'application/rdap+json',
250
+ 'application/rdf+xml',
251
+ 'application/reputon+json',
252
+ 'application/rss+xml',
253
+ 'application/rtf',
254
+ 'application/scim+json',
255
+ 'application/soap+xml',
256
+ 'application/tar',
257
+ 'application/vcard+json',
258
+ 'application/vnd.amadeus+json',
259
+ 'application/vnd.api+json',
260
+ 'application/vnd.apothekende.reservation+json',
261
+ 'application/vnd.avalon+json',
262
+ 'application/vnd.bbf.usp.msg+json',
263
+ 'application/vnd.bekitzur-stech+json',
264
+ 'application/vnd.capasystems-pg+json',
265
+ 'application/vnd.collection+json',
266
+ 'application/vnd.collection.doc+json',
267
+ 'application/vnd.collection.next+json',
268
+ 'application/vnd.coreos.ignition+json',
269
+ 'application/vnd.dart',
270
+ 'application/vnd.datapackage+json',
271
+ 'application/vnd.dataresource+json',
272
+ 'application/vnd.document+json',
273
+ 'application/vnd.drive+json',
274
+ 'application/vnd.geo+json',
275
+ 'application/vnd.google-earth.kml+xml',
276
+ 'application/vnd.hal+json',
277
+ 'application/vnd.hc+json',
278
+ 'application/vnd.heroku+json',
279
+ 'application/vnd.hyper+json',
280
+ 'application/vnd.hyper-item+json',
281
+ 'application/vnd.hyperdrive+json',
282
+ 'application/vnd.ims.lis.v2.result+json',
283
+ 'application/vnd.ims.lti.v2.toolconsumerprofile+json',
284
+ 'application/vnd.ims.lti.v2.toolproxy+json',
285
+ 'application/vnd.ims.lti.v2.toolproxy.id+json',
286
+ 'application/vnd.ims.lti.v2.toolsettings+json',
287
+ 'application/vnd.ims.lti.v2.toolsettings.simple+json',
288
+ 'application/vnd.las.las+json',
289
+ 'application/vnd.mason+json',
290
+ 'application/vnd.micro+json',
291
+ 'application/vnd.miele+json',
292
+ 'application/vnd.mozilla.xul+xml',
293
+ 'application/vnd.ms-fontobject',
294
+ 'application/vnd.ms-opentype',
295
+ 'application/vnd.nearst.inv+json',
296
+ 'application/vnd.oftn.l10n+json',
297
+ 'application/vnd.oma.lwm2m+json',
298
+ 'application/vnd.oracle.resource+json',
299
+ 'application/vnd.pagerduty+json',
300
+ 'application/vnd.restful+json',
301
+ 'application/vnd.siren+json',
302
+ 'application/vnd.sun.wadl+xml',
303
+ 'application/vnd.tableschema+json',
304
+ 'application/vnd.vel+json',
305
+ 'application/vnd.xacml+json',
306
+ 'application/voucher-cms+json',
307
+ 'application/wasm',
308
+ 'application/webpush-options+json',
309
+ 'application/x-httpd-php',
310
+ 'application/x-javascript',
311
+ 'application/x-ns-proxy-autoconfig',
312
+ 'application/x-sh',
313
+ 'application/x-tar',
314
+ 'application/x-virtualbox-hdd',
315
+ 'application/x-virtualbox-ova',
316
+ 'application/x-virtualbox-ovf',
317
+ 'application/x-virtualbox-vbox',
318
+ 'application/x-virtualbox-vdi',
319
+ 'application/x-virtualbox-vhd',
320
+ 'application/x-virtualbox-vmdk',
321
+ 'application/x-web-app-manifest+json',
322
+ 'application/x-www-form-urlencoded',
323
+ 'application/xhtml+xml',
324
+ 'application/xml',
325
+ 'application/xml-dtd',
326
+ 'application/xop+xml',
327
+ 'application/yang-data+json',
328
+ 'application/yang-patch+json',
329
+ 'font/otf',
330
+ 'image/bmp',
331
+ 'image/svg+xml',
332
+ 'image/vnd.adobe.photoshop',
333
+ 'image/x-icon',
334
+ 'image/x-ms-bmp',
335
+ 'message/imdn+xml',
336
+ 'message/rfc822',
337
+ 'model/gltf+json',
338
+ 'model/gltf-binary',
339
+ 'model/x3d+xml',
340
+ 'text/cache-manifest',
341
+ 'text/calender',
342
+ 'text/cmd',
343
+ 'text/css',
344
+ 'text/csv',
345
+ 'text/html',
346
+ 'text/javascript',
347
+ 'text/jsx',
348
+ 'text/markdown',
349
+ 'text/n3',
350
+ 'text/plain',
351
+ 'text/richtext',
352
+ 'text/rtf',
353
+ 'text/tab-separated-values',
354
+ 'text/uri-list',
355
+ 'text/vcard',
356
+ 'text/vtt',
357
+ 'text/x-gwt-rpc',
358
+ 'text/x-jquery-tmpl',
359
+ 'text/x-markdown',
360
+ 'text/x-org',
361
+ 'text/x-processing',
362
+ 'text/x-suse-ymp',
363
+ 'text/xml',
364
+ 'x-shader/x-fragment',
365
+ 'x-shader/x-vertex',
366
+ ];
367
+ const ZLIB_DEFAULT_OPTIONS = {
368
+ level: 5,
369
+ };
370
+ const acceptEncoding = (accepts, encodings) => accepts.find((accept) => encodings.some((encoding) => accept === encoding));
371
+ const getBestAcceptEncoding = (request) => {
372
+ const { headers } = request;
373
+ const acceptEncodings = (headers['accept-encoding'] || '').split(/, */);
374
+ const encoding = acceptEncoding(acceptEncodings, [
375
+ 'gzip',
376
+ 'deflate',
377
+ 'identity',
378
+ ]);
379
+ return encoding === 'deflate' && acceptEncoding(acceptEncodings, ['gzip'])
380
+ ? acceptEncoding(acceptEncodings, ['gzip', 'identity'])
381
+ : encoding;
382
+ };
383
+ const compressResponse = (response, encoding) => {
384
+ const { body, headers, ...rest } = response;
385
+ const compressed = encoding === 'gzip'
386
+ ? zlib.gzipSync(body, ZLIB_DEFAULT_OPTIONS)
387
+ : zlib.deflateSync(body, ZLIB_DEFAULT_OPTIONS);
388
+ return {
389
+ ...rest,
390
+ body: compressed.toString('base64'),
391
+ headers: {
392
+ ...headers,
393
+ 'content-encoding': encoding,
394
+ 'content-length': compressed.byteLength,
395
+ },
396
+ isBase64Encoded: true,
397
+ };
398
+ };
399
+ const compress = (response, request) => {
400
+ const { body, headers } = response;
401
+ const encoding = getBestAcceptEncoding(request);
402
+ const weShouldCompress = COMPRESSIBLE.includes(headers['content-type']) &&
403
+ encoding !== undefined &&
404
+ encoding !== 'identity' &&
405
+ body &&
406
+ body.length > 256;
407
+ return weShouldCompress && encoding
408
+ ? compressResponse(response, encoding)
409
+ : response;
410
+ };
411
+
412
+ const contentLengthHeader = (response) => {
413
+ const { body, headers, ...rest } = response;
414
+ return {
415
+ ...rest,
416
+ body,
417
+ headers: {
418
+ ...headers,
419
+ 'content-length': headers['content-length'] || Buffer.byteLength(body),
420
+ },
421
+ };
422
+ };
423
+
424
+ const DEFAULT_POLICIES = {
425
+ 'default-src': "'self' https: 'unsafe-inline'",
426
+ 'img-src': '* data: blob:',
427
+ };
428
+ const cspHeaders = (response, _, options) => {
429
+ const { headers = {}, ...rest } = response;
430
+ const cspPolicies = {
431
+ ...DEFAULT_POLICIES,
432
+ ...options.cspPolicies,
433
+ };
434
+ const cspPolicy = Object.keys(cspPolicies)
435
+ .map((policy) => `${policy} ${cspPolicies[policy]}`)
436
+ .join(';');
437
+ return {
438
+ ...rest,
439
+ headers: {
440
+ ...headers,
441
+ 'content-security-policy': cspPolicy,
442
+ 'referrer-policy': 'strict-origin-when-cross-origin',
443
+ 'x-content-type-options': 'nosniff',
444
+ 'x-frame-options': {
445
+ "'none'": 'DENY',
446
+ "'self'": 'SAMEORIGIN',
447
+ }[cspPolicies['frame-ancestors']] || undefined,
448
+ 'x-xss-protection': '1; mode=block',
449
+ },
450
+ };
451
+ };
452
+
453
+ const enforcedHeaders = (response, _, options) => ({
454
+ ...response,
455
+ headers: {
456
+ ...response.headers,
457
+ ...(options.headers ?? {}),
458
+ },
459
+ });
460
+
461
+ const EMPTY_ENTITY_TAG = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
462
+ const entitytag = (entity) => {
463
+ const hash = crypto
464
+ .createHash('sha1')
465
+ .update(entity, 'utf8')
466
+ .digest('base64')
467
+ .slice(0, 27);
468
+ const length = typeof entity === 'string'
469
+ ? Buffer.byteLength(entity, 'utf8')
470
+ : entity.length;
471
+ return `"${length.toString(16)}-${hash}"`;
472
+ };
473
+ const etag = (entity) => entity.length === 0 ? EMPTY_ENTITY_TAG : entitytag(entity);
474
+ const etagHeader = (response) => ({
475
+ ...response,
476
+ headers: {
477
+ ...response.headers,
478
+ etag: etag(response.body),
479
+ },
480
+ });
481
+
482
+ const aws = (request) => {
483
+ const { headers: requestHeaders, requestContext, context } = request;
484
+ return {
485
+ apig: {
486
+ requestId: requestContext.requestId,
487
+ resourcePath: requestContext.resourcePath,
488
+ },
489
+ awsAccountId: requestContext.accountId,
490
+ cloudfront: {
491
+ country: requestHeaders['cloudfront-viewer-country'],
492
+ desktop: requestHeaders['cloudfront-is-desktop-viewer'] === 'true',
493
+ mobile: requestHeaders['cloudfront-is-mobile-viewer'] === 'true',
494
+ smarttv: requestHeaders['cloudfront-is-smarttv-viewer'] === 'true',
495
+ tablet: requestHeaders['cloudfront-is-tablet-viewer'] === 'true',
496
+ },
497
+ deploymentStage: requestContext.stage,
498
+ lambda: {
499
+ functionName: context.functionName,
500
+ memoryLimit: Number(context.memoryLimitInMB),
501
+ remainingExecutionTime: context.getRemainingTimeInMillis && context.getRemainingTimeInMillis(),
502
+ requestId: context.awsRequestId,
503
+ },
504
+ sourceIp: requestContext.identity.sourceIp,
505
+ };
506
+ };
507
+ const logger$1 = (request, response) => {
508
+ const { source, headers: requestHeaders, meta, requestContext, provider, hostname, body, timestamp, path, query, } = request;
509
+ const error = response.statusCode >= 500 && response.statusCode < 600;
510
+ const logEntry = {
511
+ dateTime: new Date().toISOString(),
512
+ eventSource: source,
513
+ hostname,
514
+ httpMethod: requestContext.httpMethod,
515
+ httpProtocol: requestHeaders.via && requestHeaders.via.split(' ')[0],
516
+ httpReferrer: requestHeaders.referer,
517
+ httpUserAgent: requestHeaders['user-agent'],
518
+ requestContentLength: (body && body.length > 0) || 0,
519
+ requestTime: Date.now() - timestamp,
520
+ requestUri: path +
521
+ (query && Object.keys(query).length > 0
522
+ ? `?${querystring.stringify(query)}`
523
+ : ''),
524
+ responseContentLength: response.headers['content-length'],
525
+ statusCode: response.statusCode,
526
+ xForwardedHost: requestHeaders['x-forwarded-host'],
527
+ ...meta,
528
+ ...(provider === 'aws' && aws(request)),
529
+ };
530
+ return ((error ? process.stderr : process.stdout).write(JSON.stringify(logEntry)) &&
531
+ logEntry);
532
+ };
533
+ const logResponse = (response, request, options) => (options.enableLogger &&
534
+ (typeof options.logger === 'function'
535
+ ? options.logger(request, response)
536
+ : logger$1(request, response)) &&
537
+ response) ||
538
+ response;
539
+
540
+ const redirect = (responseData, _, location, statusCode = 302, options = {}) => makeResponseObject(responseData, '', statusCode, {
541
+ ...options,
542
+ headers: {
543
+ ...options.headers,
544
+ location,
545
+ },
546
+ });
547
+
548
+ const getBestMatchedFormat = (formats, accept) => {
549
+ const fallback = formats.default || 'html';
550
+ const acceptFormats = accept &&
551
+ typeof accept === 'string' &&
552
+ accept
553
+ .split(';')[0]
554
+ .split(',')
555
+ .map((type) => {
556
+ const mimeParts = type.split('/');
557
+ return mimeParts.at(-1);
558
+ });
559
+ return ((acceptFormats &&
560
+ acceptFormats.find((type) => Reflect.has(formats, type))) ||
561
+ fallback);
562
+ };
563
+ const getBestMatchedResponseHelper = (format) => {
564
+ switch (format) {
565
+ case 'html':
566
+ return html;
567
+ case 'json':
568
+ return json;
569
+ }
570
+ throw new TypeError(`"${format}" is not a valid Alagarr respondTo() format.`);
571
+ };
572
+ const getBestMatchedFormatBody = (formats, format) => {
573
+ switch (format) {
574
+ case 'html':
575
+ return formats.html;
576
+ case 'json':
577
+ return formats.json;
578
+ }
579
+ throw new TypeError(`"${format}" is not a valid Alagarr respondTo() format.`);
580
+ };
581
+ const respondTo = (responseData, request, formats, statusCode, options) => {
582
+ const { headers: { accept }, } = request;
583
+ const format = getBestMatchedFormat(formats, accept);
584
+ const responseHelper = getBestMatchedResponseHelper(format);
585
+ const body = getBestMatchedFormatBody(formats, format);
586
+ return responseHelper(responseData, request, body, statusCode, options);
587
+ };
588
+
589
+ const text = (responseData, _, body, statusCode, options) => makeResponseObject(responseData, body, statusCode, options, 'text/plain');
590
+
591
+ const middlewareMap = {
592
+ enableCompression: compress,
593
+ enableContentLength: contentLengthHeader,
594
+ enableCspHeaders: cspHeaders,
595
+ enableETagHeader: etagHeader,
596
+ enableEnforcedHeaders: enforcedHeaders,
597
+ };
598
+ var makeResponse = async (request, callback, options) => {
599
+ const responseData = makeResponseObject();
600
+ const responseWithHelpers = [
601
+ text,
602
+ html,
603
+ json,
604
+ redirect,
605
+ respondTo,
606
+ ].reduce((methods, method) => ({
607
+ ...methods,
608
+ [method.name]: async (...arguments_) => callback(null, await applyMiddleware(Object.keys(middlewareMap).reduce((middlewareList, middleware) => options[middleware]
609
+ ? [...middlewareList, middlewareMap[middleware]]
610
+ : middlewareList, [
611
+ ...(options.responseMiddleware || []),
612
+ ...(options.enableLogger ? [logResponse] : []),
613
+ ]), method(responseData, request, ...arguments_), request, options)),
614
+ }), { raw: callback });
615
+ const response = [setHeader].reduce((responseMethods, method) => ({
616
+ ...responseMethods,
617
+ [method.name]: (...arguments_) => method(response, responseData, ...arguments_),
618
+ }), responseWithHelpers);
619
+ return response;
620
+ };
621
+
622
+ const DEFAULT_OPTIONS = {
623
+ cspPolicies: [],
624
+ enableCompression: true,
625
+ enableContentLength: true,
626
+ enableCspHeaders: true,
627
+ enableETagHeader: true,
628
+ enableEnforcedHeaders: true,
629
+ enableLogger: true,
630
+ enableStrictTransportSecurity: true,
631
+ errorHandler: defaultErrorHandler,
632
+ requestMiddleware: [],
633
+ responseMiddleware: [],
634
+ };
635
+ const noopHandler = (_, _1) => {
636
+ throw new ServerError('Misconfiguration in Alagarr setup. No handler function was provided.');
637
+ };
638
+ const alagarr = (handler = noopHandler, options = DEFAULT_OPTIONS) => async (event, context, callback) => {
639
+ const mergedOptions = {
640
+ ...DEFAULT_OPTIONS,
641
+ ...options,
642
+ };
643
+ const request = await makeRequest(event, context, mergedOptions);
644
+ const response = await makeResponse(request, callback, mergedOptions);
645
+ try {
646
+ return await handler(request, response, context);
647
+ }
648
+ catch (handlerError) {
649
+ const errorHandler = typeof mergedOptions.errorHandler === 'function'
650
+ ? mergedOptions.errorHandler
651
+ : defaultErrorHandler;
652
+ try {
653
+ return await errorHandler(request, response, handlerError);
654
+ }
655
+ catch (error) {
656
+ console.error('There was an error in the error handler provided to Alagaar', error, errorHandler.toString());
657
+ return defaultErrorHandler(request, response, error);
658
+ }
659
+ }
660
+ };
661
+
12
662
  const DEFAULT_AWS_CONFIGURATION = {
13
663
  region: 'eu-central-2',
14
664
  };
@@ -333,9 +983,9 @@ const ses = (fromEmailAddress, userConfig) => {
333
983
  ...DEFAULT_CLIENT_CONFIG,
334
984
  ...userConfig,
335
985
  };
336
- const sesClient = new aws.SESClient(clientConfig);
986
+ const sesClient = new aws$1.SESClient(clientConfig);
337
987
  const transport = createTransport(userConfig.customSMTPConfig ?? {
338
- SES: { aws, ses: sesClient },
988
+ SES: { aws: aws$1, ses: sesClient },
339
989
  });
340
990
  return {
341
991
  sendMail: (options, responseCallback) => sendMail(config, transport, options, fromEmailAddress, responseCallback),
@@ -449,4 +1099,4 @@ const logger = (config) => {
449
1099
  });
450
1100
  };
451
1101
 
452
- export { DEFAULT_CLIENT_CONFIG$1 as DEFAULT_PARAMETER_STORE_CLIENT_CONFIG, DEFAULT_CLIENT_CONFIG as DEFAULT_SES_CLIENT_CONFIG, dynamodb, lambda, logger, parameterStore, ses, sqs };
1102
+ export { ClientError, DEFAULT_CLIENT_CONFIG$1 as DEFAULT_PARAMETER_STORE_CLIENT_CONFIG, DEFAULT_CLIENT_CONFIG as DEFAULT_SES_CLIENT_CONFIG, ServerError, alagarr, dynamodb, lambda, logger, parameterStore, ses, sqs };