@allthings/cloud-toolkit 4.2.0 → 4.3.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.
Files changed (36) hide show
  1. package/dist/lib.cjs.js +21 -1155
  2. package/dist/lib.esm.js +21 -1121
  3. package/package.json +3 -2
  4. package/dist/src/alagarr/index.test.d.ts +0 -1
  5. package/dist/src/alagarr/request/index.test.d.ts +0 -1
  6. package/dist/src/alagarr/request/middleware/base64Body.test.d.ts +0 -1
  7. package/dist/src/alagarr/request/middleware/cookies.test.d.ts +0 -1
  8. package/dist/src/alagarr/request/middleware/hostname.test.d.ts +0 -1
  9. package/dist/src/alagarr/request/middleware/json-body.test.d.ts +0 -1
  10. package/dist/src/alagarr/request/middleware/meta.test.d.ts +0 -1
  11. package/dist/src/alagarr/request/middleware/normalizeHeaders.test.d.ts +0 -1
  12. package/dist/src/alagarr/request/middleware/normalizeProgrammingModel.test.d.ts +0 -1
  13. package/dist/src/alagarr/request/middleware/timestamp.test.d.ts +0 -1
  14. package/dist/src/alagarr/request/middleware/urlEncodedBody.test.d.ts +0 -1
  15. package/dist/src/alagarr/response/helpers/setHeader.test.d.ts +0 -1
  16. package/dist/src/alagarr/response/html.test.d.ts +0 -1
  17. package/dist/src/alagarr/response/index.test.d.ts +0 -1
  18. package/dist/src/alagarr/response/json.test.d.ts +0 -1
  19. package/dist/src/alagarr/response/makeResponseObject.test.d.ts +0 -1
  20. package/dist/src/alagarr/response/middleware/compress.test.d.ts +0 -1
  21. package/dist/src/alagarr/response/middleware/contentLength.test.d.ts +0 -1
  22. package/dist/src/alagarr/response/middleware/csp.test.d.ts +0 -1
  23. package/dist/src/alagarr/response/middleware/enforcedHeaders.test.d.ts +0 -1
  24. package/dist/src/alagarr/response/middleware/etag.test.d.ts +0 -1
  25. package/dist/src/alagarr/response/redirect.test.d.ts +0 -1
  26. package/dist/src/alagarr/response/respondTo.test.d.ts +0 -1
  27. package/dist/src/alagarr/response/text.test.d.ts +0 -1
  28. package/dist/src/alagarr/utils/applyMiddleware.test.d.ts +0 -1
  29. package/dist/src/aws/dynamodb.test.d.ts +0 -1
  30. package/dist/src/aws/lambda.test.d.ts +0 -1
  31. package/dist/src/aws/parameterStore.test.d.ts +0 -1
  32. package/dist/src/aws/ses.test.d.ts +0 -1
  33. package/dist/src/aws/sqs.test.d.ts +0 -1
  34. package/dist/src/logger/logger.test.d.ts +0 -1
  35. package/dist/test/fixtures/requests.d.ts +0 -5
  36. package/dist/test/setup.d.ts +0 -0
