@hazeljs/serverless 0.2.0-beta.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.
@@ -0,0 +1,208 @@
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.LambdaAdapter = void 0;
7
+ exports.createLambdaHandler = createLambdaHandler;
8
+ const core_1 = require("@hazeljs/core");
9
+ const core_2 = __importDefault(require("@hazeljs/core"));
10
+ const serverless_decorator_1 = require("./serverless.decorator");
11
+ const cold_start_optimizer_1 = require("./cold-start.optimizer");
12
+ /**
13
+ * Lambda adapter for HazelJS
14
+ */
15
+ class LambdaAdapter {
16
+ constructor(moduleClass) {
17
+ this.moduleClass = moduleClass;
18
+ this.isColdStart = true;
19
+ this.optimizer = cold_start_optimizer_1.ColdStartOptimizer.getInstance();
20
+ }
21
+ /**
22
+ * Initialize the HazelJS application
23
+ */
24
+ async initialize() {
25
+ if (this.app) {
26
+ return;
27
+ }
28
+ const startTime = Date.now();
29
+ core_2.default.info('Initializing HazelJS application for Lambda...');
30
+ // Check if cold start optimization is enabled
31
+ const metadata = (0, serverless_decorator_1.getServerlessMetadata)(this.moduleClass);
32
+ if (metadata?.coldStartOptimization) {
33
+ await this.optimizer.warmUp();
34
+ }
35
+ // Create HazelJS application
36
+ this.app = new core_1.HazelApp(this.moduleClass);
37
+ const duration = Date.now() - startTime;
38
+ core_2.default.info(`Lambda initialization completed in ${duration}ms`);
39
+ }
40
+ /**
41
+ * Create Lambda handler
42
+ */
43
+ createHandler() {
44
+ return async (event, context) => {
45
+ try {
46
+ // Log cold start
47
+ if (this.isColdStart) {
48
+ core_2.default.info('Lambda cold start detected');
49
+ this.isColdStart = false;
50
+ }
51
+ // Initialize application
52
+ await this.initialize();
53
+ // Convert Lambda event to HTTP request
54
+ const request = this.convertLambdaEventToRequest(event, context);
55
+ // Process request through HazelJS
56
+ const response = await this.processRequest(request);
57
+ return response;
58
+ }
59
+ catch (error) {
60
+ core_2.default.error('Lambda handler error:', error);
61
+ return {
62
+ statusCode: 500,
63
+ body: JSON.stringify({
64
+ error: 'Internal Server Error',
65
+ message: error instanceof Error ? error.message : 'Unknown error',
66
+ }),
67
+ headers: {
68
+ 'Content-Type': 'application/json',
69
+ },
70
+ };
71
+ }
72
+ };
73
+ }
74
+ /**
75
+ * Convert Lambda event to HTTP request format
76
+ */
77
+ convertLambdaEventToRequest(event, context) {
78
+ return {
79
+ method: event.httpMethod || 'GET',
80
+ url: event.path || '/',
81
+ headers: event.headers || {},
82
+ query: event.queryStringParameters || {},
83
+ params: event.pathParameters || {},
84
+ body: event.body ? this.parseBody(event.body, event.isBase64Encoded) : undefined,
85
+ context: {
86
+ requestId: context.awsRequestId,
87
+ functionName: context.functionName,
88
+ remainingTime: context.getRemainingTimeInMillis(),
89
+ },
90
+ };
91
+ }
92
+ /**
93
+ * Parse request body
94
+ */
95
+ parseBody(body, isBase64Encoded) {
96
+ try {
97
+ if (isBase64Encoded) {
98
+ const decoded = Buffer.from(body, 'base64').toString('utf-8');
99
+ return JSON.parse(decoded);
100
+ }
101
+ return JSON.parse(body);
102
+ }
103
+ catch {
104
+ return body;
105
+ }
106
+ }
107
+ /**
108
+ * Process request through HazelJS router
109
+ */
110
+ async processRequest(request) {
111
+ if (!this.app) {
112
+ return {
113
+ statusCode: 500,
114
+ body: JSON.stringify({ message: 'Application not initialized' }),
115
+ headers: { 'Content-Type': 'application/json' },
116
+ };
117
+ }
118
+ const router = this.app.getRouter();
119
+ // Build request context for the router
120
+ const context = {
121
+ method: request.method,
122
+ url: request.url,
123
+ headers: request.headers,
124
+ query: request.query,
125
+ params: request.params,
126
+ body: request.body,
127
+ requestId: request.context.requestId,
128
+ };
129
+ try {
130
+ const route = await router.match(request.method, request.url, context);
131
+ if (!route) {
132
+ return {
133
+ statusCode: 404,
134
+ body: JSON.stringify({ statusCode: 404, message: 'Route not found' }),
135
+ headers: { 'Content-Type': 'application/json' },
136
+ };
137
+ }
138
+ // Create a synthetic request/response to capture the handler output
139
+ const syntheticReq = {
140
+ method: request.method,
141
+ url: request.url,
142
+ headers: request.headers,
143
+ query: request.query,
144
+ params: context.params || {},
145
+ body: request.body,
146
+ };
147
+ let responseBody;
148
+ let responseStatus = 200;
149
+ const responseHeaders = { 'Content-Type': 'application/json' };
150
+ const syntheticRes = {
151
+ statusCode: 200,
152
+ status(code) { responseStatus = code; return syntheticRes; },
153
+ json(data) { responseBody = data; responseStatus = responseStatus || 200; },
154
+ send(data) { responseBody = data; },
155
+ setHeader(key, value) { responseHeaders[key] = value; },
156
+ getHeader(key) { return responseHeaders[key]; },
157
+ };
158
+ const result = await route.handler(syntheticReq, syntheticRes);
159
+ // If handler returned a value directly, use it
160
+ if (result !== undefined && responseBody === undefined) {
161
+ responseBody = result;
162
+ }
163
+ return {
164
+ statusCode: responseStatus,
165
+ body: typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody),
166
+ headers: responseHeaders,
167
+ };
168
+ }
169
+ catch (error) {
170
+ const statusCode = error.statusCode || 500;
171
+ const message = error instanceof Error ? error.message : 'Internal Server Error';
172
+ return {
173
+ statusCode,
174
+ body: JSON.stringify({ statusCode, message }),
175
+ headers: { 'Content-Type': 'application/json' },
176
+ };
177
+ }
178
+ }
179
+ /**
180
+ * Get application instance
181
+ */
182
+ getApp() {
183
+ return this.app;
184
+ }
185
+ /**
186
+ * Check if this is a cold start
187
+ */
188
+ isCold() {
189
+ return this.isColdStart;
190
+ }
191
+ }
192
+ exports.LambdaAdapter = LambdaAdapter;
193
+ /**
194
+ * Create a Lambda handler for a HazelJS module
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * // handler.ts
199
+ * import { createLambdaHandler } from '@hazeljs/core';
200
+ * import { AppModule } from './app.module';
201
+ *
202
+ * export const handler = createLambdaHandler(AppModule);
203
+ * ```
204
+ */
205
+ function createLambdaHandler(moduleClass) {
206
+ const adapter = new LambdaAdapter(moduleClass);
207
+ return adapter.createHandler();
208
+ }
@@ -0,0 +1,166 @@
1
+ import 'reflect-metadata';
2
+ /**
3
+ * Serverless configuration options
4
+ */
5
+ export interface ServerlessOptions {
6
+ /**
7
+ * Memory allocation in MB
8
+ * @default 512
9
+ */
10
+ memory?: number;
11
+ /**
12
+ * Timeout in seconds
13
+ * @default 30
14
+ */
15
+ timeout?: number;
16
+ /**
17
+ * Enable cold start optimization
18
+ * @default true
19
+ */
20
+ coldStartOptimization?: boolean;
21
+ /**
22
+ * Environment variables
23
+ */
24
+ environment?: Record<string, string>;
25
+ /**
26
+ * Runtime platform
27
+ */
28
+ runtime?: 'aws-lambda' | 'gcp-functions' | 'azure-functions' | 'cloudflare-workers';
29
+ /**
30
+ * Enable automatic function splitting
31
+ * @default false
32
+ */
33
+ autoSplit?: boolean;
34
+ /**
35
+ * Reserved concurrent executions (AWS Lambda)
36
+ */
37
+ reservedConcurrency?: number;
38
+ /**
39
+ * Provisioned concurrency (AWS Lambda)
40
+ */
41
+ provisionedConcurrency?: number;
42
+ }
43
+ /**
44
+ * Decorator to mark a controller as serverless-optimized
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * @Serverless({
49
+ * memory: 512,
50
+ * timeout: 30,
51
+ * coldStartOptimization: true
52
+ * })
53
+ * @Controller('/lambda')
54
+ * export class LambdaController {
55
+ * @Get()
56
+ * async handler() {
57
+ * return { message: 'Hello from serverless!' };
58
+ * }
59
+ * }
60
+ * ```
61
+ */
62
+ export declare function Serverless(options?: ServerlessOptions): ClassDecorator;
63
+ /**
64
+ * Get serverless metadata from a class
65
+ */
66
+ export declare function getServerlessMetadata(target: object | (new (...args: unknown[]) => object)): ServerlessOptions | undefined;
67
+ /**
68
+ * Check if a class is marked as serverless
69
+ */
70
+ export declare function isServerless(target: object | (new (...args: unknown[]) => object)): boolean;
71
+ /**
72
+ * Serverless function handler type
73
+ */
74
+ export type ServerlessHandler<TEvent = unknown, TResult = unknown> = (event: TEvent, context: ServerlessContext) => Promise<TResult> | TResult;
75
+ /**
76
+ * Serverless execution context
77
+ */
78
+ export interface ServerlessContext {
79
+ /**
80
+ * Request ID
81
+ */
82
+ requestId: string;
83
+ /**
84
+ * Function name
85
+ */
86
+ functionName: string;
87
+ /**
88
+ * Function version
89
+ */
90
+ functionVersion: string;
91
+ /**
92
+ * Memory limit in MB
93
+ */
94
+ memoryLimitInMB: number;
95
+ /**
96
+ * Remaining time in milliseconds
97
+ */
98
+ getRemainingTimeInMillis(): number;
99
+ /**
100
+ * Log stream name
101
+ */
102
+ logStreamName?: string;
103
+ /**
104
+ * Log group name
105
+ */
106
+ logGroupName?: string;
107
+ /**
108
+ * Additional platform-specific context
109
+ */
110
+ [key: string]: unknown;
111
+ }
112
+ /**
113
+ * Serverless event types
114
+ */
115
+ export interface ServerlessEvent {
116
+ /**
117
+ * HTTP method
118
+ */
119
+ httpMethod?: string;
120
+ /**
121
+ * Request path
122
+ */
123
+ path?: string;
124
+ /**
125
+ * Query parameters
126
+ */
127
+ queryStringParameters?: Record<string, string>;
128
+ /**
129
+ * Headers
130
+ */
131
+ headers?: Record<string, string>;
132
+ /**
133
+ * Request body
134
+ */
135
+ body?: string;
136
+ /**
137
+ * Is base64 encoded
138
+ */
139
+ isBase64Encoded?: boolean;
140
+ /**
141
+ * Additional platform-specific event data
142
+ */
143
+ [key: string]: unknown;
144
+ }
145
+ /**
146
+ * Serverless response
147
+ */
148
+ export interface ServerlessResponse {
149
+ /**
150
+ * Status code
151
+ */
152
+ statusCode: number;
153
+ /**
154
+ * Response headers
155
+ */
156
+ headers?: Record<string, string>;
157
+ /**
158
+ * Response body
159
+ */
160
+ body: string;
161
+ /**
162
+ * Is base64 encoded
163
+ */
164
+ isBase64Encoded?: boolean;
165
+ }
166
+ //# sourceMappingURL=serverless.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverless.decorator.d.ts","sourceRoot":"","sources":["../src/serverless.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAG1B;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC;;OAEG;IACH,OAAO,CAAC,EAAE,YAAY,GAAG,eAAe,GAAG,iBAAiB,GAAG,oBAAoB,CAAC;IAEpF;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;OAEG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CAc1E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,CAAC,GACpD,iBAAiB,GAAG,SAAS,CAE/B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,CAAC,GAAG,OAAO,CAE3F;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,CACnE,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEhC;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,wBAAwB,IAAI,MAAM,CAAC;IAEnC;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE/C;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B"}
@@ -0,0 +1,56 @@
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.Serverless = Serverless;
7
+ exports.getServerlessMetadata = getServerlessMetadata;
8
+ exports.isServerless = isServerless;
9
+ require("reflect-metadata");
10
+ const core_1 = __importDefault(require("@hazeljs/core"));
11
+ const SERVERLESS_METADATA_KEY = 'hazel:serverless';
12
+ /**
13
+ * Decorator to mark a controller as serverless-optimized
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * @Serverless({
18
+ * memory: 512,
19
+ * timeout: 30,
20
+ * coldStartOptimization: true
21
+ * })
22
+ * @Controller('/lambda')
23
+ * export class LambdaController {
24
+ * @Get()
25
+ * async handler() {
26
+ * return { message: 'Hello from serverless!' };
27
+ * }
28
+ * }
29
+ * ```
30
+ */
31
+ function Serverless(options = {}) {
32
+ return (target) => {
33
+ const defaults = {
34
+ memory: 512,
35
+ timeout: 30,
36
+ coldStartOptimization: true,
37
+ autoSplit: false,
38
+ ...options,
39
+ };
40
+ const targetName = typeof target === 'function' ? target.name : 'unknown';
41
+ core_1.default.debug(`Marking ${targetName} as serverless with options:`, defaults);
42
+ Reflect.defineMetadata(SERVERLESS_METADATA_KEY, defaults, target);
43
+ };
44
+ }
45
+ /**
46
+ * Get serverless metadata from a class
47
+ */
48
+ function getServerlessMetadata(target) {
49
+ return Reflect.getMetadata(SERVERLESS_METADATA_KEY, target);
50
+ }
51
+ /**
52
+ * Check if a class is marked as serverless
53
+ */
54
+ function isServerless(target) {
55
+ return Reflect.hasMetadata(SERVERLESS_METADATA_KEY, target);
56
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@hazeljs/serverless",
3
+ "version": "0.2.0-beta.1",
4
+ "description": "Serverless adapters (AWS Lambda, Google Cloud Functions) for HazelJS framework",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "jest --coverage",
13
+ "lint": "eslint \"src/**/*.ts\"",
14
+ "lint:fix": "eslint \"src/**/*.ts\" --fix",
15
+ "clean": "rm -rf dist"
16
+ },
17
+ "dependencies": {
18
+ "@hazeljs/core": "file:../core"
19
+ },
20
+ "devDependencies": {
21
+ "@types/aws-lambda": "^8.10.145",
22
+ "@types/node": "^20.17.50",
23
+ "@typescript-eslint/eslint-plugin": "^8.18.2",
24
+ "@typescript-eslint/parser": "^8.18.2",
25
+ "eslint": "^8.56.0",
26
+ "jest": "^29.7.0",
27
+ "ts-jest": "^29.1.2",
28
+ "typescript": "^5.3.3"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/hazel-js/hazeljs.git",
36
+ "directory": "packages/serverless"
37
+ },
38
+ "keywords": [
39
+ "hazeljs",
40
+ "serverless",
41
+ "lambda",
42
+ "cloud-functions",
43
+ "aws"
44
+ ],
45
+ "author": "Muhammad Arslan <marslan@hazeljs.com>",
46
+ "license": "MIT"
47
+ }