serverless-offline 8.8.1 → 9.0.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/README.md +3 -3
- package/package.json +33 -57
- package/src/ServerlessOffline.js +412 -0
- package/src/config/commandOptions.js +155 -0
- package/src/config/constants.js +22 -0
- package/{dist → src}/config/defaultOptions.js +8 -17
- package/src/config/index.js +4 -0
- package/src/config/supportedRuntimes.js +47 -0
- package/src/events/authCanExecuteResource.js +35 -0
- package/src/events/authFunctionNameExtractor.js +75 -0
- package/src/events/authMatchPolicyResource.js +71 -0
- package/src/events/authValidateContext.js +51 -0
- package/src/events/http/Endpoint.js +135 -0
- package/src/events/http/Http.js +50 -0
- package/src/events/http/HttpEventDefinition.js +20 -0
- package/src/events/http/HttpServer.js +1277 -0
- package/src/events/http/OfflineEndpoint.js +33 -0
- package/src/events/http/authJWTSettingsExtractor.js +70 -0
- package/src/events/http/createAuthScheme.js +176 -0
- package/src/events/http/createJWTAuthScheme.js +106 -0
- package/src/events/http/index.js +1 -0
- package/src/events/http/javaHelpers.js +102 -0
- package/src/events/http/lambda-events/LambdaIntegrationEvent.js +57 -0
- package/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +233 -0
- package/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +190 -0
- package/src/events/http/lambda-events/VelocityContext.js +147 -0
- package/src/events/http/lambda-events/index.js +4 -0
- package/src/events/http/lambda-events/renderVelocityTemplateObject.js +93 -0
- package/{dist → src}/events/http/parseResources.js +73 -78
- package/src/events/http/payloadSchemaValidator.js +13 -0
- package/{dist → src}/events/http/templates/offline-default.req.vm +0 -0
- package/{dist → src}/events/http/templates/offline-default.res.vm +0 -0
- package/src/events/schedule/Schedule.js +131 -0
- package/src/events/schedule/ScheduleEvent.js +18 -0
- package/src/events/schedule/ScheduleEventDefinition.js +21 -0
- package/src/events/schedule/index.js +1 -0
- package/src/events/websocket/HttpServer.js +69 -0
- package/src/events/websocket/WebSocket.js +52 -0
- package/src/events/websocket/WebSocketClients.js +462 -0
- package/src/events/websocket/WebSocketEventDefinition.js +18 -0
- package/src/events/websocket/WebSocketServer.js +73 -0
- package/src/events/websocket/http-routes/_catchAll/catchAllRoute.js +16 -0
- package/src/events/websocket/http-routes/_catchAll/index.js +1 -0
- package/src/events/websocket/http-routes/connections/ConnectionsController.js +28 -0
- package/src/events/websocket/http-routes/connections/connectionsRoutes.js +70 -0
- package/src/events/websocket/http-routes/connections/index.js +1 -0
- package/src/events/websocket/http-routes/index.js +2 -0
- package/src/events/websocket/index.js +1 -0
- package/src/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +65 -0
- package/src/events/websocket/lambda-events/WebSocketConnectEvent.js +68 -0
- package/src/events/websocket/lambda-events/WebSocketDisconnectEvent.js +31 -0
- package/src/events/websocket/lambda-events/WebSocketEvent.js +29 -0
- package/src/events/websocket/lambda-events/WebSocketRequestContext.js +67 -0
- package/src/events/websocket/lambda-events/index.js +4 -0
- package/src/index.js +12 -0
- package/src/lambda/HttpServer.js +108 -0
- package/src/lambda/Lambda.js +68 -0
- package/src/lambda/LambdaContext.js +33 -0
- package/src/lambda/LambdaFunction.js +308 -0
- package/src/lambda/LambdaFunctionPool.js +109 -0
- package/src/lambda/__tests__/LambdaContext.test.js +30 -0
- package/src/lambda/__tests__/LambdaFunction.test.js +196 -0
- package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsJSONObject.fixture.js +47 -0
- package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsNativeString.fixture.js +46 -0
- package/src/lambda/__tests__/fixtures/Lambda/package.json +3 -0
- package/src/lambda/__tests__/fixtures/lambdaFunction.fixture.js +145 -0
- package/src/lambda/__tests__/fixtures/package.json +3 -0
- package/src/lambda/__tests__/routes/invocations/InvocationsController.test.js +42 -0
- package/src/lambda/handler-runner/HandlerRunner.js +136 -0
- package/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +72 -0
- package/src/lambda/handler-runner/child-process-runner/childProcessHelper.js +42 -0
- package/src/lambda/handler-runner/child-process-runner/index.js +1 -0
- package/src/lambda/handler-runner/docker-runner/DockerContainer.js +417 -0
- package/src/lambda/handler-runner/docker-runner/DockerImage.js +35 -0
- package/src/lambda/handler-runner/docker-runner/DockerRunner.js +63 -0
- package/src/lambda/handler-runner/docker-runner/index.js +1 -0
- package/src/lambda/handler-runner/go-runner/GoRunner.js +166 -0
- package/src/lambda/handler-runner/go-runner/index.js +1 -0
- package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +125 -0
- package/src/lambda/handler-runner/in-process-runner/index.js +1 -0
- package/src/lambda/handler-runner/index.js +1 -0
- package/src/lambda/handler-runner/java-runner/JavaRunner.js +114 -0
- package/src/lambda/handler-runner/java-runner/index.js +1 -0
- package/src/lambda/handler-runner/python-runner/PythonRunner.js +138 -0
- package/src/lambda/handler-runner/python-runner/index.js +1 -0
- package/{dist → src}/lambda/handler-runner/python-runner/invoke.py +0 -0
- package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +107 -0
- package/src/lambda/handler-runner/ruby-runner/index.js +1 -0
- package/{dist → src}/lambda/handler-runner/ruby-runner/invoke.rb +0 -0
- package/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +70 -0
- package/src/lambda/handler-runner/worker-thread-runner/index.js +1 -0
- package/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +29 -0
- package/src/lambda/index.js +1 -0
- package/src/lambda/routes/index.js +2 -0
- package/src/lambda/routes/invocations/InvocationsController.js +102 -0
- package/src/lambda/routes/invocations/index.js +1 -0
- package/src/lambda/routes/invocations/invocationsRoute.js +77 -0
- package/src/lambda/routes/invoke-async/InvokeAsyncController.js +20 -0
- package/src/lambda/routes/invoke-async/index.js +1 -0
- package/src/lambda/routes/invoke-async/invokeAsyncRoute.js +33 -0
- package/src/utils/__tests__/createUniqueId.test.js +18 -0
- package/src/utils/__tests__/formatToClfTime.test.js +14 -0
- package/src/utils/__tests__/generateHapiPath.test.js +46 -0
- package/src/utils/__tests__/lowerCaseKeys.test.js +30 -0
- package/src/utils/__tests__/parseHeaders.test.js +13 -0
- package/src/utils/__tests__/parseMultiValueHeaders.test.js +24 -0
- package/src/utils/__tests__/parseMultiValueQueryStringParameters.test.js +159 -0
- package/src/utils/__tests__/parseQueryStringParameters.test.js +15 -0
- package/src/utils/__tests__/splitHandlerPathAndName.test.js +54 -0
- package/src/utils/__tests__/unflatten.test.js +32 -0
- package/src/utils/checkDockerDaemon.js +19 -0
- package/src/utils/checkGoVersion.js +16 -0
- package/src/utils/createApiKey.js +5 -0
- package/src/utils/createUniqueId.js +5 -0
- package/src/utils/detectExecutable.js +11 -0
- package/{dist → src}/utils/formatToClfTime.js +6 -14
- package/src/utils/generateHapiPath.js +26 -0
- package/src/utils/getHttpApiCorsConfig.js +28 -0
- package/src/utils/index.js +42 -0
- package/src/utils/jsonPath.js +13 -0
- package/src/utils/logRoutes.js +64 -0
- package/src/utils/lowerCaseKeys.js +6 -0
- package/src/utils/parseHeaders.js +14 -0
- package/src/utils/parseMultiValueHeaders.js +27 -0
- package/src/utils/parseMultiValueQueryStringParameters.js +31 -0
- package/src/utils/parseQueryStringParameters.js +15 -0
- package/src/utils/resolveJoins.js +29 -0
- package/src/utils/splitHandlerPathAndName.js +31 -0
- package/src/utils/unflatten.js +11 -0
- package/CHANGELOG.md +0 -78
- package/dist/ServerlessOffline.js +0 -508
- package/dist/config/commandOptions.js +0 -149
- package/dist/config/constants.js +0 -30
- package/dist/config/index.js +0 -55
- package/dist/config/supportedRuntimes.js +0 -40
- package/dist/debugLog.js +0 -12
- package/dist/events/authCanExecuteResource.js +0 -35
- package/dist/events/authFunctionNameExtractor.js +0 -87
- package/dist/events/authMatchPolicyResource.js +0 -62
- package/dist/events/authValidateContext.js +0 -53
- package/dist/events/http/Endpoint.js +0 -173
- package/dist/events/http/Http.js +0 -77
- package/dist/events/http/HttpEventDefinition.js +0 -36
- package/dist/events/http/HttpServer.js +0 -1370
- package/dist/events/http/OfflineEndpoint.js +0 -38
- package/dist/events/http/authJWTSettingsExtractor.js +0 -76
- package/dist/events/http/createAuthScheme.js +0 -184
- package/dist/events/http/createJWTAuthScheme.js +0 -159
- package/dist/events/http/index.js +0 -15
- package/dist/events/http/javaHelpers.js +0 -99
- package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +0 -87
- package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -246
- package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -225
- package/dist/events/http/lambda-events/VelocityContext.js +0 -170
- package/dist/events/http/lambda-events/index.js +0 -39
- package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -111
- package/dist/events/http/payloadSchemaValidator.js +0 -13
- package/dist/events/schedule/Schedule.js +0 -183
- package/dist/events/schedule/ScheduleEvent.js +0 -27
- package/dist/events/schedule/ScheduleEventDefinition.js +0 -36
- package/dist/events/schedule/index.js +0 -15
- package/dist/events/websocket/HttpServer.js +0 -114
- package/dist/events/websocket/WebSocket.js +0 -78
- package/dist/events/websocket/WebSocketClients.js +0 -577
- package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
- package/dist/events/websocket/WebSocketServer.js +0 -139
- package/dist/events/websocket/http-routes/_catchAll/catchAllRoute.js +0 -33
- package/dist/events/websocket/http-routes/_catchAll/index.js +0 -15
- package/dist/events/websocket/http-routes/connections/ConnectionsController.js +0 -45
- package/dist/events/websocket/http-routes/connections/connectionsRoutes.js +0 -95
- package/dist/events/websocket/http-routes/connections/index.js +0 -15
- package/dist/events/websocket/http-routes/index.js +0 -23
- package/dist/events/websocket/index.js +0 -15
- package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +0 -99
- package/dist/events/websocket/lambda-events/WebSocketConnectEvent.js +0 -101
- package/dist/events/websocket/lambda-events/WebSocketDisconnectEvent.js +0 -47
- package/dist/events/websocket/lambda-events/WebSocketEvent.js +0 -54
- package/dist/events/websocket/lambda-events/WebSocketRequestContext.js +0 -98
- package/dist/events/websocket/lambda-events/index.js +0 -39
- package/dist/index.js +0 -15
- package/dist/lambda/HttpServer.js +0 -124
- package/dist/lambda/Lambda.js +0 -117
- package/dist/lambda/LambdaContext.js +0 -53
- package/dist/lambda/LambdaFunction.js +0 -390
- package/dist/lambda/LambdaFunctionPool.js +0 -127
- package/dist/lambda/handler-runner/HandlerRunner.js +0 -195
- package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -124
- package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -49
- package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
- package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -515
- package/dist/lambda/handler-runner/docker-runner/DockerImage.js +0 -67
- package/dist/lambda/handler-runner/docker-runner/DockerRunner.js +0 -74
- package/dist/lambda/handler-runner/docker-runner/index.js +0 -15
- package/dist/lambda/handler-runner/go-runner/GoRunner.js +0 -230
- package/dist/lambda/handler-runner/go-runner/index.js +0 -15
- package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -228
- package/dist/lambda/handler-runner/in-process-runner/index.js +0 -15
- package/dist/lambda/handler-runner/index.js +0 -15
- package/dist/lambda/handler-runner/java-runner/JavaRunner.js +0 -153
- package/dist/lambda/handler-runner/java-runner/index.js +0 -15
- package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -185
- package/dist/lambda/handler-runner/python-runner/index.js +0 -15
- package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -147
- package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
- package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -92
- package/dist/lambda/handler-runner/worker-thread-runner/index.js +0 -15
- package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +0 -31
- package/dist/lambda/index.js +0 -15
- package/dist/lambda/routes/index.js +0 -23
- package/dist/lambda/routes/invocations/InvocationsController.js +0 -142
- package/dist/lambda/routes/invocations/index.js +0 -15
- package/dist/lambda/routes/invocations/invocationsRoute.js +0 -90
- package/dist/lambda/routes/invoke-async/InvokeAsyncController.js +0 -38
- package/dist/lambda/routes/invoke-async/index.js +0 -15
- package/dist/lambda/routes/invoke-async/invokeAsyncRoute.js +0 -43
- package/dist/main.js +0 -11
- package/dist/serverlessLog.js +0 -91
- package/dist/utils/checkDockerDaemon.js +0 -27
- package/dist/utils/checkGoVersion.js +0 -27
- package/dist/utils/createApiKey.js +0 -12
- package/dist/utils/createUniqueId.js +0 -14
- package/dist/utils/detectExecutable.js +0 -21
- package/dist/utils/generateHapiPath.js +0 -28
- package/dist/utils/getHttpApiCorsConfig.js +0 -40
- package/dist/utils/index.js +0 -165
- package/dist/utils/jsonPath.js +0 -21
- package/dist/utils/lowerCaseKeys.js +0 -14
- package/dist/utils/parseHeaders.js +0 -23
- package/dist/utils/parseMultiValueHeaders.js +0 -36
- package/dist/utils/parseMultiValueQueryStringParameters.js +0 -40
- package/dist/utils/parseQueryStringParameters.js +0 -26
- package/dist/utils/resolveJoins.js +0 -36
- package/dist/utils/satisfiesVersionRange.js +0 -20
- package/dist/utils/splitHandlerPathAndName.js +0 -37
- package/dist/utils/unflatten.js +0 -18
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { log } from '@serverless/utils/log.js'
|
|
2
|
+
|
|
3
|
+
export default class InvocationsController {
|
|
4
|
+
#lambda = null
|
|
5
|
+
|
|
6
|
+
constructor(lambda) {
|
|
7
|
+
this.#lambda = lambda
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async invoke(functionName, invocationType, event, clientContext) {
|
|
11
|
+
// Reject gracefully if functionName does not exist
|
|
12
|
+
const functionNames = this.#lambda.listFunctionNames()
|
|
13
|
+
if (functionNames.length === 0 || !functionNames.includes(functionName)) {
|
|
14
|
+
log.error(
|
|
15
|
+
`Attempt to invoke function '${functionName}' failed. Function does not exists.`,
|
|
16
|
+
)
|
|
17
|
+
// Conforms to the actual response from AWS Lambda when invoking a non-existent
|
|
18
|
+
// function. Details on the error are provided in the Payload.Message key
|
|
19
|
+
return {
|
|
20
|
+
FunctionError: 'ResourceNotFoundException',
|
|
21
|
+
Payload: {
|
|
22
|
+
Message: `Function not found: ${functionName}`,
|
|
23
|
+
Type: 'User',
|
|
24
|
+
},
|
|
25
|
+
StatusCode: 404,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const lambdaFunction = this.#lambda.getByFunctionName(functionName)
|
|
30
|
+
|
|
31
|
+
lambdaFunction.setClientContext(clientContext)
|
|
32
|
+
lambdaFunction.setEvent(event)
|
|
33
|
+
|
|
34
|
+
if (invocationType === 'Event') {
|
|
35
|
+
// don't await result!
|
|
36
|
+
lambdaFunction.runHandler()
|
|
37
|
+
return {
|
|
38
|
+
Payload: '',
|
|
39
|
+
StatusCode: 202,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!invocationType || invocationType === 'RequestResponse') {
|
|
44
|
+
let result
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
result = await lambdaFunction.runHandler()
|
|
48
|
+
} catch (err) {
|
|
49
|
+
log.error(
|
|
50
|
+
`Unhandled Lambda Error during invoke of '${functionName}': ${err}`,
|
|
51
|
+
)
|
|
52
|
+
// In most circumstances this is the correct error type/structure.
|
|
53
|
+
// The API returns a StreamingBody with status code of 200
|
|
54
|
+
// that eventually spits out the error and stack trace.
|
|
55
|
+
// When the request is synchronous, aws-sdk should buffer
|
|
56
|
+
// the whole error stream, however this has not been validated.
|
|
57
|
+
return {
|
|
58
|
+
Payload: {
|
|
59
|
+
errorMessage: err.message,
|
|
60
|
+
errorType: 'Error',
|
|
61
|
+
trace: err.stack.split('\n'),
|
|
62
|
+
},
|
|
63
|
+
StatusCode: 200,
|
|
64
|
+
UnhandledError: true,
|
|
65
|
+
}
|
|
66
|
+
// TODO: Additional pre and post-handler validation can expose
|
|
67
|
+
// the following error types:
|
|
68
|
+
// RequestTooLargeException, InvalidParameterValueException,
|
|
69
|
+
// and whatever response is thrown when the response is too large.
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Checking if the result of the Lambda Invoke is a primitive string to wrap it. this is for future post-processing such as Step Functions Tasks
|
|
73
|
+
if (result) {
|
|
74
|
+
if (typeof result === 'string') {
|
|
75
|
+
result = `"${result}"`
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// result is actually the Payload.
|
|
80
|
+
// So return in a standard structure so Hapi can
|
|
81
|
+
// respond with the correct status codes
|
|
82
|
+
return {
|
|
83
|
+
Payload: result,
|
|
84
|
+
StatusCode: 200,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// TODO FIXME
|
|
89
|
+
const errMsg = `invocationType: '${invocationType}' not supported by serverless-offline`
|
|
90
|
+
|
|
91
|
+
log.error(errMsg)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
FunctionError: 'InvalidParameterValueException',
|
|
95
|
+
Payload: {
|
|
96
|
+
Message: errMsg,
|
|
97
|
+
Type: 'User',
|
|
98
|
+
},
|
|
99
|
+
StatusCode: 400,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './invocationsRoute.js'
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
import InvocationsController from './InvocationsController.js'
|
|
3
|
+
|
|
4
|
+
const { parse } = JSON
|
|
5
|
+
|
|
6
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html
|
|
7
|
+
export default function invocationsRoute(lambda, options) {
|
|
8
|
+
const invocationsController = new InvocationsController(lambda)
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
async handler(request, h) {
|
|
12
|
+
const {
|
|
13
|
+
headers,
|
|
14
|
+
params: { functionName },
|
|
15
|
+
payload,
|
|
16
|
+
} = request
|
|
17
|
+
|
|
18
|
+
const parsedHeaders = new Headers(headers)
|
|
19
|
+
const clientContextHeader = parsedHeaders.get('x-amz-client-context')
|
|
20
|
+
const invocationType = parsedHeaders.get('x-amz-invocation-type')
|
|
21
|
+
|
|
22
|
+
// default is undefined
|
|
23
|
+
let clientContext
|
|
24
|
+
|
|
25
|
+
// check client context header was set
|
|
26
|
+
if (clientContextHeader) {
|
|
27
|
+
const clientContextBuffer = Buffer.from(clientContextHeader, 'base64')
|
|
28
|
+
clientContext = parse(clientContextBuffer.toString('utf-8'))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// check if payload was set, if not, default event is an empty object
|
|
32
|
+
const event = payload.length > 0 ? parse(payload.toString('utf-8')) : {}
|
|
33
|
+
|
|
34
|
+
const invokeResults = await invocationsController.invoke(
|
|
35
|
+
functionName,
|
|
36
|
+
invocationType,
|
|
37
|
+
event,
|
|
38
|
+
clientContext,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// Return with correct status codes
|
|
42
|
+
let resultPayload = ''
|
|
43
|
+
let statusCode = 200
|
|
44
|
+
let functionError = null
|
|
45
|
+
if (invokeResults) {
|
|
46
|
+
const isPayloadDefined = typeof invokeResults.Payload !== 'undefined'
|
|
47
|
+
resultPayload = isPayloadDefined ? invokeResults.Payload : ''
|
|
48
|
+
statusCode = invokeResults.StatusCode || 200
|
|
49
|
+
functionError = invokeResults.FunctionError || null
|
|
50
|
+
}
|
|
51
|
+
const response = h.response(resultPayload).code(statusCode)
|
|
52
|
+
if (functionError) {
|
|
53
|
+
// AWS Invoke documentation is wrong. The header for error type is
|
|
54
|
+
// 'x-amzn-ErrorType' in production, not 'X-Amz-Function-Error'
|
|
55
|
+
response.header('x-amzn-ErrorType', functionError)
|
|
56
|
+
}
|
|
57
|
+
if (invokeResults && invokeResults.UnhandledError) {
|
|
58
|
+
response.header('X-Amz-Function-Error', 'Unhandled')
|
|
59
|
+
}
|
|
60
|
+
return response
|
|
61
|
+
},
|
|
62
|
+
method: 'POST',
|
|
63
|
+
options: {
|
|
64
|
+
cors: options.corsConfig,
|
|
65
|
+
payload: {
|
|
66
|
+
// allow: ['binary/octet-stream'],
|
|
67
|
+
defaultContentType: 'binary/octet-stream',
|
|
68
|
+
// Set maximum size to 6 MB to match maximum invocation payload size in synchronous responses
|
|
69
|
+
maxBytes: 1024 * 1024 * 6,
|
|
70
|
+
// request.payload will be a raw buffer
|
|
71
|
+
parse: false,
|
|
72
|
+
},
|
|
73
|
+
tags: ['api'],
|
|
74
|
+
},
|
|
75
|
+
path: '/2015-03-31/functions/{functionName}/invocations',
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default class InvokeAsyncController {
|
|
2
|
+
#lambda = null
|
|
3
|
+
|
|
4
|
+
constructor(lambda) {
|
|
5
|
+
this.#lambda = lambda
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async invokeAsync(functionName, event) {
|
|
9
|
+
const lambdaFunction = this.#lambda.getByFunctionName(functionName)
|
|
10
|
+
|
|
11
|
+
lambdaFunction.setEvent(event)
|
|
12
|
+
|
|
13
|
+
// don't await result!
|
|
14
|
+
lambdaFunction.runHandler()
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
StatusCode: 202,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './invokeAsyncRoute.js'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import InvokeAsyncController from './InvokeAsyncController.js'
|
|
2
|
+
|
|
3
|
+
const { parse } = JSON
|
|
4
|
+
|
|
5
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/API_InvokeAsync.html
|
|
6
|
+
export default function invokeRoute(lambda, options) {
|
|
7
|
+
const invokeAsyncController = new InvokeAsyncController(lambda)
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
handler(request) {
|
|
11
|
+
const {
|
|
12
|
+
params: { functionName },
|
|
13
|
+
payload,
|
|
14
|
+
} = request
|
|
15
|
+
|
|
16
|
+
const event = parse(payload.toString('utf-8'))
|
|
17
|
+
|
|
18
|
+
return invokeAsyncController.invokeAsync(functionName, event)
|
|
19
|
+
},
|
|
20
|
+
method: 'POST',
|
|
21
|
+
options: {
|
|
22
|
+
cors: options.corsConfig,
|
|
23
|
+
payload: {
|
|
24
|
+
// allow: ['binary/octet-stream'],
|
|
25
|
+
defaultContentType: 'binary/octet-stream',
|
|
26
|
+
// request.payload will be a raw buffer
|
|
27
|
+
parse: false,
|
|
28
|
+
},
|
|
29
|
+
tags: ['api'],
|
|
30
|
+
},
|
|
31
|
+
path: '/2014-11-13/functions/{functionName}/invoke-async/',
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import createUniqueId from '../createUniqueId.js'
|
|
3
|
+
|
|
4
|
+
describe('createUniqueId', () => {
|
|
5
|
+
it('should be unique', () => {
|
|
6
|
+
const items = 100000
|
|
7
|
+
const set = new Set(Array.from(Array(items)).map(createUniqueId))
|
|
8
|
+
|
|
9
|
+
assert.equal(set.size, items)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should be a 36 character string', () => {
|
|
13
|
+
const id = createUniqueId()
|
|
14
|
+
|
|
15
|
+
assert.equal(typeof id, 'string')
|
|
16
|
+
assert.equal(id.length, 36)
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import formatToClfTime from '../formatToClfTime.js'
|
|
3
|
+
|
|
4
|
+
const { now } = Date
|
|
5
|
+
|
|
6
|
+
describe('formatToClfTime', () => {
|
|
7
|
+
it('should return "common log format" formatted time', () => {
|
|
8
|
+
const millis = now()
|
|
9
|
+
const result = formatToClfTime(millis)
|
|
10
|
+
|
|
11
|
+
// expected: 17/Dec/1995:03:24:00 -0500 (with varying offset)
|
|
12
|
+
assert.match(result, /([\w:/]+\s[+-]\d{4})/)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import generateHapiPath from '../generateHapiPath.js'
|
|
3
|
+
|
|
4
|
+
const serverless = {
|
|
5
|
+
service: {
|
|
6
|
+
provider: {
|
|
7
|
+
stage: 'dev',
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('generateHapiPath', () => {
|
|
13
|
+
it('should generate url starting with a slash', () => {
|
|
14
|
+
const options = {}
|
|
15
|
+
const result = generateHapiPath('users', options, serverless)
|
|
16
|
+
assert.equal(result[0], '/')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should generate url with the stage prepended', () => {
|
|
20
|
+
const options = {}
|
|
21
|
+
const result = generateHapiPath('users', options, serverless)
|
|
22
|
+
assert.equal(result, '/dev/users')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('when a prefix option is set', () => {
|
|
26
|
+
it('the url should add the prefix', () => {
|
|
27
|
+
const options = { prefix: 'some-prefix' }
|
|
28
|
+
const result = generateHapiPath('users', options, serverless)
|
|
29
|
+
assert.equal(result, '/some-prefix/dev/users')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('when the noPrependStageInUrl option is set', () => {
|
|
34
|
+
it('the url should omit the stage', () => {
|
|
35
|
+
const options = { noPrependStageInUrl: true }
|
|
36
|
+
const result = generateHapiPath('users', options, serverless)
|
|
37
|
+
assert.equal(result, '/users')
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('the stage from options should override stage from serverless config', () => {
|
|
42
|
+
const options = { stage: 'prod' }
|
|
43
|
+
const result = generateHapiPath('users', options, serverless)
|
|
44
|
+
assert.equal(result, '/prod/users')
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import lowerCaseKeys from '../lowerCaseKeys.js'
|
|
3
|
+
|
|
4
|
+
describe('lowerCaseKeys', () => {
|
|
5
|
+
it('should handle empty object', () => {
|
|
6
|
+
const result = lowerCaseKeys({})
|
|
7
|
+
assert.deepEqual(result, {})
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('should handle object with one key', () => {
|
|
11
|
+
const result = lowerCaseKeys({ 'Some-Key': 'value' })
|
|
12
|
+
assert.deepEqual(result, { 'some-key': 'value' })
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should handle object with multiple keys', () => {
|
|
16
|
+
const result = lowerCaseKeys({
|
|
17
|
+
'already-lowercase': 'cool',
|
|
18
|
+
'Another-Key': 'anotherValue',
|
|
19
|
+
'lOts-OF-CAPitaLs': 'ButThisIsNotTouched',
|
|
20
|
+
'Some-Key': 'value',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
assert.deepEqual(result, {
|
|
24
|
+
'already-lowercase': 'cool',
|
|
25
|
+
'another-key': 'anotherValue',
|
|
26
|
+
'lots-of-capitals': 'ButThisIsNotTouched',
|
|
27
|
+
'some-key': 'value',
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
// uses the same tests as parseMultiValueHeaders
|
|
3
|
+
import tests from './parseMultiValueHeaders.test.js'
|
|
4
|
+
import parseHeaders from '../parseHeaders.js'
|
|
5
|
+
|
|
6
|
+
describe('parseQueryStringParameters', () => {
|
|
7
|
+
tests.forEach(({ description, expected, param }) => {
|
|
8
|
+
it(`should return ${description}`, () => {
|
|
9
|
+
const result = parseHeaders(param)
|
|
10
|
+
assert.deepEqual(result, expected)
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import parseMultiValueHeaders from '../parseMultiValueHeaders.js'
|
|
3
|
+
|
|
4
|
+
// TODO need more tests
|
|
5
|
+
const tests = [
|
|
6
|
+
{
|
|
7
|
+
description: 'no parameter (empty array)',
|
|
8
|
+
expected: null,
|
|
9
|
+
expectedMulti: null,
|
|
10
|
+
param: [],
|
|
11
|
+
},
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
describe('parseMultiValueHeaders', () => {
|
|
15
|
+
tests.forEach(({ description, expectedMulti, param }) => {
|
|
16
|
+
it(`should return ${description}`, () => {
|
|
17
|
+
const resultMulti = parseMultiValueHeaders(param)
|
|
18
|
+
assert.deepEqual(resultMulti, expectedMulti)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// export tests for parseHeaders
|
|
24
|
+
export default tests
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import parseMultiValueQueryStringParameters from '../parseMultiValueQueryStringParameters.js'
|
|
3
|
+
|
|
4
|
+
const tests = [
|
|
5
|
+
{
|
|
6
|
+
description: 'no parameter (empty string)',
|
|
7
|
+
expected: null,
|
|
8
|
+
expectedMulti: null,
|
|
9
|
+
param: '',
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
description: 'string parameter',
|
|
14
|
+
expected: { foo: 'bar' },
|
|
15
|
+
expectedMulti: { foo: ['bar'] },
|
|
16
|
+
param: 'foo=bar',
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
description: 'number parameter (no type casting)',
|
|
21
|
+
expected: { foo: '1' },
|
|
22
|
+
expectedMulti: { foo: ['1'] },
|
|
23
|
+
param: 'foo=1',
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
description: 'boolean parameter (no type casting)',
|
|
28
|
+
expected: { foo: 'true' },
|
|
29
|
+
expectedMulti: { foo: ['true'] },
|
|
30
|
+
param: 'foo=true',
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
description: 'multiple parameters',
|
|
35
|
+
expected: { bar: 'test2', foo: 'test1' },
|
|
36
|
+
expectedMulti: { bar: ['test2'], foo: ['test1'] },
|
|
37
|
+
param: 'foo=test1&bar=test2',
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
description: 'multiple parameters, same keys',
|
|
42
|
+
expected: { foo: 'foobar' },
|
|
43
|
+
expectedMulti: { foo: ['test', 'foobar'] },
|
|
44
|
+
param: 'foo=test&foo=foobar',
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
description: 'multiple parameters, same keys, different casing',
|
|
49
|
+
expected: { foo: 'test', FOO: 'FOOBAR' },
|
|
50
|
+
expectedMulti: { foo: ['test'], FOO: ['FOOBAR'] },
|
|
51
|
+
param: 'foo=test&FOO=FOOBAR',
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
description: 'multiple parameters, same keys, same values',
|
|
56
|
+
expected: { foo: 'test' },
|
|
57
|
+
expectedMulti: { foo: ['test', 'test'] },
|
|
58
|
+
param: 'foo=test&foo=test',
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
description: 'no value',
|
|
63
|
+
expected: { foo: '' },
|
|
64
|
+
expectedMulti: { foo: [''] },
|
|
65
|
+
param: 'foo',
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
description: 'no value with =',
|
|
70
|
+
expected: { foo: '' },
|
|
71
|
+
expectedMulti: { foo: [''] },
|
|
72
|
+
param: 'foo=',
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
description: 'no value with &',
|
|
77
|
+
expected: { foo: '' },
|
|
78
|
+
expectedMulti: { foo: [''] },
|
|
79
|
+
param: 'foo&',
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
description: 'no value with = and &',
|
|
84
|
+
expected: { foo: '' },
|
|
85
|
+
expectedMulti: { foo: [''] },
|
|
86
|
+
param: 'foo=&',
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
description: 'value is whitespace',
|
|
91
|
+
expected: { foo: ' ' },
|
|
92
|
+
expectedMulti: { foo: [' '] },
|
|
93
|
+
param: 'foo=%20',
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
description: 'key and value have whitespace',
|
|
98
|
+
expected: { ' foo ': ' test ' },
|
|
99
|
+
expectedMulti: { ' foo ': [' test '] },
|
|
100
|
+
param: '%20foo%20=%20test%20',
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
description: 'unicode',
|
|
105
|
+
expected: { Σ: '😋' },
|
|
106
|
+
expectedMulti: { Σ: ['😋'] },
|
|
107
|
+
param: 'Σ=😋',
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
description: 'encoded', // encodeURIComponent
|
|
112
|
+
expected: { '?=/&:': '?=/&:' },
|
|
113
|
+
expectedMulti: { '?=/&:': ['?=/&:'] },
|
|
114
|
+
param: '%3F%3D%2F%26%3A=%3F%3D%2F%26%3A',
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
{
|
|
118
|
+
description: 'end of line',
|
|
119
|
+
expected: { '\n': '\n' },
|
|
120
|
+
expectedMulti: { '\n': ['\n'] },
|
|
121
|
+
param: '%0A=%0A',
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// silly test section:
|
|
125
|
+
{
|
|
126
|
+
description: 'silly I.',
|
|
127
|
+
expected: { test: '?' },
|
|
128
|
+
expectedMulti: { test: ['?'] },
|
|
129
|
+
param: 'test=?',
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
{
|
|
133
|
+
description: 'silly II.',
|
|
134
|
+
expected: { test: '/' },
|
|
135
|
+
expectedMulti: { test: ['/'] },
|
|
136
|
+
param: 'test=/',
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
description: 'silly III.',
|
|
141
|
+
expected: { test: '=' },
|
|
142
|
+
expectedMulti: { test: ['='] },
|
|
143
|
+
param: 'test==',
|
|
144
|
+
},
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
describe('parseMultiValueQueryStringParameters', () => {
|
|
148
|
+
tests.forEach(({ description, expectedMulti, param }) => {
|
|
149
|
+
const url = `foo?${param}`
|
|
150
|
+
|
|
151
|
+
it(`should return ${description}`, () => {
|
|
152
|
+
const resultMulti = parseMultiValueQueryStringParameters(url)
|
|
153
|
+
assert.deepEqual(resultMulti, expectedMulti)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// export tests for parseQueryStringParameters
|
|
159
|
+
export default tests
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
// uses the same tests as parseMultiValueQueryStringParameters
|
|
3
|
+
import tests from './parseMultiValueQueryStringParameters.test.js'
|
|
4
|
+
import parseQueryStringParameters from '../parseQueryStringParameters.js'
|
|
5
|
+
|
|
6
|
+
describe('parseQueryStringParameters', () => {
|
|
7
|
+
tests.forEach(({ description, expected, param }) => {
|
|
8
|
+
const url = `/foo?${param}`
|
|
9
|
+
|
|
10
|
+
it(`should return ${description}`, () => {
|
|
11
|
+
const result = parseQueryStringParameters(url)
|
|
12
|
+
assert.deepEqual(result, expected)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import splitHandlerPathAndName from '../splitHandlerPathAndName.js'
|
|
3
|
+
|
|
4
|
+
const tests = [
|
|
5
|
+
{
|
|
6
|
+
description: 'ruby handler with namespace resolution operator ::',
|
|
7
|
+
expected: ['./src/somefolder/source', 'LambdaFunctions::Handler.process'],
|
|
8
|
+
handler: './src/somefolder/source.LambdaFunctions::Handler.process',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
description: 'ruby handler with multiple namespace resolution operators ::',
|
|
12
|
+
expected: [
|
|
13
|
+
'./src/somefolder/source',
|
|
14
|
+
'Functions::LambdaFunctions::Handler.process',
|
|
15
|
+
],
|
|
16
|
+
handler:
|
|
17
|
+
'./src/somefolder/source.Functions::LambdaFunctions::Handler.process',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
description: 'ruby handler with namespace resolution operator ::, unnested',
|
|
21
|
+
expected: ['source', 'LambdaFunctions::Handler.process'],
|
|
22
|
+
handler: 'source.LambdaFunctions::Handler.process',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
description:
|
|
26
|
+
'ruby handler with multiple namespace resolution operators ::, unnested',
|
|
27
|
+
expected: ['./source', 'Functions::LambdaFunctions::Handler.process'],
|
|
28
|
+
handler: './source.Functions::LambdaFunctions::Handler.process',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
description: 'ruby handler from kernel',
|
|
32
|
+
expected: ['./src/somefolder/function', 'handler'],
|
|
33
|
+
handler: './src/somefolder/function.handler',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
description: 'generic handler',
|
|
37
|
+
expected: ['./src/somefolder/.handlers/handler', 'run'],
|
|
38
|
+
handler: './src/somefolder/.handlers/handler.run',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
description: 'generic handler, unnested',
|
|
42
|
+
expected: ['handler', 'run'],
|
|
43
|
+
handler: 'handler.run',
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
describe('splitHandlerPathAndName', () => {
|
|
48
|
+
tests.forEach(({ description, expected, handler }) => {
|
|
49
|
+
it(`should split ${description}`, () => {
|
|
50
|
+
const result = splitHandlerPathAndName(handler)
|
|
51
|
+
assert.deepEqual(result, expected)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import unflatten from '../unflatten.js'
|
|
3
|
+
|
|
4
|
+
describe('unflatten', () => {
|
|
5
|
+
it('should work with empty array parameter', () => {
|
|
6
|
+
const value = []
|
|
7
|
+
const out = unflatten(value, 2)
|
|
8
|
+
const expected = []
|
|
9
|
+
|
|
10
|
+
assert.deepEqual(out, expected)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('should work with single pair parameter', () => {
|
|
14
|
+
const value = ['a', 1]
|
|
15
|
+
const out = unflatten(value, 2)
|
|
16
|
+
const expected = [['a', 1]]
|
|
17
|
+
|
|
18
|
+
assert.deepEqual(out, expected)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should work with multiple pair parameters', () => {
|
|
22
|
+
const value = ['a', 1, 'b', 2, 'c', 3]
|
|
23
|
+
const out = unflatten(value, 2)
|
|
24
|
+
const expected = [
|
|
25
|
+
['a', 1],
|
|
26
|
+
['b', 2],
|
|
27
|
+
['c', 3],
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
assert.deepEqual(out, expected)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
|
|
3
|
+
export default async function checkDockerDaemon() {
|
|
4
|
+
let dockerServerOS
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
;({ stdout: dockerServerOS } = await execa('docker', [
|
|
8
|
+
'version',
|
|
9
|
+
'--format',
|
|
10
|
+
'{{.Server.Os}}',
|
|
11
|
+
]))
|
|
12
|
+
} catch {
|
|
13
|
+
throw new Error('The docker daemon is not running.')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (dockerServerOS !== 'linux') {
|
|
17
|
+
throw new Error('Please switch docker daemon to linux mode.')
|
|
18
|
+
}
|
|
19
|
+
}
|