@hazeljs/serverless 0.2.0-beta.71 → 0.2.0-beta.73
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 +13 -21
- package/dist/adapters.test.js +16 -0
- package/dist/cloud-function.adapter.d.ts +16 -4
- package/dist/cloud-function.adapter.d.ts.map +1 -1
- package/dist/cloud-function.adapter.js +17 -10
- package/dist/cold-start.optimizer.d.ts +7 -4
- package/dist/cold-start.optimizer.d.ts.map +1 -1
- package/dist/cold-start.optimizer.js +8 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integration.test.d.ts +6 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +191 -0
- package/dist/lambda.adapter.d.ts +18 -2
- package/dist/lambda.adapter.d.ts.map +1 -1
- package/dist/lambda.adapter.js +44 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -167,6 +167,8 @@ export const hazelApp = createCloudFunctionHandler(AppModule, {
|
|
|
167
167
|
});
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
**Event handler (Pub/Sub, Storage):** `createCloudFunctionEventHandler(Module)` returns a stub that initializes the app and logs the event but does not route to user code. Implement your own event handling and call it from that handler, or use a separate entrypoint for event-triggered functions.
|
|
171
|
+
|
|
170
172
|
### Deployment
|
|
171
173
|
|
|
172
174
|
#### Using gcloud CLI
|
|
@@ -204,36 +206,28 @@ gcloud functions deploy hazeljs-app --config function.yaml
|
|
|
204
206
|
|
|
205
207
|
## Decorator-Based Optimization
|
|
206
208
|
|
|
207
|
-
Mark
|
|
209
|
+
Mark **controllers** (classes) for serverless optimization with `@Serverless`:
|
|
208
210
|
|
|
209
211
|
```typescript
|
|
210
212
|
import { Controller, Get } from '@hazeljs/core';
|
|
211
213
|
import { Serverless } from '@hazeljs/serverless';
|
|
212
214
|
|
|
213
215
|
@Controller('/api')
|
|
216
|
+
@Serverless({ memory: 512, timeout: 30, coldStartOptimization: true })
|
|
214
217
|
export class ApiController {
|
|
215
218
|
@Get('/hello')
|
|
216
|
-
@Serverless({ optimize: true })
|
|
217
219
|
hello() {
|
|
218
220
|
return { message: 'Hello from serverless!' };
|
|
219
221
|
}
|
|
220
|
-
|
|
221
|
-
@Get('/data')
|
|
222
|
-
@Serverless({
|
|
223
|
-
optimize: true,
|
|
224
|
-
cache: {
|
|
225
|
-
enabled: true,
|
|
226
|
-
ttl: 300, // 5 minutes
|
|
227
|
-
},
|
|
228
|
-
})
|
|
229
|
-
getData() {
|
|
230
|
-
return { data: 'cached response' };
|
|
231
|
-
}
|
|
232
222
|
}
|
|
233
223
|
```
|
|
234
224
|
|
|
225
|
+
> **Note:** The decorator is class-level only. Method-level `@Serverless({ optimize, cache })` and per-route `binaryResponse` are planned for a future release.
|
|
226
|
+
|
|
235
227
|
## Cold Start Optimization
|
|
236
228
|
|
|
229
|
+
**ColdStartOptimizer** preloads the DI container and built-in modules (http, https, crypto, buffer); behavior is best-effort and may vary by Node version. **KeepAliveHelper** runs an interval but does not perform an actual HTTP request—use provisioned concurrency, a cron job, or implement `fetch`/`http.get` in the interval to warm the function.
|
|
230
|
+
|
|
237
231
|
### Minimize Bundle Size
|
|
238
232
|
|
|
239
233
|
```typescript
|
|
@@ -328,14 +322,12 @@ getLambdaInfo(@Req() req: any) {
|
|
|
328
322
|
|
|
329
323
|
### Binary Responses
|
|
330
324
|
|
|
325
|
+
You can pass `binaryMimeTypes` in `createLambdaHandler` options for future use. Automatic base64 encoding of binary response bodies for Lambda is planned.
|
|
326
|
+
|
|
331
327
|
```typescript
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const image = await fs.readFile('image.png');
|
|
336
|
-
res.setHeader('Content-Type', 'image/png');
|
|
337
|
-
res.send(image);
|
|
338
|
-
}
|
|
328
|
+
export const handler = createLambdaHandler(AppModule, {
|
|
329
|
+
binaryMimeTypes: ['image/png', 'application/pdf'],
|
|
330
|
+
});
|
|
339
331
|
```
|
|
340
332
|
|
|
341
333
|
## Logging
|
package/dist/adapters.test.js
CHANGED
|
@@ -134,6 +134,22 @@ describe('LambdaAdapter', () => {
|
|
|
134
134
|
const result = await new lambda_adapter_1.LambdaAdapter(MockModule).createHandler()(makeEvent(), makeContext());
|
|
135
135
|
expect(result.statusCode).toBe(500);
|
|
136
136
|
});
|
|
137
|
+
it('returns base64 body and isBase64Encoded when binaryMimeTypes matches and handler sends Buffer', async () => {
|
|
138
|
+
const binaryPayload = Buffer.from('fake-png-bytes');
|
|
139
|
+
mockMatch.mockResolvedValue({
|
|
140
|
+
handler: async (_req, res) => {
|
|
141
|
+
res.setHeader('Content-Type', 'image/png');
|
|
142
|
+
res.send(binaryPayload);
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const adapter = new lambda_adapter_1.LambdaAdapter(MockModule, {
|
|
146
|
+
binaryMimeTypes: ['image/png', 'image/*'],
|
|
147
|
+
});
|
|
148
|
+
const result = await adapter.createHandler()(makeEvent(), makeContext());
|
|
149
|
+
expect(result.statusCode).toBe(200);
|
|
150
|
+
expect(result.isBase64Encoded).toBe(true);
|
|
151
|
+
expect(Buffer.from(result.body, 'base64').toString()).toBe('fake-png-bytes');
|
|
152
|
+
});
|
|
137
153
|
});
|
|
138
154
|
describe('createHandler() – request conversion', () => {
|
|
139
155
|
it('parses JSON body', async () => {
|
|
@@ -22,15 +22,25 @@ export interface CloudFunctionResponse {
|
|
|
22
22
|
json(body: unknown): void;
|
|
23
23
|
end(): void;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Options for createCloudFunctionHandler
|
|
27
|
+
*/
|
|
28
|
+
export interface CloudFunctionHandlerOptions {
|
|
29
|
+
/** Called after the HazelJS app is initialized. */
|
|
30
|
+
onInit?: (app: HazelApp) => Promise<void>;
|
|
31
|
+
/** Called when the handler throws. */
|
|
32
|
+
onError?: (error: unknown) => void;
|
|
33
|
+
}
|
|
25
34
|
/**
|
|
26
35
|
* Cloud Function adapter for HazelJS
|
|
27
36
|
*/
|
|
28
37
|
export declare class CloudFunctionAdapter {
|
|
29
38
|
private moduleClass;
|
|
39
|
+
private options;
|
|
30
40
|
private app?;
|
|
31
41
|
private optimizer;
|
|
32
42
|
private isColdStart;
|
|
33
|
-
constructor(moduleClass: Type<unknown
|
|
43
|
+
constructor(moduleClass: Type<unknown>, options?: CloudFunctionHandlerOptions);
|
|
34
44
|
/**
|
|
35
45
|
* Initialize the HazelJS application
|
|
36
46
|
*/
|
|
@@ -40,7 +50,9 @@ export declare class CloudFunctionAdapter {
|
|
|
40
50
|
*/
|
|
41
51
|
createHttpHandler(): (req: CloudFunctionRequest, res: CloudFunctionResponse) => Promise<void>;
|
|
42
52
|
/**
|
|
43
|
-
* Create Cloud Function event handler (for Pub/Sub, Storage, etc.)
|
|
53
|
+
* Create Cloud Function event handler (for Pub/Sub, Storage, etc.).
|
|
54
|
+
* Stub: initializes the app and logs the event but does not route to user-defined handlers.
|
|
55
|
+
* Implement custom event handling in your module or a separate entrypoint and invoke it from here.
|
|
44
56
|
*/
|
|
45
57
|
createEventHandler(): (event: unknown, context: unknown) => Promise<void>;
|
|
46
58
|
/**
|
|
@@ -80,7 +92,7 @@ export declare class CloudFunctionAdapter {
|
|
|
80
92
|
* export const handler = createCloudFunctionHandler(AppModule);
|
|
81
93
|
* ```
|
|
82
94
|
*/
|
|
83
|
-
export declare function createCloudFunctionHandler(moduleClass: Type<unknown
|
|
95
|
+
export declare function createCloudFunctionHandler(moduleClass: Type<unknown>, options?: CloudFunctionHandlerOptions): (req: CloudFunctionRequest, res: CloudFunctionResponse) => Promise<void>;
|
|
84
96
|
/**
|
|
85
97
|
* Create a Cloud Function event handler for a HazelJS module
|
|
86
98
|
*
|
|
@@ -93,5 +105,5 @@ export declare function createCloudFunctionHandler(moduleClass: Type<unknown>):
|
|
|
93
105
|
* export const handler = createCloudFunctionEventHandler(AppModule);
|
|
94
106
|
* ```
|
|
95
107
|
*/
|
|
96
|
-
export declare function createCloudFunctionEventHandler(moduleClass: Type<unknown
|
|
108
|
+
export declare function createCloudFunctionEventHandler(moduleClass: Type<unknown>, options?: CloudFunctionHandlerOptions): (event: unknown, context: unknown) => Promise<void>;
|
|
97
109
|
//# sourceMappingURL=cloud-function.adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-function.adapter.d.ts","sourceRoot":"","sources":["../src/cloud-function.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAKrC;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CAAC;IAC5C,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAAC;IACzD,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,GAAG,IAAI,IAAI,CAAC;CACb;AAED;;GAEG;AACH,qBAAa,oBAAoB;
|
|
1
|
+
{"version":3,"file":"cloud-function.adapter.d.ts","sourceRoot":"","sources":["../src/cloud-function.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAKrC;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CAAC;IAC5C,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAAC;IACzD,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,GAAG,IAAI,IAAI,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,mDAAmD;IACnD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,sCAAsC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAM7B,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,GAAG,CAAC,CAAW;IACvB,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,WAAW,CAAQ;gBAGjB,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAC1B,OAAO,GAAE,2BAAgC;IAKnD;;OAEG;YACW,UAAU;IAyBxB;;OAEG;IACH,iBAAiB,KACD,KAAK,oBAAoB,EAAE,KAAK,qBAAqB,KAAG,OAAO,CAAC,IAAI,CAAC;IAsCrF;;;;OAIG;IACH,kBAAkB,KACF,OAAO,OAAO,EAAE,SAAS,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC;IA0BhE;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAoBnC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAUxB;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB;;OAEG;YACW,cAAc;IA+G5B;;OAEG;IACH,MAAM,IAAI,QAAQ,GAAG,SAAS;IAI9B;;OAEG;IACH,MAAM,IAAI,OAAO;CAGlB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAC1B,OAAO,CAAC,EAAE,2BAA2B,GACpC,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAG1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAC1B,OAAO,CAAC,EAAE,2BAA2B,GACpC,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAGrD"}
|
|
@@ -14,8 +14,9 @@ const cold_start_optimizer_1 = require("./cold-start.optimizer");
|
|
|
14
14
|
* Cloud Function adapter for HazelJS
|
|
15
15
|
*/
|
|
16
16
|
class CloudFunctionAdapter {
|
|
17
|
-
constructor(moduleClass) {
|
|
17
|
+
constructor(moduleClass, options = {}) {
|
|
18
18
|
this.moduleClass = moduleClass;
|
|
19
|
+
this.options = options;
|
|
19
20
|
this.isColdStart = true;
|
|
20
21
|
this.optimizer = cold_start_optimizer_1.ColdStartOptimizer.getInstance();
|
|
21
22
|
}
|
|
@@ -35,6 +36,9 @@ class CloudFunctionAdapter {
|
|
|
35
36
|
}
|
|
36
37
|
// Create HazelJS application
|
|
37
38
|
this.app = new core_1.HazelApp(this.moduleClass);
|
|
39
|
+
if (this.options.onInit && this.app) {
|
|
40
|
+
await this.options.onInit(this.app);
|
|
41
|
+
}
|
|
38
42
|
const duration = Date.now() - startTime;
|
|
39
43
|
core_2.default.info(`Cloud Function initialization completed in ${duration}ms`);
|
|
40
44
|
}
|
|
@@ -65,6 +69,7 @@ class CloudFunctionAdapter {
|
|
|
65
69
|
res.send(response.body);
|
|
66
70
|
}
|
|
67
71
|
catch (error) {
|
|
72
|
+
this.options.onError?.(error);
|
|
68
73
|
core_2.default.error('Cloud Function handler error:', error);
|
|
69
74
|
res.status(500).json({
|
|
70
75
|
error: 'Internal Server Error',
|
|
@@ -74,7 +79,9 @@ class CloudFunctionAdapter {
|
|
|
74
79
|
};
|
|
75
80
|
}
|
|
76
81
|
/**
|
|
77
|
-
* Create Cloud Function event handler (for Pub/Sub, Storage, etc.)
|
|
82
|
+
* Create Cloud Function event handler (for Pub/Sub, Storage, etc.).
|
|
83
|
+
* Stub: initializes the app and logs the event but does not route to user-defined handlers.
|
|
84
|
+
* Implement custom event handling in your module or a separate entrypoint and invoke it from here.
|
|
78
85
|
*/
|
|
79
86
|
createEventHandler() {
|
|
80
87
|
return async (event, context) => {
|
|
@@ -90,11 +97,11 @@ class CloudFunctionAdapter {
|
|
|
90
97
|
eventType: context.eventType,
|
|
91
98
|
resource: context.resource,
|
|
92
99
|
});
|
|
93
|
-
//
|
|
94
|
-
// In a real implementation, this would route to appropriate handlers
|
|
100
|
+
// Stub: extend this to dispatch event to your handlers (e.g. Pub/Sub, Storage)
|
|
95
101
|
core_2.default.info('Event processed successfully');
|
|
96
102
|
}
|
|
97
103
|
catch (error) {
|
|
104
|
+
this.options.onError?.(error);
|
|
98
105
|
core_2.default.error('Cloud Function event handler error:', error);
|
|
99
106
|
throw error;
|
|
100
107
|
}
|
|
@@ -170,7 +177,7 @@ class CloudFunctionAdapter {
|
|
|
170
177
|
url: request.url,
|
|
171
178
|
headers: request.headers,
|
|
172
179
|
query: request.query,
|
|
173
|
-
params: context.params
|
|
180
|
+
params: route.context?.params ?? context.params ?? {},
|
|
174
181
|
body: request.body,
|
|
175
182
|
};
|
|
176
183
|
let responseBody;
|
|
@@ -196,7 +203,7 @@ class CloudFunctionAdapter {
|
|
|
196
203
|
return responseHeaders[key];
|
|
197
204
|
},
|
|
198
205
|
};
|
|
199
|
-
const result = await route.handler(syntheticReq, syntheticRes);
|
|
206
|
+
const result = await route.handler(syntheticReq, syntheticRes, route.context);
|
|
200
207
|
if (result !== undefined && responseBody === undefined) {
|
|
201
208
|
responseBody = result;
|
|
202
209
|
}
|
|
@@ -242,8 +249,8 @@ exports.CloudFunctionAdapter = CloudFunctionAdapter;
|
|
|
242
249
|
* export const handler = createCloudFunctionHandler(AppModule);
|
|
243
250
|
* ```
|
|
244
251
|
*/
|
|
245
|
-
function createCloudFunctionHandler(moduleClass) {
|
|
246
|
-
const adapter = new CloudFunctionAdapter(moduleClass);
|
|
252
|
+
function createCloudFunctionHandler(moduleClass, options) {
|
|
253
|
+
const adapter = new CloudFunctionAdapter(moduleClass, options ?? {});
|
|
247
254
|
return adapter.createHttpHandler();
|
|
248
255
|
}
|
|
249
256
|
/**
|
|
@@ -258,7 +265,7 @@ function createCloudFunctionHandler(moduleClass) {
|
|
|
258
265
|
* export const handler = createCloudFunctionEventHandler(AppModule);
|
|
259
266
|
* ```
|
|
260
267
|
*/
|
|
261
|
-
function createCloudFunctionEventHandler(moduleClass) {
|
|
262
|
-
const adapter = new CloudFunctionAdapter(moduleClass);
|
|
268
|
+
function createCloudFunctionEventHandler(moduleClass, options) {
|
|
269
|
+
const adapter = new CloudFunctionAdapter(moduleClass, options ?? {});
|
|
263
270
|
return adapter.createEventHandler();
|
|
264
271
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cold start optimization strategies
|
|
2
|
+
* Cold start optimization strategies.
|
|
3
|
+
* Best-effort and environment-dependent; preloads built-in modules and the DI container.
|
|
3
4
|
*/
|
|
4
5
|
export declare class ColdStartOptimizer {
|
|
5
6
|
private static instance;
|
|
@@ -21,7 +22,7 @@ export declare class ColdStartOptimizer {
|
|
|
21
22
|
*/
|
|
22
23
|
private preloadCriticalModules;
|
|
23
24
|
/**
|
|
24
|
-
* Setup connection pools
|
|
25
|
+
* Setup connection pools (placeholder; override or extend for DB/API connection warming).
|
|
25
26
|
*/
|
|
26
27
|
private setupConnectionPools;
|
|
27
28
|
/**
|
|
@@ -50,13 +51,15 @@ export declare class ColdStartOptimizer {
|
|
|
50
51
|
*/
|
|
51
52
|
export declare function OptimizeColdStart(): MethodDecorator;
|
|
52
53
|
/**
|
|
53
|
-
* Keep-alive helper to
|
|
54
|
+
* Keep-alive helper to schedule periodic pings.
|
|
55
|
+
* Does not perform an HTTP request; use a cron, external pinger, or implement fetch/http.get in the interval
|
|
56
|
+
* to actually warm the function.
|
|
54
57
|
*/
|
|
55
58
|
export declare class KeepAliveHelper {
|
|
56
59
|
private intervalId?;
|
|
57
60
|
private pingUrl?;
|
|
58
61
|
/**
|
|
59
|
-
* Start keep-alive
|
|
62
|
+
* Start keep-alive interval (logs only; implement your own HTTP ping or use provisioned concurrency for warming).
|
|
60
63
|
*/
|
|
61
64
|
start(url: string, intervalMs?: number): void;
|
|
62
65
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cold-start.optimizer.d.ts","sourceRoot":"","sources":["../src/cold-start.optimizer.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"cold-start.optimizer.d.ts","sourceRoot":"","sources":["../src/cold-start.optimizer.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAqB;IAC5C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO;IAEP,MAAM,CAAC,WAAW,IAAI,kBAAkB;IAOxC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B7B;;OAEG;YACW,sBAAsB;IAOpC;;OAEG;YACW,sBAAsB;IAgBpC;;OAEG;YACW,oBAAoB;IAIlC;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,kBAAkB,IAAI,MAAM,GAAG,SAAS;IAIxC;;OAEG;IACH,iBAAiB,IAAI,MAAM,GAAG,SAAS;IAKvC;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,mBAAmB,IAAI,MAAM,EAAE;CAGhC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,eAAe,CAgBnD;AAED;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAC,CAAiB;IACpC,OAAO,CAAC,OAAO,CAAC,CAAS;IAEzB;;OAEG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,GAAE,MAAsB,GAAG,IAAI;IAe5D;;OAEG;IACH,IAAI,IAAI,IAAI;CAOb"}
|
|
@@ -41,7 +41,8 @@ exports.OptimizeColdStart = OptimizeColdStart;
|
|
|
41
41
|
const core_1 = __importDefault(require("@hazeljs/core"));
|
|
42
42
|
const core_2 = require("@hazeljs/core");
|
|
43
43
|
/**
|
|
44
|
-
* Cold start optimization strategies
|
|
44
|
+
* Cold start optimization strategies.
|
|
45
|
+
* Best-effort and environment-dependent; preloads built-in modules and the DI container.
|
|
45
46
|
*/
|
|
46
47
|
class ColdStartOptimizer {
|
|
47
48
|
constructor() {
|
|
@@ -108,12 +109,10 @@ class ColdStartOptimizer {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
/**
|
|
111
|
-
* Setup connection pools
|
|
112
|
+
* Setup connection pools (placeholder; override or extend for DB/API connection warming).
|
|
112
113
|
*/
|
|
113
114
|
async setupConnectionPools() {
|
|
114
115
|
core_1.default.debug('Setting up connection pools...');
|
|
115
|
-
// Connection pools would be initialized here
|
|
116
|
-
// For now, just a placeholder
|
|
117
116
|
}
|
|
118
117
|
/**
|
|
119
118
|
* Check if the application is warmed up
|
|
@@ -168,19 +167,20 @@ function OptimizeColdStart() {
|
|
|
168
167
|
};
|
|
169
168
|
}
|
|
170
169
|
/**
|
|
171
|
-
* Keep-alive helper to
|
|
170
|
+
* Keep-alive helper to schedule periodic pings.
|
|
171
|
+
* Does not perform an HTTP request; use a cron, external pinger, or implement fetch/http.get in the interval
|
|
172
|
+
* to actually warm the function.
|
|
172
173
|
*/
|
|
173
174
|
class KeepAliveHelper {
|
|
174
175
|
/**
|
|
175
|
-
* Start keep-alive
|
|
176
|
+
* Start keep-alive interval (logs only; implement your own HTTP ping or use provisioned concurrency for warming).
|
|
176
177
|
*/
|
|
177
178
|
start(url, intervalMs = 5 * 60 * 1000) {
|
|
178
179
|
this.pingUrl = url;
|
|
179
180
|
this.intervalId = setInterval(async () => {
|
|
180
181
|
try {
|
|
181
182
|
core_1.default.debug(`Sending keep-alive ping to ${url}`);
|
|
182
|
-
//
|
|
183
|
-
// For now, just log it
|
|
183
|
+
// Implement fetch(url) or http.get(url) here to actually warm the function
|
|
184
184
|
}
|
|
185
185
|
catch (error) {
|
|
186
186
|
core_1.default.error('Keep-alive ping failed:', error);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { Serverless, getServerlessMetadata, isServerless, type ServerlessOptions, type ServerlessHandler, type ServerlessContext, type ServerlessEvent, type ServerlessResponse, } from './serverless.decorator';
|
|
5
5
|
export { ColdStartOptimizer, OptimizeColdStart, KeepAliveHelper } from './cold-start.optimizer';
|
|
6
|
-
export { LambdaAdapter, createLambdaHandler, type LambdaEvent, type LambdaContext, } from './lambda.adapter';
|
|
7
|
-
export { CloudFunctionAdapter, createCloudFunctionHandler, createCloudFunctionEventHandler, type CloudFunctionRequest, type CloudFunctionResponse, } from './cloud-function.adapter';
|
|
6
|
+
export { LambdaAdapter, createLambdaHandler, type LambdaEvent, type LambdaContext, type LambdaHandlerOptions, } from './lambda.adapter';
|
|
7
|
+
export { CloudFunctionAdapter, createCloudFunctionHandler, createCloudFunctionEventHandler, type CloudFunctionRequest, type CloudFunctionResponse, type CloudFunctionHandlerOptions, } from './cloud-function.adapter';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,KAAK,WAAW,EAChB,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,+BAA+B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,GACjC,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../src/integration.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
/**
|
|
16
|
+
* Integration tests: real HazelJS app through Lambda and Cloud Function adapters.
|
|
17
|
+
* No mocks of @hazeljs/core — uses real HazelApp, router, and controllers.
|
|
18
|
+
*/
|
|
19
|
+
require("reflect-metadata");
|
|
20
|
+
const core_1 = require("@hazeljs/core");
|
|
21
|
+
const lambda_adapter_1 = require("./lambda.adapter");
|
|
22
|
+
const cloud_function_adapter_1 = require("./cloud-function.adapter");
|
|
23
|
+
const cold_start_optimizer_1 = require("./cold-start.optimizer");
|
|
24
|
+
// ─── Minimal app for integration ───────────────────────────────────────────
|
|
25
|
+
let IntegrationController = class IntegrationController {
|
|
26
|
+
ping() {
|
|
27
|
+
return { ok: true, message: 'pong' };
|
|
28
|
+
}
|
|
29
|
+
getById(id) {
|
|
30
|
+
return { id };
|
|
31
|
+
}
|
|
32
|
+
echo(body) {
|
|
33
|
+
return { echoed: body };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
__decorate([
|
|
37
|
+
(0, core_1.Get)('/ping'),
|
|
38
|
+
__metadata("design:type", Function),
|
|
39
|
+
__metadata("design:paramtypes", []),
|
|
40
|
+
__metadata("design:returntype", void 0)
|
|
41
|
+
], IntegrationController.prototype, "ping", null);
|
|
42
|
+
__decorate([
|
|
43
|
+
(0, core_1.Get)('/params/:id'),
|
|
44
|
+
__param(0, (0, core_1.Param)('id')),
|
|
45
|
+
__metadata("design:type", Function),
|
|
46
|
+
__metadata("design:paramtypes", [String]),
|
|
47
|
+
__metadata("design:returntype", void 0)
|
|
48
|
+
], IntegrationController.prototype, "getById", null);
|
|
49
|
+
__decorate([
|
|
50
|
+
(0, core_1.Post)('/echo'),
|
|
51
|
+
__param(0, (0, core_1.Body)()),
|
|
52
|
+
__metadata("design:type", Function),
|
|
53
|
+
__metadata("design:paramtypes", [Object]),
|
|
54
|
+
__metadata("design:returntype", void 0)
|
|
55
|
+
], IntegrationController.prototype, "echo", null);
|
|
56
|
+
IntegrationController = __decorate([
|
|
57
|
+
(0, core_1.Controller)('/api')
|
|
58
|
+
], IntegrationController);
|
|
59
|
+
let IntegrationTestModule = class IntegrationTestModule {
|
|
60
|
+
};
|
|
61
|
+
IntegrationTestModule = __decorate([
|
|
62
|
+
(0, core_1.HazelModule)({
|
|
63
|
+
controllers: [IntegrationController],
|
|
64
|
+
})
|
|
65
|
+
], IntegrationTestModule);
|
|
66
|
+
// ─── Lambda helpers ──────────────────────────────────────────────────────────
|
|
67
|
+
function makeLambdaEvent(overrides = {}) {
|
|
68
|
+
return {
|
|
69
|
+
httpMethod: 'GET',
|
|
70
|
+
path: '/api/ping',
|
|
71
|
+
headers: { 'content-type': 'application/json' },
|
|
72
|
+
queryStringParameters: null,
|
|
73
|
+
body: null,
|
|
74
|
+
isBase64Encoded: false,
|
|
75
|
+
...overrides,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function makeLambdaContext(overrides = {}) {
|
|
79
|
+
return {
|
|
80
|
+
awsRequestId: 'int-test-req-1',
|
|
81
|
+
invokedFunctionArn: 'arn:aws:lambda:us-east-1:000:function:integration-test',
|
|
82
|
+
functionName: 'integration-test',
|
|
83
|
+
functionVersion: '$LATEST',
|
|
84
|
+
getRemainingTimeInMillis: () => 30000,
|
|
85
|
+
callbackWaitsForEmptyEventLoop: true,
|
|
86
|
+
...overrides,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// ─── Cloud Function helpers ──────────────────────────────────────────────────
|
|
90
|
+
function makeCloudReq(overrides = {}) {
|
|
91
|
+
return {
|
|
92
|
+
method: 'GET',
|
|
93
|
+
url: '/api/ping',
|
|
94
|
+
path: '/api/ping',
|
|
95
|
+
headers: { 'content-type': 'application/json' },
|
|
96
|
+
query: {},
|
|
97
|
+
...overrides,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function makeCloudRes() {
|
|
101
|
+
const state = {
|
|
102
|
+
_status: 200,
|
|
103
|
+
_body: undefined,
|
|
104
|
+
_headers: {},
|
|
105
|
+
};
|
|
106
|
+
return Object.assign(state, {
|
|
107
|
+
status(code) {
|
|
108
|
+
state._status = code;
|
|
109
|
+
return this;
|
|
110
|
+
},
|
|
111
|
+
set(field, value) {
|
|
112
|
+
state._headers[field] = value;
|
|
113
|
+
return this;
|
|
114
|
+
},
|
|
115
|
+
send(body) {
|
|
116
|
+
state._body = body;
|
|
117
|
+
},
|
|
118
|
+
json(body) {
|
|
119
|
+
state._body = body;
|
|
120
|
+
},
|
|
121
|
+
end() { },
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// ─── Integration tests ───────────────────────────────────────────────────────
|
|
125
|
+
describe('integration (real HazelApp)', () => {
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
cold_start_optimizer_1.ColdStartOptimizer.getInstance().reset();
|
|
128
|
+
});
|
|
129
|
+
describe('createLambdaHandler', () => {
|
|
130
|
+
const handler = (0, lambda_adapter_1.createLambdaHandler)(IntegrationTestModule);
|
|
131
|
+
it('GET /api/ping returns 200 and pong', async () => {
|
|
132
|
+
const result = await handler(makeLambdaEvent({ httpMethod: 'GET', path: '/api/ping' }), makeLambdaContext());
|
|
133
|
+
expect(result.statusCode).toBe(200);
|
|
134
|
+
const data = JSON.parse(result.body);
|
|
135
|
+
expect(data).toEqual({ ok: true, message: 'pong' });
|
|
136
|
+
});
|
|
137
|
+
it('GET /api/params/123 returns 200 and id', async () => {
|
|
138
|
+
const result = await handler(makeLambdaEvent({
|
|
139
|
+
httpMethod: 'GET',
|
|
140
|
+
path: '/api/params/123',
|
|
141
|
+
pathParameters: { id: '123' },
|
|
142
|
+
}), makeLambdaContext());
|
|
143
|
+
expect(result.statusCode).toBe(200);
|
|
144
|
+
const data = JSON.parse(result.body);
|
|
145
|
+
expect(data).toEqual({ id: '123' });
|
|
146
|
+
});
|
|
147
|
+
it('POST /api/echo returns 200 and echoed body', async () => {
|
|
148
|
+
const result = await handler(makeLambdaEvent({
|
|
149
|
+
httpMethod: 'POST',
|
|
150
|
+
path: '/api/echo',
|
|
151
|
+
body: JSON.stringify({ x: 1, text: 'hello' }),
|
|
152
|
+
}), makeLambdaContext());
|
|
153
|
+
expect(result.statusCode).toBe(200);
|
|
154
|
+
const data = JSON.parse(result.body);
|
|
155
|
+
expect(data).toEqual({ echoed: { x: 1, text: 'hello' } });
|
|
156
|
+
});
|
|
157
|
+
it('GET /api/missing returns 404', async () => {
|
|
158
|
+
const result = await handler(makeLambdaEvent({ httpMethod: 'GET', path: '/api/missing' }), makeLambdaContext());
|
|
159
|
+
expect(result.statusCode).toBe(404);
|
|
160
|
+
const data = JSON.parse(result.body);
|
|
161
|
+
expect(data.message).toBe('Route not found');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe('createCloudFunctionHandler', () => {
|
|
165
|
+
const handler = (0, cloud_function_adapter_1.createCloudFunctionHandler)(IntegrationTestModule);
|
|
166
|
+
it('GET /api/ping returns 200 and pong', async () => {
|
|
167
|
+
const res = makeCloudRes();
|
|
168
|
+
await handler(makeCloudReq({ method: 'GET', url: '/api/ping', path: '/api/ping' }), res);
|
|
169
|
+
expect(res._status).toBe(200);
|
|
170
|
+
const body = typeof res._body === 'string' ? JSON.parse(res._body) : res._body;
|
|
171
|
+
expect(body).toEqual({ ok: true, message: 'pong' });
|
|
172
|
+
});
|
|
173
|
+
it('POST /api/echo returns 200 and echoed body', async () => {
|
|
174
|
+
const res = makeCloudRes();
|
|
175
|
+
await handler(makeCloudReq({
|
|
176
|
+
method: 'POST',
|
|
177
|
+
url: '/api/echo',
|
|
178
|
+
path: '/api/echo',
|
|
179
|
+
body: { x: 2, text: 'world' },
|
|
180
|
+
}), res);
|
|
181
|
+
expect(res._status).toBe(200);
|
|
182
|
+
const body = typeof res._body === 'string' ? JSON.parse(res._body) : res._body;
|
|
183
|
+
expect(body).toEqual({ echoed: { x: 2, text: 'world' } });
|
|
184
|
+
});
|
|
185
|
+
it('GET /api/missing returns 404', async () => {
|
|
186
|
+
const res = makeCloudRes();
|
|
187
|
+
await handler(makeCloudReq({ method: 'GET', url: '/api/missing', path: '/api/missing' }), res);
|
|
188
|
+
expect(res._status).toBe(404);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
package/dist/lambda.adapter.d.ts
CHANGED
|
@@ -32,15 +32,27 @@ export interface LambdaContext extends ServerlessContext {
|
|
|
32
32
|
invokedFunctionArn: string;
|
|
33
33
|
callbackWaitsForEmptyEventLoop: boolean;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Options for createLambdaHandler
|
|
37
|
+
*/
|
|
38
|
+
export interface LambdaHandlerOptions {
|
|
39
|
+
/** Called after the HazelJS app is initialized (e.g. for custom setup). */
|
|
40
|
+
onInit?: (app: HazelApp) => Promise<void>;
|
|
41
|
+
/** Called when the handler throws (e.g. for custom logging or reporting). */
|
|
42
|
+
onError?: (error: unknown) => void;
|
|
43
|
+
/** MIME types (e.g. 'image/png', 'image/*') that trigger base64 encoding of Buffer body for Lambda. */
|
|
44
|
+
binaryMimeTypes?: string[];
|
|
45
|
+
}
|
|
35
46
|
/**
|
|
36
47
|
* Lambda adapter for HazelJS
|
|
37
48
|
*/
|
|
38
49
|
export declare class LambdaAdapter {
|
|
39
50
|
private moduleClass;
|
|
51
|
+
private options;
|
|
40
52
|
private app?;
|
|
41
53
|
private optimizer;
|
|
42
54
|
private isColdStart;
|
|
43
|
-
constructor(moduleClass: Type<unknown
|
|
55
|
+
constructor(moduleClass: Type<unknown>, options?: LambdaHandlerOptions);
|
|
44
56
|
/**
|
|
45
57
|
* Initialize the HazelJS application
|
|
46
58
|
*/
|
|
@@ -61,6 +73,10 @@ export declare class LambdaAdapter {
|
|
|
61
73
|
* Process request through HazelJS router
|
|
62
74
|
*/
|
|
63
75
|
private processRequest;
|
|
76
|
+
/**
|
|
77
|
+
* Whether the given Content-Type matches any of the binaryMimeTypes patterns.
|
|
78
|
+
*/
|
|
79
|
+
private isBinaryContentType;
|
|
64
80
|
/**
|
|
65
81
|
* Get application instance
|
|
66
82
|
*/
|
|
@@ -82,5 +98,5 @@ export declare class LambdaAdapter {
|
|
|
82
98
|
* export const handler = createLambdaHandler(AppModule);
|
|
83
99
|
* ```
|
|
84
100
|
*/
|
|
85
|
-
export declare function createLambdaHandler(moduleClass: Type<unknown
|
|
101
|
+
export declare function createLambdaHandler(moduleClass: Type<unknown>, options?: LambdaHandlerOptions): (event: LambdaEvent, context: LambdaContext) => Promise<ServerlessResponse>;
|
|
86
102
|
//# sourceMappingURL=lambda.adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lambda.adapter.d.ts","sourceRoot":"","sources":["../src/lambda.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAGhC;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,CAAC,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;QACzB,QAAQ,EAAE;YACR,QAAQ,EAAE,MAAM,CAAC;YACjB,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,8BAA8B,EAAE,OAAO,CAAC;CACzC;AAED;;GAEG;AACH,qBAAa,aAAa;
|
|
1
|
+
{"version":3,"file":"lambda.adapter.d.ts","sourceRoot":"","sources":["../src/lambda.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAGhC;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,CAAC,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;QACzB,QAAQ,EAAE;YACR,QAAQ,EAAE,MAAM,CAAC;YACjB,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,8BAA8B,EAAE,OAAO,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,6EAA6E;IAC7E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,uGAAuG;IACvG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,aAAa;IAMtB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,GAAG,CAAC,CAAW;IACvB,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,WAAW,CAAQ;gBAGjB,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAC1B,OAAO,GAAE,oBAAyB;IAK5C;;OAEG;YACW,UAAU;IAyBxB;;OAEG;IACH,aAAa,KACG,OAAO,WAAW,EAAE,SAAS,aAAa,KAAG,OAAO,CAAC,kBAAkB,CAAC;IAmCxF;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA+BnC;;OAEG;IACH,OAAO,CAAC,SAAS;IAYjB;;OAEG;YACW,cAAc;IAqI5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;OAEG;IACH,MAAM,IAAI,QAAQ,GAAG,SAAS;IAI9B;;OAEG;IACH,MAAM,IAAI,OAAO;CAGlB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAC1B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAG7E"}
|
package/dist/lambda.adapter.js
CHANGED
|
@@ -13,8 +13,9 @@ const cold_start_optimizer_1 = require("./cold-start.optimizer");
|
|
|
13
13
|
* Lambda adapter for HazelJS
|
|
14
14
|
*/
|
|
15
15
|
class LambdaAdapter {
|
|
16
|
-
constructor(moduleClass) {
|
|
16
|
+
constructor(moduleClass, options = {}) {
|
|
17
17
|
this.moduleClass = moduleClass;
|
|
18
|
+
this.options = options;
|
|
18
19
|
this.isColdStart = true;
|
|
19
20
|
this.optimizer = cold_start_optimizer_1.ColdStartOptimizer.getInstance();
|
|
20
21
|
}
|
|
@@ -34,6 +35,9 @@ class LambdaAdapter {
|
|
|
34
35
|
}
|
|
35
36
|
// Create HazelJS application
|
|
36
37
|
this.app = new core_1.HazelApp(this.moduleClass);
|
|
38
|
+
if (this.options.onInit && this.app) {
|
|
39
|
+
await this.options.onInit(this.app);
|
|
40
|
+
}
|
|
37
41
|
const duration = Date.now() - startTime;
|
|
38
42
|
core_2.default.info(`Lambda initialization completed in ${duration}ms`);
|
|
39
43
|
}
|
|
@@ -57,6 +61,7 @@ class LambdaAdapter {
|
|
|
57
61
|
return response;
|
|
58
62
|
}
|
|
59
63
|
catch (error) {
|
|
64
|
+
this.options.onError?.(error);
|
|
60
65
|
core_2.default.error('Lambda handler error:', error);
|
|
61
66
|
return {
|
|
62
67
|
statusCode: 500,
|
|
@@ -141,7 +146,7 @@ class LambdaAdapter {
|
|
|
141
146
|
url: request.url,
|
|
142
147
|
headers: request.headers,
|
|
143
148
|
query: request.query,
|
|
144
|
-
params: context.params
|
|
149
|
+
params: route.context?.params ?? context.params ?? {},
|
|
145
150
|
body: request.body,
|
|
146
151
|
};
|
|
147
152
|
let responseBody;
|
|
@@ -167,15 +172,29 @@ class LambdaAdapter {
|
|
|
167
172
|
return responseHeaders[key];
|
|
168
173
|
},
|
|
169
174
|
};
|
|
170
|
-
const result = await route.handler(syntheticReq, syntheticRes);
|
|
175
|
+
const result = await route.handler(syntheticReq, syntheticRes, route.context);
|
|
171
176
|
// If handler returned a value directly, use it
|
|
172
177
|
if (result !== undefined && responseBody === undefined) {
|
|
173
178
|
responseBody = result;
|
|
174
179
|
}
|
|
180
|
+
const contentType = responseHeaders['content-type'] ?? responseHeaders['Content-Type'] ?? '';
|
|
181
|
+
const isBinary = this.options.binaryMimeTypes?.length &&
|
|
182
|
+
this.isBinaryContentType(contentType) &&
|
|
183
|
+
(Buffer.isBuffer(responseBody) || responseBody instanceof Uint8Array);
|
|
184
|
+
let body;
|
|
185
|
+
let isBase64Encoded = false;
|
|
186
|
+
if (isBinary) {
|
|
187
|
+
body = Buffer.from(responseBody).toString('base64');
|
|
188
|
+
isBase64Encoded = true;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
body = typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody);
|
|
192
|
+
}
|
|
175
193
|
return {
|
|
176
194
|
statusCode: responseStatus,
|
|
177
|
-
body
|
|
195
|
+
body,
|
|
178
196
|
headers: responseHeaders,
|
|
197
|
+
...(isBase64Encoded && { isBase64Encoded: true }),
|
|
179
198
|
};
|
|
180
199
|
}
|
|
181
200
|
catch (error) {
|
|
@@ -188,6 +207,25 @@ class LambdaAdapter {
|
|
|
188
207
|
};
|
|
189
208
|
}
|
|
190
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Whether the given Content-Type matches any of the binaryMimeTypes patterns.
|
|
212
|
+
*/
|
|
213
|
+
isBinaryContentType(contentType) {
|
|
214
|
+
if (!this.options.binaryMimeTypes?.length || !contentType)
|
|
215
|
+
return false;
|
|
216
|
+
const ct = contentType.split(';')[0].trim().toLowerCase();
|
|
217
|
+
for (const pattern of this.options.binaryMimeTypes) {
|
|
218
|
+
const p = pattern.toLowerCase();
|
|
219
|
+
if (p.endsWith('/*')) {
|
|
220
|
+
if (ct.startsWith(p.slice(0, -1)))
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
else if (ct === p || ct.startsWith(p + '/')) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
191
229
|
/**
|
|
192
230
|
* Get application instance
|
|
193
231
|
*/
|
|
@@ -214,7 +252,7 @@ exports.LambdaAdapter = LambdaAdapter;
|
|
|
214
252
|
* export const handler = createLambdaHandler(AppModule);
|
|
215
253
|
* ```
|
|
216
254
|
*/
|
|
217
|
-
function createLambdaHandler(moduleClass) {
|
|
218
|
-
const adapter = new LambdaAdapter(moduleClass);
|
|
255
|
+
function createLambdaHandler(moduleClass, options) {
|
|
256
|
+
const adapter = new LambdaAdapter(moduleClass, options ?? {});
|
|
219
257
|
return adapter.createHandler();
|
|
220
258
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/serverless",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.73",
|
|
4
4
|
"description": "Serverless adapters (AWS Lambda, Google Cloud Functions) for HazelJS framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@hazeljs/core": ">=0.2.0-beta.0"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "24092246c221bc5b4437be1a8661cd07c3445cdf"
|
|
52
52
|
}
|