-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support function entry points (#301)
- Loading branch information
Showing
17 changed files
with
309 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'skuba': patch | ||
--- | ||
|
||
**template/lambda-sqs-worker:** Add `start` script |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
--- | ||
'skuba': minor | ||
--- | ||
|
||
**node, start:** Support function entry points | ||
|
||
You can now specify an entry point that targets an exported function: | ||
|
||
```bash | ||
skuba start --port 12345 src/app.ts#handler | ||
``` | ||
|
||
This starts up a local HTTP server that you can POST arguments to: | ||
|
||
```bash | ||
curl --data '["event", {"awsRequestId": "123"}]' --include localhost:12345 | ||
``` | ||
|
||
You may find this useful to run Lambda function handlers locally. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'skuba': patch | ||
--- | ||
|
||
**node, start:** Support `--port` option |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { log } from '../utils/logging'; | ||
import { isFunction, isObject } from '../utils/validation'; | ||
|
||
import { | ||
createRequestListenerFromFunction, | ||
serveRequestListener, | ||
} from './http'; | ||
|
||
interface Args { | ||
availablePort?: number; | ||
entryPoint: unknown; | ||
functionName: string; | ||
} | ||
|
||
/** | ||
* Create an HTTP server that calls into an exported function. | ||
*/ | ||
export const runFunctionHandler = ({ | ||
availablePort, | ||
entryPoint, | ||
functionName, | ||
}: Args) => { | ||
if (!isObject(entryPoint)) { | ||
log.subtle(log.bold(functionName), 'is not exported'); | ||
return; | ||
} | ||
|
||
const fn = entryPoint[functionName]; | ||
|
||
if (!isFunction(fn)) { | ||
log.subtle(log.bold(functionName), 'is not a function'); | ||
return; | ||
} | ||
|
||
const requestListener = createRequestListenerFromFunction(fn); | ||
|
||
return serveRequestListener(requestListener, availablePort); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import http from 'http'; | ||
import { AddressInfo } from 'net'; | ||
|
||
import { serializeError } from 'serialize-error'; | ||
|
||
import { log } from '../utils/logging'; | ||
|
||
/** | ||
* Create an HTTP request listener based on the supplied function. | ||
* | ||
* - The request body is JSON parsed and passed into the function as parameters. | ||
* - The function's return value is JSON stringified into the response body. | ||
*/ | ||
export const createRequestListenerFromFunction = ( | ||
fn: (...args: unknown[]) => unknown | Promise<unknown>, | ||
): http.RequestListener => async (req, res) => { | ||
const writeJsonResponse = (statusCode: number, jsonResponse: unknown) => | ||
new Promise<void>((resolve) => | ||
res | ||
.writeHead(statusCode, { 'Content-Type': 'application/json' }) | ||
.end(JSON.stringify(jsonResponse, null, 2), 'utf8', resolve), | ||
); | ||
|
||
try { | ||
const requestBody = await new Promise<string>((resolve, reject) => { | ||
let data = ''; | ||
|
||
req | ||
.on('data', (chunk) => (data += chunk)) | ||
.on('end', () => resolve(data)) | ||
.on('error', (err) => reject(err)); | ||
}); | ||
|
||
const jsonRequest: unknown = JSON.parse(requestBody); | ||
|
||
// Pass a non-array request body as the first parameter | ||
const args = Array.isArray(jsonRequest) ? jsonRequest : [jsonRequest]; | ||
|
||
const response: unknown = await fn(...args); | ||
|
||
await writeJsonResponse(200, response); | ||
} catch (err: unknown) { | ||
await writeJsonResponse(500, serializeError(err)); | ||
} | ||
}; | ||
|
||
/** | ||
* Create a HTTP server based on the supplied `http.RequestListener`. | ||
* | ||
* This function resolves when the server is closed. | ||
*/ | ||
export const serveRequestListener = ( | ||
requestListener: http.RequestListener, | ||
port?: number, | ||
) => { | ||
const server = http.createServer(requestListener); | ||
|
||
return new Promise<void>((resolve, reject) => | ||
server | ||
.listen(port) | ||
.on('close', resolve) | ||
.on('error', reject) | ||
.on('listening', () => { | ||
const address = server.address() as AddressInfo; | ||
|
||
log.ok('listening on port', log.bold(address.port)); | ||
}), | ||
); | ||
}; |
Oops, something went wrong.