@hazeljs/serverless 0.2.0-alpha.1
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 -0
- package/README.md +493 -0
- 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 +109 -0
- package/dist/cloud-function.adapter.d.ts.map +1 -0
- package/dist/cloud-function.adapter.js +271 -0
- package/dist/cold-start.optimizer.d.ts +70 -0
- package/dist/cold-start.optimizer.d.ts.map +1 -0
- package/dist/cold-start.optimizer.js +202 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- 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 +102 -0
- package/dist/lambda.adapter.d.ts.map +1 -0
- package/dist/lambda.adapter.js +258 -0
- package/dist/serverless.decorator.d.ts +166 -0
- package/dist/serverless.decorator.d.ts.map +1 -0
- package/dist/serverless.decorator.js +56 -0
- 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 +52 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CloudFunctionAdapter = void 0;
|
|
7
|
+
exports.createCloudFunctionHandler = createCloudFunctionHandler;
|
|
8
|
+
exports.createCloudFunctionEventHandler = createCloudFunctionEventHandler;
|
|
9
|
+
const core_1 = require("@hazeljs/core");
|
|
10
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
11
|
+
const serverless_decorator_1 = require("./serverless.decorator");
|
|
12
|
+
const cold_start_optimizer_1 = require("./cold-start.optimizer");
|
|
13
|
+
/**
|
|
14
|
+
* Cloud Function adapter for HazelJS
|
|
15
|
+
*/
|
|
16
|
+
class CloudFunctionAdapter {
|
|
17
|
+
constructor(moduleClass, options = {}) {
|
|
18
|
+
this.moduleClass = moduleClass;
|
|
19
|
+
this.options = options;
|
|
20
|
+
this.isColdStart = true;
|
|
21
|
+
this.optimizer = cold_start_optimizer_1.ColdStartOptimizer.getInstance();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Initialize the HazelJS application
|
|
25
|
+
*/
|
|
26
|
+
async initialize() {
|
|
27
|
+
if (this.app) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
core_2.default.info('Initializing HazelJS application for Cloud Functions...');
|
|
32
|
+
// Check if cold start optimization is enabled
|
|
33
|
+
const metadata = (0, serverless_decorator_1.getServerlessMetadata)(this.moduleClass);
|
|
34
|
+
if (metadata?.coldStartOptimization) {
|
|
35
|
+
await this.optimizer.warmUp();
|
|
36
|
+
}
|
|
37
|
+
// Create HazelJS application
|
|
38
|
+
this.app = new core_1.HazelApp(this.moduleClass);
|
|
39
|
+
if (this.options.onInit && this.app) {
|
|
40
|
+
await this.options.onInit(this.app);
|
|
41
|
+
}
|
|
42
|
+
const duration = Date.now() - startTime;
|
|
43
|
+
core_2.default.info(`Cloud Function initialization completed in ${duration}ms`);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create Cloud Function HTTP handler
|
|
47
|
+
*/
|
|
48
|
+
createHttpHandler() {
|
|
49
|
+
return async (req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
// Log cold start
|
|
52
|
+
if (this.isColdStart) {
|
|
53
|
+
core_2.default.info('Cloud Function cold start detected');
|
|
54
|
+
this.isColdStart = false;
|
|
55
|
+
}
|
|
56
|
+
// Initialize application
|
|
57
|
+
await this.initialize();
|
|
58
|
+
// Convert Cloud Function request to internal format
|
|
59
|
+
const request = this.convertCloudFunctionRequest(req);
|
|
60
|
+
// Process request through HazelJS
|
|
61
|
+
const response = await this.processRequest(request);
|
|
62
|
+
// Send response
|
|
63
|
+
res.status(response.statusCode);
|
|
64
|
+
if (response.headers) {
|
|
65
|
+
Object.entries(response.headers).forEach(([key, value]) => {
|
|
66
|
+
res.set(key, value);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
res.send(response.body);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
this.options.onError?.(error);
|
|
73
|
+
core_2.default.error('Cloud Function handler error:', error);
|
|
74
|
+
res.status(500).json({
|
|
75
|
+
error: 'Internal Server Error',
|
|
76
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
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.
|
|
85
|
+
*/
|
|
86
|
+
createEventHandler() {
|
|
87
|
+
return async (event, context) => {
|
|
88
|
+
try {
|
|
89
|
+
// Log cold start
|
|
90
|
+
if (this.isColdStart) {
|
|
91
|
+
core_2.default.info('Cloud Function event cold start detected');
|
|
92
|
+
this.isColdStart = false;
|
|
93
|
+
}
|
|
94
|
+
// Initialize application
|
|
95
|
+
await this.initialize();
|
|
96
|
+
core_2.default.info('Processing Cloud Function event:', {
|
|
97
|
+
eventType: context.eventType,
|
|
98
|
+
resource: context.resource,
|
|
99
|
+
});
|
|
100
|
+
// Stub: extend this to dispatch event to your handlers (e.g. Pub/Sub, Storage)
|
|
101
|
+
core_2.default.info('Event processed successfully');
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.options.onError?.(error);
|
|
105
|
+
core_2.default.error('Cloud Function event handler error:', error);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Convert Cloud Function request to internal format
|
|
112
|
+
*/
|
|
113
|
+
convertCloudFunctionRequest(req) {
|
|
114
|
+
return {
|
|
115
|
+
method: req.method,
|
|
116
|
+
url: req.url,
|
|
117
|
+
path: req.path,
|
|
118
|
+
headers: this.normalizeHeaders(req.headers),
|
|
119
|
+
query: this.normalizeQuery(req.query),
|
|
120
|
+
body: req.body,
|
|
121
|
+
rawBody: req.rawBody,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Normalize headers (convert string[] to string)
|
|
126
|
+
*/
|
|
127
|
+
normalizeHeaders(headers) {
|
|
128
|
+
const normalized = {};
|
|
129
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
130
|
+
normalized[key] = Array.isArray(value) ? value[0] : value;
|
|
131
|
+
}
|
|
132
|
+
return normalized;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Normalize query parameters
|
|
136
|
+
*/
|
|
137
|
+
normalizeQuery(query) {
|
|
138
|
+
const normalized = {};
|
|
139
|
+
for (const [key, value] of Object.entries(query)) {
|
|
140
|
+
normalized[key] = Array.isArray(value) ? value[0] : value;
|
|
141
|
+
}
|
|
142
|
+
return normalized;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Process request through HazelJS router
|
|
146
|
+
*/
|
|
147
|
+
async processRequest(request) {
|
|
148
|
+
if (!this.app) {
|
|
149
|
+
return {
|
|
150
|
+
statusCode: 500,
|
|
151
|
+
body: JSON.stringify({ message: 'Application not initialized' }),
|
|
152
|
+
headers: { 'Content-Type': 'application/json' },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const router = this.app.getRouter();
|
|
156
|
+
// Build request context for the router
|
|
157
|
+
const context = {
|
|
158
|
+
method: request.method,
|
|
159
|
+
url: request.url,
|
|
160
|
+
headers: request.headers,
|
|
161
|
+
query: request.query,
|
|
162
|
+
params: {},
|
|
163
|
+
body: request.body,
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
const route = await router.match(request.method, request.url, context);
|
|
167
|
+
if (!route) {
|
|
168
|
+
return {
|
|
169
|
+
statusCode: 404,
|
|
170
|
+
body: JSON.stringify({ statusCode: 404, message: 'Route not found' }),
|
|
171
|
+
headers: { 'Content-Type': 'application/json' },
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// Create a synthetic request/response to capture the handler output
|
|
175
|
+
const syntheticReq = {
|
|
176
|
+
method: request.method,
|
|
177
|
+
url: request.url,
|
|
178
|
+
headers: request.headers,
|
|
179
|
+
query: request.query,
|
|
180
|
+
params: route.context?.params ?? context.params ?? {},
|
|
181
|
+
body: request.body,
|
|
182
|
+
};
|
|
183
|
+
let responseBody;
|
|
184
|
+
let responseStatus = 200;
|
|
185
|
+
const responseHeaders = { 'Content-Type': 'application/json' };
|
|
186
|
+
const syntheticRes = {
|
|
187
|
+
statusCode: 200,
|
|
188
|
+
status(code) {
|
|
189
|
+
responseStatus = code;
|
|
190
|
+
return syntheticRes;
|
|
191
|
+
},
|
|
192
|
+
json(data) {
|
|
193
|
+
responseBody = data;
|
|
194
|
+
responseStatus = responseStatus || 200;
|
|
195
|
+
},
|
|
196
|
+
send(data) {
|
|
197
|
+
responseBody = data;
|
|
198
|
+
},
|
|
199
|
+
setHeader(key, value) {
|
|
200
|
+
responseHeaders[key] = value;
|
|
201
|
+
},
|
|
202
|
+
getHeader(key) {
|
|
203
|
+
return responseHeaders[key];
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
const result = await route.handler(syntheticReq, syntheticRes, route.context);
|
|
207
|
+
if (result !== undefined && responseBody === undefined) {
|
|
208
|
+
responseBody = result;
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
statusCode: responseStatus,
|
|
212
|
+
body: typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody),
|
|
213
|
+
headers: responseHeaders,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
const statusCode = error.statusCode || 500;
|
|
218
|
+
const message = error instanceof Error ? error.message : 'Internal Server Error';
|
|
219
|
+
return {
|
|
220
|
+
statusCode,
|
|
221
|
+
body: JSON.stringify({ statusCode, message }),
|
|
222
|
+
headers: { 'Content-Type': 'application/json' },
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get application instance
|
|
228
|
+
*/
|
|
229
|
+
getApp() {
|
|
230
|
+
return this.app;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Check if this is a cold start
|
|
234
|
+
*/
|
|
235
|
+
isCold() {
|
|
236
|
+
return this.isColdStart;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
exports.CloudFunctionAdapter = CloudFunctionAdapter;
|
|
240
|
+
/**
|
|
241
|
+
* Create a Cloud Function HTTP handler for a HazelJS module
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* // index.ts
|
|
246
|
+
* import { createCloudFunctionHandler } from '@hazeljs/core';
|
|
247
|
+
* import { AppModule } from './app.module';
|
|
248
|
+
*
|
|
249
|
+
* export const handler = createCloudFunctionHandler(AppModule);
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
function createCloudFunctionHandler(moduleClass, options) {
|
|
253
|
+
const adapter = new CloudFunctionAdapter(moduleClass, options ?? {});
|
|
254
|
+
return adapter.createHttpHandler();
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Create a Cloud Function event handler for a HazelJS module
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* // index.ts
|
|
262
|
+
* import { createCloudFunctionEventHandler } from '@hazeljs/core';
|
|
263
|
+
* import { AppModule } from './app.module';
|
|
264
|
+
*
|
|
265
|
+
* export const handler = createCloudFunctionEventHandler(AppModule);
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
function createCloudFunctionEventHandler(moduleClass, options) {
|
|
269
|
+
const adapter = new CloudFunctionAdapter(moduleClass, options ?? {});
|
|
270
|
+
return adapter.createEventHandler();
|
|
271
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cold start optimization strategies.
|
|
3
|
+
* Best-effort and environment-dependent; preloads built-in modules and the DI container.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ColdStartOptimizer {
|
|
6
|
+
private static instance;
|
|
7
|
+
private isWarmedUp;
|
|
8
|
+
private warmupTimestamp?;
|
|
9
|
+
private preloadedModules;
|
|
10
|
+
private constructor();
|
|
11
|
+
static getInstance(): ColdStartOptimizer;
|
|
12
|
+
/**
|
|
13
|
+
* Warm up the application
|
|
14
|
+
*/
|
|
15
|
+
warmUp(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Pre-initialize the DI container
|
|
18
|
+
*/
|
|
19
|
+
private preInitializeContainer;
|
|
20
|
+
/**
|
|
21
|
+
* Preload critical modules
|
|
22
|
+
*/
|
|
23
|
+
private preloadCriticalModules;
|
|
24
|
+
/**
|
|
25
|
+
* Setup connection pools (placeholder; override or extend for DB/API connection warming).
|
|
26
|
+
*/
|
|
27
|
+
private setupConnectionPools;
|
|
28
|
+
/**
|
|
29
|
+
* Check if the application is warmed up
|
|
30
|
+
*/
|
|
31
|
+
isWarm(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Get warmup timestamp
|
|
34
|
+
*/
|
|
35
|
+
getWarmupTimestamp(): number | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Get warmup duration
|
|
38
|
+
*/
|
|
39
|
+
getWarmupDuration(): number | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Reset warmup state (for testing)
|
|
42
|
+
*/
|
|
43
|
+
reset(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Get preloaded modules
|
|
46
|
+
*/
|
|
47
|
+
getPreloadedModules(): string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Decorator to enable cold start optimization for a method
|
|
51
|
+
*/
|
|
52
|
+
export declare function OptimizeColdStart(): MethodDecorator;
|
|
53
|
+
/**
|
|
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.
|
|
57
|
+
*/
|
|
58
|
+
export declare class KeepAliveHelper {
|
|
59
|
+
private intervalId?;
|
|
60
|
+
private pingUrl?;
|
|
61
|
+
/**
|
|
62
|
+
* Start keep-alive interval (logs only; implement your own HTTP ping or use provisioned concurrency for warming).
|
|
63
|
+
*/
|
|
64
|
+
start(url: string, intervalMs?: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* Stop keep-alive pings
|
|
67
|
+
*/
|
|
68
|
+
stop(): void;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=cold-start.optimizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.KeepAliveHelper = exports.ColdStartOptimizer = void 0;
|
|
40
|
+
exports.OptimizeColdStart = OptimizeColdStart;
|
|
41
|
+
const core_1 = __importDefault(require("@hazeljs/core"));
|
|
42
|
+
const core_2 = require("@hazeljs/core");
|
|
43
|
+
/**
|
|
44
|
+
* Cold start optimization strategies.
|
|
45
|
+
* Best-effort and environment-dependent; preloads built-in modules and the DI container.
|
|
46
|
+
*/
|
|
47
|
+
class ColdStartOptimizer {
|
|
48
|
+
constructor() {
|
|
49
|
+
this.isWarmedUp = false;
|
|
50
|
+
this.preloadedModules = new Set();
|
|
51
|
+
}
|
|
52
|
+
static getInstance() {
|
|
53
|
+
if (!ColdStartOptimizer.instance) {
|
|
54
|
+
ColdStartOptimizer.instance = new ColdStartOptimizer();
|
|
55
|
+
}
|
|
56
|
+
return ColdStartOptimizer.instance;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Warm up the application
|
|
60
|
+
*/
|
|
61
|
+
async warmUp() {
|
|
62
|
+
if (this.isWarmedUp) {
|
|
63
|
+
core_1.default.debug('Application already warmed up');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const startTime = Date.now();
|
|
67
|
+
core_1.default.info('Starting cold start optimization...');
|
|
68
|
+
try {
|
|
69
|
+
// Pre-initialize container
|
|
70
|
+
await this.preInitializeContainer();
|
|
71
|
+
// Preload critical modules
|
|
72
|
+
await this.preloadCriticalModules();
|
|
73
|
+
// Setup connection pools
|
|
74
|
+
await this.setupConnectionPools();
|
|
75
|
+
this.isWarmedUp = true;
|
|
76
|
+
this.warmupTimestamp = Date.now();
|
|
77
|
+
const duration = Date.now() - startTime;
|
|
78
|
+
core_1.default.info(`Cold start optimization completed in ${duration}ms`);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
core_1.default.error('Cold start optimization failed:', error);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Pre-initialize the DI container
|
|
87
|
+
*/
|
|
88
|
+
async preInitializeContainer() {
|
|
89
|
+
core_1.default.debug('Pre-initializing DI container...');
|
|
90
|
+
core_2.Container.getInstance();
|
|
91
|
+
// Container is already initialized, just log it
|
|
92
|
+
core_1.default.debug('DI container ready');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Preload critical modules
|
|
96
|
+
*/
|
|
97
|
+
async preloadCriticalModules() {
|
|
98
|
+
core_1.default.debug('Preloading critical modules...');
|
|
99
|
+
const criticalModules = ['http', 'https', 'crypto', 'buffer'];
|
|
100
|
+
for (const moduleName of criticalModules) {
|
|
101
|
+
try {
|
|
102
|
+
await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
|
|
103
|
+
this.preloadedModules.add(moduleName);
|
|
104
|
+
core_1.default.debug(`Preloaded module: ${moduleName}`);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
core_1.default.warn(`Failed to preload module ${moduleName}:`, error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Setup connection pools (placeholder; override or extend for DB/API connection warming).
|
|
113
|
+
*/
|
|
114
|
+
async setupConnectionPools() {
|
|
115
|
+
core_1.default.debug('Setting up connection pools...');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if the application is warmed up
|
|
119
|
+
*/
|
|
120
|
+
isWarm() {
|
|
121
|
+
return this.isWarmedUp;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get warmup timestamp
|
|
125
|
+
*/
|
|
126
|
+
getWarmupTimestamp() {
|
|
127
|
+
return this.warmupTimestamp;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get warmup duration
|
|
131
|
+
*/
|
|
132
|
+
getWarmupDuration() {
|
|
133
|
+
if (!this.warmupTimestamp)
|
|
134
|
+
return undefined;
|
|
135
|
+
return Date.now() - this.warmupTimestamp;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Reset warmup state (for testing)
|
|
139
|
+
*/
|
|
140
|
+
reset() {
|
|
141
|
+
this.isWarmedUp = false;
|
|
142
|
+
this.warmupTimestamp = undefined;
|
|
143
|
+
this.preloadedModules.clear();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get preloaded modules
|
|
147
|
+
*/
|
|
148
|
+
getPreloadedModules() {
|
|
149
|
+
return Array.from(this.preloadedModules);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.ColdStartOptimizer = ColdStartOptimizer;
|
|
153
|
+
/**
|
|
154
|
+
* Decorator to enable cold start optimization for a method
|
|
155
|
+
*/
|
|
156
|
+
function OptimizeColdStart() {
|
|
157
|
+
return (target, propertyKey, descriptor) => {
|
|
158
|
+
const originalMethod = descriptor.value;
|
|
159
|
+
descriptor.value = async function (...args) {
|
|
160
|
+
const optimizer = ColdStartOptimizer.getInstance();
|
|
161
|
+
if (!optimizer.isWarm()) {
|
|
162
|
+
await optimizer.warmUp();
|
|
163
|
+
}
|
|
164
|
+
return originalMethod.apply(this, args);
|
|
165
|
+
};
|
|
166
|
+
return descriptor;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
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.
|
|
173
|
+
*/
|
|
174
|
+
class KeepAliveHelper {
|
|
175
|
+
/**
|
|
176
|
+
* Start keep-alive interval (logs only; implement your own HTTP ping or use provisioned concurrency for warming).
|
|
177
|
+
*/
|
|
178
|
+
start(url, intervalMs = 5 * 60 * 1000) {
|
|
179
|
+
this.pingUrl = url;
|
|
180
|
+
this.intervalId = setInterval(async () => {
|
|
181
|
+
try {
|
|
182
|
+
core_1.default.debug(`Sending keep-alive ping to ${url}`);
|
|
183
|
+
// Implement fetch(url) or http.get(url) here to actually warm the function
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
core_1.default.error('Keep-alive ping failed:', error);
|
|
187
|
+
}
|
|
188
|
+
}, intervalMs);
|
|
189
|
+
core_1.default.info(`Keep-alive started for ${url} (interval: ${intervalMs}ms)`);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Stop keep-alive pings
|
|
193
|
+
*/
|
|
194
|
+
stop() {
|
|
195
|
+
if (this.intervalId) {
|
|
196
|
+
clearInterval(this.intervalId);
|
|
197
|
+
this.intervalId = undefined;
|
|
198
|
+
core_1.default.info('Keep-alive stopped');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.KeepAliveHelper = KeepAliveHelper;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hazeljs/serverless - Serverless adapters for HazelJS
|
|
3
|
+
*/
|
|
4
|
+
export { Serverless, getServerlessMetadata, isServerless, type ServerlessOptions, type ServerlessHandler, type ServerlessContext, type ServerlessEvent, type ServerlessResponse, } from './serverless.decorator';
|
|
5
|
+
export { ColdStartOptimizer, OptimizeColdStart, KeepAliveHelper } from './cold-start.optimizer';
|
|
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
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @hazeljs/serverless - Serverless adapters for HazelJS
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createCloudFunctionEventHandler = exports.createCloudFunctionHandler = exports.CloudFunctionAdapter = exports.createLambdaHandler = exports.LambdaAdapter = exports.KeepAliveHelper = exports.OptimizeColdStart = exports.ColdStartOptimizer = exports.isServerless = exports.getServerlessMetadata = exports.Serverless = void 0;
|
|
7
|
+
var serverless_decorator_1 = require("./serverless.decorator");
|
|
8
|
+
Object.defineProperty(exports, "Serverless", { enumerable: true, get: function () { return serverless_decorator_1.Serverless; } });
|
|
9
|
+
Object.defineProperty(exports, "getServerlessMetadata", { enumerable: true, get: function () { return serverless_decorator_1.getServerlessMetadata; } });
|
|
10
|
+
Object.defineProperty(exports, "isServerless", { enumerable: true, get: function () { return serverless_decorator_1.isServerless; } });
|
|
11
|
+
var cold_start_optimizer_1 = require("./cold-start.optimizer");
|
|
12
|
+
Object.defineProperty(exports, "ColdStartOptimizer", { enumerable: true, get: function () { return cold_start_optimizer_1.ColdStartOptimizer; } });
|
|
13
|
+
Object.defineProperty(exports, "OptimizeColdStart", { enumerable: true, get: function () { return cold_start_optimizer_1.OptimizeColdStart; } });
|
|
14
|
+
Object.defineProperty(exports, "KeepAliveHelper", { enumerable: true, get: function () { return cold_start_optimizer_1.KeepAliveHelper; } });
|
|
15
|
+
var lambda_adapter_1 = require("./lambda.adapter");
|
|
16
|
+
Object.defineProperty(exports, "LambdaAdapter", { enumerable: true, get: function () { return lambda_adapter_1.LambdaAdapter; } });
|
|
17
|
+
Object.defineProperty(exports, "createLambdaHandler", { enumerable: true, get: function () { return lambda_adapter_1.createLambdaHandler; } });
|
|
18
|
+
var cloud_function_adapter_1 = require("./cloud-function.adapter");
|
|
19
|
+
Object.defineProperty(exports, "CloudFunctionAdapter", { enumerable: true, get: function () { return cloud_function_adapter_1.CloudFunctionAdapter; } });
|
|
20
|
+
Object.defineProperty(exports, "createCloudFunctionHandler", { enumerable: true, get: function () { return cloud_function_adapter_1.createCloudFunctionHandler; } });
|
|
21
|
+
Object.defineProperty(exports, "createCloudFunctionEventHandler", { enumerable: true, get: function () { return cloud_function_adapter_1.createCloudFunctionEventHandler; } });
|
|
@@ -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"}
|