@webpieces/http-filters 0.2.12 → 0.2.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-filters",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Filter chain infrastructure for cross-cutting concerns",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -21,6 +21,6 @@
21
21
  "access": "public"
22
22
  },
23
23
  "dependencies": {
24
- "@webpieces/core-context": "0.2.12"
24
+ "@webpieces/core-context": "0.2.13"
25
25
  }
26
26
  }
package/src/Filter.d.ts CHANGED
@@ -1,112 +1,85 @@
1
1
  /**
2
- * Metadata about the method being invoked.
3
- * Passed to filters and contains request information.
2
+ * WpResponse - Wraps controller responses for the filter chain.
3
+ *
4
+ * Generic type parameter TResult represents the controller's return type.
5
+ * The filter chain uses WpResponse<unknown> because it handles all response types uniformly.
6
+ *
7
+ * JsonFilter is responsible for:
8
+ * 1. Serializing WpResponse.response to JSON
9
+ * 2. Writing the JSON to the HTTP response body
4
10
  */
5
- export declare class MethodMeta {
6
- /**
7
- * The HTTP method (GET, POST, etc.)
8
- */
9
- httpMethod: string;
10
- /**
11
- * The request path
12
- */
13
- path: string;
14
- /**
15
- * The method name being invoked on the controller
16
- */
17
- methodName: string;
11
+ export declare class WpResponse<TResult = unknown> {
12
+ response?: TResult;
13
+ statusCode: number;
14
+ headers: Map<string, string>;
15
+ constructor(response?: TResult, statusCode?: number);
18
16
  /**
19
- * Parameters to pass to the controller method.
20
- * Filters can modify this array (e.g., JsonFilter deserializes request body into params[0])
17
+ * Set a response header.
21
18
  */
22
- params: any[];
19
+ setHeader(name: string, value: string): WpResponse<TResult>;
23
20
  /**
24
- * The original request object (if applicable)
21
+ * Create an error response wrapper.
25
22
  */
26
- request?: any;
27
- /**
28
- * The response object (if applicable)
29
- */
30
- response?: any;
31
- /**
32
- * Additional metadata
33
- */
34
- metadata?: Map<string, any>;
35
- constructor(httpMethod: string, path: string, methodName: string, params: any[], request?: any, response?: any, metadata?: Map<string, any>);
36
- }
37
- /**
38
- * Action returned by filters and controllers.
39
- * Can represent different types of responses.
40
- */
41
- export declare class Action {
42
- type: 'json' | 'html' | 'redirect' | 'error';
43
- data?: any;
44
- statusCode?: number;
45
- headers?: Record<string, string>;
46
- constructor(type: 'json' | 'html' | 'redirect' | 'error', data?: any, statusCode?: number, headers?: Record<string, string>);
23
+ static error<T = unknown>(message: string, statusCode?: number): WpResponse<T>;
47
24
  }
48
25
  /**
49
- * Next filter class.
50
- * This is a class instead of a function type to make it easier to trace
51
- * who is calling what in the debugger/IDE.
26
+ * Service interface - Similar to Java WebPieces Service<REQ, RESP>.
27
+ * Represents any component that can process a request and return a response.
28
+ *
29
+ * Used for:
30
+ * - Final controller invocation
31
+ * - Wrapping filters as services in the chain
32
+ * - Functional composition of filters
52
33
  */
53
- export declare abstract class NextFilter {
34
+ export interface Service<REQ, RESP> {
54
35
  /**
55
- * Execute the next filter in the chain.
56
- * @returns Promise of the action
36
+ * Invoke the service with the given metadata.
37
+ * @param meta - Request metadata
38
+ * @returns Promise of the response
57
39
  */
58
- abstract execute(): Promise<Action>;
40
+ invoke(meta: REQ): Promise<RESP>;
59
41
  }
60
42
  /**
61
- * Filter interface.
62
- * Similar to Java WebPieces RouteFilter.
43
+ * Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.
63
44
  *
64
- * Filters are executed in priority order (higher priority first)
65
- * and can wrap the execution of subsequent filters and the controller.
45
+ * Filters are STATELESS and can handle N concurrent requests.
46
+ * They wrap the execution of subsequent filters and the controller.
66
47
  *
67
- * Example:
68
- * ```typescript
69
- * @injectable()
70
- * export class LoggingFilter implements Filter {
71
- * priority = 100;
48
+ * Key principles:
49
+ * - STATELESS: No instance variables for request data
50
+ * - COMPOSABLE: Use chain() methods for functional composition
72
51
  *
73
- * async filter(meta: MethodMeta, next: NextFilter): Promise<Action> {
74
- * console.log(`Request: ${meta.httpMethod} ${meta.path}`);
75
- * const action = await next.execute();
76
- * console.log(`Response: ${action.statusCode}`);
77
- * return action;
78
- * }
79
- * }
80
- * ```
52
+ * For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:
53
+ * - MethodMeta: Standardized request metadata (defined in http-server)
54
+ * - WpResponse<unknown>: Wraps any controller response
81
55
  */
82
- export interface Filter {
83
- /**
84
- * Priority of this filter.
85
- * Higher numbers execute first.
86
- * Typical values:
87
- * - 140: Context setup
88
- * - 120: Request attributes
89
- * - 90: Metrics
90
- * - 80: Logging
91
- * - 60: JSON serialization
92
- * - 40: Transactions
93
- * - 0: Controller
94
- */
95
- priority: number;
56
+ export declare abstract class Filter<REQ, RESP> {
96
57
  /**
97
58
  * Filter method that wraps the next filter/controller.
98
59
  *
99
60
  * @param meta - Metadata about the method being invoked
100
- * @param next - NextFilter instance to invoke the next filter in the chain
101
- * @returns Promise of the action to return
61
+ * @param nextFilter - Next filter/controller as a Service
62
+ * @returns Promise of the response
63
+ */
64
+ abstract filter(meta: REQ, nextFilter: Service<REQ, RESP>): Promise<RESP>;
65
+ /**
66
+ * Chain this filter with another filter.
67
+ * Returns a new Filter that composes both filters.
68
+ *
69
+ * Similar to Java: filter1.chain(filter2)
70
+ *
71
+ * @param nextFilter - The filter to execute after this one
72
+ * @returns Composed filter
102
73
  */
103
- filter(meta: MethodMeta, next: NextFilter): Promise<Action>;
74
+ chain(nextFilter: Filter<REQ, RESP>): Filter<REQ, RESP>;
75
+ /**
76
+ * Chain this filter with a final service (controller).
77
+ * Returns a Service that can be invoked.
78
+ *
79
+ * Similar to Java: filter.chain(service)
80
+ *
81
+ * @param svc - The final service (controller) to execute
82
+ * @returns Service wrapping the entire filter chain
83
+ */
84
+ chainService(svc: Service<REQ, RESP>): Service<REQ, RESP>;
104
85
  }
105
- /**
106
- * Helper to create a JSON action response.
107
- */
108
- export declare function jsonAction(data: any, statusCode?: number): Action;
109
- /**
110
- * Helper to create an error action response.
111
- */
112
- export declare function errorAction(error: Error | string, statusCode?: number): Action;
package/src/Filter.js CHANGED
@@ -1,57 +1,89 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NextFilter = exports.Action = exports.MethodMeta = void 0;
4
- exports.jsonAction = jsonAction;
5
- exports.errorAction = errorAction;
3
+ exports.Filter = exports.WpResponse = void 0;
6
4
  /**
7
- * Metadata about the method being invoked.
8
- * Passed to filters and contains request information.
5
+ * WpResponse - Wraps controller responses for the filter chain.
6
+ *
7
+ * Generic type parameter TResult represents the controller's return type.
8
+ * The filter chain uses WpResponse<unknown> because it handles all response types uniformly.
9
+ *
10
+ * JsonFilter is responsible for:
11
+ * 1. Serializing WpResponse.response to JSON
12
+ * 2. Writing the JSON to the HTTP response body
9
13
  */
10
- class MethodMeta {
11
- constructor(httpMethod, path, methodName, params, request, response, metadata) {
12
- this.httpMethod = httpMethod;
13
- this.path = path;
14
- this.methodName = methodName;
15
- this.params = params;
16
- this.request = request;
14
+ class WpResponse {
15
+ constructor(response, statusCode = 200) {
17
16
  this.response = response;
18
- this.metadata = metadata;
19
- }
20
- }
21
- exports.MethodMeta = MethodMeta;
22
- /**
23
- * Action returned by filters and controllers.
24
- * Can represent different types of responses.
25
- */
26
- class Action {
27
- constructor(type, data, statusCode, headers) {
28
- this.type = type;
29
- this.data = data;
30
17
  this.statusCode = statusCode;
31
- this.headers = headers;
18
+ this.headers = new Map();
19
+ }
20
+ /**
21
+ * Set a response header.
22
+ */
23
+ setHeader(name, value) {
24
+ this.headers.set(name, value);
25
+ return this;
26
+ }
27
+ /**
28
+ * Create an error response wrapper.
29
+ */
30
+ static error(message, statusCode = 500) {
31
+ const wrapper = new WpResponse(undefined, statusCode);
32
+ wrapper.setHeader('X-Error', message);
33
+ return wrapper;
32
34
  }
33
35
  }
34
- exports.Action = Action;
35
- /**
36
- * Next filter class.
37
- * This is a class instead of a function type to make it easier to trace
38
- * who is calling what in the debugger/IDE.
39
- */
40
- class NextFilter {
41
- }
42
- exports.NextFilter = NextFilter;
43
- /**
44
- * Helper to create a JSON action response.
45
- */
46
- function jsonAction(data, statusCode = 200) {
47
- return new Action('json', data, statusCode);
48
- }
36
+ exports.WpResponse = WpResponse;
49
37
  /**
50
- * Helper to create an error action response.
38
+ * Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.
39
+ *
40
+ * Filters are STATELESS and can handle N concurrent requests.
41
+ * They wrap the execution of subsequent filters and the controller.
42
+ *
43
+ * Key principles:
44
+ * - STATELESS: No instance variables for request data
45
+ * - COMPOSABLE: Use chain() methods for functional composition
46
+ *
47
+ * For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:
48
+ * - MethodMeta: Standardized request metadata (defined in http-server)
49
+ * - WpResponse<unknown>: Wraps any controller response
51
50
  */
52
- function errorAction(error, statusCode = 500) {
53
- return new Action('error', {
54
- error: typeof error === 'string' ? error : error.message,
55
- }, statusCode);
51
+ class Filter {
52
+ /**
53
+ * Chain this filter with another filter.
54
+ * Returns a new Filter that composes both filters.
55
+ *
56
+ * Similar to Java: filter1.chain(filter2)
57
+ *
58
+ * @param nextFilter - The filter to execute after this one
59
+ * @returns Composed filter
60
+ */
61
+ chain(nextFilter) {
62
+ const self = this;
63
+ return new class extends Filter {
64
+ async filter(meta, nextService) {
65
+ // Call outer filter, passing next filter wrapped as a Service
66
+ return self.filter(meta, {
67
+ invoke: (m) => nextFilter.filter(m, nextService)
68
+ });
69
+ }
70
+ };
71
+ }
72
+ /**
73
+ * Chain this filter with a final service (controller).
74
+ * Returns a Service that can be invoked.
75
+ *
76
+ * Similar to Java: filter.chain(service)
77
+ *
78
+ * @param svc - The final service (controller) to execute
79
+ * @returns Service wrapping the entire filter chain
80
+ */
81
+ chainService(svc) {
82
+ const self = this;
83
+ return {
84
+ invoke: (meta) => self.filter(meta, svc)
85
+ };
86
+ }
56
87
  }
88
+ exports.Filter = Filter;
57
89
  //# sourceMappingURL=Filter.js.map
package/src/Filter.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Filter.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/Filter.ts"],"names":[],"mappings":";;;AAkJA,gCAEC;AAKD,kCAWC;AApKD;;;GAGG;AACH,MAAa,UAAU;IAqCrB,YACE,UAAkB,EAClB,IAAY,EACZ,UAAkB,EAClB,MAAa,EACb,OAAa,EACb,QAAc,EACd,QAA2B;QAE3B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAtDD,gCAsDC;AAED;;;GAGG;AACH,MAAa,MAAM;IAMjB,YACE,IAA4C,EAC5C,IAAU,EACV,UAAmB,EACnB,OAAgC;QAEhC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAjBD,wBAiBC;AAED;;;;GAIG;AACH,MAAsB,UAAU;CAM/B;AAND,gCAMC;AAiDD;;GAEG;AACH,SAAgB,UAAU,CAAC,IAAS,EAAE,aAAqB,GAAG;IAC5D,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,SAAgB,WAAW,CACzB,KAAqB,EACrB,aAAqB,GAAG;IAExB,OAAO,IAAI,MAAM,CACf,OAAO,EACP;QACE,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO;KACzD,EACD,UAAU,CACX,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Metadata about the method being invoked.\n * Passed to filters and contains request information.\n */\nexport class MethodMeta {\n /**\n * The HTTP method (GET, POST, etc.)\n */\n httpMethod: string;\n\n /**\n * The request path\n */\n path: string;\n\n /**\n * The method name being invoked on the controller\n */\n methodName: string;\n\n /**\n * Parameters to pass to the controller method.\n * Filters can modify this array (e.g., JsonFilter deserializes request body into params[0])\n */\n params: any[];\n\n /**\n * The original request object (if applicable)\n */\n request?: any;\n\n /**\n * The response object (if applicable)\n */\n response?: any;\n\n /**\n * Additional metadata\n */\n metadata?: Map<string, any>;\n\n constructor(\n httpMethod: string,\n path: string,\n methodName: string,\n params: any[],\n request?: any,\n response?: any,\n metadata?: Map<string, any>\n ) {\n this.httpMethod = httpMethod;\n this.path = path;\n this.methodName = methodName;\n this.params = params;\n this.request = request;\n this.response = response;\n this.metadata = metadata;\n }\n}\n\n/**\n * Action returned by filters and controllers.\n * Can represent different types of responses.\n */\nexport class Action {\n type: 'json' | 'html' | 'redirect' | 'error';\n data?: any;\n statusCode?: number;\n headers?: Record<string, string>;\n\n constructor(\n type: 'json' | 'html' | 'redirect' | 'error',\n data?: any,\n statusCode?: number,\n headers?: Record<string, string>\n ) {\n this.type = type;\n this.data = data;\n this.statusCode = statusCode;\n this.headers = headers;\n }\n}\n\n/**\n * Next filter class.\n * This is a class instead of a function type to make it easier to trace\n * who is calling what in the debugger/IDE.\n */\nexport abstract class NextFilter {\n /**\n * Execute the next filter in the chain.\n * @returns Promise of the action\n */\n abstract execute(): Promise<Action>;\n}\n\n/**\n * Filter interface.\n * Similar to Java WebPieces RouteFilter.\n *\n * Filters are executed in priority order (higher priority first)\n * and can wrap the execution of subsequent filters and the controller.\n *\n * Example:\n * ```typescript\n * @injectable()\n * export class LoggingFilter implements Filter {\n * priority = 100;\n *\n * async filter(meta: MethodMeta, next: NextFilter): Promise<Action> {\n * console.log(`Request: ${meta.httpMethod} ${meta.path}`);\n * const action = await next.execute();\n * console.log(`Response: ${action.statusCode}`);\n * return action;\n * }\n * }\n * ```\n */\nexport interface Filter {\n /**\n * Priority of this filter.\n * Higher numbers execute first.\n * Typical values:\n * - 140: Context setup\n * - 120: Request attributes\n * - 90: Metrics\n * - 80: Logging\n * - 60: JSON serialization\n * - 40: Transactions\n * - 0: Controller\n */\n priority: number;\n\n /**\n * Filter method that wraps the next filter/controller.\n *\n * @param meta - Metadata about the method being invoked\n * @param next - NextFilter instance to invoke the next filter in the chain\n * @returns Promise of the action to return\n */\n filter(meta: MethodMeta, next: NextFilter): Promise<Action>;\n}\n\n/**\n * Helper to create a JSON action response.\n */\nexport function jsonAction(data: any, statusCode: number = 200): Action {\n return new Action('json', data, statusCode);\n}\n\n/**\n * Helper to create an error action response.\n */\nexport function errorAction(\n error: Error | string,\n statusCode: number = 500\n): Action {\n return new Action(\n 'error',\n {\n error: typeof error === 'string' ? error : error.message,\n },\n statusCode\n );\n}\n"]}
1
+ {"version":3,"file":"Filter.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/Filter.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACH,MAAa,UAAU;IAKrB,YAAY,QAAkB,EAAE,aAAqB,GAAG;QACtD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,IAAY,EAAE,KAAa;QACnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAc,OAAe,EAAE,aAAqB,GAAG;QACjE,MAAM,OAAO,GAAG,IAAI,UAAU,CAAI,SAAS,EAAE,UAAU,CAAC,CAAC;QACzD,OAAO,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AA3BD,gCA2BC;AAoBD;;;;;;;;;;;;;GAaG;AACH,MAAsB,MAAM;IAiB1B;;;;;;;;OAQG;IACH,KAAK,CAAC,UAA6B;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC;QAElB,OAAO,IAAI,KAAM,SAAQ,MAAiB;YACxC,KAAK,CAAC,MAAM,CACV,IAAS,EACT,WAA+B;gBAE/B,8DAA8D;gBAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;oBACvB,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC;iBACtD,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,YAAY,CAAC,GAAuB;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC;QAElB,OAAO;YACL,MAAM,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC;SAC9C,CAAC;IACJ,CAAC;CACF;AA1DD,wBA0DC","sourcesContent":["/**\n * WpResponse - Wraps controller responses for the filter chain.\n *\n * Generic type parameter TResult represents the controller's return type.\n * The filter chain uses WpResponse<unknown> because it handles all response types uniformly.\n *\n * JsonFilter is responsible for:\n * 1. Serializing WpResponse.response to JSON\n * 2. Writing the JSON to the HTTP response body\n */\nexport class WpResponse<TResult = unknown> {\n response?: TResult;\n statusCode: number;\n headers: Map<string, string>;\n\n constructor(response?: TResult, statusCode: number = 200) {\n this.response = response;\n this.statusCode = statusCode;\n this.headers = new Map();\n }\n\n /**\n * Set a response header.\n */\n setHeader(name: string, value: string): WpResponse<TResult> {\n this.headers.set(name, value);\n return this;\n }\n\n /**\n * Create an error response wrapper.\n */\n static error<T = unknown>(message: string, statusCode: number = 500): WpResponse<T> {\n const wrapper = new WpResponse<T>(undefined, statusCode);\n wrapper.setHeader('X-Error', message);\n return wrapper;\n }\n}\n\n/**\n * Service interface - Similar to Java WebPieces Service<REQ, RESP>.\n * Represents any component that can process a request and return a response.\n *\n * Used for:\n * - Final controller invocation\n * - Wrapping filters as services in the chain\n * - Functional composition of filters\n */\nexport interface Service<REQ, RESP> {\n /**\n * Invoke the service with the given metadata.\n * @param meta - Request metadata\n * @returns Promise of the response\n */\n invoke(meta: REQ): Promise<RESP>;\n}\n\n/**\n * Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.\n *\n * Filters are STATELESS and can handle N concurrent requests.\n * They wrap the execution of subsequent filters and the controller.\n *\n * Key principles:\n * - STATELESS: No instance variables for request data\n * - COMPOSABLE: Use chain() methods for functional composition\n *\n * For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:\n * - MethodMeta: Standardized request metadata (defined in http-server)\n * - WpResponse<unknown>: Wraps any controller response\n */\nexport abstract class Filter<REQ, RESP> {\n\n //priority is determined by how it is chained only here\n //DO NOT add priority here\n\n /**\n * Filter method that wraps the next filter/controller.\n *\n * @param meta - Metadata about the method being invoked\n * @param nextFilter - Next filter/controller as a Service\n * @returns Promise of the response\n */\n abstract filter(\n meta: REQ,\n nextFilter: Service<REQ, RESP>\n ): Promise<RESP>;\n\n /**\n * Chain this filter with another filter.\n * Returns a new Filter that composes both filters.\n *\n * Similar to Java: filter1.chain(filter2)\n *\n * @param nextFilter - The filter to execute after this one\n * @returns Composed filter\n */\n chain(nextFilter: Filter<REQ, RESP>): Filter<REQ, RESP> {\n const self = this;\n\n return new class extends Filter<REQ, RESP> {\n async filter(\n meta: REQ,\n nextService: Service<REQ, RESP>\n ): Promise<RESP> {\n // Call outer filter, passing next filter wrapped as a Service\n return self.filter(meta, {\n invoke: (m: REQ) => nextFilter.filter(m, nextService)\n });\n }\n };\n }\n\n /**\n * Chain this filter with a final service (controller).\n * Returns a Service that can be invoked.\n *\n * Similar to Java: filter.chain(service)\n *\n * @param svc - The final service (controller) to execute\n * @returns Service wrapping the entire filter chain\n */\n chainService(svc: Service<REQ, RESP>): Service<REQ, RESP> {\n const self = this;\n\n return {\n invoke: (meta: REQ) => self.filter(meta, svc)\n };\n }\n}\n"]}
@@ -1,28 +1,28 @@
1
- import { Filter, MethodMeta, Action } from './Filter';
1
+ import { Filter } from './Filter';
2
2
  /**
3
3
  * FilterChain - Manages execution of filters in priority order.
4
4
  * Similar to Java servlet filter chains.
5
5
  *
6
6
  * Filters are sorted by priority (highest first) and each filter
7
- * calls next() to invoke the next filter in the chain.
7
+ * calls nextFilter.invoke() to invoke the next filter in the chain.
8
8
  *
9
9
  * The final "filter" in the chain is the controller method itself.
10
10
  */
11
- export declare class FilterChain {
11
+ export declare class FilterChain<REQ, RESP> {
12
12
  private filters;
13
- constructor(filters: Filter[]);
13
+ constructor(filters: Filter<REQ, RESP>[]);
14
14
  /**
15
15
  * Execute the filter chain.
16
16
  *
17
- * @param meta - Method metadata
17
+ * @param meta - Request metadata
18
18
  * @param finalHandler - The controller method to execute at the end
19
- * @returns Promise of the action
19
+ * @returns Promise of the response
20
20
  */
21
- execute(meta: MethodMeta, finalHandler: () => Promise<Action>): Promise<Action>;
21
+ execute(meta: REQ, finalHandler: () => Promise<RESP>): Promise<RESP>;
22
22
  /**
23
23
  * Get all filters in the chain (sorted by priority).
24
24
  */
25
- getFilters(): Filter[];
25
+ getFilters(): Filter<REQ, RESP>[];
26
26
  /**
27
27
  * Get the number of filters in the chain.
28
28
  */
@@ -1,44 +1,49 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FilterChain = void 0;
4
- const Filter_1 = require("./Filter");
5
4
  /**
6
5
  * FilterChain - Manages execution of filters in priority order.
7
6
  * Similar to Java servlet filter chains.
8
7
  *
9
8
  * Filters are sorted by priority (highest first) and each filter
10
- * calls next() to invoke the next filter in the chain.
9
+ * calls nextFilter.invoke() to invoke the next filter in the chain.
11
10
  *
12
11
  * The final "filter" in the chain is the controller method itself.
13
12
  */
14
13
  class FilterChain {
15
14
  constructor(filters) {
16
- // Sort filters by priority (highest first)
17
- this.filters = [...filters].sort((a, b) => b.priority - a.priority);
15
+ // Filters are already sorted by priority from FilterMatcher
16
+ // No need to sort again (priority is in FilterDefinition, not Filter)
17
+ this.filters = filters;
18
18
  }
19
19
  /**
20
20
  * Execute the filter chain.
21
21
  *
22
- * @param meta - Method metadata
22
+ * @param meta - Request metadata
23
23
  * @param finalHandler - The controller method to execute at the end
24
- * @returns Promise of the action
24
+ * @returns Promise of the response
25
25
  */
26
26
  async execute(meta, finalHandler) {
27
- let index = 0;
28
27
  const filters = this.filters;
29
- const next = new class extends Filter_1.NextFilter {
30
- async execute() {
31
- if (index < filters.length) {
32
- const filter = filters[index++];
33
- return filter.filter(meta, next);
28
+ // Create Service adapter that recursively calls filters
29
+ const createServiceForIndex = (currentIndex) => {
30
+ return {
31
+ invoke: async (m) => {
32
+ if (currentIndex < filters.length) {
33
+ const filter = filters[currentIndex];
34
+ const nextService = createServiceForIndex(currentIndex + 1);
35
+ return filter.filter(m, nextService);
36
+ }
37
+ else {
38
+ // All filters executed, now execute the controller
39
+ return finalHandler();
40
+ }
34
41
  }
35
- else {
36
- // All filters have been executed, now execute the controller
37
- return finalHandler();
38
- }
39
- }
42
+ };
40
43
  };
41
- return next.execute();
44
+ // Start execution with first filter
45
+ const service = createServiceForIndex(0);
46
+ return service.invoke(meta);
42
47
  }
43
48
  /**
44
49
  * Get all filters in the chain (sorted by priority).
@@ -1 +1 @@
1
- {"version":3,"file":"FilterChain.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/FilterChain.ts"],"names":[],"mappings":";;;AAAA,qCAAkE;AAElE;;;;;;;;GAQG;AACH,MAAa,WAAW;IAGtB,YAAY,OAAiB;QAC3B,2CAA2C;QAC3C,IAAI,CAAC,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IACtE,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CACX,IAAgB,EAChB,YAAmC;QAEnC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAE7B,MAAM,IAAI,GAAe,IAAI,KAAM,SAAQ,mBAAU;YACnD,KAAK,CAAC,OAAO;gBACX,IAAI,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;oBAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;oBAChC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBACnC,CAAC;qBAAM,CAAC;oBACN,6DAA6D;oBAC7D,OAAO,YAAY,EAAE,CAAC;gBACxB,CAAC;YACH,CAAC;SACF,CAAC;QAEF,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,IAAI;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;CACF;AAlDD,kCAkDC","sourcesContent":["import { Filter, MethodMeta, Action, NextFilter } from './Filter';\n\n/**\n * FilterChain - Manages execution of filters in priority order.\n * Similar to Java servlet filter chains.\n *\n * Filters are sorted by priority (highest first) and each filter\n * calls next() to invoke the next filter in the chain.\n *\n * The final \"filter\" in the chain is the controller method itself.\n */\nexport class FilterChain {\n private filters: Filter[];\n\n constructor(filters: Filter[]) {\n // Sort filters by priority (highest first)\n this.filters = [...filters].sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Execute the filter chain.\n *\n * @param meta - Method metadata\n * @param finalHandler - The controller method to execute at the end\n * @returns Promise of the action\n */\n async execute(\n meta: MethodMeta,\n finalHandler: () => Promise<Action>\n ): Promise<Action> {\n let index = 0;\n const filters = this.filters;\n\n const next: NextFilter = new class extends NextFilter {\n async execute(): Promise<Action> {\n if (index < filters.length) {\n const filter = filters[index++];\n return filter.filter(meta, next);\n } else {\n // All filters have been executed, now execute the controller\n return finalHandler();\n }\n }\n };\n\n return next.execute();\n }\n\n /**\n * Get all filters in the chain (sorted by priority).\n */\n getFilters(): Filter[] {\n return [...this.filters];\n }\n\n /**\n * Get the number of filters in the chain.\n */\n size(): number {\n return this.filters.length;\n }\n}\n"]}
1
+ {"version":3,"file":"FilterChain.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/FilterChain.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;GAQG;AACH,MAAa,WAAW;IAGtB,YAAY,OAA4B;QACtC,4DAA4D;QAC5D,sEAAsE;QACtE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CACX,IAAS,EACT,YAAiC;QAEjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAE7B,wDAAwD;QACxD,MAAM,qBAAqB,GAAG,CAAC,YAAoB,EAAsB,EAAE;YACzE,OAAO;gBACL,MAAM,EAAE,KAAK,EAAE,CAAM,EAAiB,EAAE;oBACtC,IAAI,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;wBAClC,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;wBACrC,MAAM,WAAW,GAAG,qBAAqB,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;wBAC5D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;oBACvC,CAAC;yBAAM,CAAC;wBACN,mDAAmD;wBACnD,OAAO,YAAY,EAAE,CAAC;oBACxB,CAAC;gBACH,CAAC;aACF,CAAC;QACJ,CAAC,CAAC;QAEF,oCAAoC;QACpC,MAAM,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,IAAI;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;CACF;AAxDD,kCAwDC","sourcesContent":["import { Filter, Service } from './Filter';\n\n/**\n * FilterChain - Manages execution of filters in priority order.\n * Similar to Java servlet filter chains.\n *\n * Filters are sorted by priority (highest first) and each filter\n * calls nextFilter.invoke() to invoke the next filter in the chain.\n *\n * The final \"filter\" in the chain is the controller method itself.\n */\nexport class FilterChain<REQ, RESP> {\n private filters: Filter<REQ, RESP>[];\n\n constructor(filters: Filter<REQ, RESP>[]) {\n // Filters are already sorted by priority from FilterMatcher\n // No need to sort again (priority is in FilterDefinition, not Filter)\n this.filters = filters;\n }\n\n /**\n * Execute the filter chain.\n *\n * @param meta - Request metadata\n * @param finalHandler - The controller method to execute at the end\n * @returns Promise of the response\n */\n async execute(\n meta: REQ,\n finalHandler: () => Promise<RESP>\n ): Promise<RESP> {\n const filters = this.filters;\n\n // Create Service adapter that recursively calls filters\n const createServiceForIndex = (currentIndex: number): Service<REQ, RESP> => {\n return {\n invoke: async (m: REQ): Promise<RESP> => {\n if (currentIndex < filters.length) {\n const filter = filters[currentIndex];\n const nextService = createServiceForIndex(currentIndex + 1);\n return filter.filter(m, nextService);\n } else {\n // All filters executed, now execute the controller\n return finalHandler();\n }\n }\n };\n };\n\n // Start execution with first filter\n const service = createServiceForIndex(0);\n return service.invoke(meta);\n }\n\n /**\n * Get all filters in the chain (sorted by priority).\n */\n getFilters(): Filter<REQ, RESP>[] {\n return [...this.filters];\n }\n\n /**\n * Get the number of filters in the chain.\n */\n size(): number {\n return this.filters.length;\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -1,4 +1,2 @@
1
- export { Filter, MethodMeta, Action, NextFilter, jsonAction, errorAction, } from './Filter';
1
+ export { Filter, WpResponse, Service, } from './Filter';
2
2
  export { FilterChain } from './FilterChain';
3
- export { ContextFilter } from './filters/ContextFilter';
4
- export { JsonFilter, JsonFilterConfig, FILTER_TYPES, ValidationException, HttpException, } from './filters/JsonFilter';
package/src/index.js CHANGED
@@ -1,20 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HttpException = exports.ValidationException = exports.FILTER_TYPES = exports.JsonFilterConfig = exports.JsonFilter = exports.ContextFilter = exports.FilterChain = exports.errorAction = exports.jsonAction = exports.NextFilter = exports.Action = exports.MethodMeta = void 0;
3
+ exports.FilterChain = exports.WpResponse = exports.Filter = void 0;
4
4
  var Filter_1 = require("./Filter");
5
- Object.defineProperty(exports, "MethodMeta", { enumerable: true, get: function () { return Filter_1.MethodMeta; } });
6
- Object.defineProperty(exports, "Action", { enumerable: true, get: function () { return Filter_1.Action; } });
7
- Object.defineProperty(exports, "NextFilter", { enumerable: true, get: function () { return Filter_1.NextFilter; } });
8
- Object.defineProperty(exports, "jsonAction", { enumerable: true, get: function () { return Filter_1.jsonAction; } });
9
- Object.defineProperty(exports, "errorAction", { enumerable: true, get: function () { return Filter_1.errorAction; } });
5
+ Object.defineProperty(exports, "Filter", { enumerable: true, get: function () { return Filter_1.Filter; } });
6
+ Object.defineProperty(exports, "WpResponse", { enumerable: true, get: function () { return Filter_1.WpResponse; } });
10
7
  var FilterChain_1 = require("./FilterChain");
11
8
  Object.defineProperty(exports, "FilterChain", { enumerable: true, get: function () { return FilterChain_1.FilterChain; } });
12
- var ContextFilter_1 = require("./filters/ContextFilter");
13
- Object.defineProperty(exports, "ContextFilter", { enumerable: true, get: function () { return ContextFilter_1.ContextFilter; } });
14
- var JsonFilter_1 = require("./filters/JsonFilter");
15
- Object.defineProperty(exports, "JsonFilter", { enumerable: true, get: function () { return JsonFilter_1.JsonFilter; } });
16
- Object.defineProperty(exports, "JsonFilterConfig", { enumerable: true, get: function () { return JsonFilter_1.JsonFilterConfig; } });
17
- Object.defineProperty(exports, "FILTER_TYPES", { enumerable: true, get: function () { return JsonFilter_1.FILTER_TYPES; } });
18
- Object.defineProperty(exports, "ValidationException", { enumerable: true, get: function () { return JsonFilter_1.ValidationException; } });
19
- Object.defineProperty(exports, "HttpException", { enumerable: true, get: function () { return JsonFilter_1.HttpException; } });
20
9
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/index.ts"],"names":[],"mappings":";;;AAAA,mCAOkB;AALhB,oGAAA,UAAU,OAAA;AACV,gGAAA,MAAM,OAAA;AACN,oGAAA,UAAU,OAAA;AACV,oGAAA,UAAU,OAAA;AACV,qGAAA,WAAW,OAAA;AAGb,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AAEpB,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,mDAM8B;AAL5B,wGAAA,UAAU,OAAA;AACV,8GAAA,gBAAgB,OAAA;AAChB,0GAAA,YAAY,OAAA;AACZ,iHAAA,mBAAmB,OAAA;AACnB,2GAAA,aAAa,OAAA","sourcesContent":["export {\n Filter,\n MethodMeta,\n Action,\n NextFilter,\n jsonAction,\n errorAction,\n} from './Filter';\n\nexport { FilterChain } from './FilterChain';\n\nexport { ContextFilter } from './filters/ContextFilter';\nexport {\n JsonFilter,\n JsonFilterConfig,\n FILTER_TYPES,\n ValidationException,\n HttpException,\n} from './filters/JsonFilter';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/index.ts"],"names":[],"mappings":";;;AAAA,mCAIkB;AAHhB,gGAAA,MAAM,OAAA;AACN,oGAAA,UAAU,OAAA;AAIZ,6CAA4C;AAAnC,0GAAA,WAAW,OAAA","sourcesContent":["export {\n Filter,\n WpResponse,\n Service,\n} from './Filter';\n\nexport { FilterChain } from './FilterChain';\n"]}
@@ -1,12 +0,0 @@
1
- import { Filter, MethodMeta, Action, NextFilter } from '../Filter';
2
- /**
3
- * ContextFilter - Sets up AsyncLocalStorage context for each request.
4
- * Priority: 140 (executes first)
5
- *
6
- * This filter ensures that all subsequent filters and the controller
7
- * execute within a context that can store request-scoped data.
8
- */
9
- export declare class ContextFilter implements Filter {
10
- priority: number;
11
- filter(meta: MethodMeta, next: NextFilter): Promise<Action>;
12
- }
@@ -1,36 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ContextFilter = void 0;
4
- const tslib_1 = require("tslib");
5
- const inversify_1 = require("inversify");
6
- const http_routing_1 = require("@webpieces/http-routing");
7
- const core_context_1 = require("@webpieces/core-context");
8
- /**
9
- * ContextFilter - Sets up AsyncLocalStorage context for each request.
10
- * Priority: 140 (executes first)
11
- *
12
- * This filter ensures that all subsequent filters and the controller
13
- * execute within a context that can store request-scoped data.
14
- */
15
- let ContextFilter = class ContextFilter {
16
- constructor() {
17
- this.priority = 140;
18
- }
19
- async filter(meta, next) {
20
- // Run the rest of the filter chain within a new context
21
- return core_context_1.RequestContext.run(async () => {
22
- // Store request metadata in context for other filters to access
23
- core_context_1.RequestContext.put('METHOD_META', meta);
24
- core_context_1.RequestContext.put('REQUEST_PATH', meta.path);
25
- core_context_1.RequestContext.put('HTTP_METHOD', meta.httpMethod);
26
- return await next.execute();
27
- //RequestContext is auto cleared when done.
28
- });
29
- }
30
- };
31
- exports.ContextFilter = ContextFilter;
32
- exports.ContextFilter = ContextFilter = tslib_1.__decorate([
33
- (0, http_routing_1.provideSingleton)(),
34
- (0, inversify_1.injectable)()
35
- ], ContextFilter);
36
- //# sourceMappingURL=ContextFilter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ContextFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-filters/src/filters/ContextFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,0DAA2D;AAC3D,0DAAyD;AAGzD;;;;;;GAMG;AAGI,IAAM,aAAa,GAAnB,MAAM,aAAa;IAAnB;QACL,aAAQ,GAAG,GAAG,CAAC;IAcjB,CAAC;IAZC,KAAK,CAAC,MAAM,CAAC,IAAgB,EAAE,IAAgB;QAC7C,wDAAwD;QACxD,OAAO,6BAAc,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;YACnC,gEAAgE;YAChE,6BAAc,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YACxC,6BAAc,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9C,6BAAc,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YAEnD,OAAO,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAC5B,2CAA2C;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAfY,sCAAa;wBAAb,aAAa;IAFzB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,aAAa,CAezB","sourcesContent":["import { injectable } from 'inversify';\nimport { provideSingleton } from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, MethodMeta, Action, NextFilter } from '../Filter';\n\n/**\n * ContextFilter - Sets up AsyncLocalStorage context for each request.\n * Priority: 140 (executes first)\n *\n * This filter ensures that all subsequent filters and the controller\n * execute within a context that can store request-scoped data.\n */\n@provideSingleton()\n@injectable()\nexport class ContextFilter implements Filter {\n priority = 140;\n\n async filter(meta: MethodMeta, next: NextFilter): Promise<Action> {\n // Run the rest of the filter chain within a new context\n return RequestContext.run(async () => {\n // Store request metadata in context for other filters to access\n RequestContext.put('METHOD_META', meta);\n RequestContext.put('REQUEST_PATH', meta.path);\n RequestContext.put('HTTP_METHOD', meta.httpMethod);\n\n return await next.execute();\n //RequestContext is auto cleared when done.\n });\n }\n}\n"]}
@@ -1,95 +0,0 @@
1
- import { Filter, MethodMeta, Action, NextFilter } from '../Filter';
2
- /**
3
- * DI tokens for http-filters.
4
- */
5
- export declare const FILTER_TYPES: {
6
- JsonFilterConfig: symbol;
7
- };
8
- /**
9
- * Configuration for JsonFilter.
10
- * Register this in your DI container to customize JsonFilter behavior.
11
- *
12
- * Example:
13
- * ```typescript
14
- * export const MyModule = new ContainerModule((bind) => {
15
- * bind(FILTER_TYPES.JsonFilterConfig).toConstantValue(
16
- * new JsonFilterConfig(true, true) // validation enabled, logging enabled
17
- * );
18
- * });
19
- * ```
20
- */
21
- export declare class JsonFilterConfig {
22
- /**
23
- * Whether to enable validation using class-validator.
24
- * Default: true
25
- */
26
- validationEnabled: boolean;
27
- /**
28
- * Whether to log requests and responses.
29
- * Default: false
30
- */
31
- loggingEnabled: boolean;
32
- constructor(validationEnabled?: boolean, loggingEnabled?: boolean);
33
- }
34
- /**
35
- * JsonFilter - Handles JSON deserialization and serialization.
36
- * Priority: 60
37
- *
38
- * Similar to Java WebPieces JacksonCatchAllFilter.
39
- *
40
- * Responsibilities:
41
- * 1. Deserialize request body to DTO (if request has body)
42
- * 2. Validate DTO using class-validator (if enabled)
43
- * 3. Execute next filter/controller
44
- * 4. Serialize response to JSON
45
- * 5. Handle errors and translate to JSON error responses
46
- *
47
- * Configuration:
48
- * JsonFilter uses constructor injection to receive JsonFilterConfig.
49
- * The default config has validation enabled and logging disabled.
50
- * To customize, bind JsonFilterConfig in your DI module.
51
- */
52
- export declare class JsonFilter implements Filter {
53
- private config;
54
- priority: number;
55
- constructor(config: JsonFilterConfig);
56
- filter(meta: MethodMeta, next: NextFilter): Promise<Action>;
57
- /**
58
- * Process the request: deserialize and validate.
59
- */
60
- private processRequest;
61
- /**
62
- * Validate a DTO using class-validator.
63
- */
64
- private validateDto;
65
- /**
66
- * Format validation errors into a readable format.
67
- */
68
- private formatValidationErrors;
69
- /**
70
- * Handle errors and translate to JSON error responses.
71
- */
72
- private handleError;
73
- /**
74
- * Log the incoming request.
75
- */
76
- private logRequest;
77
- /**
78
- * Log the outgoing response.
79
- */
80
- private logResponse;
81
- }
82
- /**
83
- * Exception thrown when validation fails.
84
- */
85
- export declare class ValidationException extends Error {
86
- violations: string[];
87
- constructor(violations: string[]);
88
- }
89
- /**
90
- * HTTP exception with status code.
91
- */
92
- export declare class HttpException extends Error {
93
- statusCode: number;
94
- constructor(message: string, statusCode: number);
95
- }
@@ -1,202 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HttpException = exports.ValidationException = exports.JsonFilter = exports.JsonFilterConfig = exports.FILTER_TYPES = void 0;
4
- const tslib_1 = require("tslib");
5
- const inversify_1 = require("inversify");
6
- const http_routing_1 = require("@webpieces/http-routing");
7
- const class_validator_1 = require("class-validator");
8
- const Filter_1 = require("../Filter");
9
- const core_util_1 = require("@webpieces/core-util");
10
- /**
11
- * DI tokens for http-filters.
12
- */
13
- exports.FILTER_TYPES = {
14
- JsonFilterConfig: Symbol.for('JsonFilterConfig'),
15
- };
16
- /**
17
- * Configuration for JsonFilter.
18
- * Register this in your DI container to customize JsonFilter behavior.
19
- *
20
- * Example:
21
- * ```typescript
22
- * export const MyModule = new ContainerModule((bind) => {
23
- * bind(FILTER_TYPES.JsonFilterConfig).toConstantValue(
24
- * new JsonFilterConfig(true, true) // validation enabled, logging enabled
25
- * );
26
- * });
27
- * ```
28
- */
29
- let JsonFilterConfig = class JsonFilterConfig {
30
- constructor(validationEnabled = true, loggingEnabled = false) {
31
- this.validationEnabled = validationEnabled;
32
- this.loggingEnabled = loggingEnabled;
33
- }
34
- };
35
- exports.JsonFilterConfig = JsonFilterConfig;
36
- exports.JsonFilterConfig = JsonFilterConfig = tslib_1.__decorate([
37
- (0, inversify_1.injectable)(),
38
- tslib_1.__metadata("design:paramtypes", [Boolean, Boolean])
39
- ], JsonFilterConfig);
40
- /**
41
- * JsonFilter - Handles JSON deserialization and serialization.
42
- * Priority: 60
43
- *
44
- * Similar to Java WebPieces JacksonCatchAllFilter.
45
- *
46
- * Responsibilities:
47
- * 1. Deserialize request body to DTO (if request has body)
48
- * 2. Validate DTO using class-validator (if enabled)
49
- * 3. Execute next filter/controller
50
- * 4. Serialize response to JSON
51
- * 5. Handle errors and translate to JSON error responses
52
- *
53
- * Configuration:
54
- * JsonFilter uses constructor injection to receive JsonFilterConfig.
55
- * The default config has validation enabled and logging disabled.
56
- * To customize, bind JsonFilterConfig in your DI module.
57
- */
58
- let JsonFilter = class JsonFilter {
59
- constructor(config) {
60
- this.config = config;
61
- this.priority = 60;
62
- // Config is injected from DI container
63
- }
64
- async filter(meta, next) {
65
- try {
66
- // Deserialize and validate request if there's a body
67
- await this.processRequest(meta);
68
- if (this.config.loggingEnabled) {
69
- this.logRequest(meta);
70
- }
71
- // Execute next filter/controller
72
- const action = await next.execute();
73
- if (this.config.loggingEnabled) {
74
- this.logResponse(action);
75
- }
76
- // Ensure response is JSON
77
- if (action.type !== 'json' && action.type !== 'error') {
78
- return (0, Filter_1.jsonAction)(action.data);
79
- }
80
- return action;
81
- }
82
- catch (err) {
83
- const error = (0, core_util_1.toError)(err);
84
- // Translate error to JSON response
85
- return this.handleError(error, meta);
86
- }
87
- }
88
- /**
89
- * Process the request: deserialize and validate.
90
- */
91
- async processRequest(meta) {
92
- // If there's request data and a parameter type, deserialize it
93
- if (meta.request?.body && meta.params.length === 0) {
94
- const body = meta.request.body;
95
- // For now, we'll just pass the body as-is
96
- // In a real implementation, we'd use the parameter type from decorators
97
- // to properly deserialize and validate
98
- // If we have type information, we can do proper transformation
99
- // For this MVP, we'll store the body in params[0]
100
- meta.params[0] = body;
101
- // If validation is enabled and we have a class instance, validate it
102
- if (this.config.validationEnabled && body.constructor !== Object) {
103
- await this.validateDto(body);
104
- }
105
- }
106
- }
107
- /**
108
- * Validate a DTO using class-validator.
109
- */
110
- async validateDto(dto) {
111
- const errors = await (0, class_validator_1.validate)(dto);
112
- if (errors.length > 0) {
113
- const messages = this.formatValidationErrors(errors);
114
- throw new ValidationException(messages);
115
- }
116
- }
117
- /**
118
- * Format validation errors into a readable format.
119
- */
120
- formatValidationErrors(errors) {
121
- const messages = [];
122
- for (const error of errors) {
123
- if (error.constraints) {
124
- const constraints = Object.values(error.constraints);
125
- messages.push(...constraints);
126
- }
127
- if (error.children && error.children.length > 0) {
128
- const childMessages = this.formatValidationErrors(error.children);
129
- messages.push(...childMessages);
130
- }
131
- }
132
- return messages;
133
- }
134
- /**
135
- * Handle errors and translate to JSON error responses.
136
- */
137
- handleError(error, meta) {
138
- if (error instanceof ValidationException) {
139
- return (0, Filter_1.errorAction)({
140
- error: 'Validation failed',
141
- violations: error.violations,
142
- }, 400);
143
- }
144
- if (error instanceof HttpException) {
145
- return (0, Filter_1.errorAction)({
146
- error: error.message,
147
- code: error.statusCode,
148
- }, error.statusCode);
149
- }
150
- // Log unexpected errors
151
- console.error('Unexpected error in filter chain:', error);
152
- return (0, Filter_1.errorAction)('Internal server error', 500);
153
- }
154
- /**
155
- * Log the incoming request.
156
- */
157
- logRequest(meta) {
158
- console.log(`[JsonFilter] ${meta.httpMethod} ${meta.path}`);
159
- if (meta.params.length > 0) {
160
- console.log('[JsonFilter] Request body:', JSON.stringify(meta.params[0], null, 2));
161
- }
162
- }
163
- /**
164
- * Log the outgoing response.
165
- */
166
- logResponse(action) {
167
- console.log(`[JsonFilter] Response: ${action.statusCode}`);
168
- if (action.data) {
169
- console.log('[JsonFilter] Response body:', JSON.stringify(action.data, null, 2));
170
- }
171
- }
172
- };
173
- exports.JsonFilter = JsonFilter;
174
- exports.JsonFilter = JsonFilter = tslib_1.__decorate([
175
- (0, http_routing_1.provideSingleton)(),
176
- (0, inversify_1.injectable)(),
177
- tslib_1.__param(0, (0, inversify_1.inject)(exports.FILTER_TYPES.JsonFilterConfig)),
178
- tslib_1.__metadata("design:paramtypes", [JsonFilterConfig])
179
- ], JsonFilter);
180
- /**
181
- * Exception thrown when validation fails.
182
- */
183
- class ValidationException extends Error {
184
- constructor(violations) {
185
- super('Validation failed');
186
- this.violations = violations;
187
- this.name = 'ValidationException';
188
- }
189
- }
190
- exports.ValidationException = ValidationException;
191
- /**
192
- * HTTP exception with status code.
193
- */
194
- class HttpException extends Error {
195
- constructor(message, statusCode) {
196
- super(message);
197
- this.statusCode = statusCode;
198
- this.name = 'HttpException';
199
- }
200
- }
201
- exports.HttpException = HttpException;
202
- //# sourceMappingURL=JsonFilter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"JsonFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-filters/src/filters/JsonFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject } from 'inversify';\nimport { provideSingleton } from '@webpieces/http-routing';\nimport { plainToInstance } from 'class-transformer';\nimport { validate, ValidationError } from 'class-validator';\nimport { Filter, MethodMeta, Action, NextFilter, jsonAction, errorAction } from '../Filter';\nimport { RequestContext } from '@webpieces/core-context';\nimport { toError } from '@webpieces/core-util';\n\n/**\n * DI tokens for http-filters.\n */\nexport const FILTER_TYPES = {\n JsonFilterConfig: Symbol.for('JsonFilterConfig'),\n};\n\n/**\n * Configuration for JsonFilter.\n * Register this in your DI container to customize JsonFilter behavior.\n *\n * Example:\n * ```typescript\n * export const MyModule = new ContainerModule((bind) => {\n * bind(FILTER_TYPES.JsonFilterConfig).toConstantValue(\n * new JsonFilterConfig(true, true) // validation enabled, logging enabled\n * );\n * });\n * ```\n */\n@injectable()\nexport class JsonFilterConfig {\n /**\n * Whether to enable validation using class-validator.\n * Default: true\n */\n validationEnabled: boolean;\n\n /**\n * Whether to log requests and responses.\n * Default: false\n */\n loggingEnabled: boolean;\n\n constructor(\n validationEnabled: boolean = true,\n loggingEnabled: boolean = false\n ) {\n this.validationEnabled = validationEnabled;\n this.loggingEnabled = loggingEnabled;\n }\n}\n\n/**\n * JsonFilter - Handles JSON deserialization and serialization.\n * Priority: 60\n *\n * Similar to Java WebPieces JacksonCatchAllFilter.\n *\n * Responsibilities:\n * 1. Deserialize request body to DTO (if request has body)\n * 2. Validate DTO using class-validator (if enabled)\n * 3. Execute next filter/controller\n * 4. Serialize response to JSON\n * 5. Handle errors and translate to JSON error responses\n *\n * Configuration:\n * JsonFilter uses constructor injection to receive JsonFilterConfig.\n * The default config has validation enabled and logging disabled.\n * To customize, bind JsonFilterConfig in your DI module.\n */\n@provideSingleton()\n@injectable()\nexport class JsonFilter implements Filter {\n priority = 60;\n\n constructor(@inject(FILTER_TYPES.JsonFilterConfig) private config: JsonFilterConfig) {\n // Config is injected from DI container\n }\n\n async filter(meta: MethodMeta, next: NextFilter): Promise<Action> {\n try {\n // Deserialize and validate request if there's a body\n await this.processRequest(meta);\n\n if (this.config.loggingEnabled) {\n this.logRequest(meta);\n }\n\n // Execute next filter/controller\n const action = await next.execute();\n\n if (this.config.loggingEnabled) {\n this.logResponse(action);\n }\n\n // Ensure response is JSON\n if (action.type !== 'json' && action.type !== 'error') {\n return jsonAction(action.data);\n }\n\n return action;\n } catch (err: any) {\n const error = toError(err);\n // Translate error to JSON response\n return this.handleError(error, meta);\n }\n }\n\n /**\n * Process the request: deserialize and validate.\n */\n private async processRequest(meta: MethodMeta): Promise<void> {\n // If there's request data and a parameter type, deserialize it\n if (meta.request?.body && meta.params.length === 0) {\n const body = meta.request.body;\n\n // For now, we'll just pass the body as-is\n // In a real implementation, we'd use the parameter type from decorators\n // to properly deserialize and validate\n\n // If we have type information, we can do proper transformation\n // For this MVP, we'll store the body in params[0]\n meta.params[0] = body;\n\n // If validation is enabled and we have a class instance, validate it\n if (this.config.validationEnabled && body.constructor !== Object) {\n await this.validateDto(body);\n }\n }\n }\n\n /**\n * Validate a DTO using class-validator.\n */\n private async validateDto(dto: any): Promise<void> {\n const errors = await validate(dto);\n\n if (errors.length > 0) {\n const messages = this.formatValidationErrors(errors);\n throw new ValidationException(messages);\n }\n }\n\n /**\n * Format validation errors into a readable format.\n */\n private formatValidationErrors(errors: ValidationError[]): string[] {\n const messages: string[] = [];\n\n for (const error of errors) {\n if (error.constraints) {\n const constraints = Object.values(error.constraints);\n messages.push(...constraints);\n }\n\n if (error.children && error.children.length > 0) {\n const childMessages = this.formatValidationErrors(error.children);\n messages.push(...childMessages);\n }\n }\n\n return messages;\n }\n\n /**\n * Handle errors and translate to JSON error responses.\n */\n private handleError(error: any, meta: MethodMeta): Action {\n if (error instanceof ValidationException) {\n return errorAction(\n {\n error: 'Validation failed',\n violations: error.violations,\n } as any,\n 400\n );\n }\n\n if (error instanceof HttpException) {\n return errorAction(\n {\n error: error.message,\n code: error.statusCode,\n } as any,\n error.statusCode\n );\n }\n\n // Log unexpected errors\n console.error('Unexpected error in filter chain:', error);\n\n return errorAction(\n 'Internal server error',\n 500\n );\n }\n\n /**\n * Log the incoming request.\n */\n private logRequest(meta: MethodMeta): void {\n console.log(`[JsonFilter] ${meta.httpMethod} ${meta.path}`);\n if (meta.params.length > 0) {\n console.log('[JsonFilter] Request body:', JSON.stringify(meta.params[0], null, 2));\n }\n }\n\n /**\n * Log the outgoing response.\n */\n private logResponse(action: Action): void {\n console.log(`[JsonFilter] Response: ${action.statusCode}`);\n if (action.data) {\n console.log('[JsonFilter] Response body:', JSON.stringify(action.data, null, 2));\n }\n }\n}\n\n/**\n * Exception thrown when validation fails.\n */\nexport class ValidationException extends Error {\n constructor(public violations: string[]) {\n super('Validation failed');\n this.name = 'ValidationException';\n }\n}\n\n/**\n * HTTP exception with status code.\n */\nexport class HttpException extends Error {\n constructor(message: string, public statusCode: number) {\n super(message);\n this.name = 'HttpException';\n }\n}\n"]}