@noony-serverless/core 0.2.1 → 0.3.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 +7 -7
- package/build/core/containerPool.d.ts +44 -0
- package/build/core/containerPool.js +100 -0
- package/build/core/core.d.ts +69 -18
- package/build/core/core.js +63 -2
- package/build/core/errors.d.ts +43 -0
- package/build/core/errors.js +74 -1
- package/build/core/handler.d.ts +16 -37
- package/build/core/handler.js +42 -131
- package/build/core/index.d.ts +1 -0
- package/build/core/index.js +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +4 -0
- package/build/middlewares/ProcessingMiddleware.js +3 -1
- package/build/middlewares/bodyValidationMiddleware.d.ts +10 -12
- package/build/middlewares/bodyValidationMiddleware.js +7 -9
- package/build/middlewares/guards/RouteGuards.d.ts +2 -2
- package/build/middlewares/guards/RouteGuards.js +2 -2
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +1 -1
- package/build/middlewares/guards/guards/FastAuthGuard.d.ts +5 -5
- package/build/middlewares/guards/guards/PermissionGuardFactory.d.ts +10 -2
- package/build/middlewares/guards/guards/PermissionGuardFactory.js +10 -5
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.js +1 -1
- package/build/middlewares/guards/resolvers/PermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/PlainPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/WildcardPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/services/FastUserContextService.d.ts +34 -10
- package/build/middlewares/index.d.ts +1 -3
- package/build/middlewares/index.js +1 -6
- package/build/middlewares/validationMiddleware.d.ts +154 -0
- package/build/middlewares/validationMiddleware.js +185 -0
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A powerful and flexible serverless middleware framework for Google Cloud Functio
|
|
|
9
9
|
The `Handler` class manages the middleware execution pipeline with `before`, `after`, and `onError` lifecycle hooks:
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
|
-
const handler = new Handler<RequestType>()
|
|
12
|
+
const handler = new Handler<RequestType, UserType>()
|
|
13
13
|
.use(errorHandler())
|
|
14
14
|
.use(bodyParser())
|
|
15
15
|
.use(bodyValidator(schema))
|
|
@@ -23,7 +23,7 @@ const handler = new Handler<RequestType>()
|
|
|
23
23
|
The context system provides full TypeScript support with generic typing:
|
|
24
24
|
|
|
25
25
|
```typescript
|
|
26
|
-
interface Context<T = unknown> {
|
|
26
|
+
interface Context<T = unknown, U = unknown> {
|
|
27
27
|
req: CustomRequest<T>; // Request with parsedBody and validatedBody
|
|
28
28
|
res: CustomResponse; // Response object
|
|
29
29
|
container?: Container; // TypeDI dependency injection
|
|
@@ -72,7 +72,7 @@ const userSchema = z.object({
|
|
|
72
72
|
type UserRequest = z.infer<typeof userSchema>;
|
|
73
73
|
|
|
74
74
|
// Create handler with full type safety
|
|
75
|
-
const createUserHandler = new Handler<UserRequest>()
|
|
75
|
+
const createUserHandler = new Handler<UserRequest, unknown>()
|
|
76
76
|
.use(new ErrorHandlerMiddleware())
|
|
77
77
|
.use(new BodyValidationMiddleware(userSchema))
|
|
78
78
|
.use(new ResponseWrapperMiddleware())
|
|
@@ -117,7 +117,7 @@ const messageSchema = z.object({
|
|
|
117
117
|
type PubSubMessage = z.infer<typeof messageSchema>;
|
|
118
118
|
|
|
119
119
|
// Create Pub/Sub handler
|
|
120
|
-
const pubsubHandler = new Handler<PubSubMessage>()
|
|
120
|
+
const pubsubHandler = new Handler<PubSubMessage, unknown>()
|
|
121
121
|
.use(new ErrorHandlerMiddleware())
|
|
122
122
|
.use(new BodyParserMiddleware()) // Decodes base64 Pub/Sub messages
|
|
123
123
|
.use(new BodyValidationMiddleware(messageSchema))
|
|
@@ -296,7 +296,7 @@ app.post('/users', async (req, res) => {
|
|
|
296
296
|
### 1. Middleware Order
|
|
297
297
|
|
|
298
298
|
```typescript
|
|
299
|
-
const handler = new Handler<RequestType>()
|
|
299
|
+
const handler = new Handler<RequestType, UserType>()
|
|
300
300
|
.use(new ErrorHandlerMiddleware()) // Always first
|
|
301
301
|
.use(new HeaderVariablesMiddleware(...)) // Required headers
|
|
302
302
|
.use(new AuthenticationMiddleware(...)) // Authentication
|
|
@@ -324,7 +324,7 @@ interface UserContext {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
// Use throughout the handler
|
|
327
|
-
const handler = new Handler<UserRequest>();
|
|
327
|
+
const handler = new Handler<UserRequest, UserContext>();
|
|
328
328
|
```
|
|
329
329
|
|
|
330
330
|
### 3. Error Handling
|
|
@@ -361,7 +361,7 @@ import {
|
|
|
361
361
|
} from '@noony/serverless';
|
|
362
362
|
|
|
363
363
|
// No type casting needed with proper generics
|
|
364
|
-
const handler = new Handler<UserRequest>()
|
|
364
|
+
const handler = new Handler<UserRequest, UserContext>()
|
|
365
365
|
.handle(async (context) => {
|
|
366
366
|
// TypeScript knows validatedBody is UserRequest
|
|
367
367
|
const { name, email } = context.req.validatedBody!;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ContainerInstance } 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(): ContainerInstance;
|
|
15
|
+
/**
|
|
16
|
+
* Return a container to the pool for reuse
|
|
17
|
+
*/
|
|
18
|
+
release(container: ContainerInstance): 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,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.containerPool = exports.ContainerPool = void 0;
|
|
4
|
+
const typedi_1 = require("typedi");
|
|
5
|
+
/**
|
|
6
|
+
* Performance optimization: Container Pool for reusing TypeDI containers
|
|
7
|
+
* This reduces object creation overhead and improves memory efficiency
|
|
8
|
+
*/
|
|
9
|
+
class ContainerPool {
|
|
10
|
+
availableContainers = [];
|
|
11
|
+
maxPoolSize = 10;
|
|
12
|
+
createdContainers = 0;
|
|
13
|
+
constructor(maxPoolSize = 10) {
|
|
14
|
+
this.maxPoolSize = maxPoolSize;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get a container from the pool or create a new one
|
|
18
|
+
*/
|
|
19
|
+
acquire() {
|
|
20
|
+
if (this.availableContainers.length > 0) {
|
|
21
|
+
return this.availableContainers.pop();
|
|
22
|
+
}
|
|
23
|
+
// Create new container if pool is empty and under limit
|
|
24
|
+
if (this.createdContainers < this.maxPoolSize) {
|
|
25
|
+
this.createdContainers++;
|
|
26
|
+
return typedi_1.Container.of();
|
|
27
|
+
}
|
|
28
|
+
// If pool is at capacity, create a temporary container
|
|
29
|
+
// This should rarely happen in normal usage
|
|
30
|
+
return typedi_1.Container.of();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Return a container to the pool for reuse
|
|
34
|
+
*/
|
|
35
|
+
release(container) {
|
|
36
|
+
if (this.availableContainers.length < this.maxPoolSize) {
|
|
37
|
+
// Reset container state by removing all instances
|
|
38
|
+
// This prevents memory leaks and cross-request contamination
|
|
39
|
+
this.resetContainer(container);
|
|
40
|
+
this.availableContainers.push(container);
|
|
41
|
+
}
|
|
42
|
+
// If pool is full, let the container be garbage collected
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reset container state to prevent cross-request contamination
|
|
46
|
+
* Note: TypeDI containers are isolated by default, so we mainly need
|
|
47
|
+
* to clear any manually set values
|
|
48
|
+
*/
|
|
49
|
+
resetContainer(_container) {
|
|
50
|
+
try {
|
|
51
|
+
// For TypeDI containers created with Container.of(), each container
|
|
52
|
+
// is already isolated. We just need to ensure no memory leaks.
|
|
53
|
+
// The container will be garbage collected when released from pool
|
|
54
|
+
// if it contains too much data
|
|
55
|
+
// TypeDI containers are self-contained and don't need explicit reset
|
|
56
|
+
// This is a placeholder for future enhancements if needed
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// If any issues occur, don't add back to pool
|
|
60
|
+
console.warn('Failed to reset container, discarding:', error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get pool statistics for monitoring
|
|
65
|
+
*/
|
|
66
|
+
getStats() {
|
|
67
|
+
return {
|
|
68
|
+
available: this.availableContainers.length,
|
|
69
|
+
created: this.createdContainers,
|
|
70
|
+
maxSize: this.maxPoolSize,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Warm up the pool by pre-creating containers
|
|
75
|
+
*/
|
|
76
|
+
warmUp(count = 5) {
|
|
77
|
+
const warmUpCount = Math.min(count, this.maxPoolSize);
|
|
78
|
+
for (let i = 0; i < warmUpCount; i++) {
|
|
79
|
+
if (this.createdContainers < this.maxPoolSize) {
|
|
80
|
+
const container = typedi_1.Container.of();
|
|
81
|
+
this.createdContainers++;
|
|
82
|
+
this.availableContainers.push(container);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Clear all containers from the pool
|
|
88
|
+
*/
|
|
89
|
+
clear() {
|
|
90
|
+
this.availableContainers = [];
|
|
91
|
+
this.createdContainers = 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.ContainerPool = ContainerPool;
|
|
95
|
+
// Global container pool instance
|
|
96
|
+
const containerPool = new ContainerPool(15); // Slightly higher limit for serverless
|
|
97
|
+
exports.containerPool = containerPool;
|
|
98
|
+
// Warm up the pool for better cold start performance
|
|
99
|
+
containerPool.warmUp(3);
|
|
100
|
+
//# sourceMappingURL=containerPool.js.map
|
package/build/core/core.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Request, Response } from '@google-cloud/functions-framework';
|
|
2
|
+
import { Container, ContainerInstance } from 'typedi';
|
|
2
3
|
/**
|
|
3
4
|
* Framework-agnostic HTTP method enum
|
|
4
5
|
*/
|
|
@@ -14,7 +15,7 @@ export declare enum HttpMethod {
|
|
|
14
15
|
/**
|
|
15
16
|
* Framework-agnostic request interface that can work with any HTTP framework
|
|
16
17
|
*/
|
|
17
|
-
export interface
|
|
18
|
+
export interface GenericRequest<T = unknown> {
|
|
18
19
|
method: HttpMethod | string;
|
|
19
20
|
url: string;
|
|
20
21
|
path?: string;
|
|
@@ -31,20 +32,30 @@ export interface NoonyRequest<T = unknown> {
|
|
|
31
32
|
/**
|
|
32
33
|
* Framework-agnostic response interface that can work with any HTTP framework
|
|
33
34
|
*/
|
|
34
|
-
export interface
|
|
35
|
-
status(code: number):
|
|
36
|
-
json(data: unknown):
|
|
37
|
-
send(data: unknown):
|
|
38
|
-
header(name: string, value: string):
|
|
39
|
-
headers(headers: Record<string, string>):
|
|
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;
|
|
40
41
|
end(): void;
|
|
41
42
|
statusCode?: number;
|
|
42
43
|
headersSent?: boolean;
|
|
43
44
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
+
}
|
|
48
59
|
/**
|
|
49
60
|
* Security configuration for request processing
|
|
50
61
|
*/
|
|
@@ -63,29 +74,69 @@ export interface HandlerOptions {
|
|
|
63
74
|
security?: SecurityConfig;
|
|
64
75
|
enableAsyncContext?: boolean;
|
|
65
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Base authenticated user interface that projects can extend
|
|
79
|
+
*/
|
|
80
|
+
export interface BaseAuthenticatedUser {
|
|
81
|
+
id: string;
|
|
82
|
+
email?: string;
|
|
83
|
+
name?: string;
|
|
84
|
+
[key: string]: unknown;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Modern request type alias (recommended)
|
|
88
|
+
*/
|
|
89
|
+
export type NoonyRequest<T = unknown> = GenericRequest<T>;
|
|
90
|
+
/**
|
|
91
|
+
* Modern response type alias (recommended)
|
|
92
|
+
*/
|
|
93
|
+
export type NoonyResponse = GenericResponse;
|
|
66
94
|
/**
|
|
67
95
|
* Represents the execution context for handling a request and response in an application.
|
|
68
96
|
*
|
|
69
|
-
* @template
|
|
97
|
+
* @template TBody Specifies the type of the request body payload.
|
|
98
|
+
* @template TUser Specifies the type of the authenticated user (defaults to unknown).
|
|
70
99
|
*/
|
|
71
|
-
export interface Context<
|
|
72
|
-
readonly req: NoonyRequest<
|
|
100
|
+
export interface Context<TBody = unknown, TUser = unknown> {
|
|
101
|
+
readonly req: NoonyRequest<TBody>;
|
|
73
102
|
readonly res: NoonyResponse;
|
|
74
|
-
container
|
|
103
|
+
container: ContainerInstance;
|
|
75
104
|
error?: Error | null;
|
|
76
105
|
readonly businessData: Map<string, unknown>;
|
|
77
|
-
user?:
|
|
106
|
+
user?: TUser;
|
|
78
107
|
readonly startTime: number;
|
|
79
108
|
readonly requestId: string;
|
|
80
109
|
timeoutSignal?: AbortSignal;
|
|
81
110
|
responseData?: unknown;
|
|
82
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Legacy context interface for backward compatibility
|
|
114
|
+
* @deprecated Use Context with GenericRequest/GenericResponse instead
|
|
115
|
+
*/
|
|
116
|
+
export interface LegacyContext<T = unknown, V = unknown> {
|
|
117
|
+
req: CustomRequest<T>;
|
|
118
|
+
res: CustomResponse;
|
|
119
|
+
container?: Container;
|
|
120
|
+
error?: Error | null;
|
|
121
|
+
businessData: Map<string, unknown>;
|
|
122
|
+
user?: V;
|
|
123
|
+
}
|
|
83
124
|
/**
|
|
84
125
|
* Utility function to generate unique request IDs
|
|
85
126
|
*/
|
|
86
127
|
export declare function generateRequestId(): string;
|
|
128
|
+
/**
|
|
129
|
+
* Adapter to convert GCP Functions Request to GenericRequest
|
|
130
|
+
*/
|
|
131
|
+
export declare function adaptGCPRequest<T = unknown>(gcpRequest: Request): GenericRequest<T>;
|
|
132
|
+
/**
|
|
133
|
+
* Adapter to convert GCP Functions Response to GenericResponse
|
|
134
|
+
*/
|
|
135
|
+
export declare function adaptGCPResponse(gcpResponse: Response): GenericResponse;
|
|
87
136
|
/**
|
|
88
137
|
* Creates a context object for framework-agnostic handlers
|
|
138
|
+
* @template TBody The type of the request body
|
|
139
|
+
* @template TUser The type of the authenticated user
|
|
89
140
|
*/
|
|
90
|
-
export declare function createContext<
|
|
141
|
+
export declare function createContext<TBody = unknown, TUser = unknown>(req: NoonyRequest<TBody>, res: NoonyResponse, options?: Partial<Context<TBody, TUser>>): Context<TBody, TUser>;
|
|
91
142
|
//# sourceMappingURL=core.d.ts.map
|
package/build/core/core.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HttpMethod = void 0;
|
|
4
4
|
exports.generateRequestId = generateRequestId;
|
|
5
|
+
exports.adaptGCPRequest = adaptGCPRequest;
|
|
6
|
+
exports.adaptGCPResponse = adaptGCPResponse;
|
|
5
7
|
exports.createContext = createContext;
|
|
6
8
|
const typedi_1 = require("typedi");
|
|
7
9
|
/**
|
|
@@ -17,16 +19,75 @@ var HttpMethod;
|
|
|
17
19
|
HttpMethod["OPTIONS"] = "OPTIONS";
|
|
18
20
|
HttpMethod["HEAD"] = "HEAD";
|
|
19
21
|
})(HttpMethod || (exports.HttpMethod = HttpMethod = {}));
|
|
20
|
-
// Legacy context interface removed - use Context with NoonyRequest/NoonyResponse instead
|
|
21
22
|
/**
|
|
22
23
|
* Utility function to generate unique request IDs
|
|
23
24
|
*/
|
|
24
25
|
function generateRequestId() {
|
|
25
26
|
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Adapter to convert GCP Functions Request to GenericRequest
|
|
30
|
+
*/
|
|
31
|
+
function adaptGCPRequest(gcpRequest) {
|
|
32
|
+
return {
|
|
33
|
+
method: gcpRequest.method || HttpMethod.GET,
|
|
34
|
+
url: gcpRequest.url || '/',
|
|
35
|
+
path: gcpRequest.path,
|
|
36
|
+
headers: gcpRequest.headers || {},
|
|
37
|
+
query: gcpRequest.query || {},
|
|
38
|
+
params: gcpRequest.params || {},
|
|
39
|
+
body: gcpRequest.body,
|
|
40
|
+
rawBody: gcpRequest.rawBody,
|
|
41
|
+
ip: gcpRequest.ip,
|
|
42
|
+
userAgent: gcpRequest.get?.('user-agent'),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Adapter to convert GCP Functions Response to GenericResponse
|
|
47
|
+
*/
|
|
48
|
+
function adaptGCPResponse(gcpResponse) {
|
|
49
|
+
let currentStatusCode = 200;
|
|
50
|
+
let isHeadersSent = false;
|
|
51
|
+
return {
|
|
52
|
+
status: (code) => {
|
|
53
|
+
currentStatusCode = code;
|
|
54
|
+
gcpResponse.status(code);
|
|
55
|
+
return adaptGCPResponse(gcpResponse);
|
|
56
|
+
},
|
|
57
|
+
json: (data) => {
|
|
58
|
+
isHeadersSent = true;
|
|
59
|
+
gcpResponse.json(data);
|
|
60
|
+
},
|
|
61
|
+
send: (data) => {
|
|
62
|
+
isHeadersSent = true;
|
|
63
|
+
gcpResponse.send(data);
|
|
64
|
+
},
|
|
65
|
+
header: (name, value) => {
|
|
66
|
+
gcpResponse.header(name, value);
|
|
67
|
+
return adaptGCPResponse(gcpResponse);
|
|
68
|
+
},
|
|
69
|
+
headers: (headers) => {
|
|
70
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
71
|
+
gcpResponse.header(key, value);
|
|
72
|
+
});
|
|
73
|
+
return adaptGCPResponse(gcpResponse);
|
|
74
|
+
},
|
|
75
|
+
end: () => {
|
|
76
|
+
isHeadersSent = true;
|
|
77
|
+
gcpResponse.end();
|
|
78
|
+
},
|
|
79
|
+
get statusCode() {
|
|
80
|
+
return gcpResponse.statusCode || currentStatusCode;
|
|
81
|
+
},
|
|
82
|
+
get headersSent() {
|
|
83
|
+
return gcpResponse.headersSent || isHeadersSent;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
28
87
|
/**
|
|
29
88
|
* Creates a context object for framework-agnostic handlers
|
|
89
|
+
* @template TBody The type of the request body
|
|
90
|
+
* @template TUser The type of the authenticated user
|
|
30
91
|
*/
|
|
31
92
|
function createContext(req, res, options = {}) {
|
|
32
93
|
return {
|
package/build/core/errors.d.ts
CHANGED
|
@@ -22,4 +22,47 @@ export declare class TimeoutError extends HttpError {
|
|
|
22
22
|
export declare class TooLargeError extends HttpError {
|
|
23
23
|
constructor(message?: string, details?: unknown);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* 401 Unauthorized - Authentication required
|
|
27
|
+
* Alias for AuthenticationError for better semantic clarity
|
|
28
|
+
*/
|
|
29
|
+
export declare class UnauthorizedError extends HttpError {
|
|
30
|
+
constructor(message?: string);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 403 Forbidden - Insufficient permissions
|
|
34
|
+
* Use this for authorization failures (user is authenticated but lacks permission)
|
|
35
|
+
*/
|
|
36
|
+
export declare class ForbiddenError extends HttpError {
|
|
37
|
+
constructor(message?: string, details?: unknown);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 404 Not Found - Resource not found
|
|
41
|
+
*/
|
|
42
|
+
export declare class NotFoundError extends HttpError {
|
|
43
|
+
constructor(message?: string, details?: unknown);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 409 Conflict - Resource already exists or state conflict
|
|
47
|
+
*/
|
|
48
|
+
export declare class ConflictError extends HttpError {
|
|
49
|
+
constructor(message?: string, details?: unknown);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 500 Internal Server Error - Unexpected errors with optional cause chaining
|
|
53
|
+
*/
|
|
54
|
+
export declare class InternalServerError extends HttpError {
|
|
55
|
+
cause?: Error | undefined;
|
|
56
|
+
constructor(message?: string, cause?: Error | undefined, details?: unknown);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Service layer error with error code
|
|
60
|
+
* Use this in service classes for business logic errors
|
|
61
|
+
* Not tied to specific HTTP status codes
|
|
62
|
+
*/
|
|
63
|
+
export declare class ServiceError extends Error {
|
|
64
|
+
code: string;
|
|
65
|
+
details?: unknown | undefined;
|
|
66
|
+
constructor(message: string, code: string, details?: unknown | undefined);
|
|
67
|
+
}
|
|
25
68
|
//# sourceMappingURL=errors.d.ts.map
|
package/build/core/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TooLargeError = exports.TimeoutError = exports.SecurityError = exports.BusinessError = exports.AuthenticationError = exports.ValidationError = exports.HttpError = void 0;
|
|
3
|
+
exports.ServiceError = exports.InternalServerError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.TooLargeError = exports.TimeoutError = exports.SecurityError = exports.BusinessError = exports.AuthenticationError = exports.ValidationError = exports.HttpError = void 0;
|
|
4
4
|
class HttpError extends Error {
|
|
5
5
|
status;
|
|
6
6
|
code;
|
|
@@ -56,4 +56,77 @@ class TooLargeError extends HttpError {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
exports.TooLargeError = TooLargeError;
|
|
59
|
+
/**
|
|
60
|
+
* 401 Unauthorized - Authentication required
|
|
61
|
+
* Alias for AuthenticationError for better semantic clarity
|
|
62
|
+
*/
|
|
63
|
+
class UnauthorizedError extends HttpError {
|
|
64
|
+
constructor(message = 'Authentication required') {
|
|
65
|
+
super(401, message, 'UNAUTHORIZED_ERROR');
|
|
66
|
+
this.name = 'UnauthorizedError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.UnauthorizedError = UnauthorizedError;
|
|
70
|
+
/**
|
|
71
|
+
* 403 Forbidden - Insufficient permissions
|
|
72
|
+
* Use this for authorization failures (user is authenticated but lacks permission)
|
|
73
|
+
*/
|
|
74
|
+
class ForbiddenError extends HttpError {
|
|
75
|
+
constructor(message = 'Access denied', details) {
|
|
76
|
+
super(403, message, 'FORBIDDEN_ERROR', details);
|
|
77
|
+
this.name = 'ForbiddenError';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.ForbiddenError = ForbiddenError;
|
|
81
|
+
/**
|
|
82
|
+
* 404 Not Found - Resource not found
|
|
83
|
+
*/
|
|
84
|
+
class NotFoundError extends HttpError {
|
|
85
|
+
constructor(message = 'Resource not found', details) {
|
|
86
|
+
super(404, message, 'NOT_FOUND_ERROR', details);
|
|
87
|
+
this.name = 'NotFoundError';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.NotFoundError = NotFoundError;
|
|
91
|
+
/**
|
|
92
|
+
* 409 Conflict - Resource already exists or state conflict
|
|
93
|
+
*/
|
|
94
|
+
class ConflictError extends HttpError {
|
|
95
|
+
constructor(message = 'Resource already exists', details) {
|
|
96
|
+
super(409, message, 'CONFLICT_ERROR', details);
|
|
97
|
+
this.name = 'ConflictError';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.ConflictError = ConflictError;
|
|
101
|
+
/**
|
|
102
|
+
* 500 Internal Server Error - Unexpected errors with optional cause chaining
|
|
103
|
+
*/
|
|
104
|
+
class InternalServerError extends HttpError {
|
|
105
|
+
cause;
|
|
106
|
+
constructor(message = 'Internal server error', cause, details) {
|
|
107
|
+
super(500, message, 'INTERNAL_SERVER_ERROR', details);
|
|
108
|
+
this.cause = cause;
|
|
109
|
+
this.name = 'InternalServerError';
|
|
110
|
+
if (cause) {
|
|
111
|
+
this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.InternalServerError = InternalServerError;
|
|
116
|
+
/**
|
|
117
|
+
* Service layer error with error code
|
|
118
|
+
* Use this in service classes for business logic errors
|
|
119
|
+
* Not tied to specific HTTP status codes
|
|
120
|
+
*/
|
|
121
|
+
class ServiceError extends Error {
|
|
122
|
+
code;
|
|
123
|
+
details;
|
|
124
|
+
constructor(message, code, details) {
|
|
125
|
+
super(message);
|
|
126
|
+
this.code = code;
|
|
127
|
+
this.details = details;
|
|
128
|
+
this.name = 'ServiceError';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.ServiceError = ServiceError;
|
|
59
132
|
//# sourceMappingURL=errors.js.map
|
package/build/core/handler.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context,
|
|
1
|
+
import { Context, CustomRequest, CustomResponse, GenericRequest, GenericResponse } from './core';
|
|
2
2
|
/**
|
|
3
3
|
* Interface representing a base structure for middleware with optional lifecycle methods.
|
|
4
4
|
*
|
|
@@ -7,12 +7,13 @@ import { Context, NoonyRequest, NoonyResponse } from './core';
|
|
|
7
7
|
* executed before and after a process, as well as handling errors that
|
|
8
8
|
* occur during the process.
|
|
9
9
|
*
|
|
10
|
-
* @template T - The type of the request
|
|
10
|
+
* @template T - The type of the request or input context. Defaults to unknown.
|
|
11
|
+
* @template U - The type of the response or output context. Defaults to unknown.
|
|
11
12
|
*/
|
|
12
|
-
export interface BaseMiddleware<T = unknown> {
|
|
13
|
-
before?: (context: Context<T>) => Promise<void>;
|
|
14
|
-
after?: (context: Context<T>) => Promise<void>;
|
|
15
|
-
onError?: (error: Error, context: Context<T>) => Promise<void>;
|
|
13
|
+
export interface BaseMiddleware<T = unknown, U = unknown> {
|
|
14
|
+
before?: (context: Context<T, U>) => Promise<void>;
|
|
15
|
+
after?: (context: Context<T, U>) => Promise<void>;
|
|
16
|
+
onError?: (error: Error, context: Context<T, U>) => Promise<void>;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* The Handler class is responsible for managing and executing middleware functions
|
|
@@ -22,8 +23,6 @@ export interface BaseMiddleware<T = unknown> {
|
|
|
22
23
|
* process a request/response flow either before the main handler (via `before`),
|
|
23
24
|
* after the main handler (via `after`), or handle errors (via `onError`).
|
|
24
25
|
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```typescript
|
|
27
26
|
* interface MessagePayload {
|
|
28
27
|
* action: string;
|
|
29
28
|
* data: Record<string, unknown>;
|
|
@@ -34,29 +33,25 @@ export interface BaseMiddleware<T = unknown> {
|
|
|
34
33
|
* .use(bodyParser())
|
|
35
34
|
* .handle(async (context) => {
|
|
36
35
|
* const { req } = context;
|
|
37
|
-
* // Handle the request
|
|
36
|
+
* // Handle the request
|
|
38
37
|
* });
|
|
39
|
-
* ```
|
|
40
38
|
* @template T Type for the input request data.
|
|
39
|
+
* @template U Type for the additional context or response data.
|
|
41
40
|
*/
|
|
42
|
-
export declare class Handler<T = unknown> {
|
|
41
|
+
export declare class Handler<T = unknown, U = unknown> {
|
|
43
42
|
private baseMiddlewares;
|
|
44
43
|
private handler;
|
|
45
44
|
private reversedMiddlewares;
|
|
46
45
|
private errorMiddlewares;
|
|
47
46
|
private middlewaresPrecomputed;
|
|
48
|
-
static use<T = unknown>(middleware: BaseMiddleware<T>): Handler<T>;
|
|
49
|
-
use(middleware: BaseMiddleware<
|
|
50
|
-
handle(handler: (context: Context<T>) => Promise<void>): Handler<T>;
|
|
47
|
+
static use<T = unknown, U = unknown>(middleware: BaseMiddleware<T, U>): Handler<T, U>;
|
|
48
|
+
use<NewT = T, NewU = U>(middleware: BaseMiddleware<NewT, NewU>): Handler<NewT, NewU>;
|
|
49
|
+
handle(handler: (context: Context<T, U>) => Promise<void>): Handler<T, U>;
|
|
51
50
|
/**
|
|
52
51
|
* Performance optimization: Pre-compute middleware arrays to avoid runtime array operations
|
|
53
52
|
*/
|
|
54
53
|
private precomputeMiddlewareArrays;
|
|
55
|
-
|
|
56
|
-
* Universal execute method that works with any HTTP framework
|
|
57
|
-
* Automatically detects and adapts GCP, Express, AWS Lambda, Fastify, etc.
|
|
58
|
-
*/
|
|
59
|
-
execute(nativeReq: unknown, nativeRes: unknown): Promise<void>;
|
|
54
|
+
execute(req: CustomRequest<T>, res: CustomResponse): Promise<void>;
|
|
60
55
|
/**
|
|
61
56
|
* Execute before middlewares with optimized batching for independent middlewares
|
|
62
57
|
*/
|
|
@@ -70,24 +65,8 @@ export declare class Handler<T = unknown> {
|
|
|
70
65
|
*/
|
|
71
66
|
private executeErrorMiddlewares;
|
|
72
67
|
/**
|
|
73
|
-
*
|
|
74
|
-
*/
|
|
75
|
-
private adaptToNoonyRequest;
|
|
76
|
-
/**
|
|
77
|
-
* Universal response adapter - converts any framework's response to NoonyResponse
|
|
78
|
-
*/
|
|
79
|
-
private adaptToNoonyResponse;
|
|
80
|
-
/**
|
|
81
|
-
* Create response adapter for AWS Lambda
|
|
82
|
-
*/
|
|
83
|
-
private createAWSLambdaResponse;
|
|
84
|
-
/**
|
|
85
|
-
* Create response adapter for standard HTTP frameworks (GCP, Express, Fastify)
|
|
86
|
-
*/
|
|
87
|
-
private createStandardHTTPResponse;
|
|
88
|
-
/**
|
|
89
|
-
* @deprecated Use execute() instead - automatically detects framework
|
|
68
|
+
* Framework-agnostic execute method that works with GenericRequest/GenericResponse
|
|
90
69
|
*/
|
|
91
|
-
executeGeneric(req:
|
|
70
|
+
executeGeneric(req: GenericRequest<T>, res: GenericResponse): Promise<void>;
|
|
92
71
|
}
|
|
93
72
|
//# sourceMappingURL=handler.d.ts.map
|