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.
Files changed (235) hide show
  1. package/README.md +3 -3
  2. package/package.json +33 -57
  3. package/src/ServerlessOffline.js +412 -0
  4. package/src/config/commandOptions.js +155 -0
  5. package/src/config/constants.js +22 -0
  6. package/{dist → src}/config/defaultOptions.js +8 -17
  7. package/src/config/index.js +4 -0
  8. package/src/config/supportedRuntimes.js +47 -0
  9. package/src/events/authCanExecuteResource.js +35 -0
  10. package/src/events/authFunctionNameExtractor.js +75 -0
  11. package/src/events/authMatchPolicyResource.js +71 -0
  12. package/src/events/authValidateContext.js +51 -0
  13. package/src/events/http/Endpoint.js +135 -0
  14. package/src/events/http/Http.js +50 -0
  15. package/src/events/http/HttpEventDefinition.js +20 -0
  16. package/src/events/http/HttpServer.js +1277 -0
  17. package/src/events/http/OfflineEndpoint.js +33 -0
  18. package/src/events/http/authJWTSettingsExtractor.js +70 -0
  19. package/src/events/http/createAuthScheme.js +176 -0
  20. package/src/events/http/createJWTAuthScheme.js +106 -0
  21. package/src/events/http/index.js +1 -0
  22. package/src/events/http/javaHelpers.js +102 -0
  23. package/src/events/http/lambda-events/LambdaIntegrationEvent.js +57 -0
  24. package/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +233 -0
  25. package/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +190 -0
  26. package/src/events/http/lambda-events/VelocityContext.js +147 -0
  27. package/src/events/http/lambda-events/index.js +4 -0
  28. package/src/events/http/lambda-events/renderVelocityTemplateObject.js +93 -0
  29. package/{dist → src}/events/http/parseResources.js +73 -78
  30. package/src/events/http/payloadSchemaValidator.js +13 -0
  31. package/{dist → src}/events/http/templates/offline-default.req.vm +0 -0
  32. package/{dist → src}/events/http/templates/offline-default.res.vm +0 -0
  33. package/src/events/schedule/Schedule.js +131 -0
  34. package/src/events/schedule/ScheduleEvent.js +18 -0
  35. package/src/events/schedule/ScheduleEventDefinition.js +21 -0
  36. package/src/events/schedule/index.js +1 -0
  37. package/src/events/websocket/HttpServer.js +69 -0
  38. package/src/events/websocket/WebSocket.js +52 -0
  39. package/src/events/websocket/WebSocketClients.js +462 -0
  40. package/src/events/websocket/WebSocketEventDefinition.js +18 -0
  41. package/src/events/websocket/WebSocketServer.js +73 -0
  42. package/src/events/websocket/http-routes/_catchAll/catchAllRoute.js +16 -0
  43. package/src/events/websocket/http-routes/_catchAll/index.js +1 -0
  44. package/src/events/websocket/http-routes/connections/ConnectionsController.js +28 -0
  45. package/src/events/websocket/http-routes/connections/connectionsRoutes.js +70 -0
  46. package/src/events/websocket/http-routes/connections/index.js +1 -0
  47. package/src/events/websocket/http-routes/index.js +2 -0
  48. package/src/events/websocket/index.js +1 -0
  49. package/src/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +65 -0
  50. package/src/events/websocket/lambda-events/WebSocketConnectEvent.js +68 -0
  51. package/src/events/websocket/lambda-events/WebSocketDisconnectEvent.js +31 -0
  52. package/src/events/websocket/lambda-events/WebSocketEvent.js +29 -0
  53. package/src/events/websocket/lambda-events/WebSocketRequestContext.js +67 -0
  54. package/src/events/websocket/lambda-events/index.js +4 -0
  55. package/src/index.js +12 -0
  56. package/src/lambda/HttpServer.js +108 -0
  57. package/src/lambda/Lambda.js +68 -0
  58. package/src/lambda/LambdaContext.js +33 -0
  59. package/src/lambda/LambdaFunction.js +308 -0
  60. package/src/lambda/LambdaFunctionPool.js +109 -0
  61. package/src/lambda/__tests__/LambdaContext.test.js +30 -0
  62. package/src/lambda/__tests__/LambdaFunction.test.js +196 -0
  63. package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsJSONObject.fixture.js +47 -0
  64. package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsNativeString.fixture.js +46 -0
  65. package/src/lambda/__tests__/fixtures/Lambda/package.json +3 -0
  66. package/src/lambda/__tests__/fixtures/lambdaFunction.fixture.js +145 -0
  67. package/src/lambda/__tests__/fixtures/package.json +3 -0
  68. package/src/lambda/__tests__/routes/invocations/InvocationsController.test.js +42 -0
  69. package/src/lambda/handler-runner/HandlerRunner.js +136 -0
  70. package/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +72 -0
  71. package/src/lambda/handler-runner/child-process-runner/childProcessHelper.js +42 -0
  72. package/src/lambda/handler-runner/child-process-runner/index.js +1 -0
  73. package/src/lambda/handler-runner/docker-runner/DockerContainer.js +417 -0
  74. package/src/lambda/handler-runner/docker-runner/DockerImage.js +35 -0
  75. package/src/lambda/handler-runner/docker-runner/DockerRunner.js +63 -0
  76. package/src/lambda/handler-runner/docker-runner/index.js +1 -0
  77. package/src/lambda/handler-runner/go-runner/GoRunner.js +166 -0
  78. package/src/lambda/handler-runner/go-runner/index.js +1 -0
  79. package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +125 -0
  80. package/src/lambda/handler-runner/in-process-runner/index.js +1 -0
  81. package/src/lambda/handler-runner/index.js +1 -0
  82. package/src/lambda/handler-runner/java-runner/JavaRunner.js +114 -0
  83. package/src/lambda/handler-runner/java-runner/index.js +1 -0
  84. package/src/lambda/handler-runner/python-runner/PythonRunner.js +138 -0
  85. package/src/lambda/handler-runner/python-runner/index.js +1 -0
  86. package/{dist → src}/lambda/handler-runner/python-runner/invoke.py +0 -0
  87. package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +107 -0
  88. package/src/lambda/handler-runner/ruby-runner/index.js +1 -0
  89. package/{dist → src}/lambda/handler-runner/ruby-runner/invoke.rb +0 -0
  90. package/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +70 -0
  91. package/src/lambda/handler-runner/worker-thread-runner/index.js +1 -0
  92. package/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +29 -0
  93. package/src/lambda/index.js +1 -0
  94. package/src/lambda/routes/index.js +2 -0
  95. package/src/lambda/routes/invocations/InvocationsController.js +102 -0
  96. package/src/lambda/routes/invocations/index.js +1 -0
  97. package/src/lambda/routes/invocations/invocationsRoute.js +77 -0
  98. package/src/lambda/routes/invoke-async/InvokeAsyncController.js +20 -0
  99. package/src/lambda/routes/invoke-async/index.js +1 -0
  100. package/src/lambda/routes/invoke-async/invokeAsyncRoute.js +33 -0
  101. package/src/utils/__tests__/createUniqueId.test.js +18 -0
  102. package/src/utils/__tests__/formatToClfTime.test.js +14 -0
  103. package/src/utils/__tests__/generateHapiPath.test.js +46 -0
  104. package/src/utils/__tests__/lowerCaseKeys.test.js +30 -0
  105. package/src/utils/__tests__/parseHeaders.test.js +13 -0
  106. package/src/utils/__tests__/parseMultiValueHeaders.test.js +24 -0
  107. package/src/utils/__tests__/parseMultiValueQueryStringParameters.test.js +159 -0
  108. package/src/utils/__tests__/parseQueryStringParameters.test.js +15 -0
  109. package/src/utils/__tests__/splitHandlerPathAndName.test.js +54 -0
  110. package/src/utils/__tests__/unflatten.test.js +32 -0
  111. package/src/utils/checkDockerDaemon.js +19 -0
  112. package/src/utils/checkGoVersion.js +16 -0
  113. package/src/utils/createApiKey.js +5 -0
  114. package/src/utils/createUniqueId.js +5 -0
  115. package/src/utils/detectExecutable.js +11 -0
  116. package/{dist → src}/utils/formatToClfTime.js +6 -14
  117. package/src/utils/generateHapiPath.js +26 -0
  118. package/src/utils/getHttpApiCorsConfig.js +28 -0
  119. package/src/utils/index.js +42 -0
  120. package/src/utils/jsonPath.js +13 -0
  121. package/src/utils/logRoutes.js +64 -0
  122. package/src/utils/lowerCaseKeys.js +6 -0
  123. package/src/utils/parseHeaders.js +14 -0
  124. package/src/utils/parseMultiValueHeaders.js +27 -0
  125. package/src/utils/parseMultiValueQueryStringParameters.js +31 -0
  126. package/src/utils/parseQueryStringParameters.js +15 -0
  127. package/src/utils/resolveJoins.js +29 -0
  128. package/src/utils/splitHandlerPathAndName.js +31 -0
  129. package/src/utils/unflatten.js +11 -0
  130. package/CHANGELOG.md +0 -78
  131. package/dist/ServerlessOffline.js +0 -508
  132. package/dist/config/commandOptions.js +0 -149
  133. package/dist/config/constants.js +0 -30
  134. package/dist/config/index.js +0 -55
  135. package/dist/config/supportedRuntimes.js +0 -40
  136. package/dist/debugLog.js +0 -12
  137. package/dist/events/authCanExecuteResource.js +0 -35
  138. package/dist/events/authFunctionNameExtractor.js +0 -87
  139. package/dist/events/authMatchPolicyResource.js +0 -62
  140. package/dist/events/authValidateContext.js +0 -53
  141. package/dist/events/http/Endpoint.js +0 -173
  142. package/dist/events/http/Http.js +0 -77
  143. package/dist/events/http/HttpEventDefinition.js +0 -36
  144. package/dist/events/http/HttpServer.js +0 -1370
  145. package/dist/events/http/OfflineEndpoint.js +0 -38
  146. package/dist/events/http/authJWTSettingsExtractor.js +0 -76
  147. package/dist/events/http/createAuthScheme.js +0 -184
  148. package/dist/events/http/createJWTAuthScheme.js +0 -159
  149. package/dist/events/http/index.js +0 -15
  150. package/dist/events/http/javaHelpers.js +0 -99
  151. package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +0 -87
  152. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -246
  153. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -225
  154. package/dist/events/http/lambda-events/VelocityContext.js +0 -170
  155. package/dist/events/http/lambda-events/index.js +0 -39
  156. package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -111
  157. package/dist/events/http/payloadSchemaValidator.js +0 -13
  158. package/dist/events/schedule/Schedule.js +0 -183
  159. package/dist/events/schedule/ScheduleEvent.js +0 -27
  160. package/dist/events/schedule/ScheduleEventDefinition.js +0 -36
  161. package/dist/events/schedule/index.js +0 -15
  162. package/dist/events/websocket/HttpServer.js +0 -114
  163. package/dist/events/websocket/WebSocket.js +0 -78
  164. package/dist/events/websocket/WebSocketClients.js +0 -577
  165. package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
  166. package/dist/events/websocket/WebSocketServer.js +0 -139
  167. package/dist/events/websocket/http-routes/_catchAll/catchAllRoute.js +0 -33
  168. package/dist/events/websocket/http-routes/_catchAll/index.js +0 -15
  169. package/dist/events/websocket/http-routes/connections/ConnectionsController.js +0 -45
  170. package/dist/events/websocket/http-routes/connections/connectionsRoutes.js +0 -95
  171. package/dist/events/websocket/http-routes/connections/index.js +0 -15
  172. package/dist/events/websocket/http-routes/index.js +0 -23
  173. package/dist/events/websocket/index.js +0 -15
  174. package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +0 -99
  175. package/dist/events/websocket/lambda-events/WebSocketConnectEvent.js +0 -101
  176. package/dist/events/websocket/lambda-events/WebSocketDisconnectEvent.js +0 -47
  177. package/dist/events/websocket/lambda-events/WebSocketEvent.js +0 -54
  178. package/dist/events/websocket/lambda-events/WebSocketRequestContext.js +0 -98
  179. package/dist/events/websocket/lambda-events/index.js +0 -39
  180. package/dist/index.js +0 -15
  181. package/dist/lambda/HttpServer.js +0 -124
  182. package/dist/lambda/Lambda.js +0 -117
  183. package/dist/lambda/LambdaContext.js +0 -53
  184. package/dist/lambda/LambdaFunction.js +0 -390
  185. package/dist/lambda/LambdaFunctionPool.js +0 -127
  186. package/dist/lambda/handler-runner/HandlerRunner.js +0 -195
  187. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -124
  188. package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -49
  189. package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
  190. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -515
  191. package/dist/lambda/handler-runner/docker-runner/DockerImage.js +0 -67
  192. package/dist/lambda/handler-runner/docker-runner/DockerRunner.js +0 -74
  193. package/dist/lambda/handler-runner/docker-runner/index.js +0 -15
  194. package/dist/lambda/handler-runner/go-runner/GoRunner.js +0 -230
  195. package/dist/lambda/handler-runner/go-runner/index.js +0 -15
  196. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -228
  197. package/dist/lambda/handler-runner/in-process-runner/index.js +0 -15
  198. package/dist/lambda/handler-runner/index.js +0 -15
  199. package/dist/lambda/handler-runner/java-runner/JavaRunner.js +0 -153
  200. package/dist/lambda/handler-runner/java-runner/index.js +0 -15
  201. package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -185
  202. package/dist/lambda/handler-runner/python-runner/index.js +0 -15
  203. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -147
  204. package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
  205. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -92
  206. package/dist/lambda/handler-runner/worker-thread-runner/index.js +0 -15
  207. package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +0 -31
  208. package/dist/lambda/index.js +0 -15
  209. package/dist/lambda/routes/index.js +0 -23
  210. package/dist/lambda/routes/invocations/InvocationsController.js +0 -142
  211. package/dist/lambda/routes/invocations/index.js +0 -15
  212. package/dist/lambda/routes/invocations/invocationsRoute.js +0 -90
  213. package/dist/lambda/routes/invoke-async/InvokeAsyncController.js +0 -38
  214. package/dist/lambda/routes/invoke-async/index.js +0 -15
  215. package/dist/lambda/routes/invoke-async/invokeAsyncRoute.js +0 -43
  216. package/dist/main.js +0 -11
  217. package/dist/serverlessLog.js +0 -91
  218. package/dist/utils/checkDockerDaemon.js +0 -27
  219. package/dist/utils/checkGoVersion.js +0 -27
  220. package/dist/utils/createApiKey.js +0 -12
  221. package/dist/utils/createUniqueId.js +0 -14
  222. package/dist/utils/detectExecutable.js +0 -21
  223. package/dist/utils/generateHapiPath.js +0 -28
  224. package/dist/utils/getHttpApiCorsConfig.js +0 -40
  225. package/dist/utils/index.js +0 -165
  226. package/dist/utils/jsonPath.js +0 -21
  227. package/dist/utils/lowerCaseKeys.js +0 -14
  228. package/dist/utils/parseHeaders.js +0 -23
  229. package/dist/utils/parseMultiValueHeaders.js +0 -36
  230. package/dist/utils/parseMultiValueQueryStringParameters.js +0 -40
  231. package/dist/utils/parseQueryStringParameters.js +0 -26
  232. package/dist/utils/resolveJoins.js +0 -36
  233. package/dist/utils/satisfiesVersionRange.js +0 -20
  234. package/dist/utils/splitHandlerPathAndName.js +0 -37
  235. 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
+ }