@noony-serverless/core 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/build/core/core.d.ts +18 -50
- package/build/core/core.js +5 -67
- package/build/core/handler.d.ts +37 -16
- package/build/core/handler.js +131 -42
- package/build/core/index.d.ts +0 -1
- package/build/core/index.js +0 -1
- package/build/middlewares/ConsolidatedValidationMiddleware.d.ts +126 -0
- package/build/middlewares/ConsolidatedValidationMiddleware.js +330 -0
- package/build/middlewares/ProcessingMiddleware.d.ts +138 -0
- package/build/middlewares/ProcessingMiddleware.js +425 -0
- package/build/middlewares/SecurityMiddleware.d.ts +157 -0
- package/build/middlewares/SecurityMiddleware.js +307 -0
- package/build/middlewares/bodyValidationMiddleware.d.ts +12 -10
- package/build/middlewares/bodyValidationMiddleware.js +10 -8
- package/build/middlewares/dependencyInjectionMiddleware.js +1 -1
- package/build/middlewares/guards/RouteGuards.d.ts +239 -4
- package/build/middlewares/guards/RouteGuards.js +301 -8
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +271 -0
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.js +301 -0
- package/build/middlewares/guards/config/GuardConfiguration.d.ts +50 -0
- package/build/middlewares/guards/config/GuardConfiguration.js +59 -0
- package/build/middlewares/guards/guards/FastAuthGuard.d.ts +5 -5
- package/build/middlewares/guards/guards/PermissionGuardFactory.d.ts +5 -13
- package/build/middlewares/guards/guards/PermissionGuardFactory.js +4 -4
- package/build/middlewares/guards/index.d.ts +43 -1
- package/build/middlewares/guards/index.js +46 -1
- 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 +20 -33
- package/build/middlewares/guards/services/FastUserContextService.js +17 -4
- package/build/middlewares/httpAttributesMiddleware.js +1 -1
- package/build/middlewares/index.d.ts +3 -1
- package/build/middlewares/index.js +6 -1
- package/build/middlewares/rateLimitingMiddleware.d.ts +492 -4
- package/build/middlewares/rateLimitingMiddleware.js +514 -6
- package/package.json +11 -9
- package/build/core/containerPool.d.ts +0 -44
- package/build/core/containerPool.js +0 -103
- package/build/middlewares/validationMiddleware.d.ts +0 -154
- package/build/middlewares/validationMiddleware.js +0 -185
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ZodSchema } from 'zod';
|
|
2
|
+
import { BaseMiddleware, Context } from '../core';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for body validation
|
|
5
|
+
*/
|
|
6
|
+
export interface BodyValidationConfig<T = unknown> {
|
|
7
|
+
schema?: ZodSchema<T>;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
maxSize?: number;
|
|
10
|
+
allowEmpty?: boolean;
|
|
11
|
+
customValidator?: (body: unknown) => Promise<T> | T;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for header validation
|
|
15
|
+
*/
|
|
16
|
+
export interface HeaderValidationConfig {
|
|
17
|
+
required?: string[];
|
|
18
|
+
optional?: string[];
|
|
19
|
+
patterns?: Record<string, RegExp>;
|
|
20
|
+
customValidators?: Record<string, (value: string) => boolean>;
|
|
21
|
+
caseSensitive?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for query parameter validation
|
|
25
|
+
*/
|
|
26
|
+
export interface QueryValidationConfig {
|
|
27
|
+
allowedParams?: string[];
|
|
28
|
+
requiredParams?: string[];
|
|
29
|
+
patterns?: Record<string, RegExp>;
|
|
30
|
+
maxParams?: number;
|
|
31
|
+
parseTypes?: Record<string, 'string' | 'number' | 'boolean' | 'array'>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Complete configuration for ConsolidatedValidationMiddleware
|
|
35
|
+
*/
|
|
36
|
+
export interface ConsolidatedValidationMiddlewareConfig<T = unknown> {
|
|
37
|
+
body?: BodyValidationConfig<T>;
|
|
38
|
+
headers?: HeaderValidationConfig;
|
|
39
|
+
query?: QueryValidationConfig;
|
|
40
|
+
skipValidation?: (context: Context<T>) => boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Consolidated ValidationMiddleware that combines body validation, header validation, and query parameter validation.
|
|
44
|
+
*
|
|
45
|
+
* This middleware replaces the need for separate:
|
|
46
|
+
* - BodyValidationMiddleware
|
|
47
|
+
* - ValidationMiddleware
|
|
48
|
+
* - HeaderVariablesMiddleware
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* Complete validation setup:
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const createUserSchema = z.object({
|
|
54
|
+
* name: z.string().min(1).max(100),
|
|
55
|
+
* email: z.string().email(),
|
|
56
|
+
* age: z.number().min(18).max(120)
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* const handler = new Handler()
|
|
60
|
+
* .use(new ConsolidatedValidationMiddleware({
|
|
61
|
+
* body: {
|
|
62
|
+
* schema: createUserSchema,
|
|
63
|
+
* required: true,
|
|
64
|
+
* maxSize: 1024 * 1024 // 1MB
|
|
65
|
+
* },
|
|
66
|
+
* headers: {
|
|
67
|
+
* required: ['authorization', 'content-type'],
|
|
68
|
+
* optional: ['x-trace-id', 'user-agent'],
|
|
69
|
+
* patterns: {
|
|
70
|
+
* 'x-trace-id': /^[a-f0-9-]{36}$/
|
|
71
|
+
* }
|
|
72
|
+
* },
|
|
73
|
+
* query: {
|
|
74
|
+
* allowedParams: ['page', 'limit', 'sort'],
|
|
75
|
+
* requiredParams: ['page'],
|
|
76
|
+
* parseTypes: {
|
|
77
|
+
* page: 'number',
|
|
78
|
+
* limit: 'number'
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* }))
|
|
82
|
+
* .handle(async (context) => {
|
|
83
|
+
* // context.req.validatedBody is typed as z.infer<typeof createUserSchema>
|
|
84
|
+
* const { name, email, age } = context.req.validatedBody!;
|
|
85
|
+
* return { message: `Creating user ${name}` };
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare class ConsolidatedValidationMiddleware<T = unknown> implements BaseMiddleware<T> {
|
|
90
|
+
private config;
|
|
91
|
+
constructor(config?: ConsolidatedValidationMiddlewareConfig<T>);
|
|
92
|
+
before(context: Context<T>): Promise<void>;
|
|
93
|
+
private validateHeaders;
|
|
94
|
+
private validateQueryParameters;
|
|
95
|
+
private validateBody;
|
|
96
|
+
private normalizeHeaders;
|
|
97
|
+
private parseQueryParamType;
|
|
98
|
+
private isEmpty;
|
|
99
|
+
private getBodySize;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Factory functions for creating ConsolidatedValidationMiddleware with common configurations
|
|
103
|
+
*/
|
|
104
|
+
export declare const createConsolidatedValidationMiddleware: {
|
|
105
|
+
/**
|
|
106
|
+
* Body-only validation with Zod schema
|
|
107
|
+
*/
|
|
108
|
+
bodyOnly: <T>(schema: ZodSchema<T>) => ConsolidatedValidationMiddleware<T>;
|
|
109
|
+
/**
|
|
110
|
+
* Headers-only validation
|
|
111
|
+
*/
|
|
112
|
+
headersOnly: (required: string[], optional?: string[]) => ConsolidatedValidationMiddleware;
|
|
113
|
+
/**
|
|
114
|
+
* Query-only validation
|
|
115
|
+
*/
|
|
116
|
+
queryOnly: (config: QueryValidationConfig) => ConsolidatedValidationMiddleware;
|
|
117
|
+
/**
|
|
118
|
+
* Complete validation setup
|
|
119
|
+
*/
|
|
120
|
+
complete: <T>(config: ConsolidatedValidationMiddlewareConfig<T>) => ConsolidatedValidationMiddleware<T>;
|
|
121
|
+
/**
|
|
122
|
+
* API validation with common patterns
|
|
123
|
+
*/
|
|
124
|
+
apiValidation: <T>(schema: ZodSchema<T>) => ConsolidatedValidationMiddleware<T>;
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=ConsolidatedValidationMiddleware.d.ts.map
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createConsolidatedValidationMiddleware = exports.ConsolidatedValidationMiddleware = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const core_1 = require("../core");
|
|
6
|
+
/**
|
|
7
|
+
* Consolidated ValidationMiddleware that combines body validation, header validation, and query parameter validation.
|
|
8
|
+
*
|
|
9
|
+
* This middleware replaces the need for separate:
|
|
10
|
+
* - BodyValidationMiddleware
|
|
11
|
+
* - ValidationMiddleware
|
|
12
|
+
* - HeaderVariablesMiddleware
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* Complete validation setup:
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const createUserSchema = z.object({
|
|
18
|
+
* name: z.string().min(1).max(100),
|
|
19
|
+
* email: z.string().email(),
|
|
20
|
+
* age: z.number().min(18).max(120)
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* const handler = new Handler()
|
|
24
|
+
* .use(new ConsolidatedValidationMiddleware({
|
|
25
|
+
* body: {
|
|
26
|
+
* schema: createUserSchema,
|
|
27
|
+
* required: true,
|
|
28
|
+
* maxSize: 1024 * 1024 // 1MB
|
|
29
|
+
* },
|
|
30
|
+
* headers: {
|
|
31
|
+
* required: ['authorization', 'content-type'],
|
|
32
|
+
* optional: ['x-trace-id', 'user-agent'],
|
|
33
|
+
* patterns: {
|
|
34
|
+
* 'x-trace-id': /^[a-f0-9-]{36}$/
|
|
35
|
+
* }
|
|
36
|
+
* },
|
|
37
|
+
* query: {
|
|
38
|
+
* allowedParams: ['page', 'limit', 'sort'],
|
|
39
|
+
* requiredParams: ['page'],
|
|
40
|
+
* parseTypes: {
|
|
41
|
+
* page: 'number',
|
|
42
|
+
* limit: 'number'
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* }))
|
|
46
|
+
* .handle(async (context) => {
|
|
47
|
+
* // context.req.validatedBody is typed as z.infer<typeof createUserSchema>
|
|
48
|
+
* const { name, email, age } = context.req.validatedBody!;
|
|
49
|
+
* return { message: `Creating user ${name}` };
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
class ConsolidatedValidationMiddleware {
|
|
54
|
+
config;
|
|
55
|
+
constructor(config = {}) {
|
|
56
|
+
this.config = {
|
|
57
|
+
body: { required: false, allowEmpty: true },
|
|
58
|
+
headers: { caseSensitive: false },
|
|
59
|
+
query: { maxParams: 100 },
|
|
60
|
+
...config,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async before(context) {
|
|
64
|
+
// Skip validation if custom skip function returns true
|
|
65
|
+
if (this.config.skipValidation && this.config.skipValidation(context)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// 1. Validate headers first (early failure)
|
|
69
|
+
if (this.config.headers) {
|
|
70
|
+
await this.validateHeaders(context);
|
|
71
|
+
}
|
|
72
|
+
// 2. Validate query parameters
|
|
73
|
+
if (this.config.query) {
|
|
74
|
+
await this.validateQueryParameters(context);
|
|
75
|
+
}
|
|
76
|
+
// 3. Validate request body (most expensive, do last)
|
|
77
|
+
if (this.config.body) {
|
|
78
|
+
await this.validateBody(context);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async validateHeaders(context) {
|
|
82
|
+
const headerConfig = this.config.headers;
|
|
83
|
+
const headers = context.req.headers || {};
|
|
84
|
+
// Normalize headers for case-insensitive comparison if needed
|
|
85
|
+
const normalizedHeaders = headerConfig.caseSensitive
|
|
86
|
+
? headers
|
|
87
|
+
: this.normalizeHeaders(headers);
|
|
88
|
+
// Check required headers
|
|
89
|
+
if (headerConfig.required) {
|
|
90
|
+
for (const requiredHeader of headerConfig.required) {
|
|
91
|
+
const headerKey = headerConfig.caseSensitive
|
|
92
|
+
? requiredHeader
|
|
93
|
+
: requiredHeader.toLowerCase();
|
|
94
|
+
if (!normalizedHeaders[headerKey]) {
|
|
95
|
+
throw new core_1.ValidationError(`Missing required header: ${requiredHeader}`, `Header '${requiredHeader}' is required but not provided`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Validate header patterns
|
|
100
|
+
if (headerConfig.patterns) {
|
|
101
|
+
for (const [headerName, pattern] of Object.entries(headerConfig.patterns)) {
|
|
102
|
+
const headerKey = headerConfig.caseSensitive
|
|
103
|
+
? headerName
|
|
104
|
+
: headerName.toLowerCase();
|
|
105
|
+
const headerValue = normalizedHeaders[headerKey];
|
|
106
|
+
if (headerValue &&
|
|
107
|
+
typeof headerValue === 'string' &&
|
|
108
|
+
!pattern.test(headerValue)) {
|
|
109
|
+
throw new core_1.ValidationError(`Invalid header format: ${headerName}`, `Header '${headerName}' value '${headerValue}' does not match required pattern`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Custom header validators
|
|
114
|
+
if (headerConfig.customValidators) {
|
|
115
|
+
for (const [headerName, validator] of Object.entries(headerConfig.customValidators)) {
|
|
116
|
+
const headerKey = headerConfig.caseSensitive
|
|
117
|
+
? headerName
|
|
118
|
+
: headerName.toLowerCase();
|
|
119
|
+
const headerValue = normalizedHeaders[headerKey];
|
|
120
|
+
if (headerValue &&
|
|
121
|
+
typeof headerValue === 'string' &&
|
|
122
|
+
!validator(headerValue)) {
|
|
123
|
+
throw new core_1.ValidationError(`Invalid header value: ${headerName}`, `Header '${headerName}' failed custom validation`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async validateQueryParameters(context) {
|
|
129
|
+
const queryConfig = this.config.query;
|
|
130
|
+
const query = context.req.query || {};
|
|
131
|
+
// Check max parameters limit
|
|
132
|
+
if (queryConfig.maxParams &&
|
|
133
|
+
Object.keys(query).length > queryConfig.maxParams) {
|
|
134
|
+
throw new core_1.ValidationError('Too many query parameters', `Maximum ${queryConfig.maxParams} query parameters allowed`);
|
|
135
|
+
}
|
|
136
|
+
// Check allowed parameters
|
|
137
|
+
if (queryConfig.allowedParams) {
|
|
138
|
+
for (const paramName of Object.keys(query)) {
|
|
139
|
+
if (!queryConfig.allowedParams.includes(paramName)) {
|
|
140
|
+
throw new core_1.ValidationError(`Invalid query parameter: ${paramName}`, `Parameter '${paramName}' is not allowed`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Check required parameters
|
|
145
|
+
if (queryConfig.requiredParams) {
|
|
146
|
+
for (const requiredParam of queryConfig.requiredParams) {
|
|
147
|
+
if (!query[requiredParam]) {
|
|
148
|
+
throw new core_1.ValidationError(`Missing required query parameter: ${requiredParam}`, `Query parameter '${requiredParam}' is required`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Validate parameter patterns
|
|
153
|
+
if (queryConfig.patterns) {
|
|
154
|
+
for (const [paramName, pattern] of Object.entries(queryConfig.patterns)) {
|
|
155
|
+
const paramValue = query[paramName];
|
|
156
|
+
if (paramValue &&
|
|
157
|
+
typeof paramValue === 'string' &&
|
|
158
|
+
!pattern.test(paramValue)) {
|
|
159
|
+
throw new core_1.ValidationError(`Invalid query parameter format: ${paramName}`, `Parameter '${paramName}' value '${paramValue}' does not match required pattern`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Parse and type convert parameters
|
|
164
|
+
if (queryConfig.parseTypes) {
|
|
165
|
+
for (const [paramName, type] of Object.entries(queryConfig.parseTypes)) {
|
|
166
|
+
const paramValue = query[paramName];
|
|
167
|
+
if (paramValue) {
|
|
168
|
+
try {
|
|
169
|
+
query[paramName] = this.parseQueryParamType(paramValue, type);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
throw new core_1.ValidationError(`Invalid query parameter type: ${paramName}`, `Parameter '${paramName}' could not be parsed as ${type}: ${error}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async validateBody(context) {
|
|
179
|
+
const bodyConfig = this.config.body;
|
|
180
|
+
const body = context.req.body || context.req.parsedBody;
|
|
181
|
+
// Check if body is required
|
|
182
|
+
if (bodyConfig.required &&
|
|
183
|
+
(!body || (bodyConfig.allowEmpty === false && this.isEmpty(body)))) {
|
|
184
|
+
throw new core_1.ValidationError('Request body is required', 'Request body must be provided and non-empty');
|
|
185
|
+
}
|
|
186
|
+
// Skip validation if body is empty and empty is allowed
|
|
187
|
+
if (!body || (this.isEmpty(body) && bodyConfig.allowEmpty !== false)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Check body size limit
|
|
191
|
+
if (bodyConfig.maxSize) {
|
|
192
|
+
const bodySize = this.getBodySize(body);
|
|
193
|
+
if (bodySize > bodyConfig.maxSize) {
|
|
194
|
+
throw new core_1.ValidationError('Request body too large', `Body size ${bodySize} bytes exceeds limit of ${bodyConfig.maxSize} bytes`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Use custom validator if provided
|
|
198
|
+
if (bodyConfig.customValidator) {
|
|
199
|
+
try {
|
|
200
|
+
const validatedBody = await bodyConfig.customValidator(body);
|
|
201
|
+
context.req.validatedBody = validatedBody;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
throw new core_1.ValidationError('Custom body validation failed', error instanceof Error ? error.message : 'Custom validation error');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Use Zod schema validation if provided
|
|
209
|
+
if (bodyConfig.schema) {
|
|
210
|
+
try {
|
|
211
|
+
const validatedBody = await bodyConfig.schema.parseAsync(body);
|
|
212
|
+
context.req.validatedBody = validatedBody;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error instanceof zod_1.ZodError) {
|
|
216
|
+
const zodError = error;
|
|
217
|
+
const errorDetails = zodError.issues.map((issue) => ({
|
|
218
|
+
path: issue.path.join('.'),
|
|
219
|
+
message: issue.message,
|
|
220
|
+
code: issue.code,
|
|
221
|
+
}));
|
|
222
|
+
throw new core_1.ValidationError('Request body validation failed', `Validation errors: ${errorDetails.map((e) => `${e.path}: ${e.message}`).join(', ')}`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
throw new core_1.ValidationError('Body validation error', error instanceof Error ? error.message : 'Unknown validation error');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
normalizeHeaders(headers) {
|
|
231
|
+
const normalized = {};
|
|
232
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
233
|
+
normalized[key.toLowerCase()] = value;
|
|
234
|
+
}
|
|
235
|
+
return normalized;
|
|
236
|
+
}
|
|
237
|
+
parseQueryParamType(value, type) {
|
|
238
|
+
if (Array.isArray(value) && type !== 'array') {
|
|
239
|
+
value = value[0]; // Take first value for non-array types
|
|
240
|
+
}
|
|
241
|
+
switch (type) {
|
|
242
|
+
case 'string':
|
|
243
|
+
return typeof value === 'string' ? value : String(value);
|
|
244
|
+
case 'number':
|
|
245
|
+
if (typeof value === 'string') {
|
|
246
|
+
const parsed = Number(value);
|
|
247
|
+
if (isNaN(parsed)) {
|
|
248
|
+
throw new Error(`Cannot parse '${value}' as number`);
|
|
249
|
+
}
|
|
250
|
+
return parsed;
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`Expected string value for number parsing, got ${typeof value}`);
|
|
253
|
+
case 'boolean':
|
|
254
|
+
if (typeof value === 'string') {
|
|
255
|
+
if (value.toLowerCase() === 'true')
|
|
256
|
+
return true;
|
|
257
|
+
if (value.toLowerCase() === 'false')
|
|
258
|
+
return false;
|
|
259
|
+
throw new Error(`Cannot parse '${value}' as boolean (expected 'true' or 'false')`);
|
|
260
|
+
}
|
|
261
|
+
throw new Error(`Expected string value for boolean parsing, got ${typeof value}`);
|
|
262
|
+
case 'array':
|
|
263
|
+
return Array.isArray(value) ? value : [value];
|
|
264
|
+
default:
|
|
265
|
+
throw new Error(`Unknown type: ${type}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
isEmpty(value) {
|
|
269
|
+
if (value === null || value === undefined)
|
|
270
|
+
return true;
|
|
271
|
+
if (typeof value === 'string' && value.trim() === '')
|
|
272
|
+
return true;
|
|
273
|
+
if (Array.isArray(value) && value.length === 0)
|
|
274
|
+
return true;
|
|
275
|
+
if (typeof value === 'object' && Object.keys(value).length === 0)
|
|
276
|
+
return true;
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
getBodySize(body) {
|
|
280
|
+
if (typeof body === 'string') {
|
|
281
|
+
return Buffer.byteLength(body, 'utf8');
|
|
282
|
+
}
|
|
283
|
+
if (Buffer.isBuffer(body)) {
|
|
284
|
+
return body.length;
|
|
285
|
+
}
|
|
286
|
+
if (typeof body === 'object') {
|
|
287
|
+
return Buffer.byteLength(JSON.stringify(body), 'utf8');
|
|
288
|
+
}
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
exports.ConsolidatedValidationMiddleware = ConsolidatedValidationMiddleware;
|
|
293
|
+
/**
|
|
294
|
+
* Factory functions for creating ConsolidatedValidationMiddleware with common configurations
|
|
295
|
+
*/
|
|
296
|
+
exports.createConsolidatedValidationMiddleware = {
|
|
297
|
+
/**
|
|
298
|
+
* Body-only validation with Zod schema
|
|
299
|
+
*/
|
|
300
|
+
bodyOnly: (schema) => new ConsolidatedValidationMiddleware({ body: { schema, required: true } }),
|
|
301
|
+
/**
|
|
302
|
+
* Headers-only validation
|
|
303
|
+
*/
|
|
304
|
+
headersOnly: (required, optional = []) => new ConsolidatedValidationMiddleware({ headers: { required, optional } }),
|
|
305
|
+
/**
|
|
306
|
+
* Query-only validation
|
|
307
|
+
*/
|
|
308
|
+
queryOnly: (config) => new ConsolidatedValidationMiddleware({ query: config }),
|
|
309
|
+
/**
|
|
310
|
+
* Complete validation setup
|
|
311
|
+
*/
|
|
312
|
+
complete: (config) => new ConsolidatedValidationMiddleware(config),
|
|
313
|
+
/**
|
|
314
|
+
* API validation with common patterns
|
|
315
|
+
*/
|
|
316
|
+
apiValidation: (schema) => new ConsolidatedValidationMiddleware({
|
|
317
|
+
body: { schema, required: true },
|
|
318
|
+
headers: {
|
|
319
|
+
required: ['content-type'],
|
|
320
|
+
patterns: {
|
|
321
|
+
'content-type': /^application\/json/i,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
query: {
|
|
325
|
+
maxParams: 50,
|
|
326
|
+
allowedParams: ['page', 'limit', 'sort', 'filter'],
|
|
327
|
+
},
|
|
328
|
+
}),
|
|
329
|
+
};
|
|
330
|
+
//# sourceMappingURL=ConsolidatedValidationMiddleware.js.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { BaseMiddleware, Context, NoonyRequest } from '../core';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for body parsing functionality
|
|
4
|
+
*/
|
|
5
|
+
export interface BodyParserConfig {
|
|
6
|
+
maxSize?: number;
|
|
7
|
+
supportPubSub?: boolean;
|
|
8
|
+
allowEmptyBody?: boolean;
|
|
9
|
+
customParser?: (body: unknown) => Promise<unknown> | unknown;
|
|
10
|
+
enableAsyncParsing?: boolean;
|
|
11
|
+
asyncThreshold?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for query parameter processing
|
|
15
|
+
*/
|
|
16
|
+
export interface QueryProcessingConfig {
|
|
17
|
+
parseArrays?: boolean;
|
|
18
|
+
parseNumbers?: boolean;
|
|
19
|
+
parseBooleans?: boolean;
|
|
20
|
+
maxKeys?: number;
|
|
21
|
+
delimiter?: string;
|
|
22
|
+
arrayDelimiter?: string;
|
|
23
|
+
customParser?: (query: Record<string, unknown>) => Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Configuration for HTTP attributes extraction
|
|
27
|
+
*/
|
|
28
|
+
export interface AttributesConfig {
|
|
29
|
+
extractIP?: boolean;
|
|
30
|
+
extractUserAgent?: boolean;
|
|
31
|
+
extractTimestamp?: boolean;
|
|
32
|
+
extractContentLength?: boolean;
|
|
33
|
+
extractAcceptLanguage?: boolean;
|
|
34
|
+
extractReferer?: boolean;
|
|
35
|
+
customExtractors?: Record<string, (req: NoonyRequest) => unknown>;
|
|
36
|
+
trustProxy?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Complete configuration for ProcessingMiddleware
|
|
40
|
+
*/
|
|
41
|
+
export interface ProcessingMiddlewareConfig {
|
|
42
|
+
parser?: BodyParserConfig;
|
|
43
|
+
query?: QueryProcessingConfig;
|
|
44
|
+
attributes?: AttributesConfig;
|
|
45
|
+
skipProcessing?: (context: Context) => boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Consolidated ProcessingMiddleware that combines body parsing, query processing, and attribute extraction.
|
|
49
|
+
*
|
|
50
|
+
* This middleware replaces the need for separate:
|
|
51
|
+
* - BodyParserMiddleware
|
|
52
|
+
* - QueryParametersMiddleware
|
|
53
|
+
* - HttpAttributesMiddleware
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* Complete processing setup:
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const handler = new Handler()
|
|
59
|
+
* .use(new ProcessingMiddleware({
|
|
60
|
+
* parser: {
|
|
61
|
+
* maxSize: 1024 * 1024, // 1MB
|
|
62
|
+
* supportPubSub: true,
|
|
63
|
+
* enableAsyncParsing: true
|
|
64
|
+
* },
|
|
65
|
+
* query: {
|
|
66
|
+
* parseArrays: true,
|
|
67
|
+
* parseNumbers: true,
|
|
68
|
+
* parseBooleans: true,
|
|
69
|
+
* maxKeys: 100
|
|
70
|
+
* },
|
|
71
|
+
* attributes: {
|
|
72
|
+
* extractIP: true,
|
|
73
|
+
* extractUserAgent: true,
|
|
74
|
+
* extractTimestamp: true,
|
|
75
|
+
* trustProxy: true
|
|
76
|
+
* }
|
|
77
|
+
* }))
|
|
78
|
+
* .handle(async (context) => {
|
|
79
|
+
* // context.req.parsedBody contains parsed JSON
|
|
80
|
+
* // context.req.query contains processed query parameters
|
|
81
|
+
* // context.req.ip, context.req.userAgent, etc. are extracted
|
|
82
|
+
* return { message: 'Processing complete' };
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* Parser-only for API endpoints:
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const handler = new Handler()
|
|
90
|
+
* .use(new ProcessingMiddleware({
|
|
91
|
+
* parser: {
|
|
92
|
+
* maxSize: 512 * 1024, // 512KB
|
|
93
|
+
* supportPubSub: false
|
|
94
|
+
* }
|
|
95
|
+
* }));
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare class ProcessingMiddleware implements BaseMiddleware {
|
|
99
|
+
private config;
|
|
100
|
+
constructor(config?: ProcessingMiddlewareConfig);
|
|
101
|
+
before(context: Context): Promise<void>;
|
|
102
|
+
private extractAttributes;
|
|
103
|
+
private processQueryParameters;
|
|
104
|
+
private parseBody;
|
|
105
|
+
private parseStringBody;
|
|
106
|
+
private parsePubSubMessage;
|
|
107
|
+
private processQueryValue;
|
|
108
|
+
private extractIPAddress;
|
|
109
|
+
private extractUserAgent;
|
|
110
|
+
private extractContentLength;
|
|
111
|
+
private extractAcceptLanguage;
|
|
112
|
+
private extractReferer;
|
|
113
|
+
private isPubSubMessage;
|
|
114
|
+
private validateBase64Format;
|
|
115
|
+
private parseJsonAsync;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Factory functions for creating ProcessingMiddleware with common configurations
|
|
119
|
+
*/
|
|
120
|
+
export declare const createProcessingMiddleware: {
|
|
121
|
+
/**
|
|
122
|
+
* API processing with JSON parsing and basic attributes
|
|
123
|
+
*/
|
|
124
|
+
api: () => ProcessingMiddleware;
|
|
125
|
+
/**
|
|
126
|
+
* PubSub processing with base64 decoding
|
|
127
|
+
*/
|
|
128
|
+
pubsub: () => ProcessingMiddleware;
|
|
129
|
+
/**
|
|
130
|
+
* Lightweight processing for simple endpoints
|
|
131
|
+
*/
|
|
132
|
+
lightweight: () => ProcessingMiddleware;
|
|
133
|
+
/**
|
|
134
|
+
* Full processing with all features enabled
|
|
135
|
+
*/
|
|
136
|
+
complete: () => ProcessingMiddleware;
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=ProcessingMiddleware.d.ts.map
|