@stone-js/aws-lambda-http-adapter 0.3.1 → 0.8.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.
package/dist/index.js CHANGED
@@ -1,47 +1,14 @@
1
- import { defaultLoggerResolver, IntegrationError, Adapter, AdapterEventBuilder, isNotEmpty, defaultKernelResolver, classDecoratorLegacyWrapper, addBlueprint } from '@stone-js/core';
1
+ import { IntegrationError, Adapter, AdapterEventBuilder, defaultLoggerResolver, defaultKernelResolver, isNotEmpty, classDecoratorLegacyWrapper, addBlueprint } from '@stone-js/core';
2
+ import { IncomingHttpEvent, HTTP_INTERNAL_SERVER_ERROR, BinaryFileResponse, OutgoingHttpResponse, CookieCollection, isIpTrusted, getHostname, getProtocol, httpCoreBlueprint, isMultipart, getCharset, getFilesUploads } from '@stone-js/http-core';
2
3
  import mime from 'mime';
3
4
  import accepts from 'accepts';
4
5
  import statuses from 'statuses';
5
- import { HTTP_INTERNAL_SERVER_ERROR, IncomingHttpEvent, BinaryFileResponse, OutgoingHttpResponse, isMultipart, getFilesUploads, CookieCollection, isIpTrusted, getHostname, getProtocol, getCharset, httpCoreBlueprint } from '@stone-js/http-core';
6
+ import { cloneValue, deepMerge } from '@stone-js/config';
7
+ import { getString } from '@stone-js/env';
6
8
  import { File } from '@stone-js/filesystem';
7
9
  import proxyAddr from 'proxy-addr';
8
10
  import bytes from 'bytes';
9
11
  import typeIs from 'type-is';
10
- import { getString } from '@stone-js/env';
11
-
12
- /**
13
- * Class representing an AwsLambdaHttpErrorHandler.
14
- */
15
- class AwsLambdaHttpErrorHandler {
16
- logger;
17
- /**
18
- * Create an NodeHttpErrorHandler.
19
- *
20
- * @param options - NodeHttpErrorHandler options.
21
- */
22
- constructor({ blueprint }) {
23
- this.logger = blueprint.get('stone.logger.resolver', defaultLoggerResolver)(blueprint);
24
- }
25
- /**
26
- * Handle an error.
27
- *
28
- * @param error - The error to handle.
29
- * @param context - The context of the adapter.
30
- * @returns The raw response builder.
31
- */
32
- handle(error, context) {
33
- this.logger.error(error.message, { error });
34
- const statusCode = error.cause?.status ?? HTTP_INTERNAL_SERVER_ERROR;
35
- const type = accepts(context.rawEvent).type(['json', 'html']);
36
- const contentType = mime.getType(type !== false ? type : 'txt') ?? context.rawEvent.headers['content-type'] ?? 'text/plain';
37
- const headers = new Headers({ 'Content-Type': contentType });
38
- return context
39
- .rawResponseBuilder
40
- .add('headers', headers)
41
- .add('statusCode', statusCode)
42
- .add('statusMessage', statuses.message[statusCode]);
43
- }
44
- }
45
12
 
46
13
  /**
47
14
  * Wrapper for HTTP raw responses in AWS Lambda.
@@ -105,23 +72,40 @@ class RawHttpResponseWrapper {
105
72
  * ```
106
73
  */
