serverless-offline 8.8.1 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +3 -3
  2. package/package.json +33 -57
  3. package/src/ServerlessOffline.js +412 -0
  4. package/src/config/commandOptions.js +155 -0
  5. package/src/config/constants.js +22 -0
  6. package/{dist → src}/config/defaultOptions.js +8 -17
  7. package/src/config/index.js +4 -0
  8. package/src/config/supportedRuntimes.js +47 -0
  9. package/src/events/authCanExecuteResource.js +35 -0
  10. package/src/events/authFunctionNameExtractor.js +75 -0
  11. package/src/events/authMatchPolicyResource.js +71 -0
  12. package/src/events/authValidateContext.js +51 -0
  13. package/src/events/http/Endpoint.js +135 -0
  14. package/src/events/http/Http.js +50 -0
  15. package/src/events/http/HttpEventDefinition.js +20 -0
  16. package/src/events/http/HttpServer.js +1277 -0
  17. package/src/events/http/OfflineEndpoint.js +33 -0
  18. package/src/events/http/authJWTSettingsExtractor.js +70 -0
  19. package/src/events/http/createAuthScheme.js +176 -0
  20. package/src/events/http/createJWTAuthScheme.js +106 -0
  21. package/src/events/http/index.js +1 -0
  22. package/src/events/http/javaHelpers.js +102 -0
  23. package/src/events/http/lambda-events/LambdaIntegrationEvent.js +57 -0
  24. package/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +233 -0
  25. package/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +190 -0
  26. package/src/events/http/lambda-events/VelocityContext.js +147 -0
  27. package/src/events/http/lambda-events/index.js +4 -0
  28. package/src/events/http/lambda-events/renderVelocityTemplateObject.js +93 -0
  29. package/{dist → src}/events/http/parseResources.js +73 -78
  30. package/src/events/http/payloadSchemaValidator.js +13 -0
  31. package/{dist → src}/events/http/templates/offline-default.req.vm +0 -0
  32. package/{dist → src}/events/http/templates/offline-default.res.vm +0 -0
  33. package/src/events/schedule/Schedule.js +131 -0
  34. package/src/events/schedule/ScheduleEvent.js +18 -0
  35. package/src/events/schedule/ScheduleEventDefinition.js +21 -0
  36. package/src/events/schedule/index.js +1 -0
  37. package/src/events/websocket/HttpServer.js +69 -0
  38. package/src/events/websocket/WebSocket.js +52 -0
  39. package/src/events/websocket/WebSocketClients.js +462 -0
  40. package/src/events/websocket/WebSocketEventDefinition.js +18 -0
  41. package/src/events/websocket/WebSocketServer.js +73 -0
  42. package/src/events/websocket/http-routes/_catchAll/catchAllRoute.js +16 -0
  43. package/src/events/websocket/http-routes/_catchAll/index.js +1 -0
  44. package/src/events/websocket/http-routes/connections/ConnectionsController.js +28 -0
  45. package/src/events/websocket/http-routes/connections/connectionsRoutes.js +70 -0
  46. package/src/events/websocket/http-routes/connections/index.js +1 -0
  47. package/src/events/websocket/http-routes/index.js +2 -0
  48. package/src/events/websocket/index.js +1 -0
  49. package/src/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +65 -0
  50. package/src/events/websocket/lambda-events/WebSocketConnectEvent.js +68 -0
  51. package/src/events/websocket/lambda-events/WebSocketDisconnectEvent.js +31 -0
  52. package/src/events/websocket/lambda-events/WebSocketEvent.js +29 -0
  53. package/src/events/websocket/lambda-events/WebSocketRequestContext.js +67 -0
  54. package/src/events/websocket/lambda-events/index.js +4 -0
  55. package/src/index.js +12 -0
  56. package/src/lambda/HttpServer.js +108 -0
  57. package/src/lambda/Lambda.js +68 -0
  58. package/src/lambda/LambdaContext.js +33 -0
  59. package/src/lambda/LambdaFunction.js +308 -0
  60. package/src/lambda/LambdaFunctionPool.js +109 -0
  61. package/src/lambda/__tests__/LambdaContext.test.js +30 -0
  62. package/src/lambda/__tests__/LambdaFunction.test.js +196 -0
  63. package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsJSONObject.fixture.js +47 -0
  64. package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsNativeString.fixture.js +46 -0
  65. package/src/lambda/__tests__/fixtures/Lambda/package.json +3 -0
  66. package/src/lambda/__tests__/fixtures/lambdaFunction.fixture.js +145 -0
  67. package/src/lambda/__tests__/fixtures/package.json +3 -0
  68. package/src/lambda/__tests__/routes/invocations/InvocationsController.test.js +42 -0
  69. package/src/lambda/handler-runner/HandlerRunner.js +136 -0
  70. package/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +72 -0
  71. package/src/lambda/handler-runner/child-process-runner/childProcessHelper.js +42 -0
  72. package/src/lambda/handler-runner/child-process-runner/index.js +1 -0
  73. package/src/lambda/handler-runner/docker-runner/DockerContainer.js +417 -0
  74. package/src/lambda/handler-runner/docker-runner/DockerImage.js +35 -0
  75. package/src/lambda/handler-runner/docker-runner/DockerRunner.js +63 -0
  76. package/src/lambda/handler-runner/docker-runner/index.js +1 -0
  77. package/src/lambda/handler-runner/go-runner/GoRunner.js +166 -0
  78. package/src/lambda/handler-runner/go-runner/index.js +1 -0
  79. package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +125 -0
  80. package/src/lambda/handler-runner/in-process-runner/index.js +1 -0
  81. package/src/lambda/handler-runner/index.js +1 -0
  82. package/src/lambda/handler-runner/java-runner/JavaRunner.js +114 -0
  83. package/src/lambda/handler-runner/java-runner/index.js +1 -0
  84. package/src/lambda/handler-runner/python-runner/PythonRunner.js +138 -0
  85. package/src/lambda/handler-runner/python-runner/index.js +1 -0
  86. package/{dist → src}/lambda/handler-runner/python-runner/invoke.py +0 -0
  87. package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +107 -0
  88. package/src/lambda/handler-runner/ruby-runner/index.js +1 -0
  89. package/{dist → src}/lambda/handler-runner/ruby-runner/invoke.rb +0 -0
  90. package/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +70 -0
  91. package/src/lambda/handler-runner/worker-thread-runner/index.js +1 -0
  92. package/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +29 -0
  93. package/src/lambda/index.js +1 -0
  94. package/src/lambda/routes/index.js +2 -0
  95. package/src/lambda/routes/invocations/InvocationsController.js +102 -0
  96. package/src/lambda/routes/invocations/index.js +1 -0
  97. package/src/lambda/routes/invocations/invocationsRoute.js +77 -0
  98. package/src/lambda/routes/invoke-async/InvokeAsyncController.js +20 -0
  99. package/src/lambda/routes/invoke-async/index.js +1 -0
  100. package/src/lambda/routes/invoke-async/invokeAsyncRoute.js +33 -0
  101. package/src/utils/__tests__/createUniqueId.test.js +18 -0
  102. package/src/utils/__tests__/formatToClfTime.test.js +14 -0
  103. package/src/utils/__tests__/generateHapiPath.test.js +46 -0
  104. package/src/utils/__tests__/lowerCaseKeys.test.js +30 -0
  105. package/src/utils/__tests__/parseHeaders.test.js +13 -0
  106. package/src/utils/__tests__/parseMultiValueHeaders.test.js +24 -0
  107. package/src/utils/__tests__/parseMultiValueQueryStringParameters.test.js +159 -0
  108. package/src/utils/__tests__/parseQueryStringParameters.test.js +15 -0
  109. package/src/utils/__tests__/splitHandlerPathAndName.test.js +54 -0
  110. package/src/utils/__tests__/unflatten.test.js +32 -0
  111. package/src/utils/checkDockerDaemon.js +19 -0
  112. package/src/utils/checkGoVersion.js +16 -0
  113. package/src/utils/createApiKey.js +5 -0
  114. package/src/utils/createUniqueId.js +5 -0
  115. package/src/utils/detectExecutable.js +11 -0
  116. package/{dist → src}/utils/formatToClfTime.js +6 -14
  117. package/src/utils/generateHapiPath.js +26 -0
  118. package/src/utils/getHttpApiCorsConfig.js +28 -0
  119. package/src/utils/index.js +42 -0
  120. package/src/utils/jsonPath.js +13 -0
  121. package/src/utils/logRoutes.js +64 -0
  122. package/src/utils/lowerCaseKeys.js +6 -0
  123. package/src/utils/parseHeaders.js +14 -0
  124. package/src/utils/parseMultiValueHeaders.js +27 -0
  125. package/src/utils/parseMultiValueQueryStringParameters.js +31 -0
  126. package/src/utils/parseQueryStringParameters.js +15 -0
  127. package/src/utils/resolveJoins.js +29 -0
  128. package/src/utils/splitHandlerPathAndName.js +31 -0
  129. package/src/utils/unflatten.js +11 -0
  130. package/CHANGELOG.md +0 -78
  131. package/dist/ServerlessOffline.js +0 -508
  132. package/dist/config/commandOptions.js +0 -149
  133. package/dist/config/constants.js +0 -30
  134. package/dist/config/index.js +0 -55
  135. package/dist/config/supportedRuntimes.js +0 -40
  136. package/dist/debugLog.js +0 -12
  137. package/dist/events/authCanExecuteResource.js +0 -35
  138. package/dist/events/authFunctionNameExtractor.js +0 -87
  139. package/dist/events/authMatchPolicyResource.js +0 -62
  140. package/dist/events/authValidateContext.js +0 -53
  141. package/dist/events/http/Endpoint.js +0 -173
  142. package/dist/events/http/Http.js +0 -77
  143. package/dist/events/http/HttpEventDefinition.js +0 -36
  144. package/dist/events/http/HttpServer.js +0 -1370
  145. package/dist/events/http/OfflineEndpoint.js +0 -38
  146. package/dist/events/http/authJWTSettingsExtractor.js +0 -76
  147. package/dist/events/http/createAuthScheme.js +0 -184
  148. package/dist/events/http/createJWTAuthScheme.js +0 -159
  149. package/dist/events/http/index.js +0 -15
  150. package/dist/events/http/javaHelpers.js +0 -99
  151. package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +0 -87
  152. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -246
  153. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -225
  154. package/dist/events/http/lambda-events/VelocityContext.js +0 -170
  155. package/dist/events/http/lambda-events/index.js +0 -39
  156. package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -111
  157. package/dist/events/http/payloadSchemaValidator.js +0 -13
  158. package/dist/events/schedule/Schedule.js +0 -183
  159. package/dist/events/schedule/ScheduleEvent.js +0 -27
  160. package/dist/events/schedule/ScheduleEventDefinition.js +0 -36
  161. package/dist/events/schedule/index.js +0 -15
  162. package/dist/events/websocket/HttpServer.js +0 -114
  163. package/dist/events/websocket/WebSocket.js +0 -78
  164. package/dist/events/websocket/WebSocketClients.js +0 -577
  165. package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
  166. package/dist/events/websocket/WebSocketServer.js +0 -139
  167. package/dist/events/websocket/http-routes/_catchAll/catchAllRoute.js +0 -33
  168. package/dist/events/websocket/http-routes/_catchAll/index.js +0 -15
  169. package/dist/events/websocket/http-routes/connections/ConnectionsController.js +0 -45
  170. package/dist/events/websocket/http-routes/connections/connectionsRoutes.js +0 -95
  171. package/dist/events/websocket/http-routes/connections/index.js +0 -15
  172. package/dist/events/websocket/http-routes/index.js +0 -23
  173. package/dist/events/websocket/index.js +0 -15
  174. package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +0 -99
  175. package/dist/events/websocket/lambda-events/WebSocketConnectEvent.js +0 -101
  176. package/dist/events/websocket/lambda-events/WebSocketDisconnectEvent.js +0 -47
  177. package/dist/events/websocket/lambda-events/WebSocketEvent.js +0 -54
  178. package/dist/events/websocket/lambda-events/WebSocketRequestContext.js +0 -98
  179. package/dist/events/websocket/lambda-events/index.js +0 -39
  180. package/dist/index.js +0 -15
  181. package/dist/lambda/HttpServer.js +0 -124
  182. package/dist/lambda/Lambda.js +0 -117
  183. package/dist/lambda/LambdaContext.js +0 -53
  184. package/dist/lambda/LambdaFunction.js +0 -390
  185. package/dist/lambda/LambdaFunctionPool.js +0 -127
  186. package/dist/lambda/handler-runner/HandlerRunner.js +0 -195
  187. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -124
  188. package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -49
  189. package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
  190. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -515
  191. package/dist/lambda/handler-runner/docker-runner/DockerImage.js +0 -67
  192. package/dist/lambda/handler-runner/docker-runner/DockerRunner.js +0 -74
  193. package/dist/lambda/handler-runner/docker-runner/index.js +0 -15
  194. package/dist/lambda/handler-runner/go-runner/GoRunner.js +0 -230
  195. package/dist/lambda/handler-runner/go-runner/index.js +0 -15
  196. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -228
  197. package/dist/lambda/handler-runner/in-process-runner/index.js +0 -15
  198. package/dist/lambda/handler-runner/index.js +0 -15
  199. package/dist/lambda/handler-runner/java-runner/JavaRunner.js +0 -153
  200. package/dist/lambda/handler-runner/java-runner/index.js +0 -15
  201. package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -185
  202. package/dist/lambda/handler-runner/python-runner/index.js +0 -15
  203. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -147
  204. package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
  205. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -92
  206. package/dist/lambda/handler-runner/worker-thread-runner/index.js +0 -15
  207. package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +0 -31
  208. package/dist/lambda/index.js +0 -15
  209. package/dist/lambda/routes/index.js +0 -23
  210. package/dist/lambda/routes/invocations/InvocationsController.js +0 -142
  211. package/dist/lambda/routes/invocations/index.js +0 -15
  212. package/dist/lambda/routes/invocations/invocationsRoute.js +0 -90
  213. package/dist/lambda/routes/invoke-async/InvokeAsyncController.js +0 -38
  214. package/dist/lambda/routes/invoke-async/index.js +0 -15
  215. package/dist/lambda/routes/invoke-async/invokeAsyncRoute.js +0 -43
  216. package/dist/main.js +0 -11
  217. package/dist/serverlessLog.js +0 -91
  218. package/dist/utils/checkDockerDaemon.js +0 -27
  219. package/dist/utils/checkGoVersion.js +0 -27
  220. package/dist/utils/createApiKey.js +0 -12
  221. package/dist/utils/createUniqueId.js +0 -14
  222. package/dist/utils/detectExecutable.js +0 -21
  223. package/dist/utils/generateHapiPath.js +0 -28
  224. package/dist/utils/getHttpApiCorsConfig.js +0 -40
  225. package/dist/utils/index.js +0 -165
  226. package/dist/utils/jsonPath.js +0 -21
  227. package/dist/utils/lowerCaseKeys.js +0 -14
  228. package/dist/utils/parseHeaders.js +0 -23
  229. package/dist/utils/parseMultiValueHeaders.js +0 -36
  230. package/dist/utils/parseMultiValueQueryStringParameters.js +0 -40
  231. package/dist/utils/parseQueryStringParameters.js +0 -26
  232. package/dist/utils/resolveJoins.js +0 -36
  233. package/dist/utils/satisfiesVersionRange.js +0 -20
  234. package/dist/utils/splitHandlerPathAndName.js +0 -37
  235. package/dist/utils/unflatten.js +0 -18
