@vercube/serverless 0.0.22
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.
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/Adapters/aws-lambda/index.d.mts +34 -0
- package/dist/Adapters/aws-lambda/index.mjs +353 -0
- package/dist/ServerlessTypes-lXnSKHNA.d.mts +6 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present - Vercube
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://vercube.dev/"><img src="https://github.com/OskarLebuda/vue-lazy-hydration/raw/main/.github/assets/logo.png?raw=true" alt="Vercube logo" width="200"></a>
|
|
3
|
+
<br>
|
|
4
|
+
<br>
|
|
5
|
+
|
|
6
|
+
# @vercube/serverless
|
|
7
|
+
|
|
8
|
+
Serverless deployment adapters for Vercube applications
|
|
9
|
+
|
|
10
|
+
<a href="https://www.npmjs.com/package/@vercube/serverless">
|
|
11
|
+
<img src="https://img.shields.io/npm/v/%40vercube%2Fserverless?style=for-the-badge&logo=npm&color=%23767eff" alt="npm"/>
|
|
12
|
+
</a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/@vercube/serverless">
|
|
14
|
+
<img src="https://img.shields.io/npm/dm/%40vercube%2Fserverless?style=for-the-badge&logo=npm&color=%23767eff" alt="npm"/>
|
|
15
|
+
</a>
|
|
16
|
+
<a href="https://github.com/vercube/vercube/blob/main/LICENSE" target="_blank">
|
|
17
|
+
<img src="https://img.shields.io/npm/l/%40vercube%2Fserverless?style=for-the-badge&color=%23767eff" alt="License"/>
|
|
18
|
+
</a>
|
|
19
|
+
<br/>
|
|
20
|
+
<br/>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
Deploy your Vercube applications to serverless platforms with zero configuration. This package provides seamless adapters for AWS Lambda, Vercel, and other serverless providers, allowing you to run your Vercube apps anywhere without code changes.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🧩 `@vercube/serverless` Module
|
|
28
|
+
|
|
29
|
+
The `@vercube/serverless` module provides unified, provider-agnostic adapters for deploying Vercube applications to serverless platforms. It abstracts the differences between various serverless providers into a consistent API, enabling easy deployment across different environments without modifying your application code.
|
|
30
|
+
|
|
31
|
+
### ✅ Key Features
|
|
32
|
+
|
|
33
|
+
- **AWS Lambda Integration** - Full support for API Gateway v1 and v2
|
|
34
|
+
- **Zero Configuration** - Works out-of-the-box with existing Vercube apps
|
|
35
|
+
- **Type Safety** - Complete TypeScript support with proper type definitions
|
|
36
|
+
- **Binary Support** - Automatic handling of binary content with base64 encoding
|
|
37
|
+
- **Cookie Support** - Proper cookie handling for both API Gateway versions
|
|
38
|
+
- **Error Handling** - Robust error handling and validation
|
|
39
|
+
- **Performance Optimized** - Efficient request/response conversion
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🚀 Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm install @vercube/serverless
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ⚙️ Usage
|
|
52
|
+
|
|
53
|
+
### AWS Lambda Integration
|
|
54
|
+
|
|
55
|
+
Deploy your Vercube application to AWS Lambda with API Gateway:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// lambda.ts
|
|
59
|
+
import { createApp } from '@vercube/core';
|
|
60
|
+
import { toServerlessHandler } from '@vercube/serverless/aws-lambda';
|
|
61
|
+
|
|
62
|
+
const app = createApp();
|
|
63
|
+
export const handler = toServerlessHandler(app);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Serverless Framework Configuration
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
# serverless.yml
|
|
70
|
+
service: vercube-app
|
|
71
|
+
|
|
72
|
+
provider:
|
|
73
|
+
name: aws
|
|
74
|
+
runtime: nodejs22.x
|
|
75
|
+
region: us-east-1
|
|
76
|
+
|
|
77
|
+
functions:
|
|
78
|
+
api:
|
|
79
|
+
handler: lambda.handler
|
|
80
|
+
events:
|
|
81
|
+
- http:
|
|
82
|
+
path: /{proxy+}
|
|
83
|
+
method: ANY
|
|
84
|
+
cors: true
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 🔧 Supported Platforms
|
|
90
|
+
|
|
91
|
+
### AWS Lambda
|
|
92
|
+
|
|
93
|
+
Full support for AWS Lambda with API Gateway integration:
|
|
94
|
+
|
|
95
|
+
- **API Gateway v1** - Complete compatibility with `APIGatewayProxyEvent`
|
|
96
|
+
- **API Gateway v2** - Full support for `APIGatewayProxyEventV2`
|
|
97
|
+
- **Binary Content** - Automatic base64 encoding for binary responses
|
|
98
|
+
- **Cookies** - Proper cookie handling for both API Gateway versions
|
|
99
|
+
- **Headers** - Complete header conversion and processing
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 📋 API Reference
|
|
104
|
+
|
|
105
|
+
### `toServerlessHandler(app: App)`
|
|
106
|
+
|
|
107
|
+
Converts a Vercube App instance into a serverless handler function.
|
|
108
|
+
|
|
109
|
+
**Parameters:**
|
|
110
|
+
- `app` - The Vercube App instance that will handle requests
|
|
111
|
+
|
|
112
|
+
**Returns:**
|
|
113
|
+
- An async function that accepts serverless events and returns platform-specific responses
|
|
114
|
+
|
|
115
|
+
**Example:**
|
|
116
|
+
```ts
|
|
117
|
+
import { createApp } from '@vercube/core';
|
|
118
|
+
import { toServerlessHandler } from '@vercube/serverless/aws-lambda';
|
|
119
|
+
|
|
120
|
+
const app = createApp();
|
|
121
|
+
export const handler = toServerlessHandler(app);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 🔄 Request/Response Conversion
|
|
127
|
+
|
|
128
|
+
The serverless adapters handle automatic conversion between platform-specific events and standard web requests:
|
|
129
|
+
|
|
130
|
+
### Request Conversion
|
|
131
|
+
- **HTTP Method** - Extracted from event properties
|
|
132
|
+
- **URL Construction** - Built from path, query parameters, and headers
|
|
133
|
+
- **Headers** - Converted to standard Headers object
|
|
134
|
+
- **Body** - Properly decoded and converted to Request body
|
|
135
|
+
|
|
136
|
+
### Response Conversion
|
|
137
|
+
- **Status Code** - Mapped from Response status
|
|
138
|
+
- **Headers** - Converted to platform-specific format
|
|
139
|
+
- **Body** - Encoded appropriately (text vs binary)
|
|
140
|
+
- **Cookies** - Handled for both API Gateway versions
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 🚀 Performance Considerations
|
|
145
|
+
|
|
146
|
+
- **Streaming Support** - Efficient handling of large request/response bodies
|
|
147
|
+
- **Memory Optimization** - Minimal memory footprint for serverless environments
|
|
148
|
+
- **Cold Start Optimization** - Fast initialization and request processing
|
|
149
|
+
- **Binary Content** - Optimized base64 encoding for binary responses
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 🔍 Debugging
|
|
154
|
+
|
|
155
|
+
Enable debug logging to troubleshoot serverless deployments:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { createApp } from '@vercube/core';
|
|
159
|
+
import { toServerlessHandler } from '@vercube/serverless/aws-lambda';
|
|
160
|
+
|
|
161
|
+
const app = createApp({
|
|
162
|
+
logger: {
|
|
163
|
+
level: 'debug'
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export const handler = toServerlessHandler(app);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 📚 Documentation
|
|
173
|
+
|
|
174
|
+
Full documentation is available at [**vercube.dev**](https://vercube.dev).
|
|
175
|
+
Explore guides, API references, and best practices to master Vercube serverless deployment.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 🙌 Credits
|
|
180
|
+
|
|
181
|
+
This module is inspired by:
|
|
182
|
+
|
|
183
|
+
* [Nitro AWS Lambda Preset](https://nitro.build/presets/aws-lambda)
|
|
184
|
+
* [Hono AWS Lambda Adapter](https://hono.dev/guides/aws-lambda)
|
|
185
|
+
* [Vercel Serverless Functions](https://vercel.com/docs/functions)
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 🪪 License
|
|
190
|
+
|
|
191
|
+
[MIT License](https://github.com/vercube/vercube/blob/main/LICENSE)
|
|
192
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ServerlessHandler } from "../../ServerlessTypes-lXnSKHNA.mjs";
|
|
2
|
+
import { App } from "@vercube/core";
|
|
3
|
+
import { APIGatewayProxyEvent, APIGatewayProxyEventV2 } from "aws-lambda";
|
|
4
|
+
|
|
5
|
+
//#region src/Adapters/aws-lambda/index.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Converts a Vercube App instance into an AWS Lambda handler function for API Gateway integration.
|
|
9
|
+
*
|
|
10
|
+
* This function creates a serverless handler that bridges between AWS API Gateway events and
|
|
11
|
+
* the Vercube application framework. It handles the conversion of AWS Lambda events to standard
|
|
12
|
+
* web Request objects, processes them through the Vercube app, and converts the responses back
|
|
13
|
+
* to the format expected by AWS API Gateway.
|
|
14
|
+
*
|
|
15
|
+
* The handler supports both API Gateway v1 (APIGatewayProxyEvent) and v2 (APIGatewayProxyEventV2)
|
|
16
|
+
* event formats, automatically detecting and handling the appropriate format. It processes:
|
|
17
|
+
* - HTTP method, URL, headers, and body from the Lambda event
|
|
18
|
+
* - Converts them to a standard web Request object
|
|
19
|
+
* - Passes the request through the Vercube application
|
|
20
|
+
* - Converts the Response back to AWS API Gateway format
|
|
21
|
+
*
|
|
22
|
+
* The returned handler function can be directly used as an AWS Lambda function handler,
|
|
23
|
+
* providing seamless integration between AWS Lambda and Vercube applications.
|
|
24
|
+
*
|
|
25
|
+
* @param app - The Vercube App instance that will handle the requests
|
|
26
|
+
* @returns An async function that accepts AWS API Gateway events and returns API Gateway responses
|
|
27
|
+
*
|
|
28
|
+
* @see {@link convertEventToRequest} For details on event to request conversion
|
|
29
|
+
* @see {@link convertResponseToAWSResponse} For details on response header conversion
|
|
30
|
+
* @see {@link convertBodyToAWSResponse} For details on response body conversion
|
|
31
|
+
*/
|
|
32
|
+
declare function toServerlessHandler(app: App): ServerlessHandler<APIGatewayProxyEvent | APIGatewayProxyEventV2>;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { toServerlessHandler };
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { stringifyQuery } from "ufo";
|
|
2
|
+
|
|
3
|
+
//#region src/Adapters/aws-lambda/Utils/Request.ts
|
|
4
|
+
const DEFAULT_METHOD = "GET";
|
|
5
|
+
const DEFAULT_HOSTNAME = ".";
|
|
6
|
+
const HTTP_PROTOCOL = "http";
|
|
7
|
+
const HTTPS_PROTOCOL = "https";
|
|
8
|
+
const HEADER_KEYS = {
|
|
9
|
+
HOST: ["host", "Host"],
|
|
10
|
+
X_FORWARDED_PROTO: ["X-Forwarded-Proto", "x-forwarded-proto"],
|
|
11
|
+
COOKIE: "cookie"
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Type guard to check if an event is APIGatewayProxyEventV2
|
|
15
|
+
*/
|
|
16
|
+
function isV2Event(event) {
|
|
17
|
+
return "requestContext" in event && "http" in event.requestContext && "method" in event.requestContext.http;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Type guard to check if an event is APIGatewayProxyEvent
|
|
21
|
+
*/
|
|
22
|
+
function isV1Event(event) {
|
|
23
|
+
return "httpMethod" in event;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Safely gets a header value with case-insensitive fallback
|
|
27
|
+
*/
|
|
28
|
+
function getHeaderValue(headers, keys) {
|
|
29
|
+
if (!headers) return void 0;
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
const value = headers[key];
|
|
32
|
+
if (value !== void 0) return value;
|
|
33
|
+
}
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extracts the HTTP method from an API Gateway event.
|
|
38
|
+
*
|
|
39
|
+
* Handles both v1 and v2 event formats:
|
|
40
|
+
* - v1: Uses `httpMethod` property
|
|
41
|
+
* - v2: Uses `requestContext.http.method` property
|
|
42
|
+
*
|
|
43
|
+
* @param event - The AWS API Gateway event object (v1 or v2 format)
|
|
44
|
+
* @returns The HTTP method as a string, defaults to 'GET' if not found
|
|
45
|
+
*/
|
|
46
|
+
function getEventMethod(event) {
|
|
47
|
+
if (isV1Event(event)) return event.httpMethod || DEFAULT_METHOD;
|
|
48
|
+
if (isV2Event(event)) return event.requestContext?.http?.method || DEFAULT_METHOD;
|
|
49
|
+
return DEFAULT_METHOD;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Constructs a complete URL from an API Gateway event.
|
|
53
|
+
*
|
|
54
|
+
* Builds the URL by combining:
|
|
55
|
+
* - Protocol (http/https based on X-Forwarded-Proto header)
|
|
56
|
+
* - Hostname (from headers or requestContext)
|
|
57
|
+
* - Path (from event path properties)
|
|
58
|
+
* - Query string (from query parameters)
|
|
59
|
+
*
|
|
60
|
+
* @param event - The AWS API Gateway event object (v1 or v2 format)
|
|
61
|
+
* @returns A complete URL object
|
|
62
|
+
*/
|
|
63
|
+
function getEventUrl(event) {
|
|
64
|
+
const hostname = getEventHostname(event);
|
|
65
|
+
const path = getEventPath(event);
|
|
66
|
+
const query = getEventQuery(event);
|
|
67
|
+
const protocol = getEventProtocol(event);
|
|
68
|
+
const urlPath = query ? `${path}?${query}` : path;
|
|
69
|
+
return new URL(urlPath, `${protocol}://${hostname}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Extracts the hostname from the event
|
|
73
|
+
*/
|
|
74
|
+
function getEventHostname(event) {
|
|
75
|
+
const hostHeader = getHeaderValue(event.headers, HEADER_KEYS.HOST);
|
|
76
|
+
if (hostHeader) return hostHeader;
|
|
77
|
+
return event.requestContext?.domainName || DEFAULT_HOSTNAME;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Extracts the path from the event
|
|
81
|
+
*/
|
|
82
|
+
function getEventPath(event) {
|
|
83
|
+
if (isV1Event(event)) return event.path || "/";
|
|
84
|
+
if (isV2Event(event)) return event.rawPath || "/";
|
|
85
|
+
return "/";
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Determines the protocol from the event headers
|
|
89
|
+
*/
|
|
90
|
+
function getEventProtocol(event) {
|
|
91
|
+
const forwardedProto = getHeaderValue(event.headers, HEADER_KEYS.X_FORWARDED_PROTO);
|
|
92
|
+
return forwardedProto === HTTP_PROTOCOL ? HTTP_PROTOCOL : HTTPS_PROTOCOL;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Extracts and formats query parameters from an API Gateway event.
|
|
96
|
+
*
|
|
97
|
+
* Handles both v1 and v2 event formats:
|
|
98
|
+
* - v2: Uses `rawQueryString` if available
|
|
99
|
+
* - v1: Combines `queryStringParameters` and `multiValueQueryStringParameters`
|
|
100
|
+
*
|
|
101
|
+
* @param event - The AWS API Gateway event object (v1 or v2 format)
|
|
102
|
+
* @returns A formatted query string (without the leading '?')
|
|
103
|
+
*/
|
|
104
|
+
function getEventQuery(event) {
|
|
105
|
+
if (isV2Event(event) && typeof event.rawQueryString === "string") return event.rawQueryString;
|
|
106
|
+
const queryParams = event.queryStringParameters || {};
|
|
107
|
+
const multiValueParams = isV1Event(event) ? event.multiValueQueryStringParameters || {} : {};
|
|
108
|
+
const combinedParams = {
|
|
109
|
+
...queryParams,
|
|
110
|
+
...multiValueParams
|
|
111
|
+
};
|
|
112
|
+
return stringifyQuery(combinedParams);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Converts API Gateway event headers to a standard Headers object.
|
|
116
|
+
*
|
|
117
|
+
* Processes all headers from the event and handles cookies specially:
|
|
118
|
+
* - Sets all event headers in the Headers object
|
|
119
|
+
* - Appends cookies from the event's cookies array (v2 format)
|
|
120
|
+
*
|
|
121
|
+
* @param event - The AWS API Gateway event object (v1 or v2 format)
|
|
122
|
+
* @returns A Headers object with all event headers and cookies
|
|
123
|
+
*/
|
|
124
|
+
function getEventHeaders(event) {
|
|
125
|
+
const headers = new Headers();
|
|
126
|
+
if (event.headers) {
|
|
127
|
+
for (const [key, value] of Object.entries(event.headers)) if (value !== void 0 && value !== null) headers.set(key, value);
|
|
128
|
+
}
|
|
129
|
+
if (isV2Event(event) && event.cookies && Array.isArray(event.cookies)) {
|
|
130
|
+
for (const cookie of event.cookies) if (cookie) headers.append(HEADER_KEYS.COOKIE, cookie);
|
|
131
|
+
}
|
|
132
|
+
return headers;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Extracts the request body from an API Gateway event.
|
|
136
|
+
*
|
|
137
|
+
* Handles different body formats:
|
|
138
|
+
* - Returns undefined if no body is present
|
|
139
|
+
* - Decodes base64-encoded bodies when `isBase64Encoded` is true
|
|
140
|
+
* - Returns the raw body string for regular requests
|
|
141
|
+
*
|
|
142
|
+
* @param event - The AWS API Gateway event object (v1 or v2 format)
|
|
143
|
+
* @returns The request body as BodyInit (string, Buffer, or undefined)
|
|
144
|
+
*/
|
|
145
|
+
function getEventBody(event) {
|
|
146
|
+
if (!event.body || event.body === null) return void 0;
|
|
147
|
+
if (event.isBase64Encoded) try {
|
|
148
|
+
return Buffer.from(event.body, "base64");
|
|
149
|
+
} catch {
|
|
150
|
+
return event.body;
|
|
151
|
+
}
|
|
152
|
+
return event.body;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Converts an AWS API Gateway event to a standard Request object.
|
|
156
|
+
*
|
|
157
|
+
* This function handles both APIGatewayProxyEvent (v1) and APIGatewayProxyEventV2 (v2) formats,
|
|
158
|
+
* extracting the HTTP method, URL, headers, and body to create a web-standard Request object.
|
|
159
|
+
*
|
|
160
|
+
* This file is highly inspired by the `awsRequest` from `nitro`
|
|
161
|
+
* @see https://github.com/nitrojs/nitro/blob/v3/src/presets/aws-lambda/runtime/_utils.ts
|
|
162
|
+
*
|
|
163
|
+
* @param event - The AWS API Gateway event object (v1 or v2 format)
|
|
164
|
+
* @returns A new Request object with the extracted event data
|
|
165
|
+
* @throws {Error} If the event is invalid or missing required properties
|
|
166
|
+
*/
|
|
167
|
+
function convertEventToRequest(event) {
|
|
168
|
+
if (!event || typeof event !== "object") throw new Error("Invalid event: event must be a valid object");
|
|
169
|
+
const method = getEventMethod(event);
|
|
170
|
+
const url = getEventUrl(event);
|
|
171
|
+
const headers = getEventHeaders(event);
|
|
172
|
+
const body = getEventBody(event);
|
|
173
|
+
return new Request(url, {
|
|
174
|
+
method,
|
|
175
|
+
headers,
|
|
176
|
+
body
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/Adapters/aws-lambda/Utils/Response.ts
|
|
182
|
+
const DEFAULT_BODY = "";
|
|
183
|
+
const DEFAULT_CONTENT_TYPE = "";
|
|
184
|
+
const UTF8_ENCODING = "utf8";
|
|
185
|
+
const BASE64_ENCODING = "base64";
|
|
186
|
+
const TEXT_CONTENT_TYPE_PATTERNS = [
|
|
187
|
+
/^text\//,
|
|
188
|
+
/^application\/(json|javascript|xml|xml\+text|x-www-form-urlencoded)$/,
|
|
189
|
+
/^application\/.*\+json$/,
|
|
190
|
+
/^application\/.*\+xml$/,
|
|
191
|
+
/utf-?8/
|
|
192
|
+
];
|
|
193
|
+
/**
|
|
194
|
+
* Converts a standard web Response object to AWS API Gateway compatible response format.
|
|
195
|
+
*
|
|
196
|
+
* This function transforms the Response headers and cookies into the format expected by
|
|
197
|
+
* AWS API Gateway proxy integrations. It handles both v1 and v2 API Gateway formats:
|
|
198
|
+
* - v1: Uses `multiValueHeaders` for cookies
|
|
199
|
+
* - v2: Uses `cookies` array for cookies
|
|
200
|
+
*
|
|
201
|
+
* The function processes all response headers, converting them to the appropriate format
|
|
202
|
+
* for AWS Lambda integration. Headers with multiple values are joined with commas,
|
|
203
|
+
* and cookies are handled specially for both API Gateway versions.
|
|
204
|
+
*
|
|
205
|
+
* @param response - The standard web Response object to convert
|
|
206
|
+
* @returns An object containing headers and cookies in AWS API Gateway format
|
|
207
|
+
* @throws {Error} If the response object is invalid or headers cannot be processed
|
|
208
|
+
*/
|
|
209
|
+
function convertResponseToAWSResponse(response) {
|
|
210
|
+
if (!response || !response.headers) throw new Error("Invalid response: response must be a valid Response object with headers");
|
|
211
|
+
const headers = Object.create(null);
|
|
212
|
+
response.headers.forEach((value, key) => {
|
|
213
|
+
if (value !== void 0 && value !== null) headers[key] = Array.isArray(value) ? value.join(",") : String(value);
|
|
214
|
+
else if (value === null) headers[key] = "null";
|
|
215
|
+
else if (value === void 0) headers[key] = "undefined";
|
|
216
|
+
});
|
|
217
|
+
const cookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
|
|
218
|
+
if (cookies.length > 0) return {
|
|
219
|
+
headers,
|
|
220
|
+
cookies,
|
|
221
|
+
multiValueHeaders: { "set-cookie": cookies }
|
|
222
|
+
};
|
|
223
|
+
return { headers };
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Converts a Response body to AWS API Gateway compatible format with proper encoding.
|
|
227
|
+
*
|
|
228
|
+
* AWS Lambda proxy integrations require special handling for binary content:
|
|
229
|
+
* - Text content types are returned as UTF-8 strings
|
|
230
|
+
* - Binary content types are base64 encoded with the `isBase64Encoded` flag set
|
|
231
|
+
*
|
|
232
|
+
* This function determines the appropriate encoding based on the response's content-type
|
|
233
|
+
* header and converts the body accordingly. It supports both text and binary content
|
|
234
|
+
* types, ensuring compatibility with API Gateway's payload encoding requirements.
|
|
235
|
+
*
|
|
236
|
+
* Binary media types should be configured as * in API Gateway settings.
|
|
237
|
+
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html
|
|
238
|
+
*
|
|
239
|
+
* This function is heavily inspired by the `awsResponseBody` from `nitro`
|
|
240
|
+
* @see https://github.com/nitrojs/nitro/blob/v3/src/presets/aws-lambda/runtime/_utils.ts
|
|
241
|
+
*
|
|
242
|
+
* @param response - The standard web Response object containing the body to convert
|
|
243
|
+
* @returns A promise that resolves to an object with the encoded body and encoding flag
|
|
244
|
+
* @throws {Error} If the response body cannot be read or converted
|
|
245
|
+
*/
|
|
246
|
+
async function convertBodyToAWSResponse(response) {
|
|
247
|
+
if (!response) throw new Error("Invalid response: response must be a valid Response object");
|
|
248
|
+
if (!response.body) return { body: DEFAULT_BODY };
|
|
249
|
+
try {
|
|
250
|
+
const buffer = await toBuffer(response.body);
|
|
251
|
+
const contentType = response.headers.get("content-type") || DEFAULT_CONTENT_TYPE;
|
|
252
|
+
if (isTextType(contentType)) return { body: buffer.toString(UTF8_ENCODING) };
|
|
253
|
+
else return {
|
|
254
|
+
body: buffer.toString(BASE64_ENCODING),
|
|
255
|
+
isBase64Encoded: true
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
259
|
+
throw new Error(`Failed to convert response body: ${errorMessage}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Determines if a content type should be treated as text content.
|
|
264
|
+
*
|
|
265
|
+
* This function uses a set of patterns to identify text-based content types:
|
|
266
|
+
* - Content types starting with "text/" (e.g., text/plain, text/html)
|
|
267
|
+
* - JavaScript, JSON, or XML content types
|
|
268
|
+
* - Content types containing UTF-8 encoding specification
|
|
269
|
+
*
|
|
270
|
+
* The function performs case-insensitive matching to handle various content type
|
|
271
|
+
* formats and specifications.
|
|
272
|
+
*
|
|
273
|
+
* @param contentType - The content type string to evaluate (e.g., "text/plain", "application/json")
|
|
274
|
+
* @returns True if the content type should be treated as text, false otherwise
|
|
275
|
+
*/
|
|
276
|
+
function isTextType(contentType = DEFAULT_CONTENT_TYPE) {
|
|
277
|
+
if (!contentType) return false;
|
|
278
|
+
return TEXT_CONTENT_TYPE_PATTERNS.some((pattern) => pattern.test(contentType));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Converts a ReadableStream to a Buffer using Web Streams API.
|
|
282
|
+
*
|
|
283
|
+
* This function reads all chunks from a ReadableStream and concatenates them
|
|
284
|
+
* into a single Buffer. It uses the modern Web Streams API with WritableStream
|
|
285
|
+
* to efficiently process the stream data without blocking.
|
|
286
|
+
*
|
|
287
|
+
* The function handles stream errors gracefully and provides proper cleanup
|
|
288
|
+
* of stream resources. It's designed to work with Response.body streams
|
|
289
|
+
* from fetch API responses.
|
|
290
|
+
*
|
|
291
|
+
* @param data - The ReadableStream to convert to a Buffer
|
|
292
|
+
* @returns A promise that resolves to a Buffer containing all stream data
|
|
293
|
+
* @throws {Error} If the stream cannot be read or an error occurs during processing
|
|
294
|
+
*/
|
|
295
|
+
function toBuffer(data) {
|
|
296
|
+
return new Promise((resolve, reject) => {
|
|
297
|
+
const chunks = [];
|
|
298
|
+
const writableStream = new WritableStream({
|
|
299
|
+
write(chunk) {
|
|
300
|
+
chunks.push(chunk);
|
|
301
|
+
},
|
|
302
|
+
close() {
|
|
303
|
+
resolve(Buffer.concat(chunks));
|
|
304
|
+
},
|
|
305
|
+
abort(reason) {
|
|
306
|
+
reject(/* @__PURE__ */ new Error("Stream aborted: " + String(reason)));
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
data.pipeTo(writableStream).catch(reject);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/Adapters/aws-lambda/index.ts
|
|
315
|
+
/**
|
|
316
|
+
* Converts a Vercube App instance into an AWS Lambda handler function for API Gateway integration.
|
|
317
|
+
*
|
|
318
|
+
* This function creates a serverless handler that bridges between AWS API Gateway events and
|
|
319
|
+
* the Vercube application framework. It handles the conversion of AWS Lambda events to standard
|
|
320
|
+
* web Request objects, processes them through the Vercube app, and converts the responses back
|
|
321
|
+
* to the format expected by AWS API Gateway.
|
|
322
|
+
*
|
|
323
|
+
* The handler supports both API Gateway v1 (APIGatewayProxyEvent) and v2 (APIGatewayProxyEventV2)
|
|
324
|
+
* event formats, automatically detecting and handling the appropriate format. It processes:
|
|
325
|
+
* - HTTP method, URL, headers, and body from the Lambda event
|
|
326
|
+
* - Converts them to a standard web Request object
|
|
327
|
+
* - Passes the request through the Vercube application
|
|
328
|
+
* - Converts the Response back to AWS API Gateway format
|
|
329
|
+
*
|
|
330
|
+
* The returned handler function can be directly used as an AWS Lambda function handler,
|
|
331
|
+
* providing seamless integration between AWS Lambda and Vercube applications.
|
|
332
|
+
*
|
|
333
|
+
* @param app - The Vercube App instance that will handle the requests
|
|
334
|
+
* @returns An async function that accepts AWS API Gateway events and returns API Gateway responses
|
|
335
|
+
*
|
|
336
|
+
* @see {@link convertEventToRequest} For details on event to request conversion
|
|
337
|
+
* @see {@link convertResponseToAWSResponse} For details on response header conversion
|
|
338
|
+
* @see {@link convertBodyToAWSResponse} For details on response body conversion
|
|
339
|
+
*/
|
|
340
|
+
function toServerlessHandler(app) {
|
|
341
|
+
return async (event) => {
|
|
342
|
+
const request = convertEventToRequest(event);
|
|
343
|
+
const response = await app.fetch(request);
|
|
344
|
+
return {
|
|
345
|
+
statusCode: response.status,
|
|
346
|
+
...convertResponseToAWSResponse(response),
|
|
347
|
+
...await convertBodyToAWSResponse(response)
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
//#endregion
|
|
353
|
+
export { toServerlessHandler };
|
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vercube/serverless",
|
|
3
|
+
"version": "0.0.22",
|
|
4
|
+
"description": "Serverless module for Vercube framework",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/vercube/vercube.git",
|
|
8
|
+
"directory": "packages/serverless"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.mjs",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./dist/index.mjs",
|
|
17
|
+
"./package.json": "./package.json",
|
|
18
|
+
"./aws-lambda": "./dist/Adapters/aws-lambda/index.mjs"
|
|
19
|
+
},
|
|
20
|
+
"types": "./dist/index.d.mts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"vercube",
|
|
27
|
+
"serverless",
|
|
28
|
+
"aws",
|
|
29
|
+
"lambda",
|
|
30
|
+
"aws-lambda",
|
|
31
|
+
"aws-serverless",
|
|
32
|
+
"aws-api-gateway",
|
|
33
|
+
"aws-apigw",
|
|
34
|
+
"aws-apigw-lambda"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"ufo": "1.6.1",
|
|
38
|
+
"@vercube/core": "0.0.22",
|
|
39
|
+
"@vercube/di": "0.0.22"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/aws-lambda": "8.10.152",
|
|
43
|
+
"@azure/functions": "4.7.2-preview"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsdown --config tsdown.config.ts"
|
|
50
|
+
}
|
|
51
|
+
}
|