@vercube/serverless 0.0.23 → 0.0.25
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/README.md +52 -5
- package/dist/Adapters/aws-lambda/index.d.mts +1 -1
- package/dist/Adapters/aws-lambda/index.mjs +43 -81
- package/dist/Adapters/azure-functions/index.d.mts +32 -0
- package/dist/Adapters/azure-functions/index.mjs +192 -0
- package/dist/index.d.mts +1 -1
- package/dist/streams-jCOA2riB.mjs +108 -0
- package/package.json +7 -6
- /package/dist/{ServerlessTypes-lXnSKHNA.d.mts → ServerlessTypes-WMcCL6v7.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -34,10 +34,11 @@ The `@vercube/serverless` module provides unified, provider-agnostic adapters fo
|
|
|
34
34
|
### ✅ Key Features
|
|
35
35
|
|
|
36
36
|
- **AWS Lambda Integration** - Full support for API Gateway v1 and v2
|
|
37
|
+
- **Azure Functions Integration** - Complete support for Azure Functions HTTP triggers
|
|
37
38
|
- **Zero Configuration** - Works out-of-the-box with existing Vercube apps
|
|
38
39
|
- **Type Safety** - Complete TypeScript support with proper type definitions
|
|
39
40
|
- **Binary Support** - Automatic handling of binary content with base64 encoding
|
|
40
|
-
- **Cookie Support** - Proper cookie handling for both API Gateway versions
|
|
41
|
+
- **Cookie Support** - Proper cookie handling for both API Gateway versions and Azure Functions
|
|
41
42
|
- **Error Handling** - Robust error handling and validation
|
|
42
43
|
- **Performance Optimized** - Efficient request/response conversion
|
|
43
44
|
|
|
@@ -66,6 +67,30 @@ const app = createApp();
|
|
|
66
67
|
export const handler = toServerlessHandler(app);
|
|
67
68
|
```
|
|
68
69
|
|
|
70
|
+
### Azure Functions Integration
|
|
71
|
+
|
|
72
|
+
Deploy your Vercube application to Azure Functions:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// httpTrigger.ts
|
|
76
|
+
import { createApp } from '@vercube/core';
|
|
77
|
+
import { toServerlessHandler } from '@vercube/serverless/azure-functions';
|
|
78
|
+
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
|
79
|
+
|
|
80
|
+
const vercubeApp = createApp();
|
|
81
|
+
const handler = toServerlessHandler(vercubeApp);
|
|
82
|
+
|
|
83
|
+
export async function httpTrigger(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
|
|
84
|
+
return await handler(request);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
app.http('httpTrigger', {
|
|
88
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
|
|
89
|
+
authLevel: 'anonymous',
|
|
90
|
+
handler: httpTrigger,
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
69
94
|
### Serverless Framework Configuration
|
|
70
95
|
|
|
71
96
|
```yaml
|
|
@@ -101,6 +126,16 @@ Full support for AWS Lambda with API Gateway integration:
|
|
|
101
126
|
- **Cookies** - Proper cookie handling for both API Gateway versions
|
|
102
127
|
- **Headers** - Complete header conversion and processing
|
|
103
128
|
|
|
129
|
+
### Azure Functions
|
|
130
|
+
|
|
131
|
+
Complete support for Azure Functions HTTP triggers:
|
|
132
|
+
|
|
133
|
+
- **HTTP Triggers** - Full compatibility with Azure Functions HTTP triggers
|
|
134
|
+
- **Request/Response Conversion** - Seamless conversion between Azure Functions and web standards
|
|
135
|
+
- **Cookie Support** - Proper handling of Set-Cookie headers and cookie parsing
|
|
136
|
+
- **Headers** - Complete header conversion and processing
|
|
137
|
+
- **Streaming Support** - Efficient handling of request/response bodies with AsyncIterableIterator
|
|
138
|
+
|
|
104
139
|
---
|
|
105
140
|
|
|
106
141
|
## 📋 API Reference
|
|
@@ -117,9 +152,10 @@ Converts a Vercube App instance into a serverless handler function.
|
|
|
117
152
|
|
|
118
153
|
- An async function that accepts serverless events and returns platform-specific responses
|
|
119
154
|
|
|
120
|
-
**
|
|
155
|
+
**Examples:**
|
|
121
156
|
|
|
122
157
|
```ts
|
|
158
|
+
// AWS Lambda
|
|
123
159
|
import { createApp } from '@vercube/core';
|
|
124
160
|
import { toServerlessHandler } from '@vercube/serverless/aws-lambda';
|
|
125
161
|
|
|
@@ -127,6 +163,15 @@ const app = createApp();
|
|
|
127
163
|
export const handler = toServerlessHandler(app);
|
|
128
164
|
```
|
|
129
165
|
|
|
166
|
+
```ts
|
|
167
|
+
// Azure Functions
|
|
168
|
+
import { createApp } from '@vercube/core';
|
|
169
|
+
import { toServerlessHandler } from '@vercube/serverless/azure-functions';
|
|
170
|
+
|
|
171
|
+
const app = createApp();
|
|
172
|
+
export const handler = toServerlessHandler(app);
|
|
173
|
+
```
|
|
174
|
+
|
|
130
175
|
---
|
|
131
176
|
|
|
132
177
|
## 🔄 Request/Response Conversion
|
|
@@ -144,8 +189,8 @@ The serverless adapters handle automatic conversion between platform-specific ev
|
|
|
144
189
|
|
|
145
190
|
- **Status Code** - Mapped from Response status
|
|
146
191
|
- **Headers** - Converted to platform-specific format
|
|
147
|
-
- **Body** - Encoded appropriately (text vs binary)
|
|
148
|
-
- **Cookies** - Handled for both API Gateway versions
|
|
192
|
+
- **Body** - Encoded appropriately (text vs binary for AWS, AsyncIterableIterator for Azure)
|
|
193
|
+
- **Cookies** - Handled for both API Gateway versions and Azure Functions
|
|
149
194
|
|
|
150
195
|
---
|
|
151
196
|
|
|
@@ -154,7 +199,8 @@ The serverless adapters handle automatic conversion between platform-specific ev
|
|
|
154
199
|
- **Streaming Support** - Efficient handling of large request/response bodies
|
|
155
200
|
- **Memory Optimization** - Minimal memory footprint for serverless environments
|
|
156
201
|
- **Cold Start Optimization** - Fast initialization and request processing
|
|
157
|
-
- **Binary Content** - Optimized base64 encoding for binary responses
|
|
202
|
+
- **Binary Content** - Optimized base64 encoding for binary responses (AWS Lambda)
|
|
203
|
+
- **AsyncIterableIterator** - Efficient streaming for Azure Functions responses
|
|
158
204
|
|
|
159
205
|
---
|
|
160
206
|
|
|
@@ -191,6 +237,7 @@ This module is inspired by:
|
|
|
191
237
|
- [Nitro AWS Lambda Preset](https://nitro.build/presets/aws-lambda)
|
|
192
238
|
- [Hono AWS Lambda Adapter](https://hono.dev/guides/aws-lambda)
|
|
193
239
|
- [Vercel Serverless Functions](https://vercel.com/docs/functions)
|
|
240
|
+
- [hono-azurefunc-adapter](https://github.com/Marplex/hono-azurefunc-adapter)
|
|
194
241
|
|
|
195
242
|
---
|
|
196
243
|
|
|
@@ -1,15 +1,52 @@
|
|
|
1
|
+
import { getHeaderValue, toBuffer } from "../../streams-jCOA2riB.mjs";
|
|
1
2
|
import { stringifyQuery } from "ufo";
|
|
2
3
|
|
|
3
|
-
//#region src/
|
|
4
|
+
//#region src/Utils/content-type.ts
|
|
5
|
+
const DEFAULT_CONTENT_TYPE$1 = "";
|
|
6
|
+
const TEXT_CONTENT_TYPE_PATTERNS = [
|
|
7
|
+
/^text\//,
|
|
8
|
+
/^application\/(json|javascript|xml|xml\+text|x-www-form-urlencoded)$/,
|
|
9
|
+
/^application\/.*\+json$/,
|
|
10
|
+
/^application\/.*\+xml$/,
|
|
11
|
+
/utf-?8/
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Determines if a content type should be treated as text content.
|
|
15
|
+
*
|
|
16
|
+
* This function uses a set of patterns to identify text-based content types:
|
|
17
|
+
* - Content types starting with "text/" (e.g., text/plain, text/html)
|
|
18
|
+
* - JavaScript, JSON, or XML content types
|
|
19
|
+
* - Content types containing UTF-8 encoding specification
|
|
20
|
+
*
|
|
21
|
+
* The function performs case-insensitive matching to handle various content type
|
|
22
|
+
* formats and specifications.
|
|
23
|
+
*
|
|
24
|
+
* @param contentType - The content type string to evaluate (e.g., "text/plain", "application/json")
|
|
25
|
+
* @returns True if the content type should be treated as text, false otherwise
|
|
26
|
+
*/
|
|
27
|
+
function isTextType(contentType = DEFAULT_CONTENT_TYPE$1) {
|
|
28
|
+
if (!contentType) return false;
|
|
29
|
+
return TEXT_CONTENT_TYPE_PATTERNS.some((pattern) => pattern.test(contentType));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/Utils/constants.ts
|
|
4
34
|
const DEFAULT_METHOD = "GET";
|
|
5
35
|
const DEFAULT_HOSTNAME = ".";
|
|
6
36
|
const HTTP_PROTOCOL = "http";
|
|
7
37
|
const HTTPS_PROTOCOL = "https";
|
|
38
|
+
const DEFAULT_BODY = "";
|
|
39
|
+
const DEFAULT_CONTENT_TYPE = "";
|
|
40
|
+
const UTF8_ENCODING = "utf8";
|
|
41
|
+
const BASE64_ENCODING = "base64";
|
|
8
42
|
const HEADER_KEYS = {
|
|
9
43
|
HOST: ["host", "Host"],
|
|
10
44
|
X_FORWARDED_PROTO: ["X-Forwarded-Proto", "x-forwarded-proto"],
|
|
11
45
|
COOKIE: "cookie"
|
|
12
46
|
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/Adapters/aws-lambda/Utils/Request.ts
|
|
13
50
|
/**
|
|
14
51
|
* Type guard to check if an event is APIGatewayProxyEventV2
|
|
15
52
|
*/
|
|
@@ -23,17 +60,6 @@ function isV1Event(event) {
|
|
|
23
60
|
return "httpMethod" in event;
|
|
24
61
|
}
|
|
25
62
|
/**
|
|
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
63
|
* Extracts the HTTP method from an API Gateway event.
|
|
38
64
|
*
|
|
39
65
|
* Handles both v1 and v2 event formats:
|
|
@@ -88,8 +114,7 @@ function getEventPath(event) {
|
|
|
88
114
|
* Determines the protocol from the event headers
|
|
89
115
|
*/
|
|
90
116
|
function getEventProtocol(event) {
|
|
91
|
-
|
|
92
|
-
return forwardedProto === HTTP_PROTOCOL ? HTTP_PROTOCOL : HTTPS_PROTOCOL;
|
|
117
|
+
return getHeaderValue(event.headers, HEADER_KEYS.X_FORWARDED_PROTO) === HTTP_PROTOCOL ? HTTP_PROTOCOL : HTTPS_PROTOCOL;
|
|
93
118
|
}
|
|
94
119
|
/**
|
|
95
120
|
* Extracts and formats query parameters from an API Gateway event.
|
|
@@ -105,11 +130,10 @@ function getEventQuery(event) {
|
|
|
105
130
|
if (isV2Event(event) && typeof event.rawQueryString === "string") return event.rawQueryString;
|
|
106
131
|
const queryParams = event.queryStringParameters || {};
|
|
107
132
|
const multiValueParams = isV1Event(event) ? event.multiValueQueryStringParameters || {} : {};
|
|
108
|
-
|
|
133
|
+
return stringifyQuery({
|
|
109
134
|
...queryParams,
|
|
110
135
|
...multiValueParams
|
|
111
|
-
};
|
|
112
|
-
return stringifyQuery(combinedParams);
|
|
136
|
+
});
|
|
113
137
|
}
|
|
114
138
|
/**
|
|
115
139
|
* Converts API Gateway event headers to a standard Headers object.
|
|
@@ -143,7 +167,7 @@ function getEventHeaders(event) {
|
|
|
143
167
|
* @returns The request body as BodyInit (string, Buffer, or undefined)
|
|
144
168
|
*/
|
|
145
169
|
function getEventBody(event) {
|
|
146
|
-
if (!event.body || event.body === null) return
|
|
170
|
+
if (!event.body || event.body === null) return;
|
|
147
171
|
if (event.isBase64Encoded) try {
|
|
148
172
|
return Buffer.from(event.body, "base64");
|
|
149
173
|
} catch {
|
|
@@ -179,17 +203,6 @@ function convertEventToRequest(event) {
|
|
|
179
203
|
|
|
180
204
|
//#endregion
|
|
181
205
|
//#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
206
|
/**
|
|
194
207
|
* Converts a standard web Response object to AWS API Gateway compatible response format.
|
|
195
208
|
*
|
|
@@ -248,8 +261,7 @@ async function convertBodyToAWSResponse(response) {
|
|
|
248
261
|
if (!response.body) return { body: DEFAULT_BODY };
|
|
249
262
|
try {
|
|
250
263
|
const buffer = await toBuffer(response.body);
|
|
251
|
-
|
|
252
|
-
if (isTextType(contentType)) return { body: buffer.toString(UTF8_ENCODING) };
|
|
264
|
+
if (isTextType(response.headers.get("content-type") || DEFAULT_CONTENT_TYPE)) return { body: buffer.toString(UTF8_ENCODING) };
|
|
253
265
|
else return {
|
|
254
266
|
body: buffer.toString(BASE64_ENCODING),
|
|
255
267
|
isBase64Encoded: true
|
|
@@ -259,56 +271,6 @@ async function convertBodyToAWSResponse(response) {
|
|
|
259
271
|
throw new Error(`Failed to convert response body: ${errorMessage}`);
|
|
260
272
|
}
|
|
261
273
|
}
|
|
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
274
|
|
|
313
275
|
//#endregion
|
|
314
276
|
//#region src/Adapters/aws-lambda/index.ts
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ServerlessHandler } from "../../ServerlessTypes-WMcCL6v7.mjs";
|
|
2
|
+
import { App } from "@vercube/core";
|
|
3
|
+
import { HttpRequest } from "@azure/functions";
|
|
4
|
+
|
|
5
|
+
//#region src/Adapters/azure-functions/index.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Converts a Vercube App instance into an Azure Functions handler function for HTTP integration.
|
|
9
|
+
*
|
|
10
|
+
* This function creates a serverless handler that bridges between Azure Functions HTTP triggers
|
|
11
|
+
* and the Vercube application framework. It handles the conversion of Azure Functions HttpRequest
|
|
12
|
+
* objects to standard web Request objects, processes them through the Vercube app, and converts
|
|
13
|
+
* the responses back to the format expected by Azure Functions.
|
|
14
|
+
*
|
|
15
|
+
* The handler processes:
|
|
16
|
+
* - HTTP method, URL, headers, and body from the Azure Functions HttpRequest
|
|
17
|
+
* - Converts them to a standard web Request object
|
|
18
|
+
* - Passes the request through the Vercube application
|
|
19
|
+
* - Converts the Response back to Azure Functions HttpResponseInit format
|
|
20
|
+
*
|
|
21
|
+
* The returned handler function can be directly used as an Azure Functions HTTP trigger handler,
|
|
22
|
+
* providing seamless integration between Azure Functions and Vercube applications.
|
|
23
|
+
*
|
|
24
|
+
* @param app - The Vercube App instance that will handle the requests
|
|
25
|
+
* @returns An async function that accepts Azure Functions HttpRequest and returns HttpResponseInit
|
|
26
|
+
*
|
|
27
|
+
* @see {@link convertEventToRequest} For details on HttpRequest to Request conversion
|
|
28
|
+
* @see {@link convertResponseToAzureFunctionsResponse} For details on Response to HttpResponseInit conversion
|
|
29
|
+
*/
|
|
30
|
+
declare function toServerlessHandler(app: App): ServerlessHandler<HttpRequest, any>;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { toServerlessHandler };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { headersToObject, streamToAsyncIterator } from "../../streams-jCOA2riB.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/Utils/cookies.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parses a cookie string into a generic cookie object.
|
|
6
|
+
*
|
|
7
|
+
* This function parses a standard Set-Cookie header string and extracts all cookie attributes
|
|
8
|
+
* including name, value, path, sameSite, secure, httpOnly, domain, expires, and maxAge.
|
|
9
|
+
* It handles URL decoding of the cookie value and proper type conversion for boolean and
|
|
10
|
+
* numeric attributes.
|
|
11
|
+
*
|
|
12
|
+
* The function expects cookie strings in the format:
|
|
13
|
+
* "name=value; path=/; secure; httpOnly; sameSite=Strict; domain=example.com; expires=Wed, 09 Jun 2021 10:18:14 GMT; max-age=3600"
|
|
14
|
+
*
|
|
15
|
+
* @param cookieString - The Set-Cookie header string to parse
|
|
16
|
+
* @returns A GenericCookie object with all parsed attributes
|
|
17
|
+
* @throws {Error} If the cookie string format is invalid or cannot be parsed
|
|
18
|
+
*/
|
|
19
|
+
function parseCookieString(cookieString) {
|
|
20
|
+
if (!cookieString || typeof cookieString !== "string") throw new Error("Invalid cookie string: must be a non-empty string");
|
|
21
|
+
const [nameValue, ...attributeParts] = cookieString.split(";");
|
|
22
|
+
const [nameRaw, encodedValue] = nameValue.split("=");
|
|
23
|
+
const name = nameRaw ? nameRaw.trim().toLowerCase() : "";
|
|
24
|
+
if (!name || encodedValue === void 0) throw new Error("Invalid cookie string: must contain a name and value separated by \"=\"");
|
|
25
|
+
const attributesArray = attributeParts.map((part) => part.split("=")).map(([key, value]) => [key.trim().toLowerCase(), value ?? "true"]);
|
|
26
|
+
const attrs = Object.fromEntries(attributesArray);
|
|
27
|
+
return {
|
|
28
|
+
name,
|
|
29
|
+
value: encodedValue ? decodeURIComponent(encodedValue) : "",
|
|
30
|
+
path: attrs["path"],
|
|
31
|
+
sameSite: attrs["samesite"],
|
|
32
|
+
secure: attrs["secure"] === "true",
|
|
33
|
+
httpOnly: attrs["httponly"] === "true",
|
|
34
|
+
domain: attrs["domain"],
|
|
35
|
+
expires: attrs["expires"] ? new Date(attrs["expires"]) : void 0,
|
|
36
|
+
maxAge: attrs["max-age"] ? Number.parseInt(attrs["max-age"], 10) : void 0
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extracts cookies from a Headers object and converts them to generic cookie format.
|
|
41
|
+
*
|
|
42
|
+
* This function processes the Set-Cookie headers from a standard web Response Headers object
|
|
43
|
+
* and converts them into generic cookie objects. It handles the case where no cookies are
|
|
44
|
+
* present by returning undefined.
|
|
45
|
+
*
|
|
46
|
+
* @param headers - The Headers object from a web Response
|
|
47
|
+
* @returns An array of GenericCookie objects, or undefined if no cookies are present
|
|
48
|
+
*/
|
|
49
|
+
function cookiesFromHeaders$1(headers) {
|
|
50
|
+
const cookies = headers.getSetCookie();
|
|
51
|
+
if (cookies.length === 0) return void 0;
|
|
52
|
+
return cookies.map(parseCookieString);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/Adapters/azure-functions/Utils/Request.ts
|
|
57
|
+
/**
|
|
58
|
+
* Converts an Azure Functions HttpRequest to a standard web Request object.
|
|
59
|
+
*
|
|
60
|
+
* This function bridges the gap between Azure Functions HTTP triggers and the standard
|
|
61
|
+
* web Request API. It extracts the HTTP method, URL, headers, and body from the Azure
|
|
62
|
+
* Functions HttpRequest and creates a standard Request object that can be used with
|
|
63
|
+
* the Vercube application framework.
|
|
64
|
+
*
|
|
65
|
+
* The function handles:
|
|
66
|
+
* - HTTP method extraction from the request
|
|
67
|
+
* - URL preservation from the Azure Functions request
|
|
68
|
+
* - Header conversion from Azure Functions format to standard Headers
|
|
69
|
+
* - Body handling for non-GET/HEAD requests with proper duplex stream support
|
|
70
|
+
*
|
|
71
|
+
* This file is highly inspired by the `newRequestFromAzureFunctions` from `hono-azurefunc-adapter`
|
|
72
|
+
* @see https://github.com/Marplex/hono-azurefunc-adapter/blob/main/src/request.ts
|
|
73
|
+
*
|
|
74
|
+
* @param request - The Azure Functions HttpRequest object to convert
|
|
75
|
+
* @returns A standard web Request object compatible with the fetch API
|
|
76
|
+
* @throws {Error} If the request object is invalid or missing required properties
|
|
77
|
+
*/
|
|
78
|
+
function convertEventToRequest(request) {
|
|
79
|
+
const hasBody = !["GET", "HEAD"].includes(request.method);
|
|
80
|
+
return new Request(request.url, {
|
|
81
|
+
method: request.method,
|
|
82
|
+
headers: headersToObject(request.headers),
|
|
83
|
+
...hasBody ? {
|
|
84
|
+
body: request.body,
|
|
85
|
+
duplex: "half"
|
|
86
|
+
} : {}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/Adapters/azure-functions/Utils/Utils.ts
|
|
92
|
+
/**
|
|
93
|
+
* Extracts cookies from a Headers object and converts them to Azure Functions Cookie format.
|
|
94
|
+
*
|
|
95
|
+
* This function processes the Set-Cookie headers from a standard web Response Headers object
|
|
96
|
+
* and converts them into the Cookie format expected by Azure Functions. It handles the case
|
|
97
|
+
* where no cookies are present by returning undefined.
|
|
98
|
+
*
|
|
99
|
+
* @param headers - The Headers object from a web Response
|
|
100
|
+
* @returns An array of Cookie objects, or undefined if no cookies are present
|
|
101
|
+
*/
|
|
102
|
+
function cookiesFromHeaders(headers) {
|
|
103
|
+
const genericCookies = cookiesFromHeaders$1(headers);
|
|
104
|
+
if (!genericCookies) return void 0;
|
|
105
|
+
return genericCookies.map(convertGenericCookieToAzure);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Converts a generic cookie object to Azure Functions Cookie format.
|
|
109
|
+
*
|
|
110
|
+
* @param genericCookie - The generic cookie object to convert
|
|
111
|
+
* @returns An Azure Functions Cookie object
|
|
112
|
+
*/
|
|
113
|
+
function convertGenericCookieToAzure(genericCookie) {
|
|
114
|
+
return {
|
|
115
|
+
name: genericCookie.name,
|
|
116
|
+
value: genericCookie.value,
|
|
117
|
+
path: genericCookie.path,
|
|
118
|
+
sameSite: genericCookie.sameSite,
|
|
119
|
+
secure: genericCookie.secure,
|
|
120
|
+
httpOnly: genericCookie.httpOnly,
|
|
121
|
+
domain: genericCookie.domain,
|
|
122
|
+
expires: genericCookie.expires,
|
|
123
|
+
maxAge: genericCookie.maxAge
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/Adapters/azure-functions/Utils/Response.ts
|
|
129
|
+
/**
|
|
130
|
+
* Converts a standard web Response object to Azure Functions HttpResponseInit format.
|
|
131
|
+
*
|
|
132
|
+
* This function transforms a standard web Response object into the format expected by
|
|
133
|
+
* Azure Functions HTTP responses. It handles the conversion of headers, cookies, status
|
|
134
|
+
* code, and body to ensure compatibility with Azure Functions runtime.
|
|
135
|
+
*
|
|
136
|
+
* The function processes:
|
|
137
|
+
* - Response headers conversion to plain object format
|
|
138
|
+
* - Set-Cookie headers extraction and conversion to Azure Functions Cookie format
|
|
139
|
+
* - HTTP status code preservation
|
|
140
|
+
* - Response body conversion to AsyncIterableIterator for Azure Functions compatibility
|
|
141
|
+
*
|
|
142
|
+
* This file is highly inspired by the `newAzureFunctionsResponse` from `hono-azurefunc-adapter`
|
|
143
|
+
* @see https://github.com/Marplex/hono-azurefunc-adapter/blob/main/src/response.ts
|
|
144
|
+
*
|
|
145
|
+
* @param response - The standard web Response object to convert
|
|
146
|
+
* @returns An HttpResponseInit object compatible with Azure Functions
|
|
147
|
+
* @throws {Error} If the response object is invalid or missing required properties
|
|
148
|
+
*/
|
|
149
|
+
function convertResponseToAzureFunctionsResponse(response) {
|
|
150
|
+
const headers = headersToObject(response.headers);
|
|
151
|
+
return {
|
|
152
|
+
cookies: cookiesFromHeaders(response.headers),
|
|
153
|
+
headers,
|
|
154
|
+
status: response.status,
|
|
155
|
+
body: streamToAsyncIterator(response.body)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/Adapters/azure-functions/index.ts
|
|
161
|
+
/**
|
|
162
|
+
* Converts a Vercube App instance into an Azure Functions handler function for HTTP integration.
|
|
163
|
+
*
|
|
164
|
+
* This function creates a serverless handler that bridges between Azure Functions HTTP triggers
|
|
165
|
+
* and the Vercube application framework. It handles the conversion of Azure Functions HttpRequest
|
|
166
|
+
* objects to standard web Request objects, processes them through the Vercube app, and converts
|
|
167
|
+
* the responses back to the format expected by Azure Functions.
|
|
168
|
+
*
|
|
169
|
+
* The handler processes:
|
|
170
|
+
* - HTTP method, URL, headers, and body from the Azure Functions HttpRequest
|
|
171
|
+
* - Converts them to a standard web Request object
|
|
172
|
+
* - Passes the request through the Vercube application
|
|
173
|
+
* - Converts the Response back to Azure Functions HttpResponseInit format
|
|
174
|
+
*
|
|
175
|
+
* The returned handler function can be directly used as an Azure Functions HTTP trigger handler,
|
|
176
|
+
* providing seamless integration between Azure Functions and Vercube applications.
|
|
177
|
+
*
|
|
178
|
+
* @param app - The Vercube App instance that will handle the requests
|
|
179
|
+
* @returns An async function that accepts Azure Functions HttpRequest and returns HttpResponseInit
|
|
180
|
+
*
|
|
181
|
+
* @see {@link convertEventToRequest} For details on HttpRequest to Request conversion
|
|
182
|
+
* @see {@link convertResponseToAzureFunctionsResponse} For details on Response to HttpResponseInit conversion
|
|
183
|
+
*/
|
|
184
|
+
function toServerlessHandler(app) {
|
|
185
|
+
return async (event) => {
|
|
186
|
+
const request = convertEventToRequest(event);
|
|
187
|
+
return convertResponseToAzureFunctionsResponse(await app.fetch(request));
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
export { toServerlessHandler };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { ServerlessHandler } from "./ServerlessTypes-
|
|
1
|
+
import { ServerlessHandler } from "./ServerlessTypes-WMcCL6v7.mjs";
|
|
2
2
|
export { ServerlessHandler };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//#region src/Utils/headers.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts a loopable header object to a plain JavaScript object.
|
|
4
|
+
*
|
|
5
|
+
* This function is used to convert header objects (which have a forEach method)
|
|
6
|
+
* into standard JavaScript objects with string keys and values. This is necessary for
|
|
7
|
+
* compatibility with the standard web Headers API and other parts of the application.
|
|
8
|
+
*
|
|
9
|
+
* @param input - A header-like object with a forEach method
|
|
10
|
+
* @returns A plain object with string keys and string values representing the headers
|
|
11
|
+
*/
|
|
12
|
+
function headersToObject(input) {
|
|
13
|
+
const headers = {};
|
|
14
|
+
input.forEach((value, key) => {
|
|
15
|
+
headers[key] = value;
|
|
16
|
+
});
|
|
17
|
+
return headers;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Safely gets a header value with case-insensitive fallback.
|
|
21
|
+
*
|
|
22
|
+
* This utility function searches through a headers object using multiple possible
|
|
23
|
+
* key variations to find a header value. It's useful for handling headers that
|
|
24
|
+
* might have different casing across different platforms.
|
|
25
|
+
*
|
|
26
|
+
* @param headers - The headers object to search in
|
|
27
|
+
* @param keys - Array of possible header keys to try (in order of preference)
|
|
28
|
+
* @returns The header value if found, undefined otherwise
|
|
29
|
+
*/
|
|
30
|
+
function getHeaderValue(headers, keys) {
|
|
31
|
+
if (!headers) return;
|
|
32
|
+
for (const key of keys) {
|
|
33
|
+
const value = headers[key];
|
|
34
|
+
if (value !== void 0) return value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/Utils/streams.ts
|
|
40
|
+
/**
|
|
41
|
+
* Converts a ReadableStream to a Buffer using Web Streams API.
|
|
42
|
+
*
|
|
43
|
+
* This function reads all chunks from a ReadableStream and concatenates them
|
|
44
|
+
* into a single Buffer. It uses the modern Web Streams API with WritableStream
|
|
45
|
+
* to efficiently process the stream data without blocking.
|
|
46
|
+
*
|
|
47
|
+
* The function handles stream errors gracefully and provides proper cleanup
|
|
48
|
+
* of stream resources. It's designed to work with Response.body streams
|
|
49
|
+
* from fetch API responses.
|
|
50
|
+
*
|
|
51
|
+
* @param data - The ReadableStream to convert to a Buffer
|
|
52
|
+
* @returns A promise that resolves to a Buffer containing all stream data
|
|
53
|
+
* @throws {Error} If the stream cannot be read or an error occurs during processing
|
|
54
|
+
*/
|
|
55
|
+
function toBuffer(data) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const chunks = [];
|
|
58
|
+
const writableStream = new WritableStream({
|
|
59
|
+
write(chunk) {
|
|
60
|
+
chunks.push(chunk);
|
|
61
|
+
},
|
|
62
|
+
close() {
|
|
63
|
+
resolve(Buffer.concat(chunks));
|
|
64
|
+
},
|
|
65
|
+
abort(reason) {
|
|
66
|
+
reject(/* @__PURE__ */ new Error("Stream aborted: " + String(reason)));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
data.pipeTo(writableStream).catch(reject);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Converts a ReadableStream to an AsyncIterableIterator for platform compatibility.
|
|
74
|
+
*
|
|
75
|
+
* Some serverless platforms expect response bodies to be AsyncIterableIterator<Uint8Array>.
|
|
76
|
+
* This function wraps a standard web ReadableStream to provide the required interface
|
|
77
|
+
* for serverless HTTP responses.
|
|
78
|
+
*
|
|
79
|
+
* The returned iterator provides:
|
|
80
|
+
* - `next()` method that reads chunks from the stream
|
|
81
|
+
* - `return()` method that releases the stream reader lock
|
|
82
|
+
* - Symbol.asyncIterator for async iteration support
|
|
83
|
+
*
|
|
84
|
+
* @param readable - The ReadableStream from a Response body, or null/undefined
|
|
85
|
+
* @returns An AsyncIterableIterator<Uint8Array> or null if no readable stream provided
|
|
86
|
+
*/
|
|
87
|
+
function streamToAsyncIterator(readable) {
|
|
88
|
+
if (readable == null) return null;
|
|
89
|
+
const reader = readable.getReader();
|
|
90
|
+
return {
|
|
91
|
+
next() {
|
|
92
|
+
return reader.read();
|
|
93
|
+
},
|
|
94
|
+
return() {
|
|
95
|
+
reader.releaseLock();
|
|
96
|
+
return Promise.resolve({
|
|
97
|
+
done: true,
|
|
98
|
+
value: void 0
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
[Symbol.asyncIterator]() {
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
export { getHeaderValue, headersToObject, streamToAsyncIterator, toBuffer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercube/serverless",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"description": "Serverless module for Vercube framework",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"exports": {
|
|
16
16
|
".": "./dist/index.mjs",
|
|
17
17
|
"./package.json": "./package.json",
|
|
18
|
-
"./aws-lambda": "./dist/Adapters/aws-lambda/index.mjs"
|
|
18
|
+
"./aws-lambda": "./dist/Adapters/aws-lambda/index.mjs",
|
|
19
|
+
"./azure-functions": "./dist/Adapters/azure-functions/index.mjs"
|
|
19
20
|
},
|
|
20
21
|
"types": "./dist/index.d.mts",
|
|
21
22
|
"files": [
|
|
@@ -35,12 +36,12 @@
|
|
|
35
36
|
],
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"ufo": "1.6.1",
|
|
38
|
-
"@vercube/core": "0.0.
|
|
39
|
-
"@vercube/di": "0.0.
|
|
39
|
+
"@vercube/core": "0.0.25",
|
|
40
|
+
"@vercube/di": "0.0.25"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
43
|
+
"@azure/functions": "4.8.0",
|
|
44
|
+
"@types/aws-lambda": "8.10.155"
|
|
44
45
|
},
|
|
45
46
|
"publishConfig": {
|
|
46
47
|
"access": "public"
|
|
File without changes
|