@@ -0,0 +1,125 @@
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 { assign } = Object
7
+
8
+ const require = createRequire(import.meta.url)
9
+
10
+ export default class InProcessRunner {
11
+ #env = null
12
+
13
+ #functionKey = null
14
+
15
+ #handlerName = null
16
+
17
+ #handlerPath = null
18
+
19
+ #timeout = null
20
+
21
+ constructor(functionKey, handlerPath, handlerName, env, timeout) {
22
+ this.#env = env
23
+ this.#functionKey = functionKey
24
+ this.#handlerName = handlerName
25
+ this.#handlerPath = handlerPath
26
+ this.#timeout = timeout
27
+ }
28
+
29
+ // no-op
30
+ // () => void
31
+ cleanup() {}
32
+
33
+ async run(event, context) {
34
+ // check if the handler module path exists
35
+ if (!require.resolve(this.#handlerPath)) {
36
+ throw new Error(
37
+ `Could not find handler module '${this.#handlerPath}' for function '${
38
+ this.#functionKey
39
+ }'.`,
40
+ )
41
+ }
42
+
43
+ // process.env should be available in the handler module scope as well as in the handler function scope
44
+ // NOTE: Don't use Object spread (...) here!
45
+ // otherwise the values of the attached props are not coerced to a string
46
+ // e.g. process.env.foo = 1 should be coerced to '1' (string)
47
+ assign(process.env, this.#env)
48
+
49
+ let handler
50
+
51
+ try {
52
+ // const { [this.#handlerName]: handler } = await import(this.#handlerPath)
53
+ // eslint-disable-next-line import/no-dynamic-require
54
+ ;({ [this.#handlerName]: handler } = require(this.#handlerPath))
55
+ } catch (err) {
56
+ log.error(err)
57
+ }
58
+
59
+ if (typeof handler !== 'function') {
60
+ throw new Error(
61
+ `offline: handler '${this.#handlerName}' in ${
62
+ this.#handlerPath
63
+ } is not a function`,
64
+ )
65
+ }
66
+
67
+ let callback
68
+
69
+ const callbackCalled = new Promise((res, rej) => {
70
+ callback = (err, data) => {
71
+ if (err === 'Unauthorized') {
72
+ res('Unauthorized')
73
+ return
74
+ }
75
+ if (err) {
76
+ rej(err)
77
+ return
78
+ }
79
+ res(data)
80
+ }
81
+ })
82
+
83
+ const executionTimeout = performance.now() + this.#timeout
84
+
85
+ // attach doc-deprecated functions
86
+ // create new immutable object
87
+ const lambdaContext = {
88
+ ...context,
89
+ done: (err, data) => callback(err, data),
90
+ fail: (err) => callback(err),
91
+ getRemainingTimeInMillis: () => {
92
+ const timeLeft = executionTimeout - performance.now()
93
+
94
+ // just return 0 for now if we are beyond alotted time (timeout)
95
+ return timeLeft > 0 ? timeLeft : 0
96
+ },
97
+ succeed: (res) => callback(null, res),
98
+ }
99
+
100
+ let result
101
+
102
+ // execute (run) handler
103
+ try {
104
+ result = handler(event, lambdaContext, callback)
105
+ } catch {
106
+ throw new Error(`Uncaught error in '${this.#functionKey}' handler.`)
107
+ }
108
+
109
+ // // not a Promise, which is not supported by aws
110
+ // if (result == null || typeof result.then !== 'function') {
111
+ // throw new Error(`Synchronous function execution is not supported.`)
112
+ // }
113
+
114
+ const callbacks = [callbackCalled]
115
+
116
+ // Promise was returned
117
+ if (result != null && typeof result.then === 'function') {
118
+ callbacks.push(result)
119
+ }
120
+
121
+ const callbackResult = await Promise.race(callbacks)
122
+
123
+ return callbackResult
124
+ }
125
+ }
@@ -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 } = workerData
6
+
7
+ parentPort.on('message', async (messageData) => {
8
+ const { context, event, port, timeout } = 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'