serverless-offline 8.8.1 → 9.1.1

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 +112 -44
  2. package/package.json +33 -57
  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 +76 -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 +419 -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 +172 -0
  78. package/src/lambda/handler-runner/go-runner/index.js +1 -0
  79. package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +126 -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/CHANGELOG.md +0 -78
  130. package/dist/ServerlessOffline.js +0 -508
  131. package/dist/config/commandOptions.js +0 -149
  132. package/dist/config/constants.js +0 -30
  133. package/dist/config/index.js +0 -55
  134. package/dist/config/supportedRuntimes.js +0 -40
  135. package/dist/debugLog.js +0 -12
  136. package/dist/events/authCanExecuteResource.js +0 -35
  137. package/dist/events/authFunctionNameExtractor.js +0 -87
  138. package/dist/events/authMatchPolicyResource.js +0 -62
  139. package/dist/events/authValidateContext.js +0 -53
  140. package/dist/events/http/Endpoint.js +0 -173
  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 -1370
  144. package/dist/events/http/OfflineEndpoint.js +0 -38
  145. package/dist/events/http/authJWTSettingsExtractor.js +0 -76
  146. package/dist/events/http/createAuthScheme.js +0 -184
  147. package/dist/events/http/createJWTAuthScheme.js +0 -159
  148. package/dist/events/http/index.js +0 -15
  149. package/dist/events/http/javaHelpers.js +0 -99
  150. package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +0 -87
  151. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -246
  152. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -225
  153. package/dist/events/http/lambda-events/VelocityContext.js +0 -170
  154. package/dist/events/http/lambda-events/index.js +0 -39
  155. package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -111
  156. package/dist/events/http/payloadSchemaValidator.js +0 -13
  157. package/dist/events/schedule/Schedule.js +0 -183
  158. package/dist/events/schedule/ScheduleEvent.js +0 -27
  159. package/dist/events/schedule/ScheduleEventDefinition.js +0 -36
  160. package/dist/events/schedule/index.js +0 -15
  161. package/dist/events/websocket/HttpServer.js +0 -114
  162. package/dist/events/websocket/WebSocket.js +0 -78
  163. package/dist/events/websocket/WebSocketClients.js +0 -577
  164. package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
  165. package/dist/events/websocket/WebSocketServer.js +0 -139
  166. package/dist/events/websocket/http-routes/_catchAll/catchAllRoute.js +0 -33
  167. package/dist/events/websocket/http-routes/_catchAll/index.js +0 -15
  168. package/dist/events/websocket/http-routes/connections/ConnectionsController.js +0 -45
  169. package/dist/events/websocket/http-routes/connections/connectionsRoutes.js +0 -95
  170. package/dist/events/websocket/http-routes/connections/index.js +0 -15
  171. package/dist/events/websocket/http-routes/index.js +0 -23
  172. package/dist/events/websocket/index.js +0 -15
  173. package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +0 -99
  174. package/dist/events/websocket/lambda-events/WebSocketConnectEvent.js +0 -101
  175. package/dist/events/websocket/lambda-events/WebSocketDisconnectEvent.js +0 -47
  176. package/dist/events/websocket/lambda-events/WebSocketEvent.js +0 -54
  177. package/dist/events/websocket/lambda-events/WebSocketRequestContext.js +0 -98
  178. package/dist/events/websocket/lambda-events/index.js +0 -39
  179. package/dist/index.js +0 -15
  180. package/dist/lambda/HttpServer.js +0 -124
  181. package/dist/lambda/Lambda.js +0 -117
  182. package/dist/lambda/LambdaContext.js +0 -53
  183. package/dist/lambda/LambdaFunction.js +0 -390
  184. package/dist/lambda/LambdaFunctionPool.js +0 -127
  185. package/dist/lambda/handler-runner/HandlerRunner.js +0 -195
  186. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -124
  187. package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -49
  188. package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
  189. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -515
  190. package/dist/lambda/handler-runner/docker-runner/DockerImage.js +0 -67
  191. package/dist/lambda/handler-runner/docker-runner/DockerRunner.js +0 -74
  192. package/dist/lambda/handler-runner/docker-runner/index.js +0 -15
  193. package/dist/lambda/handler-runner/go-runner/GoRunner.js +0 -230
  194. package/dist/lambda/handler-runner/go-runner/index.js +0 -15
  195. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -228
  196. package/dist/lambda/handler-runner/in-process-runner/index.js +0 -15
  197. package/dist/lambda/handler-runner/index.js +0 -15
  198. package/dist/lambda/handler-runner/java-runner/JavaRunner.js +0 -153
  199. package/dist/lambda/handler-runner/java-runner/index.js +0 -15
  200. package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -185
  201. package/dist/lambda/handler-runner/python-runner/index.js +0 -15
  202. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -147
  203. package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
  204. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -92
  205. package/dist/lambda/handler-runner/worker-thread-runner/index.js +0 -15
  206. package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +0 -31
  207. package/dist/lambda/index.js +0 -15
  208. package/dist/lambda/routes/index.js +0 -23
  209. package/dist/lambda/routes/invocations/InvocationsController.js +0 -142
  210. package/dist/lambda/routes/invocations/index.js +0 -15
  211. package/dist/lambda/routes/invocations/invocationsRoute.js +0 -90
  212. package/dist/lambda/routes/invoke-async/InvokeAsyncController.js +0 -38
  213. package/dist/lambda/routes/invoke-async/index.js +0 -15
  214. package/dist/lambda/routes/invoke-async/invokeAsyncRoute.js +0 -43
  215. package/dist/main.js +0 -11
  216. package/dist/serverlessLog.js +0 -91
  217. package/dist/utils/checkDockerDaemon.js +0 -27
  218. package/dist/utils/checkGoVersion.js +0 -27
  219. package/dist/utils/createApiKey.js +0 -12
  220. package/dist/utils/createUniqueId.js +0 -14
  221. package/dist/utils/detectExecutable.js +0 -21
  222. package/dist/utils/generateHapiPath.js +0 -28
  223. package/dist/utils/getHttpApiCorsConfig.js +0 -40
  224. package/dist/utils/index.js +0 -165
  225. package/dist/utils/jsonPath.js +0 -21
  226. package/dist/utils/lowerCaseKeys.js +0 -14
  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 -36
  232. package/dist/utils/satisfiesVersionRange.js +0 -20
  233. package/dist/utils/splitHandlerPathAndName.js +0 -37
  234. package/dist/utils/unflatten.js +0 -18
