Skip to content

Commit b17e93e

Browse files
committed
feat: serverless-offline-lambda-function-urls plugin
0 parents  commit b17e93e

File tree

6 files changed

+161
-0
lines changed

6 files changed

+161
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.turbo/

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.turbo/

.prettierrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../configuration/prettier.json')

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Serverless Offline - Lambda Function Urls
2+
3+
## Description
4+
5+
This plugin provides a temporal solution to the issue described [here](https://github.com/dherault/serverless-offline/issues/1382).
6+
7+
### Setup
8+
9+
1. Inside your project's `serverless.yml` file, add the following entry in the `plugins` section.
10+
11+
```yaml
12+
plugins:
13+
- serverless-offline
14+
- serverless-offline-lambda-function-urls
15+
```
16+
17+
2. Configure the port where the new server will be running. By default, the server will run on 3003.
18+
19+
```yaml
20+
serverless-offline:
21+
urlLambdaFunctionsHttpPort: 3003
22+
```
23+
24+
3. Configure a lambda url function. When you add the `url` option, the handler will expose it as a `GET/POST` HTTP endpoint(`/dev/ping`). The HTTP endpoint doesn't go through the API Gateway, which means that you can set your own `timeout` and it will respect it. Traditionally, the API Gateway would timeout after 30 seconds.
25+
26+
```yaml
27+
ping:
28+
handler: ./src/functions/ping.handler
29+
url: true
30+
timeout: 120 # The handler will timeout after 2 minutes and API Gateway won't interrupt it
31+
```
32+
33+
4. Run `serverless offline` and the plugin will be triggerred by the `offline:start:init` event.
34+
35+
```bash
36+
serverless offline start
37+
```

index.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {resolve, join} from 'node:path'
2+
import {cwd} from 'node:process'
3+
4+
export default class ServerlessOfflineLambdaFunctionUrls {
5+
constructor(serverless) {
6+
const configuration = serverless.config.serverless.configurationInput
7+
this.serverless = serverless
8+
this.configuration = configuration
9+
this.hooks = {
10+
'offline:start:init': () => this.init(),
11+
}
12+
}
13+
getLambdas(functions) {
14+
return Object.entries(functions).reduce(
15+
(lambdas, [functionKey, functionDefinition]) => [
16+
...lambdas,
17+
{
18+
functionKey,
19+
functionDefinition: {
20+
...functionDefinition,
21+
handler: this.getTranspiledHandlerFilepath(functionDefinition.handler),
22+
},
23+
},
24+
],
25+
[]
26+
)
27+
}
28+
filterNonUrlEnabledFunctions(configuration) {
29+
return Object.entries(configuration.functions).reduce((functions, [functionKey, functionDefinition]) => {
30+
if (!functionDefinition.url) {
31+
return functions
32+
}
33+
return {...functions, [functionKey]: functionDefinition}
34+
}, {})
35+
}
36+
getEvents(functions) {
37+
return Object.entries(functions).reduce((events, [functionKey, {handler}]) => {
38+
const path = `/${encodeURIComponent(functionKey)}`
39+
return [
40+
...events,
41+
{functionKey, handler, http: {path, method: 'GET'}},
42+
{functionKey, handler, http: {path, method: 'POST'}},
43+
]
44+
}, [])
45+
}
46+
mergeServerlessOfflineOptions(options) {
47+
const stage = this.serverless.variables.options?.stage ?? this.configuration.provider?.stage
48+
const serverlessOfflineOptions = this.configuration.custom['serverless-offline']
49+
return {
50+
...serverlessOfflineOptions,
51+
stage,
52+
httpPort: serverlessOfflineOptions['urlLambdaFunctionsHttpPort'] ?? 3003,
53+
...options,
54+
}
55+
}
56+
getTranspiledHandlerFilepath(handler) {
57+
return join('.esbuild', '.build', handler)
58+
}
59+
getFullPath(...args) {
60+
return resolve(cwd(), ...args)
61+
}
62+
async init() {
63+
const {default: Lambda} = await import(this.getFullPath('node_modules', 'serverless-offline/src/lambda/Lambda.js'))
64+
const {default: Http} = await import(this.getFullPath('node_modules', 'serverless-offline/src/events/http/Http.js'))
65+
const functions = this.filterNonUrlEnabledFunctions(this.configuration)
66+
67+
const lambda = new Lambda(this.serverless, this.mergeServerlessOfflineOptions({noTimeout: true}))
68+
lambda.create(this.getLambdas(functions))
69+
70+
const http = new Http(this.serverless, this.mergeServerlessOfflineOptions(), lambda)
71+
72+
await http.createServer()
73+
74+
http.create(this.getEvents(functions))
75+
http.createResourceRoutes()
76+
http.create404Route()
77+
78+
await http.start()
79+
}
80+
}

package.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "serverless-offline-lambda-function-urls",
3+
"version": "0.1.0",
4+
"license": "ISC",
5+
"description": "An extension off serverless-offline that adds support to lambda function urls",
6+
"type": "module",
7+
"main": "./index.js",
8+
"files": [
9+
"index.js"
10+
],
11+
"bugs": {
12+
"url": "https://github.com/MarioSimou/serverless-offline-lambda-function-urls/issues"
13+
},
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/MarioSimou/serverless-offline-lambda-function-urls.git"
17+
},
18+
"scripts": {
19+
"prettier": "prettier '.'",
20+
"prettier:check": "pnpm prettier -c",
21+
"prettier:format": "pnpm prettier -l -w ",
22+
"clean": "rm -rf node_modules 2>dev/null"
23+
},
24+
"peerDependencies": {
25+
"serverless-offline": ">= 12.x"
26+
},
27+
"keywords": [
28+
"serverless-offline",
29+
"Lambda Function Urls"
30+
],
31+
"author": "Marios Simou",
32+
"contributors": [
33+
{
34+
"name": "Marios Simou",
35+
"url": "https://www.mariossimou.dev",
36+
"author": true
37+
}
38+
]
39+
}

0 commit comments

Comments
 (0)