package/dist/lib.esm.js CHANGED
@@ -1,1121 +1,21 @@
1
- import * as querystring from 'querystring';
2
- import * as zlib from 'zlib';
3
- import * as crypto from 'crypto';
4
- import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
5
- import { DynamoDBDocumentClient, UpdateCommand, ScanCommand, QueryCommand, PutCommand, GetCommand, DeleteCommand, BatchWriteCommand } from '@aws-sdk/lib-dynamodb';
6
- import Bottleneck from 'bottleneck';
7
- import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
8
- import { SSMClient, GetParametersCommand, GetParameterCommand } from '@aws-sdk/client-ssm';
9
- import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
10
- import { createTransport } from 'nodemailer';
11
- import { SQSClient, SendMessageCommand, SendMessageBatchCommand, GetQueueUrlCommand, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
12
- import { format, createLogger, transports } from 'winston';
13
- export { Logger } from 'winston';
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 !== -1;
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?.cookie || ''),
88
- });
89
-
90
- const setHostname = (request) => ({
91
- ...request,
92
- hostname: 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']?.split(';').shift() === 'application/json' &&
106
- typeof body === 'string'
107
- ? { ...request, body: parseJson(body) }
108
- : request;
109
- };
110
-
111
- const state = new Map([['invocationCount', 0]]);
112
- const meta = (request) => {
113
- const invocationCount = Number(state.get('invocationCount'));
114
- return {
115
- ...request,
116
- meta: {
117
- ...request.meta,
118
- coldStart: !invocationCount,
119
- invocationCount: state
120
- .set('invocationCount', invocationCount + 1)
121
- .get('invocationCount'),
122
- },
123
- };
124
- };
125
-
126
- const normalizeHeaders = ({ headers = {}, ...request }) => ({
127
- ...request,
128
- headers: Object.keys(headers).reduce((normalizedHeaders, key) => {
129
- const normalizedHeaderKey = key.toLowerCase();
130
- return request.provider === 'aws' &&
131
- Reflect.has(normalizedHeaders, normalizedHeaderKey) &&
132
- normalizedHeaderKey === 'content-type' &&
133
- headers[key] === 'application/json'
134
- ? normalizedHeaders
135
- : {
136
- ...normalizedHeaders,
137
- [normalizedHeaderKey]: headers[key],
138
- };
139
- }, {}),
140
- });
141
-
142
- const normalizeProgrammingModel = (request) => ({
143
- ...request,
144
- method: request.httpMethod ??
145
- request.requestContext?.http?.method ??
146
- '',
147
- path: request.path ??
148
- request.requestContext?.http?.path ??
149
- '',
150
- provider: 'aws',
151
- query: request.queryStringParameters || {},
152
- source: request.requestContext ? 'api-gateway' : request.source,
153
- });
154
-
155
- const addTimestamp = (request) => ({
156
- ...request,
157
- timestamp: Date.now(),
158
- });
159
-
160
- const parseUrlEncodedBody = (request) => {
161
- const { headers, body } = request;
162
- return headers['content-type']?.split(';').shift() ===
163
- 'application/x-www-form-urlencoded' && typeof body === 'string'
164
- ? { ...request, body: querystring.parse(body) }
165
- : request;
166
- };
167
-
168
- var makeRequest = async (event, context, options = {}) => applyMiddleware([
169
- addTimestamp,
170
- normalizeProgrammingModel,
171
- normalizeHeaders,
172
- decodeBase64Body,
173
- cookies,
174
- parseUrlEncodedBody,
175
- parseJsonBody,
176
- setHostname,
177
- meta,
178
- ...(options.requestMiddleware || []),
179
- ], Object.freeze({
180
- ...event,
181
- context,
182
- }), options);
183
-
184
- const setHeader = (response, responseData, key, value) => {
185
- Object.assign(responseData.headers, { [key]: value });
186
- return response;
187
- };
188
-
189
- const makeResponseObject = (responseData, body = '', statusCode = 200, { headers = {}, ...options } = {}, contentType) => Object.freeze({
190
- ...responseData,
191
- body: body || '',
192
- headers: {
193
- ...responseData?.headers,
194
- 'content-type': contentType,
195
- ...headers,
196
- },
197
- statusCode,
198
- ...options,
199
- });
200
-
201
- const html = (responseData, _, body, statusCode, options) => makeResponseObject(responseData, body, statusCode, options, 'text/html');
202
-
203
- const stringifyIfNotStringifiedJson = (data) => {
204
- const maybeTrimmedDataString = typeof data === 'string' ? data.trim() : data;
205
- const isStringifiedJson = typeof data === 'string' &&
206
- ['{', '"'].includes(maybeTrimmedDataString[0]) &&
207
- ['}', '"'].includes(maybeTrimmedDataString.at(-1));
208
- return isStringifiedJson ? maybeTrimmedDataString : JSON.stringify(data);
209
- };
210
- const json = (responseData, _, body, statusCode, options) => makeResponseObject(responseData, stringifyIfNotStringifiedJson(body), statusCode, options, 'application/json');
211
-
212
- const COMPRESSIBLE = [
213
- 'application/activity+json',
214
- 'application/alto-costmap+json',
215
- 'application/alto-costmapfilter+json',
216
- 'application/alto-directory+json',
217
- 'application/alto-endpointcost+json',
218
- 'application/alto-endpointcostparams+json',
219
- 'application/alto-endpointprop+json',
220
- 'application/alto-endpointpropparams+json',
221
- 'application/alto-error+json',
222
- 'application/alto-networkmap+json',
223
- 'application/alto-networkmapfilter+json',
224
- 'application/atom+xml',
225
- 'application/calendar+json',
226
- 'application/coap-group+json',
227
- 'application/csvm+json',
228
- 'application/dart',
229
- 'application/dicom+json',
230
- 'application/ecmascript',
231
- 'application/fhir+json',
232
- 'application/fido.trusted-apps+json',
233
- 'application/geo+json',
234
- 'application/javascript',
235
- 'application/jf2feed+json',
236
- 'application/jose+json',
237
- 'application/jrd+json',
238
- 'application/json',
239
- 'application/json-patch+json',
240
- 'application/jsonml+json',
241
- 'application/jwk+json',
242
- 'application/jwk-set+json',
243
- 'application/ld+json',
244
- 'application/manifest+json',
245
- 'application/merge-patch+json',
246
- 'application/mud+json',
247
- 'application/postscript',
248
- 'application/ppsp-tracker+json',
249
- 'application/problem+json',
250
- 'application/raml+yaml',
251
- 'application/rdap+json',
252
- 'application/rdf+xml',
253
- 'application/reputon+json',
254
- 'application/rss+xml',
255
- 'application/rtf',
256
- 'application/scim+json',
257
- 'application/soap+xml',
258
- 'application/tar',
259
- 'application/vcard+json',
260
- 'application/vnd.amadeus+json',
261
- 'application/vnd.api+json',
262
- 'application/vnd.apothekende.reservation+json',
263
- 'application/vnd.avalon+json',
264
- 'application/vnd.bbf.usp.msg+json',
265
- 'application/vnd.bekitzur-stech+json',
266
- 'application/vnd.capasystems-pg+json',
267
- 'application/vnd.collection+json',
268
- 'application/vnd.collection.doc+json',
269
- 'application/vnd.collection.next+json',
270
- 'application/vnd.coreos.ignition+json',
271
- 'application/vnd.dart',
272
- 'application/vnd.datapackage+json',
273
- 'application/vnd.dataresource+json',
274
- 'application/vnd.document+json',
275
- 'application/vnd.drive+json',
276
- 'application/vnd.geo+json',
277
- 'application/vnd.google-earth.kml+xml',
278
- 'application/vnd.hal+json',
279
- 'application/vnd.hc+json',
280
- 'application/vnd.heroku+json',
281
- 'application/vnd.hyper+json',
282
- 'application/vnd.hyper-item+json',
283
- 'application/vnd.hyperdrive+json',
284
- 'application/vnd.ims.lis.v2.result+json',
285
- 'application/vnd.ims.lti.v2.toolconsumerprofile+json',
286
- 'application/vnd.ims.lti.v2.toolproxy+json',
287
- 'application/vnd.ims.lti.v2.toolproxy.id+json',
288
- 'application/vnd.ims.lti.v2.toolsettings+json',
289
- 'application/vnd.ims.lti.v2.toolsettings.simple+json',
290
- 'application/vnd.las.las+json',
291
- 'application/vnd.mason+json',
292
- 'application/vnd.micro+json',
293
- 'application/vnd.miele+json',
294
- 'application/vnd.mozilla.xul+xml',
295
- 'application/vnd.ms-fontobject',
296
- 'application/vnd.ms-opentype',
297
- 'application/vnd.nearst.inv+json',
298
- 'application/vnd.oftn.l10n+json',
299
- 'application/vnd.oma.lwm2m+json',
300
- 'application/vnd.oracle.resource+json',
301
- 'application/vnd.pagerduty+json',
302
- 'application/vnd.restful+json',
303
- 'application/vnd.siren+json',
304
- 'application/vnd.sun.wadl+xml',
305
- 'application/vnd.tableschema+json',
306
- 'application/vnd.vel+json',
307
- 'application/vnd.xacml+json',
308
- 'application/voucher-cms+json',
309
- 'application/wasm',
310
- 'application/webpush-options+json',
311
- 'application/x-httpd-php',
312
- 'application/x-javascript',
313
- 'application/x-ns-proxy-autoconfig',
314
- 'application/x-sh',
315
- 'application/x-tar',
316
- 'application/x-virtualbox-hdd',
317
- 'application/x-virtualbox-ova',
318
- 'application/x-virtualbox-ovf',
319
- 'application/x-virtualbox-vbox',
320
- 'application/x-virtualbox-vdi',
321
- 'application/x-virtualbox-vhd',
322
- 'application/x-virtualbox-vmdk',
323
- 'application/x-web-app-manifest+json',
324
- 'application/x-www-form-urlencoded',
325
- 'application/xhtml+xml',
326
- 'application/xml',
327
- 'application/xml-dtd',
328
- 'application/xop+xml',
329
- 'application/yang-data+json',
330
- 'application/yang-patch+json',
331
- 'font/otf',
332
- 'image/bmp',
333
- 'image/svg+xml',
334
- 'image/vnd.adobe.photoshop',
335
- 'image/x-icon',
336
- 'image/x-ms-bmp',
337
- 'message/imdn+xml',
338
- 'message/rfc822',
339
- 'model/gltf+json',
340
- 'model/gltf-binary',
341
- 'model/x3d+xml',
342
- 'text/cache-manifest',
343
- 'text/calender',
344
- 'text/cmd',
345
- 'text/css',
346
- 'text/csv',
347
- 'text/html',
348
- 'text/javascript',
349
- 'text/jsx',
350
- 'text/markdown',
351
- 'text/n3',
352
- 'text/plain',
353
- 'text/richtext',
354
- 'text/rtf',
355
- 'text/tab-separated-values',
356
- 'text/uri-list',
357
- 'text/vcard',
358
- 'text/vtt',
359
- 'text/x-gwt-rpc',
360
- 'text/x-jquery-tmpl',
361
- 'text/x-markdown',
362
- 'text/x-org',
363
- 'text/x-processing',
364
- 'text/x-suse-ymp',
365
- 'text/xml',
366
- 'x-shader/x-fragment',
367
- 'x-shader/x-vertex',
368
- ];
369
- const ZLIB_DEFAULT_OPTIONS = {
370
- level: 5,
371
- };
372
- const acceptEncoding = (accepts, encodings) => accepts.find((accept) => encodings.some((encoding) => accept === encoding));
373
- const getBestAcceptEncoding = (request) => {
374
- const { headers } = request;
375
- const acceptEncodings = (headers['accept-encoding'] || '').split(/, */);
376
- const encoding = acceptEncoding(acceptEncodings, [
377
- 'gzip',
378
- 'deflate',
379
- 'identity',
380
- ]);
381
- return encoding === 'deflate' && acceptEncoding(acceptEncodings, ['gzip'])
382
- ? acceptEncoding(acceptEncodings, ['gzip', 'identity'])
383
- : encoding;
384
- };
385
- const compressResponse = (response, encoding) => {
386
- const { body, headers, ...rest } = response;
387
- const compressed = encoding === 'gzip'
388
- ? zlib.gzipSync(body, ZLIB_DEFAULT_OPTIONS)
389
- : zlib.deflateSync(body, ZLIB_DEFAULT_OPTIONS);
390
- return {
391
- ...rest,
392
- body: compressed.toString('base64'),
393
- headers: {
394
- ...headers,
395
- 'content-encoding': encoding,
396
- 'content-length': compressed.byteLength,
397
- },
398
- isBase64Encoded: true,
399
- };
400
- };
401
- const compress = (response, request) => {
402
- const { body, headers } = response;
403
- const encoding = getBestAcceptEncoding(request);
404
- const weShouldCompress = COMPRESSIBLE.includes(headers['content-type']) &&
405
- encoding !== undefined &&
406
- encoding !== 'identity' &&
407
- body &&
408
- body.length > 256;
409
- return weShouldCompress && encoding
410
- ? compressResponse(response, encoding)
411
- : response;
412
- };
413
-
414
- const contentLengthHeader = (response) => {
415
- const { body, headers, ...rest } = response;
416
- return {
417
- ...rest,
418
- body,
419
- headers: {
420
- ...headers,
421
- 'content-length': headers['content-length'] || Buffer.byteLength(body),
422
- },
423
- };
424
- };
425
-
426
- const DEFAULT_POLICIES = {
427
- 'default-src': "'self' https: 'unsafe-inline'",
428
- 'img-src': '* data: blob:',
429
- };
430
- const cspHeaders = (response, _, options) => {
431
- const { headers = {}, ...rest } = response;
432
- const cspPolicies = {
433
- ...DEFAULT_POLICIES,
434
- ...options.cspPolicies,
435
- };
436
- const cspPolicy = Object.keys(cspPolicies)
437
- .map((policy) => `${policy} ${cspPolicies[policy]}`)
438
- .join(';');
439
- return {
440
- ...rest,
441
- headers: {
442
- ...headers,
443
- 'content-security-policy': cspPolicy,
444
- 'referrer-policy': 'strict-origin-when-cross-origin',
445
- 'x-content-type-options': 'nosniff',
446
- 'x-frame-options': {
447
- "'none'": 'DENY',
448
- "'self'": 'SAMEORIGIN',
449
- }[cspPolicies['frame-ancestors']] || undefined,
450
- 'x-xss-protection': '1; mode=block',
451
- },
452
- };
453
- };
454
-
455
- const enforcedHeaders = (response, _, options) => ({
456
- ...response,
457
- headers: {
458
- ...response.headers,
459
- ...(options.headers ?? {}),
460
- },
461
- });
462
-
463
- const EMPTY_ENTITY_TAG = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
464
- const entitytag = (entity) => {
465
- const hash = crypto
466
- .createHash('sha1')
467
- .update(entity, 'utf8')
468
- .digest('base64')
469
- .slice(0, 27);
470
- const length = typeof entity === 'string'
471
- ? Buffer.byteLength(entity, 'utf8')
472
- : entity.length;
473
- return `"${length.toString(16)}-${hash}"`;
474
- };
475
- const etag = (entity) => entity.length === 0 ? EMPTY_ENTITY_TAG : entitytag(entity);
476
- const etagHeader = (response) => ({
477
- ...response,
478
- headers: {
479
- ...response.headers,
480
- etag: etag(response.body),
481
- },
482
- });
483
-
484
- const aws = (request) => {
485
- const { headers: requestHeaders, requestContext, context } = request;
486
- return {
487
- apig: {
488
- requestId: requestContext.requestId,
489
- resourcePath: requestContext.resourcePath ??
490
- '',
491
- },
492
- awsAccountId: requestContext.accountId,
493
- cloudfront: {
494
- country: requestHeaders['cloudfront-viewer-country'],
495
- desktop: requestHeaders['cloudfront-is-desktop-viewer'] === 'true',
496
- mobile: requestHeaders['cloudfront-is-mobile-viewer'] === 'true',
497
- smarttv: requestHeaders['cloudfront-is-smarttv-viewer'] === 'true',
498
- tablet: requestHeaders['cloudfront-is-tablet-viewer'] === 'true',
499
- },
500
- deploymentStage: requestContext.stage,
501
- lambda: {
502
- functionName: context.functionName,
503
- memoryLimit: Number(context.memoryLimitInMB),
504
- remainingExecutionTime: context.getRemainingTimeInMillis?.(),
505
- requestId: context.awsRequestId,
506
- },
507
- sourceIp: requestContext.identity
508
- ?.sourceIp ??
509
- requestContext?.http?.sourceIp,
510
- };
511
- };
512
- const logger$1 = (request, response) => {
513
- const { source, headers: requestHeaders, meta, requestContext, provider, hostname, body, timestamp, path, query, } = request;
514
- const error = response.statusCode >= 500 && response.statusCode < 600;
515
- const logEntry = {
516
- dateTime: new Date().toISOString(),
517
- eventSource: source,
518
- hostname,
519
- httpMethod: requestContext.httpMethod,
520
- httpProtocol: requestHeaders.via?.split(' ')[0],
521
- httpReferrer: requestHeaders.referer,
522
- httpUserAgent: requestHeaders['user-agent'],
523
- requestContentLength: (body && body.length > 0) || 0,
524
- requestTime: Date.now() - timestamp,
525
- requestUri: path +
526
- (query && Object.keys(query).length > 0
527
- ? `?${querystring.stringify(query)}`
528
- : ''),
529
- responseContentLength: response.headers['content-length'],
530
- statusCode: response.statusCode,
531
- xForwardedHost: requestHeaders['x-forwarded-host'],
532
- ...meta,
533
- ...(provider === 'aws' && aws(request)),
534
- };
535
- return ((error ? process.stderr : process.stdout).write(JSON.stringify(logEntry)) &&
536
- logEntry);
537
- };
538
- const logResponse = (response, request, options) => (options.enableLogger &&
539
- (typeof options.logger === 'function'
540
- ? options.logger(request, response)
541
- : logger$1(request, response)) &&
542
- response) ||
543
- response;
544
-
545
- const redirect = (responseData, _, location, statusCode = 302, options = {}) => makeResponseObject(responseData, '', statusCode, {
546
- ...options,
547
- headers: {
548
- ...options.headers,
549
- location,
550
- },
551
- });
552
-
553
- const getBestMatchedFormat = (formats, accept) => {
554
- const fallback = formats.default || 'html';
555
- const acceptFormats = accept &&
556
- typeof accept === 'string' &&
557
- accept
558
- .split(';')[0]
559
- .split(',')
560
- .map((type) => {
561
- const mimeParts = type.split('/');
562
- return mimeParts.at(-1);
563
- });
564
- return ((acceptFormats &&
565
- acceptFormats.find((type) => Reflect.has(formats, type))) ||
566
- fallback);
567
- };
568
- const getBestMatchedResponseHelper = (format) => {
569
- switch (format) {
570
- case 'html':
571
- return html;
572
- case 'json':
573
- return json;
574
- }
575
- throw new TypeError(`"${format}" is not a valid Alagarr respondTo() format.`);
576
- };
577
- const getBestMatchedFormatBody = (formats, format) => {
578
- switch (format) {
579
- case 'html':
580
- return formats.html;
581
- case 'json':
582
- return formats.json;
583
- }
584
- throw new TypeError(`"${format}" is not a valid Alagarr respondTo() format.`);
585
- };
586
- const respondTo = (responseData, request, formats, statusCode, options) => {
587
- const { headers: { accept }, } = request;
588
- const format = getBestMatchedFormat(formats, accept);
589
- const responseHelper = getBestMatchedResponseHelper(format);
590
- const body = getBestMatchedFormatBody(formats, format);
591
- return responseHelper(responseData, request, body, statusCode, options);
592
- };
593
-
594
- const text = (responseData, _, body, statusCode, options) => makeResponseObject(responseData, body, statusCode, options, 'text/plain');
595
-
596
- const middlewareMap = {
597
- enableCompression: compress,
598
- enableContentLength: contentLengthHeader,
599
- enableCspHeaders: cspHeaders,
600
- enableETagHeader: etagHeader,
601
- enableEnforcedHeaders: enforcedHeaders,
602
- };
603
- var makeResponse = async (request, callback, options) => {
604
- const responseData = makeResponseObject();
605
- const responseWithHelpers = [
606
- text,
607
- html,
608
- json,
609
- redirect,
610
- respondTo,
611
- ].reduce((methods, method) => ({
612
- ...methods,
613
- [method.name]: async (...arguments_) => callback(null, await applyMiddleware(Object.keys(middlewareMap).reduce((middlewareList, middleware) => options[middleware]
614
- ? [...middlewareList, middlewareMap[middleware]]
615
- : middlewareList, [
616
- ...(options.responseMiddleware || []),
617
- ...(options.enableLogger ? [logResponse] : []),
618
- ]), method(responseData, request, ...arguments_), request, options)),
619
- }), { raw: callback });
620
- const response = [setHeader].reduce((responseMethods, method) => ({
621
- ...responseMethods,
622
- [method.name]: (...arguments_) => method(response, responseData, ...arguments_),
623
- }), responseWithHelpers);
624
- return response;
625
- };
626
-
627
- const DEFAULT_OPTIONS = {
628
- cspPolicies: [],
629
- enableCompression: true,
630
- enableContentLength: true,
631
- enableCspHeaders: true,
632
- enableETagHeader: true,
633
- enableEnforcedHeaders: true,
634
- enableLogger: true,
635
- enableStrictTransportSecurity: true,
636
- errorHandler: defaultErrorHandler,
637
- requestMiddleware: [],
638
- responseMiddleware: [],
639
- };
640
- const noopHandler = (_, _1) => {
641
- throw new ServerError('Misconfiguration in Alagarr setup. No handler function was provided.');
642
- };
643
- const alagarr = (handler = noopHandler, options = DEFAULT_OPTIONS) => async (event, context, callback) => {
644
- const mergedOptions = {
645
- ...DEFAULT_OPTIONS,
646
- ...options,
647
- };
648
- const request = await makeRequest(event, context, mergedOptions);
649
- const response = await makeResponse(request, callback, mergedOptions);
650
- try {
651
- return await handler(request, response, context);
652
- }
653
- catch (handlerError) {
654
- const errorHandler = typeof mergedOptions.errorHandler === 'function'
655
- ? mergedOptions.errorHandler
656
- : defaultErrorHandler;
657
- try {
658
- return await errorHandler(request, response, handlerError);
659
- }
660
- catch (error) {
661
- console.error('There was an error in the error handler provided to Alagaar', error, errorHandler.toString());
662
- return defaultErrorHandler(request, response, error);
663
- }
664
- }
665
- };
666
-
667
- const DEFAULT_AWS_CONFIGURATION = {
668
- region: 'eu-central-2',
669
- };
670
-
671
- const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }).map((_, count) => list.slice(count * every, count * every + every));
672
- const until = async (predicate, transformer, initialValue, iterationCount = 0) => {
673
- const transformed = await transformer(initialValue, iterationCount);
674
- return (await predicate(transformed, iterationCount))
675
- ? transformed
676
- : until(predicate, transformer, transformed, iterationCount + 1);
677
- };
678
-
679
- const DEFAULT_BATCH_ITEMS_MAX_RETRIES = 50;
680
- let queue;
681
- const makeKey = (key) => {
682
- if (typeof key === 'string') {
683
- return { id: key };
684
- }
685
- return key;
686
- };
687
- const deleteItem = async (config, documentClient, table, key, options = {}) => {
688
- try {
689
- const { ReturnValues = 'NONE' } = options;
690
- const command = new DeleteCommand({
691
- Key: makeKey(key),
692
- ReturnValues,
693
- TableName: config.tableNamePrefix + table,
694
- });
695
- const result = await queue.schedule(() => documentClient.send(command));
696
- return result.Attributes;
697
- }
698
- catch (error) {
699
- if (config.logger) {
700
- config.logger.error(error);
701
- }
702
- return undefined;
703
- }
704
- };
705
- const getItem = async (config, documentClient, table, key, options = {}) => {
706
- try {
707
- const { ConsistentRead = false } = options;
708
- const command = new GetCommand({
709
- ConsistentRead,
710
- Key: makeKey(key),
711
- TableName: config.tableNamePrefix + table,
712
- });
713
- const result = await queue.schedule(() => documentClient.send(command));
714
- return result.Item;
715
- }
716
- catch (error) {
717
- if (config.logger) {
718
- config.logger.error(error);
719
- }
720
- return undefined;
721
- }
722
- };
723
- const putItem = async (config, documentClient, table, Item) => {
724
- try {
725
- const { expires, ...data } = Item;
726
- const command = new PutCommand({
727
- Item: expires === null ? data : Item,
728
- TableName: config.tableNamePrefix + table,
729
- });
730
- return (await queue.schedule(() => documentClient.send(command))) && Item;
731
- }
732
- catch (error) {
733
- if (config.logger) {
734
- config.logger.error(error);
735
- }
736
- return undefined;
737
- }
738
- };
739
- const updateItem = async (config, documentClient, table, key, options = {}) => {
740
- try {
741
- const command = new UpdateCommand({
742
- Key: makeKey(key),
743
- ...options,
744
- ReturnValues: 'ALL_NEW',
745
- TableName: config.tableNamePrefix + table,
746
- });
747
- const result = await queue.schedule(async () => documentClient.send(command));
748
- return result.Attributes;
749
- }
750
- catch (error) {
751
- if (config.logger) {
752
- config.logger.error(error);
753
- }
754
- return undefined;
755
- }
756
- };
757
- const hasNoUnprocessedItems = ({ UnprocessedItems, }) => !UnprocessedItems || Object.keys(UnprocessedItems).length === 0;
758
- const makeBatchWriteRequestWithExponentialBackoff = (documentClient, table, batchWriteMaxRetries) => async (batchItems, retries) => {
759
- if (retries > batchWriteMaxRetries) {
760
- throw new Error('Reached maximum number of DynamoDB BatchWrite request retries.');
761
- }
762
- const command = new BatchWriteCommand({
763
- RequestItems: batchItems.UnprocessedItems ?? {
764
- [table]: batchItems.map((Item) => ({
765
- PutRequest: { Item },
766
- })),
767
- },
768
- ReturnConsumedCapacity: 'TOTAL',
769
- ReturnItemCollectionMetrics: 'SIZE',
770
- });
771
- return documentClient.send(command);
772
- };
773
- const batchWriteItems = async (config, documentClient, table, items) => {
774
- try {
775
- const batches = splitEvery(25, items);
776
- const result = await Promise.all(batches.map(async (batch) => queue.schedule(async () => until(hasNoUnprocessedItems, makeBatchWriteRequestWithExponentialBackoff(documentClient, config.tableNamePrefix + table, config.batchWriteMaxRetries ?? DEFAULT_BATCH_ITEMS_MAX_RETRIES), batch))));
777
- return result && items;
778
- }
779
- catch (error) {
780
- if (config.logger) {
781
- config.logger.error(error);
782
- }
783
- return undefined;
784
- }
785
- };
786
- const scanTable = async (config, documentClient, table, options = {}) => {
787
- try {
788
- const { ConsistentRead = false } = options;
789
- const command = new ScanCommand({
790
- ...options,
791
- ConsistentRead,
792
- TableName: config.tableNamePrefix + table,
793
- });
794
- return await queue.schedule(() => documentClient.send(command));
795
- }
796
- catch (error) {
797
- if (config.logger) {
798
- config.logger.error(error);
799
- }
800
- return undefined;
801
- }
802
- };
803
- const queryTable = async (config, documentClient, table, options = {}) => {
804
- try {
805
- const { ConsistentRead = false } = options;
806
- const command = new QueryCommand({
807
- ...options,
808
- ConsistentRead,
809
- TableName: config.tableNamePrefix + table,
810
- });
811
- return await queue.schedule(() => documentClient.send(command));
812
- }
813
- catch (error) {
814
- if (config.logger) {
815
- config.logger.error(error);
816
- }
817
- return undefined;
818
- }
819
- };
820
- const dynamodb = (userConfig) => {
821
- const { clientConfig, ...config } = {
822
- ...DEFAULT_AWS_CONFIGURATION,
823
- ...userConfig,
824
- };
825
- const ddbClient = new DynamoDBClient(config.dockerConfig
826
- ? {
827
- credentials: {
828
- accessKeyId: 'foobarkey',
829
- secretAccessKey: 'foobarsecret',
830
- },
831
- endpoint: config.dockerConfig.endpoint,
832
- region: 'local',
833
- retryMode: 'standard',
834
- }
835
- : { retryMode: 'standard', ...clientConfig });
836
- const documentClient = DynamoDBDocumentClient.from(ddbClient);
837
- queue = new Bottleneck({
838
- maxConcurrent: config.queueConfig?.queueConcurrency ?? 25,
839
- minTime: config.queueConfig?.queueDelay ?? undefined,
840
- });
841
- return {
842
- batchWriteItems: (table, items) => batchWriteItems(config, documentClient, table, items),
843
- deleteItem: (table, key, options) => deleteItem(config, documentClient, table, key, options),
844
- getItem: (table, key, options) => getItem(config, documentClient, table, key, options),
845
- putItem: (table, item) => putItem(config, documentClient, table, item),
846
- queryTable: (table, options) => queryTable(config, documentClient, table, options),
847
- scanTable: (table, options) => scanTable(config, documentClient, table, options),
848
- updateItem: (table, key, options) => updateItem(config, documentClient, table, key, options),
849
- };
850
- };
851
-
852
- const invokeLambda = async (lambdaClient, config, invocationType, payload) => {
853
- try {
854
- const invokeCommand = new InvokeCommand({
855
- FunctionName: config.functionName,
856
- InvocationType: invocationType,
857
- Payload: Buffer.from(JSON.stringify(payload)),
858
- });
859
- const result = await lambdaClient.send(invokeCommand);
860
- if (config.logger) {
861
- config.logger.info(JSON.stringify(result));
862
- }
863
- return true;
864
- }
865
- catch (error) {
866
- if (config.logger) {
867
- config.logger.error(error);
868
- }
869
- return false;
870
- }
871
- };
872
- const lambda = (userConfig) => {
873
- const { clientConfig, ...config } = {
874
- ...DEFAULT_AWS_CONFIGURATION,
875
- ...userConfig,
876
- };
877
- const lambdaClient = new LambdaClient(clientConfig);
878
- return {
879
- invokeLambda: (invocationType, payload) => invokeLambda(lambdaClient, config, invocationType, payload),
880
- };
881
- };
882
-
883
- const DEFAULT_CLIENT_CONFIG$1 = {
884
- clientConfig: DEFAULT_AWS_CONFIGURATION,
885
- onlyDefault: false,
886
- prefix: process.env.SSM_PARAM_PATH,
887
- };
888
- const getSecrets = async (ssm, config, parameters_, defaultValues) => {
889
- if (config.onlyDefault) {
890
- return defaultValues;
891
- }
892
- try {
893
- const command = new GetParametersCommand({
894
- Names: parameters_.map((parameter) => `${config.prefix ?? ''}${parameter}`),
895
- WithDecryption: true,
896
- });
897
- const parameters = await ssm.send(command);
898
- return (parameters?.Parameters?.map((parameter) => {
899
- const defaultIfNoParameter = !parameter.Value && parameter.Name
900
- ? defaultValues.find((defaultValue) => defaultValue.name === parameter.Name)
901
- : defaultValues[0];
902
- return {
903
- name: parameter.Name ?? defaultIfNoParameter?.name ?? '',
904
- value: parameter.Value ?? defaultIfNoParameter?.value ?? '',
905
- };
906
- }) ?? defaultValues);
907
- }
908
- catch (error) {
909
- if (config.logger) {
910
- config.logger.error(error);
911
- }
912
- return defaultValues;
913
- }
914
- };
915
- const getSecret = async (ssm, config, parameter_, defaultValue) => {
916
- if (config.onlyDefault) {
917
- return defaultValue;
918
- }
919
- try {
920
- const parameter = await ssm.send(new GetParameterCommand({
921
- Name: `${config.prefix ?? ''}${parameter_}`,
922
- WithDecryption: true,
923
- }));
924
- return parameter?.Parameter?.Value ?? defaultValue;
925
- }
926
- catch (error) {
927
- if (config.logger) {
928
- config.logger.error(error);
929
- }
930
- return defaultValue;
931
- }
932
- };
933
- const parameterStore = (userConfig) => {
934
- const { clientConfig, ...config } = {
935
- ...DEFAULT_CLIENT_CONFIG$1,
936
- ...userConfig,
937
- };
938
- const ssm = new SSMClient(clientConfig);
939
- return {
940
- getSecret: (parameter, defaultValue = '') => getSecret(ssm, config, parameter, defaultValue),
941
- getSecrets: (parameters, defaultValues = []) => getSecrets(ssm, config, parameters, defaultValues),
942
- };
943
- };
944
-
945
- const DEFAULT_CLIENT_CONFIG = {
946
- clientConfig: { region: 'eu-west-1' },
947
- };
948
- const sendMail = async (config, transport, options, responseCallback) => {
949
- try {
950
- if (!options.html && !options.text) {
951
- if (config.logger) {
952
- config.logger.error('The option text or html needs to be set.');
953
- }
954
- return false;
955
- }
956
- if (!options.to || !options.subject) {
957
- if (config.logger) {
958
- config.logger.error('The fields to and subject have to be set.');
959
- }
960
- return false;
961
- }
962
- const transportResponse = await transport.sendMail(options);
963
- if (responseCallback) {
964
- await responseCallback(transportResponse);
965
- }
966
- if (!transportResponse) {
967
- if (config.logger) {
968
- config.logger.error('Sending email failed!');
969
- }
970
- return false;
971
- }
972
- return true;
973
- }
974
- catch (error) {
975
- if (config.logger) {
976
- config.logger.error(error);
977
- }
978
- return false;
979
- }
980
- };
981
- const ses = (userConfig) => {
982
- const { clientConfig, ...config } = {
983
- ...DEFAULT_CLIENT_CONFIG,
984
- ...userConfig,
985
- };
986
- const sesClient = new SESv2Client(clientConfig);
987
- const transport = createTransport(userConfig.customSMTPConfig ?? {
988
- SES: {
989
- SendEmailCommand,
990
- sesClient,
991
- },
992
- });
993
- return {
994
- sendMail: (options, responseCallback) => sendMail(config, transport, options, responseCallback),
995
- };
996
- };
997
-
998
- const deleteQueueItem = async (sqsClient, config, queueUrl, receiptHandle) => {
999
- try {
1000
- const deleteMessageCommand = new DeleteMessageCommand({
1001
- QueueUrl: queueUrl,
1002
- ReceiptHandle: receiptHandle,
1003
- });
1004
- return await sqsClient.send(deleteMessageCommand);
1005
- }
1006
- catch (error) {
1007
- if (config.logger) {
1008
- config.logger.error(error);
1009
- }
1010
- return undefined;
1011
- }
1012
- };
1013
- const getQueueItems = async (sqsClient, config, queueUrl, maxNumberOfMessages = 1) => {
1014
- try {
1015
- const receiveMessageCommand = new ReceiveMessageCommand({
1016
- MaxNumberOfMessages: maxNumberOfMessages,
1017
- QueueUrl: queueUrl,
1018
- });
1019
- return await sqsClient.send(receiveMessageCommand);
1020
- }
1021
- catch (error) {
1022
- if (config.logger) {
1023
- config.logger.error(error);
1024
- }
1025
- return undefined;
1026
- }
1027
- };
1028
- const getQueueUrl = async (sqsClient, config, queueName) => {
1029
- try {
1030
- const getUrlCommand = new GetQueueUrlCommand({ QueueName: queueName });
1031
- const result = await sqsClient.send(getUrlCommand);
1032
- return result.QueueUrl;
1033
- }
1034
- catch (error) {
1035
- if (config.logger) {
1036
- config.logger.error(error);
1037
- }
1038
- return undefined;
1039
- }
1040
- };
1041
- const sendMessage = async (sqsClient, config, queueUrl, message, fifoSettings = {}, messageAttributes, messageDelay = 0) => {
1042
- try {
1043
- const sendMessageCommand = new SendMessageCommand({
1044
- ...fifoSettings,
1045
- ...(messageAttributes ? { MessageAttributes: messageAttributes } : {}),
1046
- DelaySeconds: messageDelay,
1047
- MessageBody: message,
1048
- QueueUrl: queueUrl,
1049
- });
1050
- const result = await sqsClient.send(sendMessageCommand);
1051
- return result.MessageId;
1052
- }
1053
- catch (error) {
1054
- if (config.logger) {
1055
- config.logger.error(error);
1056
- }
1057
- return undefined;
1058
- }
1059
- };
1060
- const sendBatchMessages = async (sqsClient, config, queueUrl, queueEntries) => {
1061
- try {
1062
- const sendMessageBatchCommand = new SendMessageBatchCommand({
1063
- Entries: queueEntries,
1064
- QueueUrl: queueUrl,
1065
- });
1066
- const result = await sqsClient.send(sendMessageBatchCommand);
1067
- return { failed: result.Failed, successful: result.Successful };
1068
- }
1069
- catch (error) {
1070
- if (config.logger) {
1071
- config.logger.error(error);
1072
- }
1073
- return { failed: undefined, successful: undefined };
1074
- }
1075
- };
1076
- const sqs = (userConfig) => {
1077
- const { clientConfig, ...config } = {
1078
- ...DEFAULT_AWS_CONFIGURATION,
1079
- ...userConfig,
1080
- };
1081
- const sqsClient = new SQSClient(clientConfig);
1082
- return {
1083
- deleteQueueItem: (queueUrl, receiptHandle) => deleteQueueItem(sqsClient, config, queueUrl, receiptHandle),
1084
- getQueueItems: (queueUrl, maxNumberOfMessages) => getQueueItems(sqsClient, config, queueUrl, maxNumberOfMessages),
1085
- getQueueUrl: (queueName) => getQueueUrl(sqsClient, config, queueName),
1086
- sendBatchMessages: (queueUrl, queueEntries) => sendBatchMessages(sqsClient, config, queueUrl, queueEntries),
1087
- sendMessage: (queueUrl, message, fifoSettings, messageAttributes, messageDelay) => sendMessage(sqsClient, config, queueUrl, message, fifoSettings, messageAttributes, messageDelay),
1088
- };
1089
- };
1090
-
1091
- const logger = (config) => {
1092
- const loggerConfig = {
1093
- defaultMeta: {
1094
- ...config.defaultLogInformation,
1095
- deploymentStage: config.deploymentStage,
1096
- service: config.service,
1097
- },
1098
- format: format.combine(format.timestamp({
1099
- format: 'DD-MM-YYYY HH:mm:ss',
1100
- }), format.json()),
1101
- level: config.level || 'info',
1102
- };
1103
- if (config.datadogApiKey) {
1104
- const httpOptions = {
1105
- host: 'http-intake.logs.datadoghq.eu',
1106
- path: `/api/v2/logs?dd-api-key=${config.datadogApiKey}&ddsource=nodejs&service=${config.service}`,
1107
- port: 443,
1108
- ssl: true,
1109
- };
1110
- return createLogger({
1111
- ...loggerConfig,
1112
- transports: [new transports.Http(httpOptions)],
1113
- });
1114
- }
1115
- return createLogger({
1116
- ...loggerConfig,
1117
- transports: [new transports.Console({ forceConsole: true })],
1118
- });
1119
- };
1120
-
1121
- 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 };
1
+ var __importDefault = (undefined && undefined.__importDefault) || function (mod) {
2
+ return (mod && mod.__esModule) ? mod : { "default": mod };
3
+ };
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.Logger = exports.logger = exports.sqs = exports.ses = exports.parameterStore = exports.lambda = exports.dynamodb = exports.DEFAULT_SES_CLIENT_CONFIG = exports.DEFAULT_PARAMETER_STORE_CLIENT_CONFIG = exports.ServerError = exports.ClientError = exports.alagarr = void 0;
6
+ var alagarr_1 = require("./alagarr");
7
+ Object.defineProperty(exports, "alagarr", { enumerable: true, get: function () { return __importDefault(alagarr_1).default; } });
8
+ Object.defineProperty(exports, "ClientError", { enumerable: true, get: function () { return alagarr_1.ClientError; } });
9
+ Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return alagarr_1.ServerError; } });
10
+ var aws_1 = require("./aws");
11
+ Object.defineProperty(exports, "DEFAULT_PARAMETER_STORE_CLIENT_CONFIG", { enumerable: true, get: function () { return aws_1.DEFAULT_PARAMETER_STORE_CLIENT_CONFIG; } });
12
+ Object.defineProperty(exports, "DEFAULT_SES_CLIENT_CONFIG", { enumerable: true, get: function () { return aws_1.DEFAULT_SES_CLIENT_CONFIG; } });
13
+ Object.defineProperty(exports, "dynamodb", { enumerable: true, get: function () { return aws_1.dynamodb; } });
14
+ Object.defineProperty(exports, "lambda", { enumerable: true, get: function () { return aws_1.lambda; } });
15
+ Object.defineProperty(exports, "parameterStore", { enumerable: true, get: function () { return aws_1.parameterStore; } });
16
+ Object.defineProperty(exports, "ses", { enumerable: true, get: function () { return aws_1.ses; } });
17
+ Object.defineProperty(exports, "sqs", { enumerable: true, get: function () { return aws_1.sqs; } });
18
+ var logger_1 = require("./logger/logger");
19
+ Object.defineProperty(exports, "logger", { enumerable: true, get: function () { return __importDefault(logger_1).default; } });
20
+ var winston_1 = require("winston");
21
+ Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return winston_1.Logger; } });