@hazeljs/serverless 0.2.0-beta.70 → 0.2.0-beta.72

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 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 routes for serverless optimization:
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
- @Get('/image')
333
- @Serverless({ binaryResponse: true })
334
- async getImage(@Res() res: Response) {
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
@@ -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>): (req: CloudFunctionRequest, res: CloudFunctionResponse) => Promise<void>;
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>): (event: unknown, context: unknown) => Promise<void>;
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;IAKnB,OAAO,CAAC,WAAW;IAJ/B,OAAO,CAAC,GAAG,CAAC,CAAW;IACvB,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,WAAW,CAAQ;gBAEP,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC;IAI9C;;OAEG;YACW,UAAU;IAqBxB;;OAEG;IACH,iBAAiB,KACD,KAAK,oBAAoB,EAAE,KAAK,qBAAqB,KAAG,OAAO,CAAC,IAAI,CAAC;IAqCrF;;OAEG;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;IA2G5B;;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,GACzB,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,GACzB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAGrD"}
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
- // Process event
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 prevent cold starts
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 pings
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;;GAEG;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;IAMlC;;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;;GAEG;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;IAgB5D;;OAEG;IACH,IAAI,IAAI,IAAI;CAOb"}
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 prevent cold starts
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 pings
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
- // In a real implementation, you would make an HTTP request here
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
@@ -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,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,+BAA+B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC"}
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,6 @@
1
+ /**
2
+ * Integration tests: real HazelJS app through Lambda and Cloud Function adapters.
3
+ * No mocks of @hazeljs/core — uses real HazelApp, router, and controllers.
4
+ */
5
+ import 'reflect-metadata';
6
+ //# sourceMappingURL=integration.test.d.ts.map
@@ -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
+ });
@@ -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>): (event: LambdaEvent, context: LambdaContext) => Promise<ServerlessResponse>;
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;IAKZ,OAAO,CAAC,WAAW;IAJ/B,OAAO,CAAC,GAAG,CAAC,CAAW;IACvB,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,WAAW,CAAQ;gBAEP,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC;IAI9C;;OAEG;YACW,UAAU;IAqBxB;;OAEG;IACH,aAAa,KACG,OAAO,WAAW,EAAE,SAAS,aAAa,KAAG,OAAO,CAAC,kBAAkB,CAAC;IAkCxF;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA+BnC;;OAEG;IACH,OAAO,CAAC,SAAS;IAYjB;;OAEG;YACW,cAAc;IAiH5B;;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,GACzB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAG7E"}
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"}
@@ -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: typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody),
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.70",
3
+ "version": "0.2.0-beta.72",
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": "ff7e43369af054d18e064bb342d1e6c5f058ca87"
51
+ "gitHead": "04468cb4dddc7a4faf829af5c5410a85cae14d74"
52
52
  }