@allthings/cloud-toolkit 5.1.0 → 5.2.0-beta.2

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