@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.
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=serverless.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverless.test.d.ts","sourceRoot":"","sources":["../src/serverless.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,246 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const serverless_decorator_1 = require("./serverless.decorator");
13
+ const cold_start_optimizer_1 = require("./cold-start.optimizer");
14
+ const lambda_adapter_1 = require("./lambda.adapter");
15
+ const cloud_function_adapter_1 = require("./cloud-function.adapter");
16
+ const core_1 = require("@hazeljs/core");
17
+ describe('Serverless Decorator', () => {
18
+ it('should mark class as serverless', () => {
19
+ let TestController = class TestController {
20
+ };
21
+ TestController = __decorate([
22
+ (0, serverless_decorator_1.Serverless)()
23
+ ], TestController);
24
+ expect((0, serverless_decorator_1.isServerless)(TestController)).toBe(true);
25
+ });
26
+ it('should store serverless metadata', () => {
27
+ const options = {
28
+ memory: 1024,
29
+ timeout: 60,
30
+ coldStartOptimization: true,
31
+ };
32
+ let TestController = class TestController {
33
+ };
34
+ TestController = __decorate([
35
+ (0, serverless_decorator_1.Serverless)(options)
36
+ ], TestController);
37
+ const metadata = (0, serverless_decorator_1.getServerlessMetadata)(TestController);
38
+ expect(metadata).toBeDefined();
39
+ expect(metadata?.memory).toBe(1024);
40
+ expect(metadata?.timeout).toBe(60);
41
+ expect(metadata?.coldStartOptimization).toBe(true);
42
+ });
43
+ it('should use default options', () => {
44
+ let TestController = class TestController {
45
+ };
46
+ TestController = __decorate([
47
+ (0, serverless_decorator_1.Serverless)()
48
+ ], TestController);
49
+ const metadata = (0, serverless_decorator_1.getServerlessMetadata)(TestController);
50
+ expect(metadata?.memory).toBe(512);
51
+ expect(metadata?.timeout).toBe(30);
52
+ expect(metadata?.coldStartOptimization).toBe(true);
53
+ expect(metadata?.autoSplit).toBe(false);
54
+ });
55
+ it('should return undefined for non-serverless class', () => {
56
+ class TestController {
57
+ }
58
+ expect((0, serverless_decorator_1.isServerless)(TestController)).toBe(false);
59
+ expect((0, serverless_decorator_1.getServerlessMetadata)(TestController)).toBeUndefined();
60
+ });
61
+ });
62
+ describe('ColdStartOptimizer', () => {
63
+ let optimizer;
64
+ beforeEach(() => {
65
+ optimizer = cold_start_optimizer_1.ColdStartOptimizer.getInstance();
66
+ optimizer.reset();
67
+ });
68
+ describe('warmUp', () => {
69
+ it('should warm up the application', async () => {
70
+ expect(optimizer.isWarm()).toBe(false);
71
+ await optimizer.warmUp();
72
+ expect(optimizer.isWarm()).toBe(true);
73
+ expect(optimizer.getWarmupTimestamp()).toBeDefined();
74
+ });
75
+ it('should not warm up twice', async () => {
76
+ await optimizer.warmUp();
77
+ const firstTimestamp = optimizer.getWarmupTimestamp();
78
+ await optimizer.warmUp();
79
+ const secondTimestamp = optimizer.getWarmupTimestamp();
80
+ expect(firstTimestamp).toBe(secondTimestamp);
81
+ });
82
+ it('should preload critical modules', async () => {
83
+ await optimizer.warmUp();
84
+ const preloaded = optimizer.getPreloadedModules();
85
+ expect(preloaded.length).toBeGreaterThan(0);
86
+ expect(preloaded).toContain('http');
87
+ expect(preloaded).toContain('crypto');
88
+ });
89
+ });
90
+ describe('isWarm', () => {
91
+ it('should return false initially', () => {
92
+ expect(optimizer.isWarm()).toBe(false);
93
+ });
94
+ it('should return true after warmup', async () => {
95
+ await optimizer.warmUp();
96
+ expect(optimizer.isWarm()).toBe(true);
97
+ });
98
+ });
99
+ describe('getWarmupDuration', () => {
100
+ it('should return undefined before warmup', () => {
101
+ expect(optimizer.getWarmupDuration()).toBeUndefined();
102
+ });
103
+ it('should return duration after warmup', async () => {
104
+ await optimizer.warmUp();
105
+ const duration = optimizer.getWarmupDuration();
106
+ expect(duration).toBeDefined();
107
+ expect(duration).toBeGreaterThanOrEqual(0);
108
+ });
109
+ });
110
+ describe('reset', () => {
111
+ it('should reset warmup state', async () => {
112
+ await optimizer.warmUp();
113
+ expect(optimizer.isWarm()).toBe(true);
114
+ optimizer.reset();
115
+ expect(optimizer.isWarm()).toBe(false);
116
+ expect(optimizer.getWarmupTimestamp()).toBeUndefined();
117
+ expect(optimizer.getPreloadedModules()).toHaveLength(0);
118
+ });
119
+ });
120
+ });
121
+ describe('OptimizeColdStart Decorator', () => {
122
+ let optimizer;
123
+ beforeEach(() => {
124
+ optimizer = cold_start_optimizer_1.ColdStartOptimizer.getInstance();
125
+ optimizer.reset();
126
+ });
127
+ it('should warm up before method execution', async () => {
128
+ class TestClass {
129
+ async testMethod() {
130
+ return 'result';
131
+ }
132
+ }
133
+ __decorate([
134
+ (0, cold_start_optimizer_1.OptimizeColdStart)(),
135
+ __metadata("design:type", Function),
136
+ __metadata("design:paramtypes", []),
137
+ __metadata("design:returntype", Promise)
138
+ ], TestClass.prototype, "testMethod", null);
139
+ const instance = new TestClass();
140
+ expect(optimizer.isWarm()).toBe(false);
141
+ const result = await instance.testMethod();
142
+ expect(result).toBe('result');
143
+ expect(optimizer.isWarm()).toBe(true);
144
+ });
145
+ });
146
+ describe('KeepAliveHelper', () => {
147
+ let helper;
148
+ beforeEach(() => {
149
+ helper = new cold_start_optimizer_1.KeepAliveHelper();
150
+ });
151
+ afterEach(() => {
152
+ helper.stop();
153
+ });
154
+ it('should start keep-alive', () => {
155
+ expect(() => {
156
+ helper.start('http://example.com', 1000);
157
+ }).not.toThrow();
158
+ });
159
+ it('should stop keep-alive', () => {
160
+ helper.start('http://example.com', 1000);
161
+ expect(() => {
162
+ helper.stop();
163
+ }).not.toThrow();
164
+ });
165
+ });
166
+ describe('LambdaAdapter', () => {
167
+ let TestModule = class TestModule {
168
+ };
169
+ TestModule = __decorate([
170
+ (0, core_1.HazelModule)({
171
+ controllers: [],
172
+ })
173
+ ], TestModule);
174
+ let adapter;
175
+ beforeEach(() => {
176
+ adapter = new lambda_adapter_1.LambdaAdapter(TestModule);
177
+ });
178
+ it('should create Lambda adapter', () => {
179
+ expect(adapter).toBeDefined();
180
+ });
181
+ it('should create handler function', () => {
182
+ const handler = adapter.createHandler();
183
+ expect(handler).toBeDefined();
184
+ expect(typeof handler).toBe('function');
185
+ });
186
+ it('should be cold on first check', () => {
187
+ expect(adapter.isCold()).toBe(true);
188
+ });
189
+ });
190
+ describe('createLambdaHandler', () => {
191
+ let TestModule = class TestModule {
192
+ };
193
+ TestModule = __decorate([
194
+ (0, core_1.HazelModule)({
195
+ controllers: [],
196
+ })
197
+ ], TestModule);
198
+ it('should create Lambda handler', () => {
199
+ const handler = (0, lambda_adapter_1.createLambdaHandler)(TestModule);
200
+ expect(handler).toBeDefined();
201
+ expect(typeof handler).toBe('function');
202
+ });
203
+ });
204
+ describe('CloudFunctionAdapter', () => {
205
+ let TestModule = class TestModule {
206
+ };
207
+ TestModule = __decorate([
208
+ (0, core_1.HazelModule)({
209
+ controllers: [],
210
+ })
211
+ ], TestModule);
212
+ let adapter;
213
+ beforeEach(() => {
214
+ adapter = new cloud_function_adapter_1.CloudFunctionAdapter(TestModule);
215
+ });
216
+ it('should create Cloud Function adapter', () => {
217
+ expect(adapter).toBeDefined();
218
+ });
219
+ it('should create HTTP handler', () => {
220
+ const handler = adapter.createHttpHandler();
221
+ expect(handler).toBeDefined();
222
+ expect(typeof handler).toBe('function');
223
+ });
224
+ it('should create event handler', () => {
225
+ const handler = adapter.createEventHandler();
226
+ expect(handler).toBeDefined();
227
+ expect(typeof handler).toBe('function');
228
+ });
229
+ it('should be cold on first check', () => {
230
+ expect(adapter.isCold()).toBe(true);
231
+ });
232
+ });
233
+ describe('createCloudFunctionHandler', () => {
234
+ let TestModule = class TestModule {
235
+ };
236
+ TestModule = __decorate([
237
+ (0, core_1.HazelModule)({
238
+ controllers: [],
239
+ })
240
+ ], TestModule);
241
+ it('should create Cloud Function HTTP handler', () => {
242
+ const handler = (0, cloud_function_adapter_1.createCloudFunctionHandler)(TestModule);
243
+ expect(handler).toBeDefined();
244
+ expect(typeof handler).toBe('function');
245
+ });
246
+ });
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@hazeljs/serverless",
3
+ "version": "0.2.0-alpha.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
+ "devDependencies": {
18
+ "@types/aws-lambda": "^8.10.145",
19
+ "@types/node": "^20.17.50",
20
+ "@typescript-eslint/eslint-plugin": "^8.18.2",
21
+ "@typescript-eslint/parser": "^8.18.2",
22
+ "eslint": "^8.56.0",
23
+ "jest": "^29.7.0",
24
+ "ts-jest": "^29.1.2",
25
+ "typescript": "^5.3.3"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/hazel-js/hazeljs.git",
33
+ "directory": "packages/serverless"
34
+ },
35
+ "keywords": [
36
+ "hazeljs",
37
+ "serverless",
38
+ "lambda",
39
+ "cloud-functions",
40
+ "aws"
41
+ ],
42
+ "author": "Muhammad Arslan <muhammad.arslan@hazeljs.com>",
43
+ "license": "Apache-2.0",
44
+ "bugs": {
45
+ "url": "https://github.com/hazeljs/hazel-js/issues"
46
+ },
47
+ "homepage": "https://hazeljs.com",
48
+ "peerDependencies": {
49
+ "@hazeljs/core": ">=0.2.0-beta.0"
50
+ },
51
+ "gitHead": "cbc5ee2c12ced28fd0576faf13c5f078c1e8421e"
52
+ }