@hazeljs/serverless 0.2.0-beta.8 → 0.2.0-beta.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -21
- package/README.md +20 -27
- package/dist/adapters.test.d.ts +2 -0
- package/dist/adapters.test.d.ts.map +1 -0
- package/dist/adapters.test.js +432 -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/dist/serverless.test.d.ts +2 -0
- package/dist/serverless.test.d.ts.map +1 -0
- package/dist/serverless.test.js +246 -0
- package/package.json +11 -7
|
@@ -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
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverless.test.d.ts","sourceRoot":"","sources":["../src/serverless.test.ts"],"names":[],"mappings":""}
|