@@ -0,0 +1,126 @@
1
+ import { createRequire } from 'node:module'
2
+ import { performance } from 'node:perf_hooks'
3
+ import process from 'node:process'
4
+ import { log } from '@serverless/utils/log.js'
5
+
6
+ const { floor } = Math
7
+ const { assign } = Object
8
+
9
+ const require = createRequire(import.meta.url)
10
+
11
+ export default class InProcessRunner {
12
+ #env = null
13
+
14
+ #functionKey = null
15
+
16
+ #handlerName = null
17
+
18
+ #handlerPath = null
19
+
20
+ #timeout = null
21
+
22
+ constructor(functionKey, handlerPath, handlerName, env, timeout) {
23
+ this.#env = env
24
+ this.#functionKey = functionKey
25
+ this.#handlerName = handlerName
26
+ this.#handlerPath = handlerPath
27
+ this.#timeout = timeout
28
+ }
29
+
30
+ // no-op
31
+ // () => void
32
+ cleanup() {}
33
+
34
+ async run(event, context) {
35
+ // check if the handler module path exists
36
+ if (!require.resolve(this.#handlerPath)) {
37
+ throw new Error(
38
+ `Could not find handler module '${this.#handlerPath}' for function '${
39
+ this.#functionKey
40
+ }'.`,
41
+ )
42
+ }
43
+
44
+ // process.env should be available in the handler module scope as well as in the handler function scope
45
+ // NOTE: Don't use Object spread (...) here!
46
+ // otherwise the values of the attached props are not coerced to a string
47
+ // e.g. process.env.foo = 1 should be coerced to '1' (string)
48
+ assign(process.env, this.#env)
49
+
50
+ let handler
51
+
52
+ try {
53
+ // const { [this.#handlerName]: handler } = await import(this.#handlerPath)
54
+ // eslint-disable-next-line import/no-dynamic-require
55
+ ;({ [this.#handlerName]: handler } = require(this.#handlerPath))
56
+ } catch (err) {
57
+ log.error(err)
58
+ }
59
+
60
+ if (typeof handler !== 'function') {
61
+ throw new Error(
62
+ `offline: handler '${this.#handlerName}' in ${
63
+ this.#handlerPath
64
+ } is not a function`,
65
+ )
66
+ }
67
+
68
+ let callback
69
+
70
+ const callbackCalled = new Promise((res, rej) => {
71
+ callback = (err, data) => {
72
+ if (err === 'Unauthorized') {
73
+ res('Unauthorized')
74
+ return
75
+ }
76
+ if (err) {
77
+ rej(err)
78
+ return
79
+ }
80
+ res(data)
81
+ }
82
+ })
83
+
84
+ const executionTimeout = performance.now() + this.#timeout
85
+
86
+ // attach doc-deprecated functions
87
+ // create new immutable object
88
+ const lambdaContext = {
89
+ ...context,
90
+ done: (err, data) => callback(err, data),
91
+ fail: (err) => callback(err),
92
+ getRemainingTimeInMillis() {
93
+ const timeLeft = executionTimeout - performance.now()
94
+
95
+ // just return 0 for now if we are beyond alotted time (timeout)
96
+ return timeLeft > 0 ? floor(timeLeft) : 0
97
+ },
98
+ succeed: (res) => callback(null, res),
99
+ }
100
+
101
+ let result
102
+
103
+ // execute (run) handler
104
+ try {
105
+ result = handler(event, lambdaContext, callback)
106
+ } catch {
107
+ throw new Error(`Uncaught error in '${this.#functionKey}' handler.`)
108
+ }
109
+
110
+ // // not a Promise, which is not supported by aws
111
+ // if (result == null || typeof result.then !== 'function') {
112
+ // throw new Error(`Synchronous function execution is not supported.`)
113
+ // }
114
+
115
+ const callbacks = [callbackCalled]
116
+
117
+ // Promise was returned
118
+ if (result != null && typeof result.then === 'function') {
119
+ callbacks.push(result)
120
+ }
121
+
122
+ const callbackResult = await Promise.race(callbacks)
123
+
124
+ return callbackResult
125
+ }
126
+ }
@@ -0,0 +1 @@
1
+ export { default } from './InProcessRunner.js'
@@ -0,0 +1 @@
1
+ export { default } from './HandlerRunner.js'
@@ -0,0 +1,114 @@
1
+ import { EOL } from 'node:os'
2
+ import process from 'node:process'
3
+ import { log } from '@serverless/utils/log.js'
4
+ import { invokeJavaLocal } from 'java-invoke-local'
5
+
6
+ const { parse, stringify } = JSON
7
+ const { has } = Reflect
8
+
9
+ export default class JavaRunner {
10
+ #deployPackage = null
11
+
12
+ #env = null
13
+
14
+ #functionName = null
15
+
16
+ #handler = null
17
+
18
+ constructor(funOptions, env) {
19
+ const { functionName, handler, servicePackage, functionPackage } =
20
+ funOptions
21
+
22
+ this.#deployPackage = functionPackage || servicePackage
23
+ this.#env = env
24
+ this.#functionName = functionName
25
+ this.#handler = handler
26
+ }
27
+
28
+ // no-op
29
+ // () => void
30
+ cleanup() {}
31
+
32
+ #parsePayload(value) {
33
+ for (const item of value.split(EOL)) {
34
+ let json
35
+
36
+ // first check if it's JSON
37
+ try {
38
+ json = parse(item)
39
+ // nope, it's not JSON
40
+ } catch {
41
+ // no-op
42
+ }
43
+
44
+ // now let's see if we have a property __offline_payload__
45
+ if (
46
+ json &&
47
+ typeof json === 'object' &&
48
+ has(json, '__offline_payload__')
49
+ ) {
50
+ return json.__offline_payload__
51
+ }
52
+ }
53
+
54
+ return undefined
55
+ }
56
+
57
+ async run(event, context) {
58
+ const input = stringify({
59
+ context,
60
+ event,
61
+ })
62
+
63
+ const data = stringify({
64
+ artifact: this.#deployPackage,
65
+ data: input,
66
+ function: this.#functionName,
67
+ handler: this.#handler,
68
+ jsonOutput: true,
69
+ serverlessOffline: true,
70
+ })
71
+
72
+ const httpOptions = {
73
+ body: data,
74
+ method: 'POST',
75
+ }
76
+
77
+ const port = process.env.JAVA_OFFLINE_SERVER || 8080
78
+
79
+ let result
80
+
81
+ try {
82
+ // Assume java-invoke-local server is running
83
+
84
+ const response = await fetch(
85
+ `http://localhost:${port}/invoke`,
86
+ httpOptions,
87
+ )
88
+ result = await response.text()
89
+ } catch {
90
+ log.notice(
91
+ 'Local java server not running. For faster local invocations, run "java-invoke-local --server" in your project directory',
92
+ )
93
+
94
+ // Fallback invocation
95
+ const args = [
96
+ '-c',
97
+ this.#handler,
98
+ '-a',
99
+ this.#deployPackage,
100
+ '-f',
101
+ this.#functionName,
102
+ '-d',
103
+ input,
104
+ '--json-output',
105
+ '--serverless-offline',
106
+ ]
107
+ result = invokeJavaLocal(args, this.#env)
108
+
109
+ log.notice(result)
110
+ }
111
+
112
+ return this.#parsePayload(result)
113
+ }
114
+ }
@@ -0,0 +1 @@
1
+ export { default } from './JavaRunner.js'
@@ -0,0 +1,138 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { EOL, platform } from 'node:os'
3
+ import { delimiter, dirname, join, relative, resolve } from 'node:path'
4
+ import process, { cwd } from 'node:process'
5
+ import readline from 'node:readline'
6
+ import { fileURLToPath } from 'node:url'
7
+ import { log } from '@serverless/utils/log.js'
8
+
9
+ const { parse, stringify } = JSON
10
+ const { assign } = Object
11
+ const { has } = Reflect
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url))
14
+
15
+ export default class PythonRunner {
16
+ #env = null
17
+
18
+ #handlerName = null
19
+
20
+ #handlerPath = null
21
+
22
+ #runtime = null
23
+
24
+ constructor(funOptions, env) {
25
+ const { handlerName, handlerPath, runtime } = funOptions
26
+
27
+ this.#env = env
28
+ this.#handlerName = handlerName
29
+ this.#handlerPath = handlerPath
30
+ this.#runtime = platform() === 'win32' ? 'python.exe' : runtime
31
+
32
+ if (process.env.VIRTUAL_ENV) {
33
+ const runtimeDir = platform() === 'win32' ? 'Scripts' : 'bin'
34
+
35
+ process.env.PATH = [
36
+ join(process.env.VIRTUAL_ENV, runtimeDir),
37
+ delimiter,
38
+ process.env.PATH,
39
+ ].join('')
40
+ }
41
+
42
+ const [pythonExecutable] = this.#runtime.split('.')
43
+
44
+ this.handlerProcess = spawn(
45
+ pythonExecutable,
46
+ [
47
+ '-u',
48
+ resolve(__dirname, 'invoke.py'),
49
+ relative(cwd(), this.#handlerPath),
50
+ this.#handlerName,
51
+ ],
52
+ {
53
+ env: assign(process.env, this.#env),
54
+ shell: true,
55
+ },
56
+ )
57
+
58
+ this.handlerProcess.stdout.readline = readline.createInterface({
59
+ input: this.handlerProcess.stdout,
60
+ })
61
+ }
62
+
63
+ // () => void
64
+ cleanup() {
65
+ this.handlerProcess.kill()
66
+ }
67
+
68
+ #parsePayload(value) {
69
+ let payload
70
+
71
+ for (const item of value.split(EOL)) {
72
+ let json
73
+
74
+ // first check if it's JSON
75
+ try {
76
+ json = parse(item)
77
+ // nope, it's not JSON
78
+ } catch {
79
+ // no-op
80
+ }
81
+
82
+ // now let's see if we have a property __offline_payload__
83
+ if (
84
+ json &&
85
+ typeof json === 'object' &&
86
+ has(json, '__offline_payload__')
87
+ ) {
88
+ payload = json.__offline_payload__
89
+ // everything else is print(), logging, ...
90
+ } else {
91
+ log.notice(item)
92
+ }
93
+ }
94
+
95
+ return payload
96
+ }
97
+
98
+ // invokeLocalPython, loosely based on:
99
+ // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/index.js#L410
100
+ // invoke.py, based on:
101
+ // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/invoke.py
102
+ async run(event, context) {
103
+ return new Promise((accept, reject) => {
104
+ const input = stringify({
105
+ context,
106
+ event,
107
+ })
108
+
109
+ const onErr = (data) => {
110
+ // TODO
111
+
112
+ log.notice(data.toString())
113
+ }
114
+
115
+ const onLine = (line) => {
116
+ try {
117
+ const parsed = this.#parsePayload(line.toString())
118
+ if (parsed) {
119
+ this.handlerProcess.stdout.readline.removeListener('line', onLine)
120
+ this.handlerProcess.stderr.removeListener('data', onErr)
121
+ return accept(parsed)
122
+ }
123
+ return null
124
+ } catch (err) {
125
+ return reject(err)
126
+ }
127
+ }
128
+
129
+ this.handlerProcess.stdout.readline.on('line', onLine)
130
+ this.handlerProcess.stderr.on('data', onErr)
131
+
132
+ process.nextTick(() => {
133
+ this.handlerProcess.stdin.write(input)
134
+ this.handlerProcess.stdin.write('\n')
135
+ })
136
+ })
137
+ }
138
+ }
@@ -0,0 +1 @@
1
+ export { default } from './PythonRunner.js'
@@ -0,0 +1,107 @@
1
+ import { EOL, platform } from 'node:os'
2
+ import { dirname, relative, resolve } from 'node:path'
3
+ import { cwd } from 'node:process'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { log } from '@serverless/utils/log.js'
6
+ import { execa } from 'execa'
7
+
8
+ const { parse, stringify } = JSON
9
+ const { has } = Reflect
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url))
12
+
13
+ export default class RubyRunner {
14
+ #env = null
15
+
16
+ #handlerName = null
17
+
18
+ #handlerPath = null
19
+
20
+ constructor(funOptions, env) {
21
+ const { handlerName, handlerPath } = funOptions
22
+
23
+ this.#env = env
24
+ this.#handlerName = handlerName
25
+ this.#handlerPath = handlerPath
26
+ }
27
+
28
+ // no-op
29
+ // () => void
30
+ cleanup() {}
31
+
32
+ #parsePayload(value) {
33
+ let payload
34
+
35
+ for (const item of value.split(EOL)) {
36
+ let json
37
+
38
+ // first check if it's JSON
39
+ try {
40
+ json = parse(item)
41
+ // nope, it's not JSON
42
+ } catch {
43
+ // no-op
44
+ }
45
+
46
+ // now let's see if we have a property __offline_payload__
47
+ if (
48
+ json &&
49
+ typeof json === 'object' &&
50
+ has(json, '__offline_payload__')
51
+ ) {
52
+ payload = json.__offline_payload__
53
+ } else {
54
+ log.notice(item)
55
+ }
56
+ }
57
+
58
+ return payload
59
+ }
60
+
61
+ // invokeLocalRuby, loosely based on:
62
+ // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/index.js#L556
63
+ // invoke.rb, copy/pasted entirely as is:
64
+ // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/invoke.rb
65
+ async run(event, context) {
66
+ const runtime = platform() === 'win32' ? 'ruby.exe' : 'ruby'
67
+
68
+ // https://docs.aws.amazon.com/lambda/latest/dg/ruby-context.html
69
+
70
+ // https://docs.aws.amazon.com/lambda/latest/dg/ruby-context.html
71
+ // exclude callbackWaitsForEmptyEventLoop, don't mutate context
72
+ const { callbackWaitsForEmptyEventLoop, ..._context } = context
73
+
74
+ const input = stringify({
75
+ context: _context,
76
+ event,
77
+ })
78
+
79
+ // console.log(input)
80
+
81
+ const ruby = execa(
82
+ runtime,
83
+ [
84
+ resolve(__dirname, 'invoke.rb'),
85
+ relative(cwd(), this.#handlerPath),
86
+ this.#handlerName,
87
+ ],
88
+ {
89
+ env: this.#env,
90
+ input,
91
+ // shell: true,
92
+ },
93
+ )
94
+
95
+ const result = await ruby
96
+
97
+ const { stderr, stdout } = result
98
+
99
+ if (stderr) {
100
+ // TODO
101
+
102
+ log.notice(stderr)
103
+ }
104
+
105
+ return this.#parsePayload(stdout)
106
+ }
107
+ }
@@ -0,0 +1 @@
1
+ export { default } from './RubyRunner.js'
@@ -0,0 +1,70 @@
1
+ import { dirname, resolve } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { MessageChannel, Worker } from 'node:worker_threads'
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url))
6
+ const workerThreadHelperPath = resolve(__dirname, './workerThreadHelper.js')
7
+
8
+ export default class WorkerThreadRunner {
9
+ #workerThread = null
10
+
11
+ constructor(funOptions /* options */, env) {
12
+ // this._options = options
13
+
14
+ const { functionKey, handlerName, handlerPath, timeout } = funOptions
15
+
16
+ this.#workerThread = new Worker(workerThreadHelperPath, {
17
+ // don't pass process.env from the main process!
18
+ env,
19
+ workerData: {
20
+ functionKey,
21
+ handlerName,
22
+ handlerPath,
23
+ timeout,
24
+ },
25
+ })
26
+ }
27
+
28
+ // () => Promise<number>
29
+ cleanup() {
30
+ // TODO console.log('worker thread cleanup')
31
+
32
+ return this.#workerThread.terminate()
33
+ }
34
+
35
+ run(event, context) {
36
+ return new Promise((res, rej) => {
37
+ const { port1, port2 } = new MessageChannel()
38
+
39
+ port1
40
+ .on('message', (value) => {
41
+ if (value instanceof Error) {
42
+ rej(value)
43
+ return
44
+ }
45
+
46
+ res(value)
47
+ })
48
+ // emitted if the worker thread throws an uncaught exception.
49
+ // In that case, the worker will be terminated.
50
+ .on('error', rej)
51
+ // TODO
52
+ .on('exit', (code) => {
53
+ if (code !== 0) {
54
+ rej(new Error(`Worker stopped with exit code ${code}`))
55
+ }
56
+ })
57
+
58
+ this.#workerThread.postMessage(
59
+ {
60
+ context,
61
+ event,
62
+ // port2 is part of the payload, for the other side to answer messages
63
+ port: port2,
64
+ },
65
+ // port2 is also required to be part of the transfer list
66
+ [port2],
67
+ )
68
+ })
69
+ }
70
+ }
@@ -0,0 +1 @@
1
+ export { default } from './WorkerThreadRunner.js'
@@ -0,0 +1,29 @@
1
+ import { env } from 'node:process'
2
+ import { parentPort, workerData } from 'node:worker_threads'
3
+ import InProcessRunner from '../in-process-runner/index.js'
4
+
5
+ const { functionKey, handlerName, handlerPath, timeout } = workerData
6
+
7
+ parentPort.on('message', async (messageData) => {
8
+ const { context, event, port } = messageData
9
+
10
+ // TODO we could probably cache this in the module scope?
11
+ const inProcessRunner = new InProcessRunner(
12
+ functionKey,
13
+ handlerPath,
14
+ handlerName,
15
+ env,
16
+ timeout,
17
+ )
18
+
19
+ let result
20
+
21
+ try {
22
+ result = await inProcessRunner.run(event, context)
23
+ } catch (err) {
24
+ port.postMessage(err)
25
+ }
26
+
27
+ // TODO check serializeability (contains function, symbol etc)
28
+ port.postMessage(result)
29
+ })
@@ -0,0 +1 @@
1
+ export { default } from './Lambda.js'
@@ -0,0 +1,2 @@
1
+ export { default as invocationsRoute } from './invocations/index.js'
2
+ export { default as invokeAsyncRoute } from './invoke-async/index.js'