serverless-offline 8.8.0 → 9.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.
Files changed (233) hide show
  1. package/README.md +12 -11
  2. package/package.json +33 -58
  3. package/src/ServerlessOffline.js +409 -0
  4. package/src/config/commandOptions.js +159 -0
  5. package/src/config/constants.js +22 -0
  6. package/{dist → src}/config/defaultOptions.js +9 -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 +1242 -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 +309 -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 +167 -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/splitHandlerPathAndName.js +31 -0
  128. package/src/utils/unflatten.js +11 -0
  129. package/dist/ServerlessOffline.js +0 -514
  130. package/dist/config/commandOptions.js +0 -149
  131. package/dist/config/constants.js +0 -30
  132. package/dist/config/index.js +0 -55
  133. package/dist/config/supportedRuntimes.js +0 -40
  134. package/dist/debugLog.js +0 -12
  135. package/dist/events/authCanExecuteResource.js +0 -35
  136. package/dist/events/authFunctionNameExtractor.js +0 -87
  137. package/dist/events/authMatchPolicyResource.js +0 -62
  138. package/dist/events/authValidateContext.js +0 -53
  139. package/dist/events/http/Endpoint.js +0 -173
  140. package/dist/events/http/Http.js +0 -77
  141. package/dist/events/http/HttpEventDefinition.js +0 -36
  142. package/dist/events/http/HttpServer.js +0 -1370
  143. package/dist/events/http/OfflineEndpoint.js +0 -38
  144. package/dist/events/http/authJWTSettingsExtractor.js +0 -76
  145. package/dist/events/http/createAuthScheme.js +0 -184
  146. package/dist/events/http/createJWTAuthScheme.js +0 -159
  147. package/dist/events/http/index.js +0 -15
  148. package/dist/events/http/javaHelpers.js +0 -99
  149. package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +0 -87
  150. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -246
  151. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -225
  152. package/dist/events/http/lambda-events/VelocityContext.js +0 -170
  153. package/dist/events/http/lambda-events/index.js +0 -39
  154. package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -111
  155. package/dist/events/http/payloadSchemaValidator.js +0 -13
  156. package/dist/events/schedule/Schedule.js +0 -183
  157. package/dist/events/schedule/ScheduleEvent.js +0 -27
  158. package/dist/events/schedule/ScheduleEventDefinition.js +0 -36
  159. package/dist/events/schedule/index.js +0 -15
  160. package/dist/events/websocket/HttpServer.js +0 -114
  161. package/dist/events/websocket/WebSocket.js +0 -78
  162. package/dist/events/websocket/WebSocketClients.js +0 -577
  163. package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
  164. package/dist/events/websocket/WebSocketServer.js +0 -139
  165. package/dist/events/websocket/http-routes/_catchAll/catchAllRoute.js +0 -33
  166. package/dist/events/websocket/http-routes/_catchAll/index.js +0 -15
  167. package/dist/events/websocket/http-routes/connections/ConnectionsController.js +0 -45
  168. package/dist/events/websocket/http-routes/connections/connectionsRoutes.js +0 -95
  169. package/dist/events/websocket/http-routes/connections/index.js +0 -15
  170. package/dist/events/websocket/http-routes/index.js +0 -23
  171. package/dist/events/websocket/index.js +0 -15
  172. package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +0 -99
  173. package/dist/events/websocket/lambda-events/WebSocketConnectEvent.js +0 -101
  174. package/dist/events/websocket/lambda-events/WebSocketDisconnectEvent.js +0 -47
  175. package/dist/events/websocket/lambda-events/WebSocketEvent.js +0 -54
  176. package/dist/events/websocket/lambda-events/WebSocketRequestContext.js +0 -98
  177. package/dist/events/websocket/lambda-events/index.js +0 -39
  178. package/dist/index.js +0 -15
  179. package/dist/lambda/HttpServer.js +0 -124
  180. package/dist/lambda/Lambda.js +0 -117
  181. package/dist/lambda/LambdaContext.js +0 -53
  182. package/dist/lambda/LambdaFunction.js +0 -390
  183. package/dist/lambda/LambdaFunctionPool.js +0 -127
  184. package/dist/lambda/handler-runner/HandlerRunner.js +0 -195
  185. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -124
  186. package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -49
  187. package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
  188. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -515
  189. package/dist/lambda/handler-runner/docker-runner/DockerImage.js +0 -67
  190. package/dist/lambda/handler-runner/docker-runner/DockerRunner.js +0 -74
  191. package/dist/lambda/handler-runner/docker-runner/index.js +0 -15
  192. package/dist/lambda/handler-runner/go-runner/GoRunner.js +0 -230
  193. package/dist/lambda/handler-runner/go-runner/index.js +0 -15
  194. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -228
  195. package/dist/lambda/handler-runner/in-process-runner/index.js +0 -15
  196. package/dist/lambda/handler-runner/index.js +0 -15
  197. package/dist/lambda/handler-runner/java-runner/JavaRunner.js +0 -153
  198. package/dist/lambda/handler-runner/java-runner/index.js +0 -15
  199. package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -185
  200. package/dist/lambda/handler-runner/python-runner/index.js +0 -15
  201. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -147
  202. package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
  203. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -92
  204. package/dist/lambda/handler-runner/worker-thread-runner/index.js +0 -15
  205. package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +0 -31
  206. package/dist/lambda/index.js +0 -15
  207. package/dist/lambda/routes/index.js +0 -23
  208. package/dist/lambda/routes/invocations/InvocationsController.js +0 -142
  209. package/dist/lambda/routes/invocations/index.js +0 -15
  210. package/dist/lambda/routes/invocations/invocationsRoute.js +0 -90
  211. package/dist/lambda/routes/invoke-async/InvokeAsyncController.js +0 -38
  212. package/dist/lambda/routes/invoke-async/index.js +0 -15
  213. package/dist/lambda/routes/invoke-async/invokeAsyncRoute.js +0 -43
  214. package/dist/main.js +0 -11
  215. package/dist/serverlessLog.js +0 -91
  216. package/dist/utils/checkDockerDaemon.js +0 -27
  217. package/dist/utils/checkGoVersion.js +0 -27
  218. package/dist/utils/createApiKey.js +0 -12
  219. package/dist/utils/createUniqueId.js +0 -14
  220. package/dist/utils/detectExecutable.js +0 -21
  221. package/dist/utils/generateHapiPath.js +0 -28
  222. package/dist/utils/getHttpApiCorsConfig.js +0 -40
  223. package/dist/utils/index.js +0 -165
  224. package/dist/utils/jsonPath.js +0 -21
  225. package/dist/utils/lowerCaseKeys.js +0 -14
  226. package/dist/utils/parseHeaders.js +0 -23
  227. package/dist/utils/parseMultiValueHeaders.js +0 -36
  228. package/dist/utils/parseMultiValueQueryStringParameters.js +0 -40
  229. package/dist/utils/parseQueryStringParameters.js +0 -26
  230. package/dist/utils/resolveJoins.js +0 -36
  231. package/dist/utils/satisfiesVersionRange.js +0 -20
  232. package/dist/utils/splitHandlerPathAndName.js +0 -37
  233. package/dist/utils/unflatten.js +0 -18