107
74
  respond() {
108
- return {
75
+ const headers = new Headers(this.options.headers ?? {});
76
+ const setCookies = this.extractSetCookies(headers);
77
+ headers.delete('set-cookie');
78
+ const response = {
109
79
  ...this.options,
110
80
  statusCode: this.options.statusCode ?? 500,
111
- headers: this.normalizeHeaders(this.options.headers)
81
+ headers: Object.fromEntries(headers)
112
82
  };
83
+ // Emit multiple Set-Cookie correctly per trigger: v2/Function URLs use the `cookies` field,
84
+ // v1/ALB use `multiValueHeaders`. Folding them into a single comma-joined header (the old
85
+ // behaviour) is unparseable by browsers and leaks a cookie's attributes into the next.
86
+ if (setCookies.length > 0) {
87
+ if (this.options.version === 'v2') {
88
+ response.cookies = setCookies;
89
+ }
90
+ else {
91
+ response.multiValueHeaders = { ...this.options.multiValueHeaders, 'set-cookie': setCookies };
92
+ }
93
+ }
94
+ return response;
113
95
  }
114
96
  /**
115
- * Normalizes the headers to a consistent format.
116
- *
117
- * Converts Headers or Record<string, string> to a normalized Record<string, string>
118
- * with all keys in lowercase.
97
+ * Extract all `Set-Cookie` values from a Headers instance, tolerant of runtimes without
98
+ * `getSetCookie()`.
119
99
  *
120
- * @param headers - The headers to normalize.
121
- * @returns A normalized record of headers.
100
+ * @param headers - The response headers.
101
+ * @returns The raw `Set-Cookie` strings.
122
102
  */
123
- normalizeHeaders(headers) {
124
- return Object.fromEntries(new Headers(headers ?? {}));
103
+ extractSetCookies(headers) {
104
+ if (typeof headers.getSetCookie === 'function') {
105
+ return headers.getSetCookie();
106
+ }
107
+ const raw = headers.get('set-cookie');
108
+ return raw !== null && raw.length > 0 ? [raw] : [];
125
109
  }
126
110
  }
127
111
 
@@ -214,7 +198,7 @@ class AwsLambdaHttpAdapter extends Adapter {
214
198
  */
215
199
  async onStart() {
216
200
  if (typeof window === 'object') {
217
- throw new AwsLambdaHttpAdapterError('This `AWSLambdaAdapter` must be used only in AWS Lambda context.');
201
+ throw new AwsLambdaHttpAdapterError('This `AwsLambdaHttpAdapter` must be used only in AWS Lambda context.');
218
202
  }
219
203
  await this.executeHooks('onStart');
220
204
  }
@@ -255,6 +239,46 @@ class AwsLambdaHttpAdapter extends Adapter {
255
239
  }
256
240
  }
257
241
 
242
+ /**
243
+ * Class representing an AwsLambdaHttpErrorHandler.
244
+ */
245
+ class AwsLambdaHttpErrorHandler {
246
+ logger;
247
+ /**
248
+ * Create an NodeHttpErrorHandler.
249
+ *
250
+ * @param options - NodeHttpErrorHandler options.
251
+ */
252
+ constructor({ blueprint }) {
253
+ this.logger = blueprint.get('stone.logger.resolver', defaultLoggerResolver)(blueprint);
254
+ }
255
+ /**
256
+ * Handle an error.
257
+ *
258
+ * @param error - The error to handle.
259
+ * @param context - The context of the adapter.
260
+ * @returns The raw response builder.
261
+ */
262
+ handle(error, context) {
263
+ this.logger.error(error.message, { error });
264
+ // http-core's HttpError carries `statusCode` on the error itself; fall back to a cause and
265
+ // finally to 500. (Reading only `cause.status` turned every typed HTTP error into a 500.)
266
+ const statusCode = error.statusCode ??
267
+ error.cause?.statusCode ??
268
+ error.cause?.status ??
269
+ HTTP_INTERNAL_SERVER_ERROR;
270
+ const requestHeaders = context.rawEvent.headers ?? {};
271
+ const type = accepts({ headers: requestHeaders }).type(['json', 'html']);
272
+ const contentType = mime.getType(type !== false ? type : 'txt') ?? requestHeaders['content-type'] ?? 'text/plain';
273
+ const headers = new Headers({ 'Content-Type': contentType });
274
+ return context
275
+ .rawResponseBuilder
276
+ .add('headers', headers)
277
+ .add('statusCode', statusCode)
278
+ .add('statusMessage', statuses.message[statusCode]);
279
+ }
280
+ }
281
+
258
282
  /**
259
283
  * A constant representing the AWS Lambda HTTP platform identifier.
260
284
  *
@@ -264,6 +288,171 @@ class AwsLambdaHttpAdapter extends Adapter {
264
288
  */
265
289
  const AWS_LAMBDA_HTTP_PLATFORM = 'aws_lambda_http';
266
290
 
291
+ /**
292
+ * Detect which AWS HTTP trigger produced the event.
293
+ *
294
+ * @param event - The raw Lambda event.
295
+ * @returns The trigger family.
296
+ */
297
+ function detectEventVersion(event) {
298
+ const requestContext = event.requestContext;
299
+ if (requestContext?.elb !== undefined) {
300
+ return 'alb';
301
+ }
302
+ if (event.version === '2.0' || requestContext?.http !== undefined) {
303
+ return 'v2';
304
+ }
305
+ return 'v1';
306
+ }
307
+ /**
308
+ * Normalize headers to lower-cased keys, merging single and multi-value headers.
309
+ *
310
+ * @param event - The raw Lambda event.
311
+ * @returns Lower-cased headers with multi-value entries joined by `, `.
312
+ */
313
+ function normalizeHeaders(event) {
314
+ const out = {};
315
+ const multi = event.multiValueHeaders;
316
+ if (multi !== null && multi !== undefined) {
317
+ for (const [name, values] of Object.entries(multi)) {
318
+ if (Array.isArray(values)) {
319
+ out[name.toLowerCase()] = values.join(', ');
320
+ }
321
+ }
322
+ }
323
+ const single = event.headers;
324
+ if (single !== null && single !== undefined) {
325
+ for (const [name, value] of Object.entries(single)) {
326
+ if (value !== undefined) {
327
+ out[name.toLowerCase()] = value;
328
+ }
329
+ }
330
+ }
331
+ return out;
332
+ }
333
+ /**
334
+ * Build the raw query string from whichever representation the trigger provides.
335
+ *
336
+ * v2 gives the fidelity-preserving `rawQueryString`. v1/ALB give an object (single value) and
337
+ * optionally `multiValueQueryStringParameters` (preferred, preserves repeated keys). Values are
338
+ * URL-encoded so repeated/array values survive.
339
+ *
340
+ * @param event - The raw Lambda event.
341
+ * @param version - The detected trigger family.
342
+ * @returns The raw query string (no leading `?`).
343
+ */
344
+ function buildRawQueryString(event, version) {
345
+ if (version === 'v2' && typeof event.rawQueryString === 'string') {
346
+ return event.rawQueryString;
347
+ }
348
+ const multi = event.multiValueQueryStringParameters;
349
+ if (multi !== null && multi !== undefined) {
350
+ const params = new URLSearchParams();
351
+ for (const [key, values] of Object.entries(multi)) {
352
+ for (const value of values ?? []) {
353
+ params.append(key, value);
354
+ }
355
+ }
356
+ return params.toString();
357
+ }
358
+ const single = event.queryStringParameters;
359
+ if (single !== null && single !== undefined) {
360
+ const params = new URLSearchParams();
361
+ for (const [key, value] of Object.entries(single)) {
362
+ if (value !== undefined) {
363
+ params.append(key, value);
364
+ }
365
+ }
366
+ return params.toString();
367
+ }
368
+ return '';
369
+ }
370
+ /**
371
+ * Collect the raw cookie strings, regardless of trigger.
372
+ *
373
+ * v2 delivers `event.cookies: string[]`. v1/ALB deliver a single `Cookie` header (already merged
374
+ * into {@link NormalizedHttpEvent.headers}).
375
+ *
376
+ * @param event - The raw Lambda event.
377
+ * @param headers - The normalized headers.
378
+ * @returns The raw cookie strings.
379
+ */
380
+ function collectCookies(event, headers) {
381
+ if (Array.isArray(event.cookies)) {
382
+ return event.cookies;
383
+ }
384
+ const header = headers.cookie;
385
+ return typeof header === 'string' && header.length > 0 ? header.split(/; */) : [];
386
+ }
387
+ /**
388
+ * Resolve the client source IP across triggers.
389
+ *
390
+ * @param event - The raw Lambda event.
391
+ * @param version - The detected trigger family.
392
+ * @param headers - The normalized headers.
393
+ * @returns The source IP (empty string if unknown).
394
+ */
395
+ function resolveSourceIp(event, version, headers) {
396
+ const requestContext = event.requestContext;
397
+ if (version === 'v2') {
398
+ return String(requestContext?.http?.sourceIp ?? '');
399
+ }
400
+ if (version === 'v1') {
401
+ return String(requestContext?.identity?.sourceIp ?? '');
402
+ }
403
+ // ALB carries no identity; the client IP is the first hop in X-Forwarded-For.
404
+ return (headers['x-forwarded-for'] ?? '').split(',')[0].trim();
405
+ }
406
+ /**
407
+ * Normalize any AWS HTTP Lambda event into the canonical {@link NormalizedHttpEvent}.
408
+ *
409
+ * @param event - The raw Lambda event.
410
+ * @returns The normalized request.
411
+ */
412
+ function normalizeHttpEvent(event) {
413
+ const version = detectEventVersion(event);
414
+ const headers = normalizeHeaders(event);
415
+ const requestContext = event.requestContext;
416
+ const method = String((version === 'v2' ? requestContext?.http?.method : undefined) ??
417
+ event.httpMethod ??
418
+ requestContext?.httpMethod ??
419
+ requestContext?.http?.method ??
420
+ 'GET').toUpperCase();
421
+ const path = String((version === 'v2' ? (event.rawPath ?? requestContext?.http?.path) : undefined) ??
422
+ event.path ??
423
+ event.rawPath ??
424
+ '/');
425
+ return {
426
+ version,
427
+ method,
428
+ path,
429
+ headers,
430
+ rawQueryString: buildRawQueryString(event, version),
431
+ cookies: collectCookies(event, headers),
432
+ sourceIp: resolveSourceIp(event, version, headers),
433
+ isBase64Encoded: event.isBase64Encoded === true,
434
+ body: typeof event.body === 'string' ? event.body : undefined
435
+ };
436
+ }
437
+ /**
438
+ * Extract the raw request body exactly as received, decoding base64 to a Buffer when needed.
439
+ *
440
+ * This is what the adapter exposes as `metadata.rawBody`: the untouched payload the client sent,
441
+ * available to consumers even when no body-parsing middleware is installed. Binary payloads are
442
+ * returned as a `Buffer` (never a lossy UTF-8 round-trip); text payloads as a string. A
443
+ * non-string, non-base64 body (e.g. a pre-parsed object from a test/custom integration) is
444
+ * returned as-is.
445
+ *
446
+ * @param event - The raw Lambda event.
447
+ * @returns The raw body as a Buffer, string, the original value, or undefined.
448
+ */
449
+ function getRawBody(event) {
450
+ if (typeof event.body === 'string') {
451
+ return event.isBase64Encoded === true ? Buffer.from(event.body, 'base64') : event.body;
452
+ }
453
+ return event.body;
454
+ }
455
+
267
456
  /**
268
457
  * Adapter resolver for AWS Lambda HTTP adapter.
269
458
  *
@@ -308,83 +497,14 @@ const metaAdapterBlueprintMiddleware = [
308
497
  { module: SetAwsLambdaHttpResponseResolverMiddleware, priority: 6 }
309
498
  ];
310
499
 
311
- /**
312
- * Class representing a FilesEventMiddleware.
313
- *
314
- * @author Mr. Stone <evensstone@gmail.com>
315
- */
316
- class FilesEventMiddleware {
317
- /**
318
- * The blueprint for resolving configuration and dependencies.
319
- */
320
- blueprint;
321
- /**
322
- * Create a FilesEventMiddleware.
323
- *
324
- * @param {blueprint} options - Options for creating the FilesEventMiddleware.
325
- */
326
- constructor({ blueprint }) {
327
- this.blueprint = blueprint;
328
- }
329
- /**
330
- * Handles the incoming event, processes it, and invokes the next middleware in the pipeline.
331
- *
332
- * @param context - The adapter context containing the raw event, execution context, and other data.
333
- * @param next - The next middleware to be invoked in the pipeline.
334
- * @returns A promise that resolves to the destination type after processing.
335
- *
336
- * @throws {AwsLambdaHttpAdapterError} If required components such as the rawEvent or IncomingEventBuilder are not provided.
337
- */
338
- async handle(context, next) {
339
- if (context.rawEvent === undefined || context.incomingEventBuilder?.add === undefined) {
340
- throw new AwsLambdaHttpAdapterError('The context is missing required components.');
341
- }
342
- if (isMultipart(this.normalizeEvent(context.rawEvent))) {
343
- const options = this.blueprint.get('stone.http.files.upload', {});
344
- const response = await getFilesUploads(this.normalizeEvent(context.rawEvent), options);
345
- const method = response.fields.$method$;
346
- context
347
- .incomingEventBuilder
348
- .add('files', response.files)
349
- .add('body', response.fields);
350
- // In fullstack forms, the method is spoofed and sent as a hidden field
351
- isNotEmpty(method) && context.incomingEventBuilder.add('method', method);
352
- }
353
- return await next(context);
354
- }
355
- /**
356
- * Normalize the incoming event to an IncomingMessage.
357
- *
358
- * @param rawEvent - The raw event to be normalized.
359
- * @returns The normalized event.
360
- */
361
- normalizeEvent(rawEvent) {
362
- let body = rawEvent.body;
363
- if (typeof rawEvent.body === 'string') {
364
- body = rawEvent.isBase64Encoded === true
365
- ? Buffer.from(rawEvent.body, 'base64')
366
- : Buffer.from(rawEvent.body, 'utf-8');
367
- }
368
- return {
369
- body,
370
- headers: {
371
- 'content-type': rawEvent.headers['content-type'] ?? rawEvent.headers['Content-Type'],
372
- 'content-length': rawEvent.headers['content-length'] ?? rawEvent.headers['Content-Length'],
373
- 'transfer-encoding': rawEvent.headers['transfer-encoding'] ?? rawEvent.headers['Transfer-Encoding']
374
- }
375
- };
376
- }
377
- }
378
- /**
379
- * Meta Middleware for processing files uploads.
380
- */
381
- const MetaFilesEventMiddleware = { module: FilesEventMiddleware, isClass: true };
382
-
383
500
  /**
384
501
  * Middleware for handling incoming events and transforming them into Stone.js events.
385
502
  *
386
- * This class processes incoming HTTP requests, extracting relevant data such as URL, IP addresses,
387
- * headers, cookies, and more, and forwards them to the next middleware in the pipeline.
503
+ * It first normalizes the raw AWS event (API Gateway v1/v2, ALB, Function URLs) into a single
504
+ * canonical shape, then extracts URL, IP addresses, headers, cookies, query and the raw body,
505
+ * so the pipeline never has to reason about which trigger fired. The untouched request body is
506
+ * always exposed as `metadata.rawBody` — even when no body-parsing middleware is installed — so
507
+ * consumers can read the original payload (e.g. to verify a webhook signature).
388
508
  */
389
509
  class IncomingEventMiddleware {
390
510
  /**
@@ -394,7 +514,7 @@ class IncomingEventMiddleware {
394
514
  /**
395
515
  * Create an IncomingEventMiddleware instance.
396
516
  *
397
- * @param {blueprint} options - Options containing the blueprint for resolving configuration and dependencies.
517
+ * @param options - Options containing the blueprint for resolving configuration and dependencies.
398
518
  */
399
519
  constructor({ blueprint }) {
400
520
  this.blueprint = blueprint;
@@ -411,23 +531,23 @@ class IncomingEventMiddleware {
411
531
  if ((context.rawEvent === undefined) || ((context.incomingEventBuilder?.add) === undefined)) {
412
532
  throw new AwsLambdaHttpAdapterError('The context is missing required components.');
413
533
  }
534
+ const event = normalizeHttpEvent(context.rawEvent);
414
535
  const proxyOptions = this.getProxyOptions();
415
536
  const cookieOptions = this.getCookieOptions();
416
- const url = this.extractUrl(context.rawEvent, proxyOptions);
417
- const ipAddresses = this.extractIpAddresses(context.rawEvent, proxyOptions);
418
537
  context
419
538
  .incomingEventBuilder
420
- .add('url', url)
421
- .add('ips', ipAddresses)
539
+ .add('url', this.extractUrl(event, proxyOptions))
540
+ .add('ips', this.extractIpAddresses(event, proxyOptions))
422
541
  .add('source', this.getSource(context))
423
- .add('headers', context.rawEvent.headers)
424
- // If not defined by other middleware
425
- // In fullstack forms, the method is spoofed and sent as a hidden field
426
- .addIf('method', this.getMethod(context.rawEvent))
427
- .add('queryString', context.rawEvent.queryStringParameters)
428
- .add('protocol', this.getProtocol(context.rawEvent, proxyOptions))
429
- .add('cookies', CookieCollection.create(context.rawEvent.headers.cookie, cookieOptions, this.getCookieSecret()))
430
- .add('ip', proxyAddr(this.toNodeMessage(context.rawEvent), isIpTrusted(proxyOptions.trustedIp, proxyOptions.untrustedIp)));
542
+ .add('headers', event.headers)
543
+ // Expose the untouched request body so any consumer can read it, regardless of parsing.
544
+ .add('metadata', { rawBody: getRawBody(context.rawEvent) })
545
+ // If not defined by other middleware; in fullstack forms the method is spoofed via a field.
546
+ .addIf('method', event.method)
547
+ .add('queryString', event.rawQueryString)
548
+ .add('protocol', this.getProtocol(event, proxyOptions))
549
+ .add('cookies', CookieCollection.create(event.cookies.join('; '), cookieOptions, this.getCookieSecret()))
550
+ .add('ip', proxyAddr(this.toNodeMessage(event), isIpTrusted(proxyOptions.trustedIp, proxyOptions.untrustedIp)));
431
551
  return await next(context);
432
552
  }
433
553
  /**
@@ -443,18 +563,6 @@ class IncomingEventMiddleware {
443
563
  rawContext: context.executionContext
444
564
  };
445
565
  }
446
- /**
447
- * Extracts the HTTP method from the incoming rawEvent.
448
- *
449
- * @param rawEvent - The incoming rawEvent.
450
- * @returns The HTTP method string.
451
- */
452
- getMethod(rawEvent) {
453
- return rawEvent.httpMethod ??
454
- rawEvent.requestContext?.httpMethod ??
455
- rawEvent.requestContext?.http?.method ??
456
- 'GET';
457
- }
458
566
  /**
459
567
  * Extracts proxy-related options from the blueprint.
460
568
  *
@@ -462,7 +570,7 @@ class IncomingEventMiddleware {
462
570
  */
463
571
  getProxyOptions() {
464
572
  const defaultProxyOptions = { trusted: [], trustedIp: [], untrustedIp: [] };
465
- const proxyOptions = this.blueprint.get('stone.http.proxies', defaultProxyOptions);
573
+ const proxyOptions = { ...this.blueprint.get('stone.http.proxies', defaultProxyOptions) };
466
574
  proxyOptions.trusted = this.blueprint.get('stone.http.hosts.trusted', []);
467
575
  return proxyOptions;
468
576
  }
@@ -483,58 +591,58 @@ class IncomingEventMiddleware {
483
591
  return this.blueprint.get('stone.http.cookie.secret', this.blueprint.get('stone.secret', ''));
484
592
  }
485
593
  /**
486
- * Extracts and parses the URL from the incoming rawEvent.
594
+ * Extracts and parses the URL (including the query string) from the normalized event.
487
595
  *
488
- * @param rawEvent - The incoming HTTP rawEvent.
596
+ * @param event - The normalized HTTP event.
489
597
  * @param options - Proxy options.
490
598
  * @returns The parsed URL object.
491
599
  */
492
- extractUrl(rawEvent, options) {
493
- const hostname = getHostname(this.getRemoteAddress(rawEvent), rawEvent.headers, options);
494
- const proto = getProtocol(this.getRemoteAddress(rawEvent), rawEvent.headers, true, options);
495
- return new URL(rawEvent.path ?? rawEvent.rawPath ?? '', `${String(proto)}://${String(hostname)}`);
600
+ extractUrl(event, options) {
601
+ const hostname = getHostname(event.sourceIp, event.headers, options);
602
+ const proto = getProtocol(event.sourceIp, event.headers, true, options);
603
+ const pathWithQuery = event.rawQueryString.length > 0 ? `${event.path}?${event.rawQueryString}` : event.path;
604
+ return new URL(pathWithQuery, `${String(proto)}://${String(hostname)}`);
496
605
  }
497
606
  /**
498
- * Extracts a list of IP addresses from the incoming rawEvent.
607
+ * Extracts a list of IP addresses from the normalized event.
499
608
  *
500
- * @param rawEvent - The incoming HTTP rawEvent.
609
+ * @param event - The normalized HTTP event.
501
610
  * @param options - Proxy options.
502
611
  * @returns An array of IP addresses.
503
612
  */
504
- extractIpAddresses(rawEvent, options) {
613
+ extractIpAddresses(event, options) {
505
614
  const isTrusted = isIpTrusted(options.trustedIp, options.untrustedIp);
506
- return proxyAddr.all(this.toNodeMessage(rawEvent), isTrusted).slice(1).reverse();
615
+ return proxyAddr.all(this.toNodeMessage(event), isTrusted).slice(1).reverse();
507
616
  }
508
617
  /**
509
- * Converts the incoming rawEvent to a Node.js IncomingMessage.
618
+ * Converts the normalized event to a minimal Node.js IncomingMessage for `proxy-addr`.
619
+ *
620
+ * All standard forwarding headers are forwarded (not just `x-forwarded-for`) so proxy-addr can
621
+ * honour the deployment's trust configuration.
510
622
  *
511
- * @param rawEvent - The incoming rawEvent.
623
+ * @param event - The normalized HTTP event.
512
624
  * @returns The converted IncomingMessage.
513
625
  */
514
- toNodeMessage(rawEvent) {
626
+ toNodeMessage(event) {
515
627
  return {
516
- connection: { remoteAddress: this.getRemoteAddress(rawEvent) },
517
- headers: { 'x-forwarded-for': rawEvent.headers['x-forwarded-for'] ?? rawEvent.headers['X-Forwarded-For'] }
628
+ connection: { remoteAddress: event.sourceIp },
629
+ socket: { remoteAddress: event.sourceIp },
630
+ headers: {
631
+ forwarded: event.headers.forwarded,
632
+ 'x-real-ip': event.headers['x-real-ip'],
633
+ 'x-forwarded-for': event.headers['x-forwarded-for'] ?? event.sourceIp
634
+ }
518
635
  };
519
636
  }
520
637
  /**
521
- * Determines the protocol from the incoming rawEvent.
638
+ * Determines the protocol from the normalized event.
522
639
  *
523
- * @param rawEvent - The incoming rawEvent.
640
+ * @param event - The normalized HTTP event.
524
641
  * @param options - Proxy options.
525
642
  * @returns The protocol string.
526
643
  */
527
- getProtocol(rawEvent, options) {
528
- return getProtocol(this.getRemoteAddress(rawEvent), rawEvent.headers, true, options);
529
- }
530
- /**
531
- * Retrieves the remote address from the incoming rawEvent.
532
- * This method is used as a fallback when the remote address is not found in the rawEvent.
533
- * @param rawEvent - The incoming rawEvent.
534
- * @returns The remote address string.
535
- */
536
- getRemoteAddress(rawEvent) {
537
- return rawEvent.requestContext?.http?.sourceIp ?? rawEvent.requestContext?.identity?.sourceIp ?? '';
644
+ getProtocol(event, options) {
645
+ return getProtocol(event.sourceIp, event.headers, true, options);
538
646
  }
539
647
  }
540
648
  /**
@@ -564,6 +672,7 @@ class ServerResponseMiddleware {
564
672
  }
565
673
  rawResponseBuilder
566
674
  .add('headers', context.outgoingResponse.headers)
675
+ .add('version', detectEventVersion(context.rawEvent))
567
676
  .add('statusCode', context.outgoingResponse.statusCode ?? 500)
568
677
  .add('statusMessage', context.outgoingResponse.statusMessage ?? statuses.message[context.outgoingResponse.statusCode ?? 500]);
569
678
  if (!context.incomingEvent.isMethod('HEAD')) {
@@ -579,8 +688,7 @@ class ServerResponseMiddleware {
579
688
  : context.outgoingResponse.content;
580
689
  rawResponseBuilder
581
690
  .add('body', content)
582
- .add('isBase64Encoded', isBuffer)
583
- .add('charset', context.outgoingResponse.charset);
691
+ .add('isBase64Encoded', isBuffer);
584
692
  }
585
693
  }
586
694
  return rawResponseBuilder;
@@ -591,6 +699,79 @@ class ServerResponseMiddleware {
591
699
  */
592
700
  const MetaServerResponseMiddleware = { module: ServerResponseMiddleware, isClass: true };
593
701
 
702
+ /**
703
+ * Default blueprint configuration for the AWS Lambda Http Adapter.
704
+ *
705
+ * This blueprint defines the initial configuration for the AWS Lambda Http adapter
706
+ * within the Stone.js framework. It includes:
707
+ * - An alias for the AWS Lambda platform (`AWS_LAMBDA_HTTP_PLATFORM`).
708
+ * - A default resolver function (currently a placeholder).
709
+ * - Middleware, hooks, and state flags (`current`, `default`, `preferred`).
710
+ */
711
+ const awsLambdaHttpAdapterBlueprint = {
712
+ stone: {
713
+ ...httpCoreBlueprint.stone,
714
+ blueprint: {
715
+ middleware: metaAdapterBlueprintMiddleware
716
+ },
717
+ adapters: [
718
+ {
719
+ current: false,
720
+ variant: 'server',
721
+ platform: AWS_LAMBDA_HTTP_PLATFORM,
722
+ middleware: [
723
+ MetaIncomingEventMiddleware,
724
+ MetaServerResponseMiddleware
725
+ ],
726
+ resolver: awsLambdaHttpAdapterResolver,
727
+ eventHandlerResolver: defaultKernelResolver,
728
+ errorHandlers: {
729
+ default: { module: AwsLambdaHttpErrorHandler, isClass: true }
730
+ },
731
+ default: isNotEmpty(getString('AWS_LAMBDA_FUNCTION_NAME', ''))
732
+ }
733
+ ]
734
+ }
735
+ };
736
+
737
+ /**
738
+ * A Stone.js decorator that integrates the AWS Lambda HTTP Adapter with a class.
739
+ *
740
+ * This decorator modifies the class to seamlessly enable AWS Lambda HTTP as the
741
+ * execution environment for a Stone.js application. By applying this decorator,
742
+ * the class is automatically configured with the necessary blueprint for AWS Lambda HTTP.
743
+ *
744
+ * @template T - The type of the class being decorated. Defaults to `ClassType`.
745
+ * @param options - Optional configuration to customize the AWS Lambda HTTP Adapter.
746
+ *
747
+ * @returns A class decorator that applies the AWS Lambda HTTP adapter configuration.
748
+ *
749
+ * @example
750
+ * ```typescript
751
+ * import { AwsLambdaHttp } from '@stone-js/aws-lambda-http-adapter';
752
+ *
753
+ * @AwsLambdaHttp({
754
+ * alias: 'MyAwsLambdaHttpAdapter',
755
+ * current: true,
756
+ * })
757
+ * class App {
758
+ * // Your application logic here
759
+ * }
760
+ * ```
761
+ */
762
+ const AwsLambdaHttp = (options = {}) => {
763
+ return classDecoratorLegacyWrapper((target, context) => {
764
+ // Clone the module-level default before merging so decorating a class never mutates the shared
765
+ // singleton (which would leak options across classes and tests). cloneValue recreates plain
766
+ // objects/arrays while keeping functions and class references intact.
767
+ const blueprint = cloneValue(awsLambdaHttpAdapterBlueprint);
768
+ if (blueprint.stone?.adapters?.[0] !== undefined) {
769
+ blueprint.stone.adapters[0] = deepMerge(blueprint.stone.adapters[0], options);
770
+ }
771
+ addBlueprint(target, context, blueprint);
772
+ });
773
+ };
774
+
594
775
  /**
595
776
  * Class representing a BodyEventMiddleware.
596
777
  *
@@ -626,16 +807,33 @@ class BodyEventMiddleware {
626
807
  }
627
808
  if (!isMultipart(this.toNodeMessage(context.rawEvent))) {
628
809
  const body = this.getBody(this.toNodeMessage(context.rawEvent), context.rawEvent);
629
- const method = body.$method$;
810
+ const method = this.extractSpoofedMethod(body);
630
811
  context
631
812
  .incomingEventBuilder
632
813
  .add('body', body)
633
- .add('metadata', body);
634
- // In fullstack forms, the method is spoofed and sent as a hidden field
814
+ // Keep the untouched payload available even after parsing (webhook signatures, etc.).
815
+ .add('metadata', { rawBody: getRawBody(context.rawEvent) });
816
+ // In fullstack forms, the method is spoofed and sent as a hidden field.
635
817
  isNotEmpty(method) && context.incomingEventBuilder.add('method', method);
636
818
  }
637
819
  return await next(context);
638
820
  }
821
+ /**
822
+ * Extract a spoofed HTTP method from a parsed body, supporting both JSON objects and
823
+ * urlencoded bodies (whose fields live on a `URLSearchParams`, not as own properties).
824
+ *
825
+ * @param body - The parsed body.
826
+ * @returns The spoofed method, or undefined.
827
+ */
828
+ extractSpoofedMethod(body) {
829
+ if (body instanceof URLSearchParams) {
830
+ return body.get('$method$') ?? undefined;
831
+ }
832
+ if (typeof body === 'object' && body !== null) {
833
+ return body.$method$;
834
+ }
835
+ return undefined;
836
+ }
639
837
  /**
640
838
  * Convert the raw event into a Node.js IncomingMessage.
641
839
  *
@@ -643,11 +841,12 @@ class BodyEventMiddleware {
643
841
  * @returns The converted IncomingMessage.
644
842
  */
645
843
  toNodeMessage(rawEvent) {
844
+ const headers = normalizeHeaders(rawEvent);
646
845
  return {
647
846
  headers: {
648
- 'content-type': rawEvent.headers['content-type'] ?? rawEvent.headers['Content-Type'],
649
- 'content-length': rawEvent.headers['content-length'] ?? rawEvent.headers['Content-Length'],
650
- 'transfer-encoding': rawEvent.headers['transfer-encoding'] ?? rawEvent.headers['Transfer-Encoding']
847
+ 'content-type': headers['content-type'],
848
+ 'content-length': headers['content-length'],
849
+ 'transfer-encoding': headers['transfer-encoding']
651
850
  }
652
851
  };
653
852
  }
@@ -667,50 +866,54 @@ class BodyEventMiddleware {
667
866
  const limit = bytes.parse(rawLimit) ?? 100000;
668
867
  const encoding = getCharset(message, defaultCharset);
669
868
  const type = typeIs(message, ['urlencoded', 'json', 'text', 'bin']) ?? defaultType;
670
- const rawBodyContent = this.getNormalizedRawBody(rawEvent, encoding);
671
- if (Buffer.byteLength(rawBodyContent, encoding) > limit) {
869
+ // Decode ONCE to a Buffer. Binary payloads must never round-trip through a lossy UTF-8 string
870
+ // (which corrupts any non-UTF-8 byte); the limit is measured on the true byte length.
871
+ const rawBuffer = this.getRawBuffer(rawEvent, encoding);
872
+ if (rawBuffer.byteLength > limit) {
672
873
  throw new AwsLambdaHttpAdapterError('Body payload exceeds configured limit.');
673
874
  }
674
- return this.parseBodyContent(type, rawBodyContent, encoding);
875
+ return this.parseBodyContent(type, rawBuffer, encoding);
675
876
  }
676
877
  /**
677
- * Get the normalized raw body from the event.
878
+ * Decode the request body into a Buffer, honouring base64 encoding.
678
879
  *
679
880
  * @param rawEvent - The raw event containing the body.
680
- * @param encoding - The encoding to use for the body.
681
- * @returns The normalized body as a string.
881
+ * @param encoding - The charset for a plain-text (non-base64) body.
882
+ * @returns The body as a Buffer.
682
883
  */
683
- getNormalizedRawBody(rawEvent, encoding) {
884
+ getRawBuffer(rawEvent, encoding) {
684
885
  if (typeof rawEvent.body === 'string') {
685
886
  return rawEvent.isBase64Encoded === true
686
- ? Buffer.from(rawEvent.body, 'base64').toString(encoding)
687
- : rawEvent.body;
887
+ ? Buffer.from(rawEvent.body, 'base64')
888
+ : Buffer.from(rawEvent.body, encoding);
688
889
  }
689
890
  if (typeof rawEvent.body === 'object' && rawEvent.body !== null) {
690
- return JSON.stringify(rawEvent.body);
891
+ return Buffer.from(JSON.stringify(rawEvent.body), encoding);
691
892
  }
692
- return '';
893
+ return Buffer.alloc(0);
693
894
  }
694
895
  /**
695
896
  * Parse the body content based on the specified type and encoding.
696
897
  *
697
898
  * @param type - The content type of the body.
698
- * @param body - The raw body content as a string.
899
+ * @param buffer - The raw body content as a Buffer.
699
900
  * @param encoding - The encoding of the body content.
700
901
  * @returns The parsed body content as an object, string, or Buffer.
701
902
  * @throws {AwsLambdaHttpAdapterError} If parsing fails.
702
903
  */
703
- parseBodyContent(type, body, encoding) {
904
+ parseBodyContent(type, buffer, encoding) {
704
905
  try {
705
906
  switch (type) {
706
- case 'json':
707
- return isNotEmpty(body) ? JSON.parse(body) : {};
907
+ case 'json': {
908
+ const text = buffer.toString(encoding);
909
+ return isNotEmpty(text) ? JSON.parse(text) : {};
910
+ }
708
911
  case 'text':
709
- return body;
912
+ return buffer.toString(encoding);
710
913
  case 'urlencoded':
711
- return new URLSearchParams(body);
914
+ return new URLSearchParams(buffer.toString(encoding));
712
915
  case 'bin':
713
- return Buffer.from(body, encoding);
916
+ return buffer; // Return the raw bytes untouched — no lossy re-encoding.
714
917
  default:
715
918
  return {};
716
919
  }
@@ -725,223 +928,79 @@ class BodyEventMiddleware {
725
928
  */
726
929
  const MetaBodyEventMiddleware = { module: BodyEventMiddleware, isClass: true };
727
930
 
728
- function getDefaultExportFromCjs (x) {
729
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
730
- }
731
-
732
- var cjs;
733
- var hasRequiredCjs;
734
-
735
- function requireCjs () {
736
- if (hasRequiredCjs) return cjs;
737
- hasRequiredCjs = 1;
738
-
739
- var isMergeableObject = function isMergeableObject(value) {
740
- return isNonNullObject(value)
741
- && !isSpecial(value)
742
- };
743
-
744
- function isNonNullObject(value) {
745
- return !!value && typeof value === 'object'
746
- }
747
-
748
- function isSpecial(value) {
749
- var stringValue = Object.prototype.toString.call(value);
750
-
751
- return stringValue === '[object RegExp]'
752
- || stringValue === '[object Date]'
753
- || isReactElement(value)
754
- }
755
-
756
- // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
757
- var canUseSymbol = typeof Symbol === 'function' && Symbol.for;
758
- var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7;
759
-
760
- function isReactElement(value) {
761
- return value.$$typeof === REACT_ELEMENT_TYPE
762
- }
763
-
764
- function emptyTarget(val) {
765
- return Array.isArray(val) ? [] : {}
766
- }
767
-
768
- function cloneUnlessOtherwiseSpecified(value, options) {
769
- return (options.clone !== false && options.isMergeableObject(value))
770
- ? deepmerge(emptyTarget(value), value, options)
771
- : value
772
- }
773
-
774
- function defaultArrayMerge(target, source, options) {
775
- return target.concat(source).map(function(element) {
776
- return cloneUnlessOtherwiseSpecified(element, options)
777
- })
778
- }
779
-
780
- function getMergeFunction(key, options) {
781
- if (!options.customMerge) {
782
- return deepmerge
783
- }
784
- var customMerge = options.customMerge(key);
785
- return typeof customMerge === 'function' ? customMerge : deepmerge
786
- }
787
-
788
- function getEnumerableOwnPropertySymbols(target) {
789
- return Object.getOwnPropertySymbols
790
- ? Object.getOwnPropertySymbols(target).filter(function(symbol) {
791
- return Object.propertyIsEnumerable.call(target, symbol)
792
- })
793
- : []
794
- }
795
-
796
- function getKeys(target) {
797
- return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target))
798
- }
799
-
800
- function propertyIsOnObject(object, property) {
801
- try {
802
- return property in object
803
- } catch(_) {
804
- return false
805
- }
806
- }
807
-
808
- // Protects from prototype poisoning and unexpected merging up the prototype chain.
809
- function propertyIsUnsafe(target, key) {
810
- return propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet,
811
- && !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain,
812
- && Object.propertyIsEnumerable.call(target, key)) // and also unsafe if they're nonenumerable.
813
- }
814
-
815
- function mergeObject(target, source, options) {
816
- var destination = {};
817
- if (options.isMergeableObject(target)) {
818
- getKeys(target).forEach(function(key) {
819
- destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
820
- });
821
- }
822
- getKeys(source).forEach(function(key) {
823
- if (propertyIsUnsafe(target, key)) {
824
- return
825
- }
826
-
827
- if (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) {
828
- destination[key] = getMergeFunction(key, options)(target[key], source[key], options);
829
- } else {
830
- destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
831
- }
832
- });
833
- return destination
834
- }
835
-
836
- function deepmerge(target, source, options) {
837
- options = options || {};
838
- options.arrayMerge = options.arrayMerge || defaultArrayMerge;
839
- options.isMergeableObject = options.isMergeableObject || isMergeableObject;
840
- // cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge()
841
- // implementations can use it. The caller may not replace it.
842
- options.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified;
843
-
844
- var sourceIsArray = Array.isArray(source);
845
- var targetIsArray = Array.isArray(target);
846
- var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
847
-
848
- if (!sourceAndTargetTypesMatch) {
849
- return cloneUnlessOtherwiseSpecified(source, options)
850
- } else if (sourceIsArray) {
851
- return options.arrayMerge(target, source, options)
852
- } else {
853
- return mergeObject(target, source, options)
854
- }
855
- }
856
-
857
- deepmerge.all = function deepmergeAll(array, options) {
858
- if (!Array.isArray(array)) {
859
- throw new Error('first argument should be an array')
860
- }
861
-
862
- return array.reduce(function(prev, next) {
863
- return deepmerge(prev, next, options)
864
- }, {})
865
- };
866
-
867
- var deepmerge_1 = deepmerge;
868
-
869
- cjs = deepmerge_1;
870
- return cjs;
871
- }
872
-
873
- var cjsExports = requireCjs();
874
- var deepmerge = /*@__PURE__*/getDefaultExportFromCjs(cjsExports);
875
-
876
931
  /**
877
- * Default blueprint configuration for the AWS Lambda Http Adapter.
932
+ * Class representing a FilesEventMiddleware.
878
933
  *
879
- * This blueprint defines the initial configuration for the AWS Lambda Http adapter
880
- * within the Stone.js framework. It includes:
881
- * - An alias for the AWS Lambda platform (`AWS_LAMBDA_HTTP_PLATFORM`).
882
- * - A default resolver function (currently a placeholder).
883
- * - Middleware, hooks, and state flags (`current`, `default`, `preferred`).
934
+ * @author Mr. Stone <evensstone@gmail.com>
884
935
  */
885
- const awsLambdaHttpAdapterBlueprint = {
886
- stone: {
887
- ...httpCoreBlueprint.stone,
888
- blueprint: {
889
- middleware: metaAdapterBlueprintMiddleware
890
- },
891
- adapters: [
892
- {
893
- current: false,
894
- variant: 'server',
895
- platform: AWS_LAMBDA_HTTP_PLATFORM,
896
- middleware: [
897
- MetaIncomingEventMiddleware,
898
- MetaServerResponseMiddleware
899
- ],
900
- resolver: awsLambdaHttpAdapterResolver,
901
- eventHandlerResolver: defaultKernelResolver,
902
- errorHandlers: {
903
- default: { module: AwsLambdaHttpErrorHandler, isClass: true }
904
- },
905
- default: isNotEmpty(getString('AWS_LAMBDA_FUNCTION_NAME', ''))
936
+ class FilesEventMiddleware {
937
+ /**
938
+ * The blueprint for resolving configuration and dependencies.
939
+ */
940
+ blueprint;
941
+ /**
942
+ * Create a FilesEventMiddleware.
943
+ *
944
+ * @param {blueprint} options - Options for creating the FilesEventMiddleware.
945
+ */
946
+ constructor({ blueprint }) {
947
+ this.blueprint = blueprint;
948
+ }
949
+ /**
950
+ * Handles the incoming event, processes it, and invokes the next middleware in the pipeline.
951
+ *
952
+ * @param context - The adapter context containing the raw event, execution context, and other data.
953
+ * @param next - The next middleware to be invoked in the pipeline.
954
+ * @returns A promise that resolves to the destination type after processing.
955
+ *
956
+ * @throws {AwsLambdaHttpAdapterError} If required components such as the rawEvent or IncomingEventBuilder are not provided.
957
+ */
958
+ async handle(context, next) {
959
+ if (context.rawEvent === undefined || context.incomingEventBuilder?.add === undefined) {
960
+ throw new AwsLambdaHttpAdapterError('The context is missing required components.');
961
+ }
962
+ if (isMultipart(this.normalizeEvent(context.rawEvent))) {
963
+ const options = this.blueprint.get('stone.http.files.upload', {});
964
+ const response = await getFilesUploads(this.normalizeEvent(context.rawEvent), options);
965
+ const method = response.fields.$method$;
966
+ context
967
+ .incomingEventBuilder
968
+ .add('files', response.files)
969
+ .add('body', response.fields)
970
+ // Keep the untouched multipart payload available (webhook signatures, re-streaming, etc.).
971
+ .add('metadata', { rawBody: getRawBody(context.rawEvent) });
972
+ // In fullstack forms, the method is spoofed and sent as a hidden field.
973
+ isNotEmpty(method) && context.incomingEventBuilder.add('method', method);
974
+ }
975
+ return await next(context);
976
+ }
977
+ /**
978
+ * Normalize the incoming event to an IncomingMessage.
979
+ *
980
+ * @param rawEvent - The raw event to be normalized.
981
+ * @returns The normalized event.
982
+ */
983
+ normalizeEvent(rawEvent) {
984
+ const headers = normalizeHeaders(rawEvent);
985
+ let body = rawEvent.body;
986
+ if (typeof rawEvent.body === 'string') {
987
+ body = rawEvent.isBase64Encoded === true
988
+ ? Buffer.from(rawEvent.body, 'base64')
989
+ : Buffer.from(rawEvent.body, 'utf-8');
990
+ }
991
+ return {
992
+ body,
993
+ headers: {
994
+ 'content-type': headers['content-type'],
995
+ 'content-length': headers['content-length'],
996
+ 'transfer-encoding': headers['transfer-encoding']
906
997
  }
907
- ]
998
+ };
908
999
  }
909
- };
910
-
1000
+ }
911
1001
  /**
912
- * A Stone.js decorator that integrates the AWS Lambda HTTP Adapter with a class.
913
- *
914
- * This decorator modifies the class to seamlessly enable AWS Lambda HTTP as the
915
- * execution environment for a Stone.js application. By applying this decorator,
916
- * the class is automatically configured with the necessary blueprint for AWS Lambda HTTP.
917
- *
918
- * @template T - The type of the class being decorated. Defaults to `ClassType`.
919
- * @param options - Optional configuration to customize the AWS Lambda HTTP Adapter.
920
- *
921
- * @returns A class decorator that applies the AWS Lambda HTTP adapter configuration.
922
- *
923
- * @example
924
- * ```typescript
925
- * import { AwsLambdaHttp } from '@stone-js/aws-lambda-http-adapter';
926
- *
927
- * @AwsLambdaHttp({
928
- * alias: 'MyAwsLambdaHttpAdapter',
929
- * current: true,
930
- * })
931
- * class App {
932
- * // Your application logic here
933
- * }
934
- * ```
1002
+ * Meta Middleware for processing files uploads.
935
1003
  */
936
- const AwsLambdaHttp = (options = {}) => {
937
- return classDecoratorLegacyWrapper((target, context) => {
938
- if (awsLambdaHttpAdapterBlueprint.stone?.adapters?.[0] !== undefined) {
939
- // Merge provided options with the default AWS Lambda HTTP adapter blueprint.
940
- awsLambdaHttpAdapterBlueprint.stone.adapters[0] = deepmerge(awsLambdaHttpAdapterBlueprint.stone.adapters[0], options);
941
- }
942
- // Add the modified blueprint to the target class.
943
- addBlueprint(target, context, awsLambdaHttpAdapterBlueprint);
944
- });
945
- };
1004
+ const MetaFilesEventMiddleware = { module: FilesEventMiddleware, isClass: true };
946
1005
 
947
- export { AWS_LAMBDA_HTTP_PLATFORM, AwsLambdaHttp, AwsLambdaHttpAdapter, AwsLambdaHttpAdapterError, AwsLambdaHttpErrorHandler, BodyEventMiddleware, FilesEventMiddleware, IncomingEventMiddleware, MetaBodyEventMiddleware, MetaFilesEventMiddleware, MetaIncomingEventMiddleware, MetaServerResponseMiddleware, RawHttpResponseWrapper, ServerResponseMiddleware, SetAwsLambdaHttpResponseResolverMiddleware, awsLambdaHttpAdapterBlueprint, awsLambdaHttpAdapterResolver, metaAdapterBlueprintMiddleware };
1006
+ export { AWS_LAMBDA_HTTP_PLATFORM, AwsLambdaHttp, AwsLambdaHttpAdapter, AwsLambdaHttpAdapterError, AwsLambdaHttpErrorHandler, BodyEventMiddleware, FilesEventMiddleware, IncomingEventMiddleware, MetaBodyEventMiddleware, MetaFilesEventMiddleware, MetaIncomingEventMiddleware, MetaServerResponseMiddleware, RawHttpResponseWrapper, ServerResponseMiddleware, SetAwsLambdaHttpResponseResolverMiddleware, awsLambdaHttpAdapterBlueprint, awsLambdaHttpAdapterResolver, buildRawQueryString, collectCookies, detectEventVersion, getRawBody, metaAdapterBlueprintMiddleware, normalizeHeaders, normalizeHttpEvent, resolveSourceIp };