@stone-js/aws-lambda-http-adapter 0.1.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.
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/index.d.ts +640 -0
- package/dist/index.js +889 -0
- package/package.json +110 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
import { IntegrationError, Adapter, AdapterEventBuilder, defaultLoggerResolver, isNotEmpty, defaultKernelResolver, classDecoratorLegacyWrapper, addBlueprint } from '@stone-js/core';
|
|
2
|
+
import { IncomingHttpEvent, HTTP_INTERNAL_SERVER_ERROR, BinaryFileResponse, OutgoingHttpResponse, CookieCollection, isIpTrusted, getHostname, getProtocol, httpCoreBlueprint, isMultipart, getCharset, getType, getFilesUploads } from '@stone-js/http-core';
|
|
3
|
+
import mime from 'mime';
|
|
4
|
+
import accepts from 'accepts';
|
|
5
|
+
import statuses from 'statuses';
|
|
6
|
+
import { getString } from '@stone-js/env';
|
|
7
|
+
import { File } from '@stone-js/filesystem';
|
|
8
|
+
import proxyAddr from 'proxy-addr';
|
|
9
|
+
import bytes from 'bytes';
|
|
10
|
+
import typeIs from 'type-is';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Wrapper for HTTP raw responses in AWS Lambda.
|
|
14
|
+
*
|
|
15
|
+
* The `RawHttpResponseWrapper` is responsible for constructing and returning
|
|
16
|
+
* a raw HTTP response that conforms to the expected structure for AWS Lambda.
|
|
17
|
+
* It implements the `IRawResponseWrapper` interface, ensuring compatibility
|
|
18
|
+
* with the Stone.js framework.
|
|
19
|
+
*/
|
|
20
|
+
class RawHttpResponseWrapper {
|
|
21
|
+
options;
|
|
22
|
+
/**
|
|
23
|
+
* Factory method to create an instance of `RawHttpResponseWrapper`.
|
|
24
|
+
*
|
|
25
|
+
* This method accepts partial response options, allowing the user to configure
|
|
26
|
+
* only the required fields. It initializes the wrapper with these options.
|
|
27
|
+
*
|
|
28
|
+
* @param options - Partial options to configure the HTTP response.
|
|
29
|
+
* @returns A new instance of `RawHttpResponseWrapper`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const responseWrapper = RawHttpResponseWrapper.create({
|
|
34
|
+
* statusCode: 200,
|
|
35
|
+
* body: { message: 'Success' },
|
|
36
|
+
* headers: { 'Content-Type': 'application/json' }
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* const response = responseWrapper.respond();
|
|
40
|
+
* console.log(response); // { statusCode: 200, body: '{"message":"Success"}', headers: { 'Content-Type': 'application/json' } }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
static create(options) {
|
|
44
|
+
return new this(options);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Constructs an instance of `RawHttpResponseWrapper`.
|
|
48
|
+
*
|
|
49
|
+
* This constructor is private and should not be called directly.
|
|
50
|
+
* Use the `create` method to initialize an instance.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Partial options for configuring the HTTP response.
|
|
53
|
+
*/
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this.options = options;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Constructs and returns the raw HTTP response.
|
|
59
|
+
*
|
|
60
|
+
* The `respond` method generates a `RawHttpResponse` object based on the
|
|
61
|
+
* provided options. If any required fields are missing, it assigns default values:
|
|
62
|
+
* - `statusCode`: Defaults to `500`.
|
|
63
|
+
*
|
|
64
|
+
* @returns A `RawHttpResponse` object.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const responseWrapper = RawHttpResponseWrapper.create({ body: 'Hello, world!', statusCode: 200 });
|
|
69
|
+
* const response = responseWrapper.respond();
|
|
70
|
+
* console.log(response); // { statusCode: 500, statusMessage: '', body: 'Hello, world!', headers: undefined }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
respond() {
|
|
74
|
+
return {
|
|
75
|
+
...this.options,
|
|
76
|
+
statusCode: this.options.statusCode ?? 500
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Custom error for AWS Lambda adapter operations.
|
|
83
|
+
*/
|
|
84
|
+
class AwsLambdaHttpAdapterError extends IntegrationError {
|
|
85
|
+
constructor(message, options) {
|
|
86
|
+
super(message, options);
|
|
87
|
+
this.name = 'AwsLambdaHttpAdapterError';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* AWS Lambda HTTP Adapter for Stone.js.
|
|
93
|
+
*
|
|
94
|
+
* The `AwsLambdaHttpAdapter` extends the functionality of the Stone.js `Adapter`
|
|
95
|
+
* to provide seamless integration with AWS Lambda for HTTP-based events. This adapter
|
|
96
|
+
* transforms incoming HTTP events from AWS Lambda into `IncomingHttpEvent` instances
|
|
97
|
+
* and produces a `RawHttpResponse` as output.
|
|
98
|
+
*
|
|
99
|
+
* This adapter simplifies the process of handling HTTP events within AWS Lambda
|
|
100
|
+
* while adhering to the Stone.js framework's event-driven architecture.
|
|
101
|
+
*
|
|
102
|
+
* @template AwsLambdaHttpEvent - The type of the raw HTTP event from AWS Lambda.
|
|
103
|
+
* @template RawHttpResponse - The type of the raw HTTP response to send back.
|
|
104
|
+
* @template AwsLambdaContext - The AWS Lambda execution context type.
|
|
105
|
+
* @template IncomingHttpEvent - The type of the processed incoming HTTP event.
|
|
106
|
+
* @template IncomingHttpEventOptions - Options used to create an incoming HTTP event.
|
|
107
|
+
* @template OutgoingHttpResponse - The type of the outgoing HTTP response after processing.
|
|
108
|
+
* @template AwsLambdaHttpAdapterContext - Context type specific to the HTTP adapter.
|
|
109
|
+
*
|
|
110
|
+
* @extends Adapter
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* import { AwsLambdaHttpAdapter } from '@stone-js/aws-lambda-http-adapter';
|
|
115
|
+
*
|
|
116
|
+
* const adapter = AwsLambdaHttpAdapter.create({...});
|
|
117
|
+
*
|
|
118
|
+
* const handler = await adapter.run();
|
|
119
|
+
*
|
|
120
|
+
* export { handler };
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @see {@link https://stone-js.com/docs Stone.js Documentation}
|
|
124
|
+
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/ AWS Lambda Documentation}
|
|
125
|
+
*/
|
|
126
|
+
class AwsLambdaHttpAdapter extends Adapter {
|
|
127
|
+
/**
|
|
128
|
+
* Creates an instance of the `AwsLambdaHttpAdapter`.
|
|
129
|
+
*
|
|
130
|
+
* @param blueprint - The application blueprint.
|
|
131
|
+
* @returns A new instance of `AwsLambdaHttpAdapter`.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* const adapter = AwsLambdaHttpAdapter.create(blueprint);
|
|
136
|
+
* await adapter.run();
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
static create(blueprint) {
|
|
140
|
+
return new this(blueprint);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Executes the adapter and provides an AWS Lambda-compatible HTTP handler function.
|
|
144
|
+
*
|
|
145
|
+
* This method initializes the adapter and returns a handler function that can
|
|
146
|
+
* process HTTP events in AWS Lambda. It transforms raw events into `IncomingHttpEvent`
|
|
147
|
+
* instances and produces `RawHttpResponse` objects as output.
|
|
148
|
+
*
|
|
149
|
+
* @template ExecutionResultType - The type representing the AWS Lambda event handler function.
|
|
150
|
+
* @returns A promise resolving to the AWS Lambda HTTP handler function.
|
|
151
|
+
* @throws {AwsLambdaHttpAdapterError} If used outside the AWS Lambda environment.
|
|
152
|
+
*/
|
|
153
|
+
async run() {
|
|
154
|
+
await this.onStart();
|
|
155
|
+
const handler = async (rawEvent, executionContext) => {
|
|
156
|
+
return await this.eventListener(rawEvent, executionContext);
|
|
157
|
+
};
|
|
158
|
+
return handler;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Initializes the adapter and validates its execution context.
|
|
162
|
+
*
|
|
163
|
+
* Ensures that the adapter is running in an AWS Lambda environment. Throws an error
|
|
164
|
+
* if it detects that the adapter is being used in an unsupported environment (e.g., a browser).
|
|
165
|
+
*
|
|
166
|
+
* @throws {AwsLambdaHttpAdapterError} If executed outside an AWS Lambda environment.
|
|
167
|
+
*/
|
|
168
|
+
async onStart() {
|
|
169
|
+
if (typeof window === 'object') {
|
|
170
|
+
throw new AwsLambdaHttpAdapterError('This `AWSLambdaAdapter` must be used only in AWS Lambda context.');
|
|
171
|
+
}
|
|
172
|
+
await this.executeHooks('onStart');
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Processes an incoming AWS Lambda HTTP event.
|
|
176
|
+
*
|
|
177
|
+
* Converts a raw AWS Lambda HTTP event into an `IncomingHttpEvent`, processes it through
|
|
178
|
+
* the Stone.js pipeline, and generates a `RawHttpResponse` to send back.
|
|
179
|
+
*
|
|
180
|
+
* @param rawEvent - The raw HTTP event received from AWS Lambda.
|
|
181
|
+
* @param executionContext - The AWS Lambda execution context associated with the event.
|
|
182
|
+
* @returns A promise resolving to the processed `RawHttpResponse`.
|
|
183
|
+
*/
|
|
184
|
+
async eventListener(rawEvent, executionContext) {
|
|
185
|
+
const incomingEventBuilder = AdapterEventBuilder.create({
|
|
186
|
+
resolver: (options) => IncomingHttpEvent.create(options)
|
|
187
|
+
});
|
|
188
|
+
const rawResponseBuilder = AdapterEventBuilder.create({
|
|
189
|
+
resolver: (options) => RawHttpResponseWrapper.create(options)
|
|
190
|
+
});
|
|
191
|
+
const rawResponse = { statusCode: 500 };
|
|
192
|
+
const context = {
|
|
193
|
+
rawEvent,
|
|
194
|
+
rawResponse,
|
|
195
|
+
executionContext,
|
|
196
|
+
rawResponseBuilder,
|
|
197
|
+
incomingEventBuilder
|
|
198
|
+
};
|
|
199
|
+
try {
|
|
200
|
+
const eventHandler = this.resolveEventHandler();
|
|
201
|
+
await this.executeEventHandlerHooks('onInit', eventHandler);
|
|
202
|
+
return await this.sendEventThroughDestination(context, eventHandler);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
const rawResponseBuilder = await this.handleError(error, context);
|
|
206
|
+
return await this.buildRawResponse({ ...context, rawResponseBuilder });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Class representing an AwsLambdaHttpErrorHandler.
|
|
213
|
+
*/
|
|
214
|
+
class AwsLambdaHttpErrorHandler {
|
|
215
|
+
logger;
|
|
216
|
+
/**
|
|
217
|
+
* Create an NodeHttpErrorHandler.
|
|
218
|
+
*
|
|
219
|
+
* @param options - NodeHttpErrorHandler options.
|
|
220
|
+
*/
|
|
221
|
+
constructor({ blueprint }) {
|
|
222
|
+
this.logger = blueprint.get('stone.logger.resolver', defaultLoggerResolver)(blueprint);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Handle an error.
|
|
226
|
+
*
|
|
227
|
+
* @param error - The error to handle.
|
|
228
|
+
* @param context - The context of the adapter.
|
|
229
|
+
* @returns The raw response builder.
|
|
230
|
+
*/
|
|
231
|
+
handle(error, context) {
|
|
232
|
+
this.logger.error(error.message, { error });
|
|
233
|
+
const statusCode = error.cause?.status ?? HTTP_INTERNAL_SERVER_ERROR;
|
|
234
|
+
const type = accepts(context.rawEvent).type(['json', 'html']);
|
|
235
|
+
const contentType = mime.getType(type !== false ? type : 'txt') ?? context.rawEvent.headers['content-type'] ?? 'text/plain';
|
|
236
|
+
const headers = new Headers({ 'Content-Type': contentType });
|
|
237
|
+
return context
|
|
238
|
+
.rawResponseBuilder
|
|
239
|
+
.add('headers', headers)
|
|
240
|
+
.add('statusCode', statusCode)
|
|
241
|
+
.add('statusMessage', statuses.message[statusCode]);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* A constant representing the AWS Lambda HTTP platform identifier.
|
|
247
|
+
*
|
|
248
|
+
* This constant is used as an alias for the AWS Lambda HTTP Adapter within the Stone.js framework.
|
|
249
|
+
* It helps in identifying and configuring platform-specific adapters or components for handling
|
|
250
|
+
* HTTP requests and responses.
|
|
251
|
+
*/
|
|
252
|
+
const AWS_LAMBDA_HTTP_PLATFORM = 'aws_lambda_http';
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Adapter resolver for AWS Lambda HTTP adapter.
|
|
256
|
+
*
|
|
257
|
+
* Creates and configures an `AWSLambdaHttpAdapter` for handling HTTP events in AWS Lambda.
|
|
258
|
+
*
|
|
259
|
+
* @param blueprint - The `IBlueprint` providing configuration and dependencies.
|
|
260
|
+
* @returns An `AWSLambdaHttpAdapter` instance.
|
|
261
|
+
*/
|
|
262
|
+
const awsLambdaHttpAdapterResolver = (blueprint) => {
|
|
263
|
+
return AwsLambdaHttpAdapter.create(blueprint);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
function getDefaultExportFromCjs (x) {
|
|
267
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
var cjs;
|
|
271
|
+
var hasRequiredCjs;
|
|
272
|
+
|
|
273
|
+
function requireCjs () {
|
|
274
|
+
if (hasRequiredCjs) return cjs;
|
|
275
|
+
hasRequiredCjs = 1;
|
|
276
|
+
|
|
277
|
+
var isMergeableObject = function isMergeableObject(value) {
|
|
278
|
+
return isNonNullObject(value)
|
|
279
|
+
&& !isSpecial(value)
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
function isNonNullObject(value) {
|
|
283
|
+
return !!value && typeof value === 'object'
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function isSpecial(value) {
|
|
287
|
+
var stringValue = Object.prototype.toString.call(value);
|
|
288
|
+
|
|
289
|
+
return stringValue === '[object RegExp]'
|
|
290
|
+
|| stringValue === '[object Date]'
|
|
291
|
+
|| isReactElement(value)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
|
|
295
|
+
var canUseSymbol = typeof Symbol === 'function' && Symbol.for;
|
|
296
|
+
var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7;
|
|
297
|
+
|
|
298
|
+
function isReactElement(value) {
|
|
299
|
+
return value.$$typeof === REACT_ELEMENT_TYPE
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function emptyTarget(val) {
|
|
303
|
+
return Array.isArray(val) ? [] : {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function cloneUnlessOtherwiseSpecified(value, options) {
|
|
307
|
+
return (options.clone !== false && options.isMergeableObject(value))
|
|
308
|
+
? deepmerge(emptyTarget(value), value, options)
|
|
309
|
+
: value
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function defaultArrayMerge(target, source, options) {
|
|
313
|
+
return target.concat(source).map(function(element) {
|
|
314
|
+
return cloneUnlessOtherwiseSpecified(element, options)
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function getMergeFunction(key, options) {
|
|
319
|
+
if (!options.customMerge) {
|
|
320
|
+
return deepmerge
|
|
321
|
+
}
|
|
322
|
+
var customMerge = options.customMerge(key);
|
|
323
|
+
return typeof customMerge === 'function' ? customMerge : deepmerge
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function getEnumerableOwnPropertySymbols(target) {
|
|
327
|
+
return Object.getOwnPropertySymbols
|
|
328
|
+
? Object.getOwnPropertySymbols(target).filter(function(symbol) {
|
|
329
|
+
return Object.propertyIsEnumerable.call(target, symbol)
|
|
330
|
+
})
|
|
331
|
+
: []
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function getKeys(target) {
|
|
335
|
+
return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target))
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function propertyIsOnObject(object, property) {
|
|
339
|
+
try {
|
|
340
|
+
return property in object
|
|
341
|
+
} catch(_) {
|
|
342
|
+
return false
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Protects from prototype poisoning and unexpected merging up the prototype chain.
|
|
347
|
+
function propertyIsUnsafe(target, key) {
|
|
348
|
+
return propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet,
|
|
349
|
+
&& !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain,
|
|
350
|
+
&& Object.propertyIsEnumerable.call(target, key)) // and also unsafe if they're nonenumerable.
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function mergeObject(target, source, options) {
|
|
354
|
+
var destination = {};
|
|
355
|
+
if (options.isMergeableObject(target)) {
|
|
356
|
+
getKeys(target).forEach(function(key) {
|
|
357
|
+
destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
getKeys(source).forEach(function(key) {
|
|
361
|
+
if (propertyIsUnsafe(target, key)) {
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) {
|
|
366
|
+
destination[key] = getMergeFunction(key, options)(target[key], source[key], options);
|
|
367
|
+
} else {
|
|
368
|
+
destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return destination
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function deepmerge(target, source, options) {
|
|
375
|
+
options = options || {};
|
|
376
|
+
options.arrayMerge = options.arrayMerge || defaultArrayMerge;
|
|
377
|
+
options.isMergeableObject = options.isMergeableObject || isMergeableObject;
|
|
378
|
+
// cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge()
|
|
379
|
+
// implementations can use it. The caller may not replace it.
|
|
380
|
+
options.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified;
|
|
381
|
+
|
|
382
|
+
var sourceIsArray = Array.isArray(source);
|
|
383
|
+
var targetIsArray = Array.isArray(target);
|
|
384
|
+
var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
|
|
385
|
+
|
|
386
|
+
if (!sourceAndTargetTypesMatch) {
|
|
387
|
+
return cloneUnlessOtherwiseSpecified(source, options)
|
|
388
|
+
} else if (sourceIsArray) {
|
|
389
|
+
return options.arrayMerge(target, source, options)
|
|
390
|
+
} else {
|
|
391
|
+
return mergeObject(target, source, options)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
deepmerge.all = function deepmergeAll(array, options) {
|
|
396
|
+
if (!Array.isArray(array)) {
|
|
397
|
+
throw new Error('first argument should be an array')
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return array.reduce(function(prev, next) {
|
|
401
|
+
return deepmerge(prev, next, options)
|
|
402
|
+
}, {})
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
var deepmerge_1 = deepmerge;
|
|
406
|
+
|
|
407
|
+
cjs = deepmerge_1;
|
|
408
|
+
return cjs;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
var cjsExports = requireCjs();
|
|
412
|
+
var deepmerge = /*@__PURE__*/getDefaultExportFromCjs(cjsExports);
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Middleware to dynamically set response resolver for adapter.
|
|
416
|
+
*
|
|
417
|
+
* @param context - The configuration context containing modules and blueprint.
|
|
418
|
+
* @param next - The next pipeline function to continue processing.
|
|
419
|
+
* @returns The updated blueprint or a promise resolving to it.
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* ```typescript
|
|
423
|
+
* SetAwsLambdaHttpResponseResolverMiddleware(context, next)
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
const SetAwsLambdaHttpResponseResolverMiddleware = async (context, next) => {
|
|
427
|
+
if (context.blueprint.get('stone.adapter.platform') === AWS_LAMBDA_HTTP_PLATFORM) {
|
|
428
|
+
context.blueprint.set('stone.kernel.responseResolver', (options) => {
|
|
429
|
+
return options.content instanceof File
|
|
430
|
+
? BinaryFileResponse.file({ ...options, content: undefined, file: options.content })
|
|
431
|
+
: OutgoingHttpResponse.create(options);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
return await next(context);
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* Configuration for adapter processing middleware.
|
|
438
|
+
*
|
|
439
|
+
* This array defines a list of middleware pipes, each with a `pipe` function and a `priority`.
|
|
440
|
+
* These pipes are executed in the order of their priority values, with lower values running first.
|
|
441
|
+
*/
|
|
442
|
+
const metaAdapterBlueprintMiddleware = [
|
|
443
|
+
{ module: SetAwsLambdaHttpResponseResolverMiddleware, priority: 6 }
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Middleware for handling incoming events and transforming them into Stone.js events.
|
|
448
|
+
*
|
|
449
|
+
* This class processes incoming HTTP requests, extracting relevant data such as URL, IP addresses,
|
|
450
|
+
* headers, cookies, and more, and forwards them to the next middleware in the pipeline.
|
|
451
|
+
*/
|
|
452
|
+
class IncomingEventMiddleware {
|
|
453
|
+
/**
|
|
454
|
+
* The blueprint for resolving configuration and dependencies.
|
|
455
|
+
*/
|
|
456
|
+
blueprint;
|
|
457
|
+
/**
|
|
458
|
+
* Create an IncomingEventMiddleware instance.
|
|
459
|
+
*
|
|
460
|
+
* @param {blueprint} options - Options containing the blueprint for resolving configuration and dependencies.
|
|
461
|
+
*/
|
|
462
|
+
constructor({ blueprint }) {
|
|
463
|
+
this.blueprint = blueprint;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Handles the incoming event, processes it, and invokes the next middleware in the pipeline.
|
|
467
|
+
*
|
|
468
|
+
* @param context - The adapter context containing the raw event, execution context, and other data.
|
|
469
|
+
* @param next - The next middleware to be invoked in the pipeline.
|
|
470
|
+
* @returns A promise that resolves to the processed context.
|
|
471
|
+
* @throws {AwsLambdaHttpAdapterError} If required components are missing in the context.
|
|
472
|
+
*/
|
|
473
|
+
async handle(context, next) {
|
|
474
|
+
if ((context.rawEvent === undefined) || ((context.incomingEventBuilder?.add) === undefined)) {
|
|
475
|
+
throw new AwsLambdaHttpAdapterError('The context is missing required components.');
|
|
476
|
+
}
|
|
477
|
+
const proxyOptions = this.getProxyOptions();
|
|
478
|
+
const cookieOptions = this.getCookieOptions();
|
|
479
|
+
const url = this.extractUrl(context.rawEvent, proxyOptions);
|
|
480
|
+
const ipAddresses = this.extractIpAddresses(context.rawEvent, proxyOptions);
|
|
481
|
+
context
|
|
482
|
+
.incomingEventBuilder
|
|
483
|
+
.add('url', url)
|
|
484
|
+
.add('ips', ipAddresses)
|
|
485
|
+
.add('source', this.getSource(context))
|
|
486
|
+
.add('headers', context.rawEvent.headers)
|
|
487
|
+
// If not defined by other middleware
|
|
488
|
+
// In fullstack forms, the method is spoofed and sent as a hidden field
|
|
489
|
+
.addIf('method', this.getMethod(context.rawEvent))
|
|
490
|
+
.add('queryString', context.rawEvent.queryStringParameters)
|
|
491
|
+
.add('protocol', this.getProtocol(context.rawEvent, proxyOptions))
|
|
492
|
+
.add('cookies', CookieCollection.create(context.rawEvent.headers.cookie, cookieOptions, this.getCookieSecret()))
|
|
493
|
+
.add('ip', proxyAddr(this.toNodeMessage(context.rawEvent), isIpTrusted(proxyOptions.trustedIp, proxyOptions.untrustedIp)));
|
|
494
|
+
return await next(context);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Create the IncomingEventSource from the context.
|
|
498
|
+
*
|
|
499
|
+
* @param context - The adapter context containing the raw event, execution context, and other data.
|
|
500
|
+
* @returns The Incoming Event Source.
|
|
501
|
+
*/
|
|
502
|
+
getSource(context) {
|
|
503
|
+
return {
|
|
504
|
+
rawEvent: context.rawEvent,
|
|
505
|
+
platform: AWS_LAMBDA_HTTP_PLATFORM,
|
|
506
|
+
rawContext: context.executionContext
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Extracts the HTTP method from the incoming rawEvent.
|
|
511
|
+
*
|
|
512
|
+
* @param rawEvent - The incoming rawEvent.
|
|
513
|
+
* @returns The HTTP method string.
|
|
514
|
+
*/
|
|
515
|
+
getMethod(rawEvent) {
|
|
516
|
+
return rawEvent.httpMethod ??
|
|
517
|
+
rawEvent.requestContext?.httpMethod ??
|
|
518
|
+
rawEvent.requestContext?.http?.method ??
|
|
519
|
+
'GET';
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Extracts proxy-related options from the blueprint.
|
|
523
|
+
*
|
|
524
|
+
* @returns Proxy options.
|
|
525
|
+
*/
|
|
526
|
+
getProxyOptions() {
|
|
527
|
+
const defaultProxyOptions = { trusted: [], trustedIp: [], untrustedIp: [] };
|
|
528
|
+
const proxyOptions = this.blueprint.get('stone.http.proxies', defaultProxyOptions);
|
|
529
|
+
proxyOptions.trusted = this.blueprint.get('stone.http.hosts.trusted', []);
|
|
530
|
+
return proxyOptions;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Retrieves cookie-related options from the blueprint.
|
|
534
|
+
*
|
|
535
|
+
* @returns Cookie options.
|
|
536
|
+
*/
|
|
537
|
+
getCookieOptions() {
|
|
538
|
+
return this.blueprint.get('stone.http.cookie.options', {});
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Retrieves the cookie secret from the blueprint.
|
|
542
|
+
*
|
|
543
|
+
* @returns The cookie secret string.
|
|
544
|
+
*/
|
|
545
|
+
getCookieSecret() {
|
|
546
|
+
return this.blueprint.get('stone.http.cookie.secret', this.blueprint.get('stone.secret', ''));
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Extracts and parses the URL from the incoming rawEvent.
|
|
550
|
+
*
|
|
551
|
+
* @param rawEvent - The incoming HTTP rawEvent.
|
|
552
|
+
* @param options - Proxy options.
|
|
553
|
+
* @returns The parsed URL object.
|
|
554
|
+
*/
|
|
555
|
+
extractUrl(rawEvent, options) {
|
|
556
|
+
const hostname = getHostname(this.getRemoteAddress(rawEvent), rawEvent.headers, options);
|
|
557
|
+
const proto = getProtocol(this.getRemoteAddress(rawEvent), rawEvent.headers, true, options);
|
|
558
|
+
return new URL(rawEvent.path ?? rawEvent.rawPath ?? '', `${String(proto)}://${String(hostname)}`);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Extracts a list of IP addresses from the incoming rawEvent.
|
|
562
|
+
*
|
|
563
|
+
* @param rawEvent - The incoming HTTP rawEvent.
|
|
564
|
+
* @param options - Proxy options.
|
|
565
|
+
* @returns An array of IP addresses.
|
|
566
|
+
*/
|
|
567
|
+
extractIpAddresses(rawEvent, options) {
|
|
568
|
+
const isTrusted = isIpTrusted(options.trustedIp, options.untrustedIp);
|
|
569
|
+
return proxyAddr.all(this.toNodeMessage(rawEvent), isTrusted).slice(1).reverse();
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Converts the incoming rawEvent to a Node.js IncomingMessage.
|
|
573
|
+
*
|
|
574
|
+
* @param rawEvent - The incoming rawEvent.
|
|
575
|
+
* @returns The converted IncomingMessage.
|
|
576
|
+
*/
|
|
577
|
+
toNodeMessage(rawEvent) {
|
|
578
|
+
return {
|
|
579
|
+
connection: { remoteAddress: this.getRemoteAddress(rawEvent) },
|
|
580
|
+
headers: { 'x-forwarded-for': rawEvent.headers['x-forwarded-for'] ?? rawEvent.headers['X-Forwarded-For'] }
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Determines the protocol from the incoming rawEvent.
|
|
585
|
+
*
|
|
586
|
+
* @param rawEvent - The incoming rawEvent.
|
|
587
|
+
* @param options - Proxy options.
|
|
588
|
+
* @returns The protocol string.
|
|
589
|
+
*/
|
|
590
|
+
getProtocol(rawEvent, options) {
|
|
591
|
+
return getProtocol(this.getRemoteAddress(rawEvent), rawEvent.headers, true, options);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Retrieves the remote address from the incoming rawEvent.
|
|
595
|
+
* This method is used as a fallback when the remote address is not found in the rawEvent.
|
|
596
|
+
* @param rawEvent - The incoming rawEvent.
|
|
597
|
+
* @returns The remote address string.
|
|
598
|
+
*/
|
|
599
|
+
getRemoteAddress(rawEvent) {
|
|
600
|
+
return rawEvent.requestContext?.http?.sourceIp ?? rawEvent.requestContext?.identity?.sourceIp ?? '';
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Meta Middleware for processing incoming events.
|
|
605
|
+
*/
|
|
606
|
+
const MetaIncomingEventMiddleware = { module: IncomingEventMiddleware, isClass: true };
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Middleware for handling server responses and transforming them into the appropriate HTTP responses.
|
|
610
|
+
*
|
|
611
|
+
* This middleware processes outgoing responses and attaches the necessary headers, status codes,
|
|
612
|
+
* and body content to the HTTP response.
|
|
613
|
+
*/
|
|
614
|
+
class ServerResponseMiddleware {
|
|
615
|
+
/**
|
|
616
|
+
* Handles the outgoing response, processes it, and invokes the next middleware in the pipeline.
|
|
617
|
+
*
|
|
618
|
+
* @param context - The adapter context containing the raw event, execution context, and other data.
|
|
619
|
+
* @param next - The next middleware to be invoked in the pipeline.
|
|
620
|
+
* @returns A promise resolving to the rawResponseBuilder.
|
|
621
|
+
* @throws {AwsLambdaHttpAdapterError} If required components are missing in the context.
|
|
622
|
+
*/
|
|
623
|
+
async handle(context, next) {
|
|
624
|
+
const rawResponseBuilder = await next(context);
|
|
625
|
+
if (context.rawEvent === undefined || context.incomingEvent === undefined || context.outgoingResponse === undefined || rawResponseBuilder?.add === undefined) {
|
|
626
|
+
throw new AwsLambdaHttpAdapterError('The context is missing required components.');
|
|
627
|
+
}
|
|
628
|
+
rawResponseBuilder
|
|
629
|
+
.add('headers', context.outgoingResponse.headers)
|
|
630
|
+
.add('statusCode', context.outgoingResponse.statusCode ?? 500)
|
|
631
|
+
.add('statusMessage', context.outgoingResponse.statusMessage ?? statuses.message[context.outgoingResponse.statusCode ?? 500]);
|
|
632
|
+
if (!context.incomingEvent.isMethod('HEAD')) {
|
|
633
|
+
if (context.outgoingResponse instanceof BinaryFileResponse) {
|
|
634
|
+
rawResponseBuilder
|
|
635
|
+
.add('isBase64Encoded', true)
|
|
636
|
+
.add('body', context.outgoingResponse.file.getContent('base64'));
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
const isBuffer = Buffer.isBuffer(context.outgoingResponse.content);
|
|
640
|
+
const content = isBuffer
|
|
641
|
+
? context.outgoingResponse.content.toString('base64')
|
|
642
|
+
: context.outgoingResponse.content;
|
|
643
|
+
rawResponseBuilder
|
|
644
|
+
.add('body', content)
|
|
645
|
+
.add('isBase64Encoded', isBuffer)
|
|
646
|
+
.add('charset', context.outgoingResponse.charset);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return rawResponseBuilder;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Meta Middleware for processing server responses.
|
|
654
|
+
*/
|
|
655
|
+
const MetaServerResponseMiddleware = { module: ServerResponseMiddleware, isClass: true };
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Default blueprint configuration for the AWS Lambda Http Adapter.
|
|
659
|
+
*
|
|
660
|
+
* This blueprint defines the initial configuration for the AWS Lambda Http adapter
|
|
661
|
+
* within the Stone.js framework. It includes:
|
|
662
|
+
* - An alias for the AWS Lambda platform (`AWS_LAMBDA_HTTP_PLATFORM`).
|
|
663
|
+
* - A default resolver function (currently a placeholder).
|
|
664
|
+
* - Middleware, hooks, and state flags (`current`, `default`, `preferred`).
|
|
665
|
+
*/
|
|
666
|
+
const awsLambdaHttpAdapterBlueprint = {
|
|
667
|
+
stone: {
|
|
668
|
+
...httpCoreBlueprint.stone,
|
|
669
|
+
blueprint: {
|
|
670
|
+
middleware: metaAdapterBlueprintMiddleware
|
|
671
|
+
},
|
|
672
|
+
adapters: [
|
|
673
|
+
{
|
|
674
|
+
current: false,
|
|
675
|
+
variant: 'server',
|
|
676
|
+
platform: AWS_LAMBDA_HTTP_PLATFORM,
|
|
677
|
+
middleware: [
|
|
678
|
+
MetaIncomingEventMiddleware,
|
|
679
|
+
MetaServerResponseMiddleware
|
|
680
|
+
],
|
|
681
|
+
resolver: awsLambdaHttpAdapterResolver,
|
|
682
|
+
eventHandlerResolver: defaultKernelResolver,
|
|
683
|
+
errorHandlers: {
|
|
684
|
+
default: { module: AwsLambdaHttpErrorHandler, isClass: true }
|
|
685
|
+
},
|
|
686
|
+
default: isNotEmpty(getString('AWS_LAMBDA_FUNCTION_NAME', ''))
|
|
687
|
+
}
|
|
688
|
+
]
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* A Stone.js decorator that integrates the AWS Lambda HTTP Adapter with a class.
|
|
694
|
+
*
|
|
695
|
+
* This decorator modifies the class to seamlessly enable AWS Lambda HTTP as the
|
|
696
|
+
* execution environment for a Stone.js application. By applying this decorator,
|
|
697
|
+
* the class is automatically configured with the necessary blueprint for AWS Lambda HTTP.
|
|
698
|
+
*
|
|
699
|
+
* @template T - The type of the class being decorated. Defaults to `ClassType`.
|
|
700
|
+
* @param options - Optional configuration to customize the AWS Lambda HTTP Adapter.
|
|
701
|
+
*
|
|
702
|
+
* @returns A class decorator that applies the AWS Lambda HTTP adapter configuration.
|
|
703
|
+
*
|
|
704
|
+
* @example
|
|
705
|
+
* ```typescript
|
|
706
|
+
* import { AwsLambdaHttp } from '@stone-js/aws-lambda-http-adapter';
|
|
707
|
+
*
|
|
708
|
+
* @AwsLambdaHttp({
|
|
709
|
+
* alias: 'MyAwsLambdaHttpAdapter',
|
|
710
|
+
* current: true,
|
|
711
|
+
* })
|
|
712
|
+
* class App {
|
|
713
|
+
* // Your application logic here
|
|
714
|
+
* }
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
const AwsLambdaHttp = (options = {}) => {
|
|
718
|
+
return classDecoratorLegacyWrapper((target, context) => {
|
|
719
|
+
if (awsLambdaHttpAdapterBlueprint.stone?.adapters?.[0] !== undefined) {
|
|
720
|
+
// Merge provided options with the default AWS Lambda HTTP adapter blueprint.
|
|
721
|
+
awsLambdaHttpAdapterBlueprint.stone.adapters[0] = deepmerge(awsLambdaHttpAdapterBlueprint.stone.adapters[0], options);
|
|
722
|
+
}
|
|
723
|
+
// Add the modified blueprint to the target class.
|
|
724
|
+
addBlueprint(target, context, awsLambdaHttpAdapterBlueprint);
|
|
725
|
+
});
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Class representing a BodyEventMiddleware.
|
|
730
|
+
*
|
|
731
|
+
* This middleware handles platform-specific messages and transforms them into Stone.js IncomingEvent objects.
|
|
732
|
+
*
|
|
733
|
+
* @author Mr. Stone
|
|
734
|
+
*/
|
|
735
|
+
class BodyEventMiddleware {
|
|
736
|
+
/**
|
|
737
|
+
* The blueprint for resolving configuration and dependencies.
|
|
738
|
+
*/
|
|
739
|
+
blueprint;
|
|
740
|
+
/**
|
|
741
|
+
* Create a BodyEventMiddleware.
|
|
742
|
+
*
|
|
743
|
+
* @param {blueprint} options - Options for creating the BodyEventMiddleware.
|
|
744
|
+
*/
|
|
745
|
+
constructor({ blueprint }) {
|
|
746
|
+
this.blueprint = blueprint;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Handles the incoming event, processes it, and invokes the next middleware in the pipeline.
|
|
750
|
+
*
|
|
751
|
+
* @param context - The adapter context containing the raw event, execution context, and other data.
|
|
752
|
+
* @param next - The next middleware to be invoked in the pipeline.
|
|
753
|
+
* @returns A promise that resolves to the destination type after processing.
|
|
754
|
+
*
|
|
755
|
+
* @throws {AwsLambdaHttpAdapterError} If required components such as the rawEvent or IncomingEventBuilder are not provided.
|
|
756
|
+
*/
|
|
757
|
+
async handle(context, next) {
|
|
758
|
+
if (context.rawEvent === undefined || context.incomingEventBuilder?.add === undefined) {
|
|
759
|
+
throw new AwsLambdaHttpAdapterError('The context is missing required components.');
|
|
760
|
+
}
|
|
761
|
+
if (!isMultipart(this.toNodeMessage(context.rawEvent))) {
|
|
762
|
+
const body = this.getBody(this.toNodeMessage(context.rawEvent), context.rawEvent);
|
|
763
|
+
const method = body.$method$;
|
|
764
|
+
context
|
|
765
|
+
.incomingEventBuilder
|
|
766
|
+
.add('body', body)
|
|
767
|
+
.add('metadata', body);
|
|
768
|
+
// In fullstack forms, the method is spoofed and sent as a hidden field
|
|
769
|
+
isNotEmpty(method) && context.incomingEventBuilder.add('method', method);
|
|
770
|
+
}
|
|
771
|
+
return await next(context);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Convert the raw event into a Node.js IncomingMessage.
|
|
775
|
+
*
|
|
776
|
+
* @param rawEvent - The raw event from the platform.
|
|
777
|
+
* @returns The converted IncomingMessage.
|
|
778
|
+
*/
|
|
779
|
+
toNodeMessage(rawEvent) {
|
|
780
|
+
return {
|
|
781
|
+
headers: {
|
|
782
|
+
'content-type': rawEvent.headers['content-type'] ?? rawEvent.headers['Content-Type'],
|
|
783
|
+
'content-length': rawEvent.headers['content-length'] ?? rawEvent.headers['Content-Length'],
|
|
784
|
+
'transfer-encoding': rawEvent.headers['transfer-encoding'] ?? rawEvent.headers['Transfer-Encoding']
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Extract and parse the body from the message.
|
|
790
|
+
*
|
|
791
|
+
* @param message - The incoming HTTP message.
|
|
792
|
+
* @returns A Promise resolving to the parsed body.
|
|
793
|
+
* @throws {AwsLambdaHttpAdapterError} If the body parsing fails or is invalid.
|
|
794
|
+
*/
|
|
795
|
+
getBody(message, rawEvent) {
|
|
796
|
+
if (!typeIs.hasBody(message)) {
|
|
797
|
+
return {};
|
|
798
|
+
}
|
|
799
|
+
const defaultOptions = { limit: '100kb', defaultType: 'text/plain', defaultCharset: 'utf-8' };
|
|
800
|
+
const { defaultType, defaultCharset, limit: rawLimit } = this.blueprint.get('stone.http.body', defaultOptions);
|
|
801
|
+
const limit = bytes.parse(rawLimit) ?? 100000;
|
|
802
|
+
const encoding = getCharset(message, defaultCharset);
|
|
803
|
+
const type = getType(message, defaultType);
|
|
804
|
+
if (typeIs.is(type, ['urlencoded', 'json', 'text', 'bin']) === false) {
|
|
805
|
+
return {};
|
|
806
|
+
}
|
|
807
|
+
const stringifiedBody = typeof rawEvent.body === 'string'
|
|
808
|
+
? rawEvent.body
|
|
809
|
+
: (typeof rawEvent.body === 'object' && rawEvent.body !== null
|
|
810
|
+
? JSON.stringify(rawEvent.body)
|
|
811
|
+
: '');
|
|
812
|
+
if (Buffer.byteLength(stringifiedBody, encoding) > limit) {
|
|
813
|
+
throw new AwsLambdaHttpAdapterError('The context is missing required components.');
|
|
814
|
+
}
|
|
815
|
+
return rawEvent.body;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Meta Middleware for processing the request body.
|
|
820
|
+
*/
|
|
821
|
+
const MetaBodyEventMiddleware = { module: BodyEventMiddleware, isClass: true };
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Class representing a FilesEventMiddleware.
|
|
825
|
+
*
|
|
826
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
827
|
+
*/
|
|
828
|
+
class FilesEventMiddleware {
|
|
829
|
+
/**
|
|
830
|
+
* The blueprint for resolving configuration and dependencies.
|
|
831
|
+
*/
|
|
832
|
+
blueprint;
|
|
833
|
+
/**
|
|
834
|
+
* Create a FilesEventMiddleware.
|
|
835
|
+
*
|
|
836
|
+
* @param {blueprint} options - Options for creating the FilesEventMiddleware.
|
|
837
|
+
*/
|
|
838
|
+
constructor({ blueprint }) {
|
|
839
|
+
this.blueprint = blueprint;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Handles the incoming event, processes it, and invokes the next middleware in the pipeline.
|
|
843
|
+
*
|
|
844
|
+
* @param context - The adapter context containing the raw event, execution context, and other data.
|
|
845
|
+
* @param next - The next middleware to be invoked in the pipeline.
|
|
846
|
+
* @returns A promise that resolves to the destination type after processing.
|
|
847
|
+
*
|
|
848
|
+
* @throws {AwsLambdaHttpAdapterError} If required components such as the rawEvent or IncomingEventBuilder are not provided.
|
|
849
|
+
*/
|
|
850
|
+
async handle(context, next) {
|
|
851
|
+
if (context.rawEvent === undefined || context.incomingEventBuilder?.add === undefined) {
|
|
852
|
+
throw new AwsLambdaHttpAdapterError('The context is missing required components.');
|
|
853
|
+
}
|
|
854
|
+
if (isMultipart(this.normalizeEvent(context.rawEvent))) {
|
|
855
|
+
const options = this.blueprint.get('stone.http.files.upload', {});
|
|
856
|
+
const response = await getFilesUploads(this.normalizeEvent(context.rawEvent), options);
|
|
857
|
+
const method = response.fields.$method$;
|
|
858
|
+
context
|
|
859
|
+
.incomingEventBuilder
|
|
860
|
+
.add('files', response.files)
|
|
861
|
+
.add('body', response.fields);
|
|
862
|
+
// In fullstack forms, the method is spoofed and sent as a hidden field
|
|
863
|
+
isNotEmpty(method) && context.incomingEventBuilder.add('method', method);
|
|
864
|
+
}
|
|
865
|
+
return await next(context);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Normalize the incoming event to an IncomingMessage.
|
|
869
|
+
*
|
|
870
|
+
* @param rawEvent - The raw event to be normalized.
|
|
871
|
+
* @returns The normalized event.
|
|
872
|
+
*/
|
|
873
|
+
normalizeEvent(rawEvent) {
|
|
874
|
+
return {
|
|
875
|
+
body: rawEvent.body,
|
|
876
|
+
headers: {
|
|
877
|
+
'content-type': rawEvent.headers['content-type'] ?? rawEvent.headers['Content-Type'],
|
|
878
|
+
'content-length': rawEvent.headers['content-length'] ?? rawEvent.headers['Content-Length'],
|
|
879
|
+
'transfer-encoding': rawEvent.headers['transfer-encoding'] ?? rawEvent.headers['Transfer-Encoding']
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Meta Middleware for processing files uploads.
|
|
886
|
+
*/
|
|
887
|
+
const MetaFilesEventMiddleware = { module: FilesEventMiddleware, isClass: true };
|
|
888
|
+
|
|
889
|
+
export { AWS_LAMBDA_HTTP_PLATFORM, AwsLambdaHttp, AwsLambdaHttpAdapter, AwsLambdaHttpAdapterError, AwsLambdaHttpErrorHandler, BodyEventMiddleware, FilesEventMiddleware, IncomingEventMiddleware, MetaBodyEventMiddleware, MetaFilesEventMiddleware, MetaIncomingEventMiddleware, MetaServerResponseMiddleware, RawHttpResponseWrapper, ServerResponseMiddleware, SetAwsLambdaHttpResponseResolverMiddleware, awsLambdaHttpAdapterBlueprint, awsLambdaHttpAdapterResolver, metaAdapterBlueprintMiddleware };
|