@@ -0,0 +1,309 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import process from 'node:process'
4
+ import { performance } from 'node:perf_hooks'
5
+ import { log } from '@serverless/utils/log.js'
6
+ import { emptyDir, ensureDir, remove } from 'fs-extra'
7
+ import jszip from 'jszip'
8
+ import HandlerRunner from './handler-runner/index.js'
9
+ import LambdaContext from './LambdaContext.js'
10
+ import {
11
+ DEFAULT_LAMBDA_MEMORY_SIZE,
12
+ DEFAULT_LAMBDA_RUNTIME,
13
+ DEFAULT_LAMBDA_TIMEOUT,
14
+ supportedRuntimes,
15
+ } from '../config/index.js'
16
+ import { createUniqueId, splitHandlerPathAndName } from '../utils/index.js'
17
+
18
+ const { entries, fromEntries } = Object
19
+ const { ceil } = Math
20
+
21
+ export default class LambdaFunction {
22
+ #artifact = null
23
+
24
+ #clientContext = null
25
+
26
+ #codeDir = null
27
+
28
+ #event = null
29
+
30
+ #executionTimeEnded = null
31
+
32
+ #executionTimeStarted = null
33
+
34
+ #functionKey = null
35
+
36
+ #functionName = null
37
+
38
+ #handler = null
39
+
40
+ #handlerRunner = null
41
+
42
+ #idleTimeStarted = null
43
+
44
+ #initialized = false
45
+
46
+ #lambdaContext = null
47
+
48
+ #lambdaDir = null
49
+
50
+ #memorySize = null
51
+
52
+ #region = null
53
+
54
+ #runtime = null
55
+
56
+ #timeout = null
57
+
58
+ status = 'IDLE' // can be 'BUSY' or 'IDLE'
59
+
60
+ constructor(functionKey, functionDefinition, serverless, options) {
61
+ const {
62
+ service,
63
+ config: { serverlessPath, servicePath },
64
+ service: { provider, package: servicePackage = {} },
65
+ } = serverless
66
+
67
+ // TEMP options.location, for compatibility with serverless-webpack:
68
+ // https://github.com/dherault/serverless-offline/issues/787
69
+ // TODO FIXME look into better way to work with serverless-webpack
70
+ const servicepath = resolve(servicePath, options.location || '')
71
+
72
+ const { handler, name, package: functionPackage = {} } = functionDefinition
73
+ const [handlerPath, handlerName] = splitHandlerPathAndName(handler)
74
+
75
+ const memorySize =
76
+ functionDefinition.memorySize ||
77
+ provider.memorySize ||
78
+ DEFAULT_LAMBDA_MEMORY_SIZE
79
+
80
+ const runtime =
81
+ functionDefinition.runtime || provider.runtime || DEFAULT_LAMBDA_RUNTIME
82
+
83
+ const timeout =
84
+ (functionDefinition.timeout ||
85
+ provider.timeout ||
86
+ DEFAULT_LAMBDA_TIMEOUT) * 1000
87
+
88
+ // this._executionTimeout = null
89
+ this.#functionKey = functionKey
90
+ this.#functionName = name
91
+ this.#handler = handler
92
+ this.#memorySize = memorySize
93
+ this.#region = provider.region
94
+ this.#runtime = runtime
95
+ this.#timeout = timeout
96
+
97
+ this.#verifySupportedRuntime()
98
+
99
+ const env = {
100
+ ...(options.localEnvironment
101
+ ? process.env
102
+ : // we always copy all AWS_xxxx environment variables over from local env
103
+ fromEntries(
104
+ entries(process.env).filter(([key]) => key.startsWith('AWS_')),
105
+ )),
106
+ ...this.#getAwsEnvVars(),
107
+ ...provider.environment,
108
+ ...functionDefinition.environment,
109
+ IS_OFFLINE: 'true',
110
+ }
111
+
112
+ this.#artifact = functionDefinition.package?.artifact
113
+
114
+ if (!this.#artifact) {
115
+ this.#artifact = service.package?.artifact
116
+ }
117
+
118
+ if (this.#artifact) {
119
+ // lambda directory contains code and layers
120
+ this.#lambdaDir = join(
121
+ servicepath,
122
+ '.serverless-offline',
123
+ 'services',
124
+ service.service,
125
+ functionKey,
126
+ createUniqueId(),
127
+ )
128
+ }
129
+
130
+ this.#codeDir = this.#lambdaDir
131
+ ? resolve(this.#lambdaDir, 'code')
132
+ : servicepath
133
+
134
+ // TEMP
135
+ const funOptions = {
136
+ codeDir: this.#codeDir,
137
+ functionKey,
138
+ functionName: name,
139
+ functionPackage: functionPackage.artifact
140
+ ? resolve(servicepath, functionPackage.artifact)
141
+ : undefined,
142
+ handler,
143
+ handlerName,
144
+ handlerPath: resolve(this.#codeDir, handlerPath),
145
+ layers: functionDefinition.layers || [],
146
+ provider,
147
+ runtime,
148
+ serverlessPath,
149
+ servicePackage: servicePackage.artifact
150
+ ? resolve(servicepath, servicePackage.artifact)
151
+ : undefined,
152
+ servicePath: servicepath,
153
+ timeout,
154
+ }
155
+
156
+ this.#handlerRunner = new HandlerRunner(funOptions, options, env)
157
+ this.#lambdaContext = new LambdaContext(name, memorySize)
158
+ }
159
+
160
+ #startExecutionTimer() {
161
+ this.#executionTimeStarted = performance.now()
162
+ // this._executionTimeout = this.#executionTimeStarted + this.#timeout * 1000
163
+ }
164
+
165
+ #stopExecutionTimer() {
166
+ this.#executionTimeEnded = performance.now()
167
+ }
168
+
169
+ #startIdleTimer() {
170
+ this.#idleTimeStarted = performance.now()
171
+ }
172
+
173
+ #verifySupportedRuntime() {
174
+ // print message but keep working (don't error out or exit process)
175
+ if (!supportedRuntimes.has(this.#runtime)) {
176
+ log.warning()
177
+ log.warning(
178
+ `Warning: found unsupported runtime '${this.#runtime}' for function '${
179
+ this.#functionKey
180
+ }'`,
181
+ )
182
+ }
183
+ }
184
+
185
+ // based on:
186
+ // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/index.js#L108
187
+ #getAwsEnvVars() {
188
+ return {
189
+ _HANDLER: this.#handler,
190
+ AWS_DEFAULT_REGION: this.#region,
191
+ AWS_LAMBDA_FUNCTION_MEMORY_SIZE: this.#memorySize,
192
+ AWS_LAMBDA_FUNCTION_NAME: this.#functionName,
193
+ AWS_LAMBDA_FUNCTION_VERSION: '$LATEST',
194
+ // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/lib/naming.js#L123
195
+ AWS_LAMBDA_LOG_GROUP_NAME: `/aws/lambda/${this.#functionName}`,
196
+ AWS_LAMBDA_LOG_STREAM_NAME:
197
+ '2016/12/02/[$LATEST]f77ff5e4026c45bda9a9ebcec6bc9cad',
198
+ AWS_REGION: this.#region,
199
+ LAMBDA_RUNTIME_DIR: '/var/runtime',
200
+ LAMBDA_TASK_ROOT: '/var/task',
201
+ LANG: 'en_US.UTF-8',
202
+ LD_LIBRARY_PATH:
203
+ '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib',
204
+ NODE_PATH: '/var/runtime:/var/task:/var/runtime/node_modules',
205
+ }
206
+ }
207
+
208
+ setClientContext(clientContext) {
209
+ this.#clientContext = clientContext
210
+ }
211
+
212
+ setEvent(event) {
213
+ this.#event = event
214
+ }
215
+
216
+ // () => Promise<void>
217
+ async cleanup() {
218
+ // TODO console.log('lambda cleanup')
219
+ await this.#handlerRunner.cleanup()
220
+ if (this.#lambdaDir) {
221
+ await remove(this.#lambdaDir)
222
+ }
223
+ }
224
+
225
+ #executionTimeInMillis() {
226
+ return this.#executionTimeEnded - this.#executionTimeStarted
227
+ }
228
+
229
+ // round up to the nearest ms
230
+ #billedExecutionTimeInMillis() {
231
+ return ceil(this.#executionTimeEnded - this.#executionTimeStarted)
232
+ }
233
+
234
+ // extractArtifact, loosely based on:
235
+ // https://github.com/serverless/serverless/blob/v1.57.0/lib/plugins/aws/invokeLocal/index.js#L312
236
+ async #extractArtifact() {
237
+ if (!this.#artifact) {
238
+ return
239
+ }
240
+
241
+ await emptyDir(this.#codeDir)
242
+
243
+ const data = await readFile(this.#artifact)
244
+ const zip = await jszip.loadAsync(data)
245
+
246
+ await Promise.all(
247
+ entries(zip.files).map(async ([filename, jsZipObj]) => {
248
+ const fileData = await jsZipObj.async('nodebuffer')
249
+ if (filename.endsWith('/')) {
250
+ return Promise.resolve()
251
+ }
252
+ await ensureDir(join(this.#codeDir, dirname(filename)))
253
+ return writeFile(join(this.#codeDir, filename), fileData, {
254
+ mode: jsZipObj.unixPermissions,
255
+ })
256
+ }),
257
+ )
258
+ }
259
+
260
+ async #initialize() {
261
+ await this.#extractArtifact()
262
+ this.#initialized = true
263
+ }
264
+
265
+ get idleTimeInMinutes() {
266
+ return (performance.now() - this.#idleTimeStarted) / 1000 / 60
267
+ }
268
+
269
+ get functionName() {
270
+ return this.#functionName
271
+ }
272
+
273
+ async runHandler() {
274
+ this.status = 'BUSY'
275
+
276
+ if (!this.#initialized) {
277
+ await this.#initialize()
278
+ }
279
+
280
+ const requestId = createUniqueId()
281
+
282
+ this.#lambdaContext.setRequestId(requestId)
283
+ this.#lambdaContext.setClientContext(this.#clientContext)
284
+
285
+ const context = this.#lambdaContext.create()
286
+
287
+ this.#startExecutionTimer()
288
+
289
+ const result = await this.#handlerRunner.run(this.#event, context)
290
+
291
+ this.#stopExecutionTimer()
292
+
293
+ // TEMP TODO FIXME find better solution
294
+ if (!this.#handlerRunner.isDockerRunner()) {
295
+ log.notice(
296
+ `(λ: ${
297
+ this.#functionKey
298
+ }) RequestId: ${requestId} Duration: ${this.#executionTimeInMillis().toFixed(
299
+ 2,
300
+ )} ms Billed Duration: ${this.#billedExecutionTimeInMillis()} ms`,
301
+ )
302
+ }
303
+
304
+ this.status = 'IDLE'
305
+ this.#startIdleTimer()
306
+
307
+ return result
308
+ }
309
+ }
@@ -0,0 +1,109 @@
1
+ import LambdaFunction from './LambdaFunction.js'
2
+
3
+ export default class LambdaFunctionPool {
4
+ #options = null
5
+
6
+ #pool = new Map()
7
+
8
+ #serverless = null
9
+
10
+ #timerRef = null
11
+
12
+ constructor(serverless, options) {
13
+ this.#options = options
14
+ this.#serverless = serverless
15
+ }
16
+
17
+ start() {
18
+ // start cleaner
19
+ this.#startCleanTimer()
20
+ }
21
+
22
+ #startCleanTimer() {
23
+ // NOTE: don't use setInterval, as it would schedule always a new run,
24
+ // regardless of function processing time and e.g. user action (debugging)
25
+ this.#timerRef = setTimeout(() => {
26
+ // console.log('run cleanup')
27
+ this.#pool.forEach((lambdaFunctions) => {
28
+ lambdaFunctions.forEach((lambdaFunction) => {
29
+ const { idleTimeInMinutes, status } = lambdaFunction
30
+ // console.log(idleTimeInMinutes, status)
31
+
32
+ if (
33
+ status === 'IDLE' &&
34
+ idleTimeInMinutes >=
35
+ this.#options.functionCleanupIdleTimeSeconds / 60
36
+ ) {
37
+ // console.log(`removed Lambda Function ${lambdaFunction.functionName}`)
38
+ lambdaFunction.cleanup()
39
+ lambdaFunctions.delete(lambdaFunction)
40
+ }
41
+ })
42
+ })
43
+
44
+ // schedule new timer
45
+ this.#startCleanTimer()
46
+ }, (this.#options.functionCleanupIdleTimeSeconds * 1000) / 2)
47
+ }
48
+
49
+ #cleanupPool() {
50
+ const wait = []
51
+
52
+ this.#pool.forEach((lambdaFunctions) => {
53
+ lambdaFunctions.forEach((lambdaFunction) => {
54
+ // collect promises
55
+ wait.push(lambdaFunction.cleanup())
56
+ lambdaFunctions.delete(lambdaFunction)
57
+ })
58
+ })
59
+
60
+ return Promise.all(wait)
61
+ }
62
+
63
+ // TODO make sure to call this
64
+ async cleanup() {
65
+ clearTimeout(this.#timerRef)
66
+
67
+ return this.#cleanupPool()
68
+ }
69
+
70
+ get(functionKey, functionDefinition) {
71
+ const lambdaFunctions = this.#pool.get(functionKey)
72
+ let lambdaFunction
73
+
74
+ // we don't have any instances
75
+ if (lambdaFunctions == null) {
76
+ lambdaFunction = new LambdaFunction(
77
+ functionKey,
78
+ functionDefinition,
79
+ this.#serverless,
80
+ this.#options,
81
+ )
82
+ this.#pool.set(functionKey, new Set([lambdaFunction]))
83
+
84
+ return lambdaFunction
85
+ }
86
+
87
+ if (!this.#options.reloadHandler) {
88
+ // find any IDLE
89
+ lambdaFunction = Array.from(lambdaFunctions).find(
90
+ ({ status }) => status === 'IDLE',
91
+ )
92
+
93
+ if (lambdaFunction != null) {
94
+ return lambdaFunction
95
+ }
96
+ }
97
+
98
+ // we don't have any IDLE instances
99
+ lambdaFunction = new LambdaFunction(
100
+ functionKey,
101
+ functionDefinition,
102
+ this.#serverless,
103
+ this.#options,
104
+ )
105
+ lambdaFunctions.add(lambdaFunction)
106
+
107
+ return lambdaFunction
108
+ }
109
+ }
@@ -0,0 +1,30 @@
1
+ import assert from 'node:assert'
2
+ import LambdaContext from '../LambdaContext.js'
3
+
4
+ describe('LambdaContext', () => {
5
+ it('should create LambdaContext with correct values', () => {
6
+ const functionName = 'foo'
7
+ const memorySize = 512
8
+ const requestId = 'abc123'
9
+
10
+ const lambdaContext = new LambdaContext(functionName, memorySize)
11
+ lambdaContext.setRequestId(requestId)
12
+ const context = lambdaContext.create()
13
+
14
+ const expected = {
15
+ awsRequestId: 'abc123',
16
+ callbackWaitsForEmptyEventLoop: true,
17
+ clientContext: undefined,
18
+ functionName: 'foo',
19
+ functionVersion: '$LATEST',
20
+ identity: undefined,
21
+ invokedFunctionArn: 'offline_invokedFunctionArn_for_foo',
22
+ logGroupName: 'offline_logGroupName_for_foo',
23
+ logStreamName: 'offline_logStreamName_for_foo',
24
+ memoryLimitInMB: '512',
25
+ }
26
+
27
+ // expect(context).toEqual(expected)
28
+ assert.deepEqual(context, expected)
29
+ })
30
+ })
@@ -0,0 +1,196 @@
1
+ import assert from 'node:assert'
2
+ import { dirname, resolve } from 'node:path'
3
+ // import { performance } from 'node:perf_hooks'
4
+ import { fileURLToPath } from 'node:url'
5
+ import LambdaFunction from '../LambdaFunction.js'
6
+ import { DEFAULT_LAMBDA_TIMEOUT } from '../../config/index.js'
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url))
9
+
10
+ describe('LambdaFunction', () => {
11
+ const functionName = 'foo'
12
+
13
+ const serverless = {
14
+ config: {
15
+ serverlessPath: '',
16
+ servicePath: resolve(__dirname),
17
+ },
18
+ service: {
19
+ provider: {
20
+ runtime: 'nodejs12.x',
21
+ },
22
+ },
23
+ }
24
+
25
+ describe('Handler tests', () => {
26
+ ;[
27
+ {
28
+ description: 'should return result when handler is context.done',
29
+ expected: 'foo',
30
+ handler: 'fixtures/lambdaFunction.fixture.contextDoneHandler',
31
+ },
32
+ {
33
+ description:
34
+ 'should return result when handler is context.done which is deferred',
35
+ expected: 'foo',
36
+ handler: 'fixtures/lambdaFunction.fixture.contextDoneHandlerDeferred',
37
+ },
38
+ {
39
+ description: 'should return result when handler is context.succeed',
40
+ expected: 'foo',
41
+ handler: 'fixtures/lambdaFunction.fixture.contextSucceedHandler',
42
+ },
43
+ {
44
+ description:
45
+ 'should return result when handler is context.succeed which is deferred',
46
+ expected: 'foo',
47
+ handler:
48
+ 'fixtures/lambdaFunction.fixture.contextSucceedHandlerDeferred',
49
+ },
50
+ {
51
+ description: 'should return result when handler is a callback',
52
+ expected: 'foo',
53
+ handler: 'fixtures/lambdaFunction.fixture.callbackHandler',
54
+ },
55
+ {
56
+ description:
57
+ 'should return result when handler is a callback which is deferred',
58
+ expected: 'foo',
59
+ handler: 'fixtures/lambdaFunction.fixture.callbackHandlerDeferred',
60
+ },
61
+ {
62
+ description: 'should return result when handler returns a promise',
63
+ expected: 'foo',
64
+ handler: 'fixtures/lambdaFunction.fixture.promiseHandler',
65
+ },
66
+ {
67
+ description:
68
+ 'should return result when handler returns a promise which is deferred',
69
+ expected: 'foo',
70
+ handler: 'fixtures/lambdaFunction.fixture.promiseHandlerDeferred',
71
+ },
72
+ {
73
+ description: 'should return result when handler is an async function',
74
+ expected: 'foo',
75
+ handler: 'fixtures/lambdaFunction.fixture.asyncFunctionHandler',
76
+ },
77
+ // NOTE: mix and matching of callbacks and promises is not recommended,
78
+ // nonetheless, we test some of the behaviour to match AWS execution precedence
79
+ {
80
+ description:
81
+ 'should return result when handler returns a callback but defines a callback parameter',
82
+ expected: 'Hello Promise!',
83
+ handler:
84
+ 'fixtures/lambdaFunction.fixture.promiseWithDefinedCallbackHandler',
85
+ },
86
+ {
87
+ description:
88
+ 'should return result when handler calls context.succeed and context.done',
89
+ expected: 'Hello Context.succeed!',
90
+ handler:
91
+ 'fixtures/lambdaFunction.fixture.contextSucceedWithContextDoneHandler',
92
+ },
93
+ {
94
+ description:
95
+ 'should return result when handler calls callback and context.done',
96
+ expected: 'Hello Callback!',
97
+ handler:
98
+ 'fixtures/lambdaFunction.fixture.callbackWithContextDoneHandler',
99
+ },
100
+ {
101
+ description:
102
+ 'should return result when handler calls callback and returns Promise',
103
+ expected: 'Hello Callback!',
104
+ handler: 'fixtures/lambdaFunction.fixture.callbackWithPromiseHandler',
105
+ },
106
+ {
107
+ description:
108
+ 'should return result when handler calls callback inside returned Promise',
109
+ expected: 'Hello Callback!',
110
+ handler: 'fixtures/lambdaFunction.fixture.callbackInsidePromiseHandler',
111
+ },
112
+ ].forEach(({ description, expected, handler }) => {
113
+ it(description, async () => {
114
+ const functionDefinition = {
115
+ handler,
116
+ }
117
+ const options = {}
118
+ const lambdaFunction = new LambdaFunction(
119
+ functionName,
120
+ functionDefinition,
121
+ serverless,
122
+ options,
123
+ )
124
+ const result = await lambdaFunction.runHandler()
125
+
126
+ await lambdaFunction.cleanup()
127
+
128
+ assert.equal(result, expected)
129
+ })
130
+ })
131
+ })
132
+
133
+ it('should pass remaining time to LambdaContext', async () => {
134
+ const functionDefinition = {
135
+ handler: 'fixtures/lambdaFunction.fixture.remainingExecutionTimeHandler',
136
+ }
137
+ const options = {}
138
+ const lambdaFunction = new LambdaFunction(
139
+ functionName,
140
+ functionDefinition,
141
+ serverless,
142
+ options,
143
+ )
144
+ const [first, second, third] = await lambdaFunction.runHandler()
145
+
146
+ await lambdaFunction.cleanup()
147
+
148
+ // handler "pauses" for 100 ms
149
+ assert.ok(first > second - 100)
150
+ assert.ok(second > third - 200)
151
+ })
152
+
153
+ it.skip('should use default lambda timeout when timeout is not provided', async () => {
154
+ const functionDefinition = {
155
+ handler: 'fixtures/lambdaFunction.fixture.defaultTimeoutHandler',
156
+ }
157
+ const options = {}
158
+ const lambdaFunction = new LambdaFunction(
159
+ functionName,
160
+ functionDefinition,
161
+ serverless,
162
+ options,
163
+ )
164
+ const remainingTime = await lambdaFunction.runHandler()
165
+
166
+ await lambdaFunction.cleanup()
167
+
168
+ assert.ok(remainingTime < DEFAULT_LAMBDA_TIMEOUT * 1000)
169
+
170
+ // result might be flaky/unreliable:
171
+ // (assmuning handler runs no longer than 1 s)
172
+ assert.ok(remainingTime + 1000 > DEFAULT_LAMBDA_TIMEOUT * 1000)
173
+ })
174
+
175
+ // // might run flaky (unreliable)
176
+ // test('executionTimeInMillis should return execution time', async () => {
177
+ // const functionDefinition = {
178
+ // handler: 'fixtures/lambdaFunction.fixture.executionTimeInMillisHandler',
179
+ // }
180
+ // const options = {}
181
+ // const lambdaFunction = new LambdaFunction(
182
+ // functionName,
183
+ // functionDefinition,
184
+ // provider,
185
+ // config,
186
+ // options,
187
+ // )
188
+ // const timerStart = performance.now()
189
+ // await lambdaFunction.runHandler()
190
+ // const timerEnd = performance.now()
191
+ //
192
+ // expect(lambdaFunction.executionTimeInMillis).toBeLessThanOrEqual(
193
+ // timerEnd - timerStart + 10,
194
+ // )
195
+ // })
196
+ })
@@ -0,0 +1,47 @@
1
+ import { dirname, resolve } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import LambdaFunction from '../../../LambdaFunction.js'
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url))
6
+
7
+ export default class LambdaFunctionThatReturnsJSONObject {
8
+ #lambdaFunction
9
+
10
+ options = {}
11
+
12
+ serverless = {
13
+ config: {
14
+ serverlessPath: '',
15
+ servicePath: resolve(__dirname),
16
+ },
17
+ service: {
18
+ provider: {
19
+ runtime: 'nodejs12.x',
20
+ },
21
+ },
22
+ }
23
+
24
+ listFunctionNames() {
25
+ return ['foo']
26
+ }
27
+
28
+ getByFunctionName(functionName) {
29
+ const functionDefinition = {
30
+ handler:
31
+ '../../fixtures/lambdaFunction.fixture.asyncFunctionHandlerObject',
32
+ }
33
+
34
+ this.#lambdaFunction = new LambdaFunction(
35
+ functionName,
36
+ functionDefinition,
37
+ this.serverless,
38
+ this.options,
39
+ )
40
+
41
+ return this.#lambdaFunction
42
+ }
43
+
44
+ async cleanup() {
45
+ await this.#lambdaFunction.cleanup()
46
+ }
47
+ }