serverless-offline 8.7.0 → 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 (234) hide show
  1. package/README.md +91 -95
  2. package/package.json +41 -69
  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/dist/ServerlessOffline.js +0 -507
  131. package/dist/checkEngine.js +0 -21
  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 -10
  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/http/Endpoint.js +0 -171
  141. package/dist/events/http/Http.js +0 -77
  142. package/dist/events/http/HttpEventDefinition.js +0 -36
  143. package/dist/events/http/HttpServer.js +0 -1363
  144. package/dist/events/http/OfflineEndpoint.js +0 -40
  145. package/dist/events/http/authJWTSettingsExtractor.js +0 -76
  146. package/dist/events/http/authValidateContext.js +0 -48
  147. package/dist/events/http/createAuthScheme.js +0 -184
  148. package/dist/events/http/createJWTAuthScheme.js +0 -155
  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 -85
  152. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -244
  153. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -221
  154. package/dist/events/http/lambda-events/VelocityContext.js +0 -168
  155. package/dist/events/http/lambda-events/index.js +0 -39
  156. package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -108
  157. package/dist/events/http/payloadSchemaValidator.js +0 -13
  158. package/dist/events/schedule/Schedule.js +0 -182
  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 -112
  163. package/dist/events/websocket/WebSocket.js +0 -78
  164. package/dist/events/websocket/WebSocketClients.js +0 -550
  165. package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
  166. package/dist/events/websocket/WebSocketServer.js +0 -140
  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 -19
  181. package/dist/lambda/HttpServer.js +0 -122
  182. package/dist/lambda/Lambda.js +0 -113
  183. package/dist/lambda/LambdaContext.js +0 -53
  184. package/dist/lambda/LambdaFunction.js +0 -391
  185. package/dist/lambda/LambdaFunctionPool.js +0 -127
  186. package/dist/lambda/handler-runner/HandlerRunner.js +0 -223
  187. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -132
  188. package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -40
  189. package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
  190. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -517
  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 -211
  195. package/dist/lambda/handler-runner/go-runner/index.js +0 -15
  196. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -234
  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 -151
  200. package/dist/lambda/handler-runner/java-runner/index.js +0 -15
  201. package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -180
  202. package/dist/lambda/handler-runner/python-runner/index.js +0 -15
  203. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -148
  204. package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
  205. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -94
  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 -30
  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 -44
  225. package/dist/utils/index.js +0 -158
  226. package/dist/utils/jsonPath.js +0 -21
  227. package/dist/utils/parseHeaders.js +0 -23
  228. package/dist/utils/parseMultiValueHeaders.js +0 -36
  229. package/dist/utils/parseMultiValueQueryStringParameters.js +0 -40
  230. package/dist/utils/parseQueryStringParameters.js +0 -26
  231. package/dist/utils/resolveJoins.js +0 -34
  232. package/dist/utils/satisfiesVersionRange.js +0 -20
  233. package/dist/utils/splitHandlerPathAndName.js +0 -41
  234. package/dist/utils/unflatten.js +0 -18
