@noony-serverless/core 0.1.0
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 +443 -0
- package/build/core/containerPool.d.ts +44 -0
- package/build/core/containerPool.js +103 -0
- package/build/core/core.d.ts +123 -0
- package/build/core/core.js +107 -0
- package/build/core/errors.d.ts +25 -0
- package/build/core/errors.js +59 -0
- package/build/core/handler.d.ts +72 -0
- package/build/core/handler.js +151 -0
- package/build/core/index.d.ts +8 -0
- package/build/core/index.js +24 -0
- package/build/core/logger.d.ts +42 -0
- package/build/core/logger.js +135 -0
- package/build/core/performanceMonitor.d.ts +73 -0
- package/build/core/performanceMonitor.js +189 -0
- package/build/index.d.ts +3 -0
- package/build/index.js +19 -0
- package/build/middlewares/authenticationMiddleware.d.ts +52 -0
- package/build/middlewares/authenticationMiddleware.js +204 -0
- package/build/middlewares/bodyParserMiddleware.d.ts +31 -0
- package/build/middlewares/bodyParserMiddleware.js +217 -0
- package/build/middlewares/bodyValidationMiddleware.d.ts +12 -0
- package/build/middlewares/bodyValidationMiddleware.js +34 -0
- package/build/middlewares/dependencyInjectionMiddleware.d.ts +14 -0
- package/build/middlewares/dependencyInjectionMiddleware.js +48 -0
- package/build/middlewares/errorHandlerMiddleware.d.ts +6 -0
- package/build/middlewares/errorHandlerMiddleware.js +64 -0
- package/build/middlewares/headerVariablesMiddleware.d.ts +8 -0
- package/build/middlewares/headerVariablesMiddleware.js +32 -0
- package/build/middlewares/httpAttributesMiddleware.d.ts +10 -0
- package/build/middlewares/httpAttributesMiddleware.js +71 -0
- package/build/middlewares/index.d.ts +14 -0
- package/build/middlewares/index.js +30 -0
- package/build/middlewares/queryParametersMiddleware.d.ts +8 -0
- package/build/middlewares/queryParametersMiddleware.js +51 -0
- package/build/middlewares/rateLimitingMiddleware.d.ts +157 -0
- package/build/middlewares/rateLimitingMiddleware.js +237 -0
- package/build/middlewares/responseWrapperMiddleware.d.ts +11 -0
- package/build/middlewares/responseWrapperMiddleware.js +34 -0
- package/build/middlewares/securityAuditMiddleware.d.ts +124 -0
- package/build/middlewares/securityAuditMiddleware.js +395 -0
- package/build/middlewares/securityHeadersMiddleware.d.ts +128 -0
- package/build/middlewares/securityHeadersMiddleware.js +183 -0
- package/build/middlewares/validationMiddleware.d.ts +9 -0
- package/build/middlewares/validationMiddleware.js +40 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# Noony Serverless Framework
|
|
2
|
+
|
|
3
|
+
A powerful and flexible serverless middleware framework for Google Cloud Functions with full TypeScript support. This framework provides a clean, type-safe way to handle HTTP and Pub/Sub requests through a composable middleware system inspired by Middy.js.
|
|
4
|
+
|
|
5
|
+
## Core Architecture
|
|
6
|
+
|
|
7
|
+
### Handler System
|
|
8
|
+
|
|
9
|
+
The `Handler` class manages the middleware execution pipeline with `before`, `after`, and `onError` lifecycle hooks:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const handler = new Handler<RequestType, UserType>()
|
|
13
|
+
.use(errorHandler())
|
|
14
|
+
.use(bodyParser())
|
|
15
|
+
.use(bodyValidator(schema))
|
|
16
|
+
.handle(async (context) => {
|
|
17
|
+
// Your business logic here
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Type-Safe Context
|
|
22
|
+
|
|
23
|
+
The context system provides full TypeScript support with generic typing:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
interface Context<T = unknown, U = unknown> {
|
|
27
|
+
req: CustomRequest<T>; // Request with parsedBody and validatedBody
|
|
28
|
+
res: CustomResponse; // Response object
|
|
29
|
+
container?: Container; // TypeDI dependency injection
|
|
30
|
+
error?: Error | null; // Error handling
|
|
31
|
+
businessData: Map<string, unknown>; // Inter-middleware data sharing
|
|
32
|
+
user?: U; // Authenticated user data
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Middleware Lifecycle
|
|
37
|
+
|
|
38
|
+
Middlewares support three lifecycle hooks:
|
|
39
|
+
- **before**: Execute before the main handler
|
|
40
|
+
- **after**: Execute after the main handler (reverse order)
|
|
41
|
+
- **onError**: Handle errors (reverse order)
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install @noony/serverless
|
|
49
|
+
# or
|
|
50
|
+
yarn add @noony/serverless
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Basic HTTP Function
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { http } from '@google-cloud/functions-framework';
|
|
57
|
+
import { z } from 'zod';
|
|
58
|
+
import {
|
|
59
|
+
Handler,
|
|
60
|
+
ErrorHandlerMiddleware,
|
|
61
|
+
BodyValidationMiddleware,
|
|
62
|
+
ResponseWrapperMiddleware,
|
|
63
|
+
} from '@noony/serverless';
|
|
64
|
+
|
|
65
|
+
// Define request schema
|
|
66
|
+
const userSchema = z.object({
|
|
67
|
+
name: z.string().min(2),
|
|
68
|
+
email: z.string().email(),
|
|
69
|
+
age: z.number().min(18),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
type UserRequest = z.infer<typeof userSchema>;
|
|
73
|
+
|
|
74
|
+
// Create handler with full type safety
|
|
75
|
+
const createUserHandler = new Handler<UserRequest, unknown>()
|
|
76
|
+
.use(new ErrorHandlerMiddleware())
|
|
77
|
+
.use(new BodyValidationMiddleware(userSchema))
|
|
78
|
+
.use(new ResponseWrapperMiddleware())
|
|
79
|
+
.handle(async (context) => {
|
|
80
|
+
// TypeScript knows validatedBody is UserRequest
|
|
81
|
+
const { name, email, age } = context.req.validatedBody!;
|
|
82
|
+
|
|
83
|
+
// Your business logic
|
|
84
|
+
const user = await createUser({ name, email, age });
|
|
85
|
+
|
|
86
|
+
context.res.json({
|
|
87
|
+
message: 'User created successfully',
|
|
88
|
+
userId: user.id,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Export Google Cloud Function
|
|
93
|
+
export const createUser = http('createUser', (req, res) => {
|
|
94
|
+
return createUserHandler.execute(req, res);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Pub/Sub Function Example
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { cloudEvent } from '@google-cloud/functions-framework';
|
|
102
|
+
import { z } from 'zod';
|
|
103
|
+
import {
|
|
104
|
+
Handler,
|
|
105
|
+
ErrorHandlerMiddleware,
|
|
106
|
+
BodyParserMiddleware,
|
|
107
|
+
BodyValidationMiddleware,
|
|
108
|
+
} from '@noony/serverless';
|
|
109
|
+
|
|
110
|
+
// Define message schema
|
|
111
|
+
const messageSchema = z.object({
|
|
112
|
+
userId: z.string().uuid(),
|
|
113
|
+
action: z.enum(['CREATE', 'UPDATE', 'DELETE']),
|
|
114
|
+
payload: z.record(z.unknown()),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
type PubSubMessage = z.infer<typeof messageSchema>;
|
|
118
|
+
|
|
119
|
+
// Create Pub/Sub handler
|
|
120
|
+
const pubsubHandler = new Handler<PubSubMessage, unknown>()
|
|
121
|
+
.use(new ErrorHandlerMiddleware())
|
|
122
|
+
.use(new BodyParserMiddleware()) // Decodes base64 Pub/Sub messages
|
|
123
|
+
.use(new BodyValidationMiddleware(messageSchema))
|
|
124
|
+
.handle(async (context) => {
|
|
125
|
+
const { action, payload } = context.req.validatedBody!;
|
|
126
|
+
|
|
127
|
+
// Process message based on action
|
|
128
|
+
switch (action) {
|
|
129
|
+
case 'CREATE':
|
|
130
|
+
await handleCreateAction(payload);
|
|
131
|
+
break;
|
|
132
|
+
case 'UPDATE':
|
|
133
|
+
await handleUpdateAction(payload);
|
|
134
|
+
break;
|
|
135
|
+
case 'DELETE':
|
|
136
|
+
await handleDeleteAction(payload);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Export Cloud Function
|
|
142
|
+
export const processPubSubMessage = cloudEvent('processPubSubMessage', (cloudEvent) => {
|
|
143
|
+
return pubsubHandler.execute(cloudEvent.data, {});
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Built-in Middlewares
|
|
148
|
+
|
|
149
|
+
### ErrorHandlerMiddleware
|
|
150
|
+
|
|
151
|
+
Centralized error handling with custom error types:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
.use(new ErrorHandlerMiddleware())
|
|
155
|
+
|
|
156
|
+
// Handles these error types:
|
|
157
|
+
throw new HttpError(400, 'Bad Request');
|
|
158
|
+
throw new ValidationError('Invalid input');
|
|
159
|
+
throw new AuthenticationError('Unauthorized');
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### BodyParserMiddleware
|
|
163
|
+
|
|
164
|
+
Automatically parses JSON and Pub/Sub messages:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
.use(new BodyParserMiddleware())
|
|
168
|
+
// Sets context.req.parsedBody
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### BodyValidationMiddleware
|
|
172
|
+
|
|
173
|
+
Zod schema validation with TypeScript integration:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const schema = z.object({ name: z.string() });
|
|
177
|
+
.use(new BodyValidationMiddleware(schema))
|
|
178
|
+
// Sets context.req.validatedBody with proper typing
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### AuthenticationMiddleware
|
|
182
|
+
|
|
183
|
+
JWT token verification:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const tokenVerifier = {
|
|
187
|
+
async verifyToken(token: string) {
|
|
188
|
+
// Your verification logic
|
|
189
|
+
return { userId: '123', role: 'user' };
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
.use(new AuthenticationMiddleware(tokenVerifier))
|
|
193
|
+
// Sets context.user
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### ResponseWrapperMiddleware
|
|
197
|
+
|
|
198
|
+
Standardized response format:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
.use(new ResponseWrapperMiddleware())
|
|
202
|
+
// Wraps responses in: { success: true, payload: data, timestamp }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### HeaderVariablesMiddleware
|
|
206
|
+
|
|
207
|
+
Validate required headers:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
.use(new HeaderVariablesMiddleware(['authorization', 'content-type']))
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### QueryParametersMiddleware
|
|
214
|
+
|
|
215
|
+
Process query parameters:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
.use(new QueryParametersMiddleware())
|
|
219
|
+
// Processes context.req.query
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### DependencyInjectionMiddleware
|
|
223
|
+
|
|
224
|
+
TypeDI container integration:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
.use(new DependencyInjectionMiddleware([
|
|
228
|
+
{ id: 'userService', value: new UserService() }
|
|
229
|
+
]))
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Error Handling
|
|
233
|
+
|
|
234
|
+
Built-in error classes with proper HTTP status codes:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// HTTP errors with custom status codes
|
|
238
|
+
throw new HttpError(400, 'Bad Request', 'INVALID_INPUT');
|
|
239
|
+
|
|
240
|
+
// Validation errors (400 status)
|
|
241
|
+
throw new ValidationError('Invalid email format', zodErrors);
|
|
242
|
+
|
|
243
|
+
// Authentication errors (401 status)
|
|
244
|
+
throw new AuthenticationError('Invalid token');
|
|
245
|
+
|
|
246
|
+
// Authorization errors (403 status)
|
|
247
|
+
throw new AuthorizationError('Insufficient permissions');
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Framework Integration
|
|
251
|
+
|
|
252
|
+
### Google Cloud Functions
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { http } from '@google-cloud/functions-framework';
|
|
256
|
+
|
|
257
|
+
export const myFunction = http('myFunction', (req, res) => {
|
|
258
|
+
return handler.execute(req, res);
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Fastify Integration
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import Fastify from 'fastify';
|
|
266
|
+
import { Handler } from '@noony/serverless';
|
|
267
|
+
|
|
268
|
+
const fastify = Fastify();
|
|
269
|
+
|
|
270
|
+
fastify.post('/users', async (request, reply) => {
|
|
271
|
+
const req = { ...request, body: request.body };
|
|
272
|
+
const res = {
|
|
273
|
+
status: (code: number) => reply.status(code),
|
|
274
|
+
json: (data: any) => reply.send(data)
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
await handler.execute(req, res);
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Express Integration
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import express from 'express';
|
|
285
|
+
import { Handler } from '@noony/serverless';
|
|
286
|
+
|
|
287
|
+
const app = express();
|
|
288
|
+
|
|
289
|
+
app.post('/users', async (req, res) => {
|
|
290
|
+
await handler.execute(req, res);
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Best Practices
|
|
295
|
+
|
|
296
|
+
### 1. Middleware Order
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
const handler = new Handler<RequestType, UserType>()
|
|
300
|
+
.use(new ErrorHandlerMiddleware()) // Always first
|
|
301
|
+
.use(new HeaderVariablesMiddleware(...)) // Required headers
|
|
302
|
+
.use(new AuthenticationMiddleware(...)) // Authentication
|
|
303
|
+
.use(new BodyParserMiddleware()) // Parse body
|
|
304
|
+
.use(new BodyValidationMiddleware(...)) // Validate
|
|
305
|
+
.use(new DependencyInjectionMiddleware(...))
|
|
306
|
+
.use(new ResponseWrapperMiddleware()) // Always last
|
|
307
|
+
.handle(async (context) => {
|
|
308
|
+
// Business logic
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 2. Type Safety
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// Define clear interfaces
|
|
316
|
+
interface UserRequest {
|
|
317
|
+
name: string;
|
|
318
|
+
email: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
interface UserContext {
|
|
322
|
+
userId: string;
|
|
323
|
+
role: string;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Use throughout the handler
|
|
327
|
+
const handler = new Handler<UserRequest, UserContext>();
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 3. Error Handling
|
|
331
|
+
|
|
332
|
+
- Always use ErrorHandlerMiddleware first
|
|
333
|
+
- Throw appropriate error types
|
|
334
|
+
- Handle errors gracefully in business logic
|
|
335
|
+
- Use proper HTTP status codes
|
|
336
|
+
|
|
337
|
+
### 4. Testing
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// Mock context for testing
|
|
341
|
+
const mockContext = {
|
|
342
|
+
req: { validatedBody: { name: 'test' } },
|
|
343
|
+
res: { json: jest.fn() },
|
|
344
|
+
businessData: new Map(),
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
await handler.handle(mockContext);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## TypeScript Support
|
|
351
|
+
|
|
352
|
+
The framework provides full type safety through generic types:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import {
|
|
356
|
+
Handler,
|
|
357
|
+
Context,
|
|
358
|
+
BaseMiddleware,
|
|
359
|
+
ErrorHandlerMiddleware,
|
|
360
|
+
BodyValidationMiddleware,
|
|
361
|
+
} from '@noony/serverless';
|
|
362
|
+
|
|
363
|
+
// No type casting needed with proper generics
|
|
364
|
+
const handler = new Handler<UserRequest, UserContext>()
|
|
365
|
+
.handle(async (context) => {
|
|
366
|
+
// TypeScript knows validatedBody is UserRequest
|
|
367
|
+
const { name, email } = context.req.validatedBody!;
|
|
368
|
+
// TypeScript knows user is UserContext
|
|
369
|
+
const { userId } = context.user!;
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Development Commands
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
npm run build # Compile TypeScript
|
|
377
|
+
npm run watch # Watch mode compilation
|
|
378
|
+
npm run test # Run Jest tests
|
|
379
|
+
npm run test:coverage # Test with coverage
|
|
380
|
+
npm run lint # ESLint check
|
|
381
|
+
npm run format # Prettier formatting
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Example API Usage
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Create user with authentication
|
|
388
|
+
curl -X POST http://localhost:3000/api/users \
|
|
389
|
+
-H "Content-Type: application/json" \
|
|
390
|
+
-H "Authorization: Bearer valid-token" \
|
|
391
|
+
-H "x-api-version: v1" \
|
|
392
|
+
-d '{"name":"John Doe","email":"john@example.com","age":30}'
|
|
393
|
+
|
|
394
|
+
# Get user by ID
|
|
395
|
+
curl -H "Authorization: Bearer valid-token" \
|
|
396
|
+
http://localhost:3000/api/users/123
|
|
397
|
+
|
|
398
|
+
# List users with query parameters
|
|
399
|
+
curl -H "Authorization: Bearer valid-token" \
|
|
400
|
+
"http://localhost:3000/api/users?name=john"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Deployment
|
|
404
|
+
|
|
405
|
+
### Google Cloud Functions
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# Deploy HTTP function
|
|
409
|
+
gcloud functions deploy myFunction \
|
|
410
|
+
--runtime nodejs20 \
|
|
411
|
+
--trigger-http \
|
|
412
|
+
--entry-point myFunction \
|
|
413
|
+
--allow-unauthenticated
|
|
414
|
+
|
|
415
|
+
# Deploy Pub/Sub function
|
|
416
|
+
gcloud functions deploy myPubSubFunction \
|
|
417
|
+
--runtime nodejs20 \
|
|
418
|
+
--trigger-topic my-topic \
|
|
419
|
+
--entry-point myPubSubFunction
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Cloud Run
|
|
423
|
+
|
|
424
|
+
```dockerfile
|
|
425
|
+
FROM node:20-alpine
|
|
426
|
+
WORKDIR /app
|
|
427
|
+
COPY package*.json ./
|
|
428
|
+
RUN npm ci --only=production
|
|
429
|
+
COPY . .
|
|
430
|
+
RUN npm run build
|
|
431
|
+
EXPOSE 8080
|
|
432
|
+
CMD ["npm", "start"]
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Community & Support
|
|
436
|
+
|
|
437
|
+
- 📖 [Documentation](https://github.com/noony-org/noony-serverless)
|
|
438
|
+
- 🐛 [Issue Tracker](https://github.com/noony-org/noony-serverless/issues)
|
|
439
|
+
- 💬 [Discussions](https://github.com/noony-org/noony-serverless/discussions)
|
|
440
|
+
|
|
441
|
+
## License
|
|
442
|
+
|
|
443
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Container from 'typedi';
|
|
2
|
+
/**
|
|
3
|
+
* Performance optimization: Container Pool for reusing TypeDI containers
|
|
4
|
+
* This reduces object creation overhead and improves memory efficiency
|
|
5
|
+
*/
|
|
6
|
+
declare class ContainerPool {
|
|
7
|
+
private availableContainers;
|
|
8
|
+
private maxPoolSize;
|
|
9
|
+
private createdContainers;
|
|
10
|
+
constructor(maxPoolSize?: number);
|
|
11
|
+
/**
|
|
12
|
+
* Get a container from the pool or create a new one
|
|
13
|
+
*/
|
|
14
|
+
acquire(): Container;
|
|
15
|
+
/**
|
|
16
|
+
* Return a container to the pool for reuse
|
|
17
|
+
*/
|
|
18
|
+
release(container: Container): void;
|
|
19
|
+
/**
|
|
20
|
+
* Reset container state to prevent cross-request contamination
|
|
21
|
+
* Note: TypeDI containers are isolated by default, so we mainly need
|
|
22
|
+
* to clear any manually set values
|
|
23
|
+
*/
|
|
24
|
+
private resetContainer;
|
|
25
|
+
/**
|
|
26
|
+
* Get pool statistics for monitoring
|
|
27
|
+
*/
|
|
28
|
+
getStats(): {
|
|
29
|
+
available: number;
|
|
30
|
+
created: number;
|
|
31
|
+
maxSize: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Warm up the pool by pre-creating containers
|
|
35
|
+
*/
|
|
36
|
+
warmUp(count?: number): void;
|
|
37
|
+
/**
|
|
38
|
+
* Clear all containers from the pool
|
|
39
|
+
*/
|
|
40
|
+
clear(): void;
|
|
41
|
+
}
|
|
42
|
+
declare const containerPool: ContainerPool;
|
|
43
|
+
export { ContainerPool, containerPool };
|
|
44
|
+
//# sourceMappingURL=containerPool.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
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.containerPool = exports.ContainerPool = void 0;
|
|
7
|
+
const typedi_1 = __importDefault(require("typedi"));
|
|
8
|
+
/**
|
|
9
|
+
* Performance optimization: Container Pool for reusing TypeDI containers
|
|
10
|
+
* This reduces object creation overhead and improves memory efficiency
|
|
11
|
+
*/
|
|
12
|
+
class ContainerPool {
|
|
13
|
+
availableContainers = [];
|
|
14
|
+
maxPoolSize = 10;
|
|
15
|
+
createdContainers = 0;
|
|
16
|
+
constructor(maxPoolSize = 10) {
|
|
17
|
+
this.maxPoolSize = maxPoolSize;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get a container from the pool or create a new one
|
|
21
|
+
*/
|
|
22
|
+
acquire() {
|
|
23
|
+
if (this.availableContainers.length > 0) {
|
|
24
|
+
return this.availableContainers.pop();
|
|
25
|
+
}
|
|
26
|
+
// Create new container if pool is empty and under limit
|
|
27
|
+
if (this.createdContainers < this.maxPoolSize) {
|
|
28
|
+
this.createdContainers++;
|
|
29
|
+
return typedi_1.default.of();
|
|
30
|
+
}
|
|
31
|
+
// If pool is at capacity, create a temporary container
|
|
32
|
+
// This should rarely happen in normal usage
|
|
33
|
+
return typedi_1.default.of();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Return a container to the pool for reuse
|
|
37
|
+
*/
|
|
38
|
+
release(container) {
|
|
39
|
+
if (this.availableContainers.length < this.maxPoolSize) {
|
|
40
|
+
// Reset container state by removing all instances
|
|
41
|
+
// This prevents memory leaks and cross-request contamination
|
|
42
|
+
this.resetContainer(container);
|
|
43
|
+
this.availableContainers.push(container);
|
|
44
|
+
}
|
|
45
|
+
// If pool is full, let the container be garbage collected
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Reset container state to prevent cross-request contamination
|
|
49
|
+
* Note: TypeDI containers are isolated by default, so we mainly need
|
|
50
|
+
* to clear any manually set values
|
|
51
|
+
*/
|
|
52
|
+
resetContainer(_container) {
|
|
53
|
+
try {
|
|
54
|
+
// For TypeDI containers created with Container.of(), each container
|
|
55
|
+
// is already isolated. We just need to ensure no memory leaks.
|
|
56
|
+
// The container will be garbage collected when released from pool
|
|
57
|
+
// if it contains too much data
|
|
58
|
+
// TypeDI containers are self-contained and don't need explicit reset
|
|
59
|
+
// This is a placeholder for future enhancements if needed
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// If any issues occur, don't add back to pool
|
|
63
|
+
console.warn('Failed to reset container, discarding:', error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get pool statistics for monitoring
|
|
68
|
+
*/
|
|
69
|
+
getStats() {
|
|
70
|
+
return {
|
|
71
|
+
available: this.availableContainers.length,
|
|
72
|
+
created: this.createdContainers,
|
|
73
|
+
maxSize: this.maxPoolSize,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Warm up the pool by pre-creating containers
|
|
78
|
+
*/
|
|
79
|
+
warmUp(count = 5) {
|
|
80
|
+
const warmUpCount = Math.min(count, this.maxPoolSize);
|
|
81
|
+
for (let i = 0; i < warmUpCount; i++) {
|
|
82
|
+
if (this.createdContainers < this.maxPoolSize) {
|
|
83
|
+
const container = typedi_1.default.of();
|
|
84
|
+
this.createdContainers++;
|
|
85
|
+
this.availableContainers.push(container);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Clear all containers from the pool
|
|
91
|
+
*/
|
|
92
|
+
clear() {
|
|
93
|
+
this.availableContainers = [];
|
|
94
|
+
this.createdContainers = 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.ContainerPool = ContainerPool;
|
|
98
|
+
// Global container pool instance
|
|
99
|
+
const containerPool = new ContainerPool(15); // Slightly higher limit for serverless
|
|
100
|
+
exports.containerPool = containerPool;
|
|
101
|
+
// Warm up the pool for better cold start performance
|
|
102
|
+
containerPool.warmUp(3);
|
|
103
|
+
//# sourceMappingURL=containerPool.js.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Request, Response } from '@google-cloud/functions-framework';
|
|
2
|
+
import Container from 'typedi';
|
|
3
|
+
/**
|
|
4
|
+
* Framework-agnostic HTTP method enum
|
|
5
|
+
*/
|
|
6
|
+
export declare enum HttpMethod {
|
|
7
|
+
GET = "GET",
|
|
8
|
+
POST = "POST",
|
|
9
|
+
PUT = "PUT",
|
|
10
|
+
DELETE = "DELETE",
|
|
11
|
+
PATCH = "PATCH",
|
|
12
|
+
OPTIONS = "OPTIONS",
|
|
13
|
+
HEAD = "HEAD"
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Framework-agnostic request interface that can work with any HTTP framework
|
|
17
|
+
*/
|
|
18
|
+
export interface GenericRequest<T = unknown> {
|
|
19
|
+
method: HttpMethod | string;
|
|
20
|
+
url: string;
|
|
21
|
+
path?: string;
|
|
22
|
+
headers: Record<string, string | string[] | undefined>;
|
|
23
|
+
query: Record<string, string | string[] | undefined>;
|
|
24
|
+
params: Record<string, string>;
|
|
25
|
+
body?: unknown;
|
|
26
|
+
rawBody?: Buffer | string;
|
|
27
|
+
parsedBody?: T;
|
|
28
|
+
validatedBody?: T;
|
|
29
|
+
ip?: string;
|
|
30
|
+
userAgent?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Framework-agnostic response interface that can work with any HTTP framework
|
|
34
|
+
*/
|
|
35
|
+
export interface GenericResponse {
|
|
36
|
+
status(code: number): GenericResponse;
|
|
37
|
+
json(data: unknown): GenericResponse | void;
|
|
38
|
+
send(data: unknown): GenericResponse | void;
|
|
39
|
+
header(name: string, value: string): GenericResponse;
|
|
40
|
+
headers(headers: Record<string, string>): GenericResponse;
|
|
41
|
+
end(): void;
|
|
42
|
+
statusCode?: number;
|
|
43
|
+
headersSent?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Legacy GCP Functions-specific request interface for backward compatibility
|
|
47
|
+
* @deprecated Use GenericRequest instead
|
|
48
|
+
*/
|
|
49
|
+
export interface CustomRequest<T = unknown> extends Request {
|
|
50
|
+
parsedBody?: T;
|
|
51
|
+
validatedBody?: T;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Legacy GCP Functions-specific response interface for backward compatibility
|
|
55
|
+
* @deprecated Use GenericResponse instead
|
|
56
|
+
*/
|
|
57
|
+
export interface CustomResponse extends Response {
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Security configuration for request processing
|
|
61
|
+
*/
|
|
62
|
+
export interface SecurityConfig {
|
|
63
|
+
maxBodySize?: number;
|
|
64
|
+
maxDepth?: number;
|
|
65
|
+
allowedContentTypes?: string[];
|
|
66
|
+
enableSanitization?: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Handler configuration options
|
|
70
|
+
*/
|
|
71
|
+
export interface HandlerOptions {
|
|
72
|
+
timeout?: number;
|
|
73
|
+
middlewareTimeout?: number;
|
|
74
|
+
security?: SecurityConfig;
|
|
75
|
+
enableAsyncContext?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Represents the execution context for handling a request and response in an application.
|
|
79
|
+
*
|
|
80
|
+
* @template T Specifies the type of the custom request payload.
|
|
81
|
+
* @template V Specifies the type of the user-related information.
|
|
82
|
+
*/
|
|
83
|
+
export interface Context<T = unknown, V = unknown> {
|
|
84
|
+
readonly req: GenericRequest<T>;
|
|
85
|
+
readonly res: GenericResponse;
|
|
86
|
+
container?: Container;
|
|
87
|
+
error?: Error | null;
|
|
88
|
+
readonly businessData: Map<string, unknown>;
|
|
89
|
+
user?: V;
|
|
90
|
+
readonly startTime: number;
|
|
91
|
+
readonly requestId: string;
|
|
92
|
+
timeoutSignal?: AbortSignal;
|
|
93
|
+
responseData?: unknown;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Legacy context interface for backward compatibility
|
|
97
|
+
* @deprecated Use Context with GenericRequest/GenericResponse instead
|
|
98
|
+
*/
|
|
99
|
+
export interface LegacyContext<T = unknown, V = unknown> {
|
|
100
|
+
req: CustomRequest<T>;
|
|
101
|
+
res: CustomResponse;
|
|
102
|
+
container?: Container;
|
|
103
|
+
error?: Error | null;
|
|
104
|
+
businessData: Map<string, unknown>;
|
|
105
|
+
user?: V;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Utility function to generate unique request IDs
|
|
109
|
+
*/
|
|
110
|
+
export declare function generateRequestId(): string;
|
|
111
|
+
/**
|
|
112
|
+
* Adapter to convert GCP Functions Request to GenericRequest
|
|
113
|
+
*/
|
|
114
|
+
export declare function adaptGCPRequest<T = unknown>(gcpRequest: Request): GenericRequest<T>;
|
|
115
|
+
/**
|
|
116
|
+
* Adapter to convert GCP Functions Response to GenericResponse
|
|
117
|
+
*/
|
|
118
|
+
export declare function adaptGCPResponse(gcpResponse: Response): GenericResponse;
|
|
119
|
+
/**
|
|
120
|
+
* Creates a context object for framework-agnostic handlers
|
|
121
|
+
*/
|
|
122
|
+
export declare function createContext<T = unknown, V = unknown>(req: GenericRequest<T>, res: GenericResponse, options?: Partial<Context<T, V>>): Context<T, V>;
|
|
123
|
+
//# sourceMappingURL=core.d.ts.map
|