@@ -0,0 +1,308 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import { performance } from 'node:perf_hooks'
4
+ import { log } from '@serverless/utils/log.js'
5
+ import { emptyDir, ensureDir, remove } from 'fs-extra'
6
+ import jszip from 'jszip'
7
+ import HandlerRunner from './handler-runner/index.js'
8
+ import LambdaContext from './LambdaContext.js'
9
+ import resolveJoins from '../utils/resolveJoins.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 } = 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
+ #handlerRunner = null
39
+
40
+ #idleTimeStarted = null
41
+
42
+ #initialized = false
43
+
44
+ #lambdaContext = null
45
+
46
+ #lambdaDir = null
47
+
48
+ #memorySize = null
49
+
50
+ #region = null
51
+
52
+ #runtime = null
53
+
54
+ #timeout = null
55
+
56
+ status = 'IDLE' // can be 'BUSY' or 'IDLE'
57
+
58
+ constructor(functionKey, functionDefinition, serverless, options) {
59
+ const {
60
+ service,
61
+ config: { serverlessPath, servicePath },
62
+ service: { provider, package: servicePackage = {} },
63
+ } = serverless
64
+
65
+ // TEMP options.location, for compatibility with serverless-webpack:
66
+ // https://github.com/dherault/serverless-offline/issues/787
67
+ // TODO FIXME look into better way to work with serverless-webpack
68
+ const servicepath = resolve(servicePath, options.location || '')
69
+
70
+ const { handler, name, package: functionPackage = {} } = functionDefinition
71
+ const [handlerPath, handlerName] = splitHandlerPathAndName(handler)
72
+
73
+ const memorySize =
74
+ functionDefinition.memorySize ||
75
+ provider.memorySize ||
76
+ DEFAULT_LAMBDA_MEMORY_SIZE
77
+
78
+ const runtime =
79
+ functionDefinition.runtime || provider.runtime || DEFAULT_LAMBDA_RUNTIME
80
+
81
+ const timeout =
82
+ (functionDefinition.timeout ||
83
+ provider.timeout ||
84
+ DEFAULT_LAMBDA_TIMEOUT) * 1000
85
+
86
+ // this._executionTimeout = null
87
+ this.#functionKey = functionKey
88
+ this.#functionName = name
89
+ this.#memorySize = memorySize
90
+ this.#region = provider.region
91
+ this.#runtime = runtime
92
+ this.#timeout = timeout
93
+
94
+ this.#verifySupportedRuntime()
95
+
96
+ const env = this.#getEnv(
97
+ resolveJoins(provider.environment),
98
+ functionDefinition.environment,
99
+ handler,
100
+ )
101
+
102
+ this.#artifact = functionDefinition.package?.artifact
103
+
104
+ if (!this.#artifact) {
105
+ this.#artifact = service.package?.artifact
106
+ }
107
+
108
+ if (this.#artifact) {
109
+ // lambda directory contains code and layers
110
+ this.#lambdaDir = join(
111
+ servicepath,
112
+ '.serverless-offline',
113
+ 'services',
114
+ service.service,
115
+ functionKey,
116
+ createUniqueId(),
117
+ )
118
+ }
119
+
120
+ this.#codeDir = this.#lambdaDir
121
+ ? resolve(this.#lambdaDir, 'code')
122
+ : servicepath
123
+
124
+ // TEMP
125
+ const funOptions = {
126
+ codeDir: this.#codeDir,
127
+ functionKey,
128
+ functionName: name,
129
+ functionPackage: functionPackage.artifact
130
+ ? resolve(servicepath, functionPackage.artifact)
131
+ : undefined,
132
+ handler,
133
+ handlerName,
134
+ handlerPath: resolve(this.#codeDir, handlerPath),
135
+ layers: functionDefinition.layers || [],
136
+ provider,
137
+ runtime,
138
+ serverlessPath,
139
+ servicePackage: servicePackage.artifact
140
+ ? resolve(servicepath, servicePackage.artifact)
141
+ : undefined,
142
+ servicePath: servicepath,
143
+ timeout,
144
+ }
145
+
146
+ this.#handlerRunner = new HandlerRunner(funOptions, options, env)
147
+ this.#lambdaContext = new LambdaContext(name, memorySize)
148
+ }
149
+
150
+ #startExecutionTimer() {
151
+ this.#executionTimeStarted = performance.now()
152
+ // this._executionTimeout = this.#executionTimeStarted + this.#timeout * 1000
153
+ }
154
+
155
+ #stopExecutionTimer() {
156
+ this.#executionTimeEnded = performance.now()
157
+ }
158
+
159
+ #startIdleTimer() {
160
+ this.#idleTimeStarted = performance.now()
161
+ }
162
+
163
+ #verifySupportedRuntime() {
164
+ // print message but keep working (don't error out or exit process)
165
+ if (!supportedRuntimes.has(this.#runtime)) {
166
+ log.warning()
167
+ log.warning(
168
+ `Warning: found unsupported runtime '${this.#runtime}' for function '${
169
+ this.#functionKey
170
+ }'`,
171
+ )
172
+ }
173
+ }
174
+
175
+ // based on:
176
+ // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/index.js#L108
177
+ #getAwsEnvVars() {
178
+ return {
179
+ AWS_DEFAULT_REGION: this.#region,
180
+ AWS_LAMBDA_FUNCTION_MEMORY_SIZE: this.#memorySize,
181
+ AWS_LAMBDA_FUNCTION_NAME: this.#functionName,
182
+ AWS_LAMBDA_FUNCTION_VERSION: '$LATEST',
183
+ // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/lib/naming.js#L123
184
+ AWS_LAMBDA_LOG_GROUP_NAME: `/aws/lambda/${this.#functionName}`,
185
+ AWS_LAMBDA_LOG_STREAM_NAME:
186
+ '2016/12/02/[$LATEST]f77ff5e4026c45bda9a9ebcec6bc9cad',
187
+ AWS_REGION: this.#region,
188
+ LAMBDA_RUNTIME_DIR: '/var/runtime',
189
+ LAMBDA_TASK_ROOT: '/var/task',
190
+ LANG: 'en_US.UTF-8',
191
+ LD_LIBRARY_PATH:
192
+ '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib',
193
+ NODE_PATH: '/var/runtime:/var/task:/var/runtime/node_modules',
194
+ }
195
+ }
196
+
197
+ #getEnv(providerEnv, functionDefinitionEnv, handler) {
198
+ return {
199
+ ...this.#getAwsEnvVars(),
200
+ ...providerEnv,
201
+ ...functionDefinitionEnv,
202
+ _HANDLER: handler, // TODO is this available in AWS?
203
+ IS_OFFLINE: true,
204
+ }
205
+ }
206
+
207
+ setClientContext(clientContext) {
208
+ this.#clientContext = clientContext
209
+ }
210
+
211
+ setEvent(event) {
212
+ this.#event = event
213
+ }
214
+
215
+ // () => Promise<void>
216
+ async cleanup() {
217
+ // TODO console.log('lambda cleanup')
218
+ await this.#handlerRunner.cleanup()
219
+ if (this.#lambdaDir) {
220
+ await remove(this.#lambdaDir)
221
+ }
222
+ }
223
+
224
+ #executionTimeInMillis() {
225
+ return this.#executionTimeEnded - this.#executionTimeStarted
226
+ }
227
+
228
+ // round up to the nearest ms
229
+ #billedExecutionTimeInMillis() {
230
+ return ceil(this.#executionTimeEnded - this.#executionTimeStarted)
231
+ }
232
+
233
+ // extractArtifact, loosely based on:
234
+ // https://github.com/serverless/serverless/blob/v1.57.0/lib/plugins/aws/invokeLocal/index.js#L312
235
+ async #extractArtifact() {
236
+ if (!this.#artifact) {
237
+ return
238
+ }
239
+
240
+ await emptyDir(this.#codeDir)
241
+
242
+ const data = await readFile(this.#artifact)
243
+ const zip = await jszip.loadAsync(data)
244
+
245
+ await Promise.all(
246
+ entries(zip.files).map(async ([filename, jsZipObj]) => {
247
+ const fileData = await jsZipObj.async('nodebuffer')
248
+ if (filename.endsWith('/')) {
249
+ return Promise.resolve()
250
+ }
251
+ await ensureDir(join(this.#codeDir, dirname(filename)))
252
+ return writeFile(join(this.#codeDir, filename), fileData, {
253
+ mode: jsZipObj.unixPermissions,
254
+ })
255
+ }),
256
+ )
257
+ }
258
+
259
+ async #initialize() {
260
+ await this.#extractArtifact()
261
+ this.#initialized = true
262
+ }
263
+
264
+ get idleTimeInMinutes() {
265
+ return (performance.now() - this.#idleTimeStarted) / 1000 / 60
266
+ }
267
+
268
+ get functionName() {
269
+ return this.#functionName
270
+ }
271
+
272
+ async runHandler() {
273
+ this.status = 'BUSY'
274
+
275
+ if (!this.#initialized) {
276
+ await this.#initialize()
277
+ }
278
+
279
+ const requestId = createUniqueId()
280
+
281
+ this.#lambdaContext.setRequestId(requestId)
282
+ this.#lambdaContext.setClientContext(this.#clientContext)
283
+
284
+ const context = this.#lambdaContext.create()
285
+
286
+ this.#startExecutionTimer()
287
+
288
+ const result = await this.#handlerRunner.run(this.#event, context)
289
+
290
+ this.#stopExecutionTimer()
291
+
292
+ // TEMP TODO FIXME find better solution
293
+ if (!this.#handlerRunner.isDockerRunner()) {
294
+ log.notice(
295
+ `(λ: ${
296
+ this.#functionKey
297
+ }) RequestId: ${requestId} Duration: ${this.#executionTimeInMillis().toFixed(
298
+ 2,
299
+ )} ms Billed Duration: ${this.#billedExecutionTimeInMillis()} ms`,
300
+ )
301
+ }
302
+
303
+ this.status = 'IDLE'
304
+ this.#startIdleTimer()
305
+
306
+ return result
307
+ }
308
+ }
@@ -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
+ }