@webpieces/core-util 0.3.263 → 0.3.265
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 +1 -1
- package/src/http/ContextReader.d.ts +37 -0
- package/src/http/ContextReader.js +3 -0
- package/src/http/ContextReader.js.map +1 -0
- package/src/http/HeaderMethods.d.ts +71 -0
- package/src/http/HeaderMethods.js +124 -0
- package/src/http/HeaderMethods.js.map +1 -0
- package/src/http/HeaderRegistry.d.ts +58 -0
- package/src/http/HeaderRegistry.js +121 -0
- package/src/http/HeaderRegistry.js.map +1 -0
- package/src/http/HeaderTypes.d.ts +29 -0
- package/src/http/HeaderTypes.js +33 -0
- package/src/http/HeaderTypes.js.map +1 -0
- package/src/http/LogApiCall.d.ts +41 -0
- package/src/http/LogApiCall.js +83 -0
- package/src/http/LogApiCall.js.map +1 -0
- package/src/http/PlatformHeader.d.ts +51 -0
- package/src/http/PlatformHeader.js +63 -0
- package/src/http/PlatformHeader.js.map +1 -0
- package/src/http/PlatformHeadersExtension.d.ts +51 -0
- package/src/http/PlatformHeadersExtension.js +59 -0
- package/src/http/PlatformHeadersExtension.js.map +1 -0
- package/src/http/WebpiecesCoreHeaders.d.ts +62 -0
- package/src/http/WebpiecesCoreHeaders.js +87 -0
- package/src/http/WebpiecesCoreHeaders.js.map +1 -0
- package/src/http/datetime.d.ts +284 -0
- package/src/http/datetime.js +270 -0
- package/src/http/datetime.js.map +1 -0
- package/src/http/decorators.d.ts +212 -0
- package/src/http/decorators.js +365 -0
- package/src/http/decorators.js.map +1 -0
- package/src/http/errors.d.ts +118 -0
- package/src/http/errors.js +189 -0
- package/src/http/errors.js.map +1 -0
- package/src/http/recorder/DoNotRecord.d.ts +22 -0
- package/src/http/recorder/DoNotRecord.js +39 -0
- package/src/http/recorder/DoNotRecord.js.map +1 -0
- package/src/http/recorder/RecordSerializer.d.ts +39 -0
- package/src/http/recorder/RecordSerializer.js +80 -0
- package/src/http/recorder/RecordSerializer.js.map +1 -0
- package/src/http/recorder/RecordedEndpoint.d.ts +49 -0
- package/src/http/recorder/RecordedEndpoint.js +66 -0
- package/src/http/recorder/RecordedEndpoint.js.map +1 -0
- package/src/http/recorder/TestCaseRecorder.d.ts +36 -0
- package/src/http/recorder/TestCaseRecorder.js +16 -0
- package/src/http/recorder/TestCaseRecorder.js.map +1 -0
- package/src/http/validators.d.ts +41 -0
- package/src/http/validators.js +3 -0
- package/src/http/validators.js.map +1 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +102 -1
- package/src/index.js.map +1 -1
- package/src/logging/ConsoleLogger.d.ts +25 -0
- package/src/logging/ConsoleLogger.js +43 -0
- package/src/logging/ConsoleLogger.js.map +1 -0
- package/src/logging/ConsoleLoggerFactory.d.ts +13 -0
- package/src/logging/ConsoleLoggerFactory.js +24 -0
- package/src/logging/ConsoleLoggerFactory.js.map +1 -0
- package/src/logging/LogManager.d.ts +36 -0
- package/src/logging/LogManager.js +46 -0
- package/src/logging/LogManager.js.map +1 -0
- package/src/logging/Logger.d.ts +36 -0
- package/src/logging/Logger.js +3 -0
- package/src/logging/Logger.js.map +1 -0
- package/src/logging/LoggerFactory.d.ts +16 -0
- package/src/logging/LoggerFactory.js +3 -0
- package/src/logging/LoggerFactory.js.map +1 -0
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ContextKey } from '../ContextKey';
|
|
2
|
+
import { PlatformHeader } from './PlatformHeader';
|
|
3
|
+
/**
|
|
4
|
+
* ContextReader - Interface for reading header values from context.
|
|
5
|
+
*
|
|
6
|
+
* Different implementations for different environments:
|
|
7
|
+
* - RequestContextReader: Node.js with AsyncLocalStorage (in @webpieces/http-routing, server-side only)
|
|
8
|
+
* - MutableContextStore / StaticContextReader: Browser or testing with manual header
|
|
9
|
+
* management (in @webpieces/http-client)
|
|
10
|
+
* - CompositeContextReader: Combines multiple readers with priority (in @webpieces/http-client)
|
|
11
|
+
*
|
|
12
|
+
* This interface is defined in @webpieces/http-api so both http-routing and http-client
|
|
13
|
+
* can use it without creating circular dependencies. It is DI-independent so it works in
|
|
14
|
+
* both server (Inversify) and client (Angular/React, browser) environments.
|
|
15
|
+
*
|
|
16
|
+
* This is a business-logic interface (per CLAUDE.md: behavior = interface).
|
|
17
|
+
*/
|
|
18
|
+
export interface ContextReader {
|
|
19
|
+
/**
|
|
20
|
+
* Read the value of a platform header.
|
|
21
|
+
* Returns undefined if header not available.
|
|
22
|
+
*
|
|
23
|
+
* @param header - The platform header to read
|
|
24
|
+
* @returns The header value, or undefined if not present
|
|
25
|
+
*/
|
|
26
|
+
read(header: PlatformHeader): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* OPTIONAL: read a non-header context value (e.g. the active
|
|
29
|
+
* TestCaseRecorder under RecorderKeys.RECORDER).
|
|
30
|
+
*
|
|
31
|
+
* Server-side readers (RequestContextReader) implement this over the
|
|
32
|
+
* RequestContext; browser readers may omit it (no server-side recording
|
|
33
|
+
* in browsers - same as Java). This keeps http-client free of any
|
|
34
|
+
* Node-only imports while still letting it find the recorder.
|
|
35
|
+
*/
|
|
36
|
+
readValue?(key: ContextKey): unknown;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextReader.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextReader.ts"],"names":[],"mappings":"","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { PlatformHeader } from './PlatformHeader';\n\n/**\n * ContextReader - Interface for reading header values from context.\n *\n * Different implementations for different environments:\n * - RequestContextReader: Node.js with AsyncLocalStorage (in @webpieces/http-routing, server-side only)\n * - MutableContextStore / StaticContextReader: Browser or testing with manual header\n * management (in @webpieces/http-client)\n * - CompositeContextReader: Combines multiple readers with priority (in @webpieces/http-client)\n *\n * This interface is defined in @webpieces/http-api so both http-routing and http-client\n * can use it without creating circular dependencies. It is DI-independent so it works in\n * both server (Inversify) and client (Angular/React, browser) environments.\n *\n * This is a business-logic interface (per CLAUDE.md: behavior = interface).\n */\nexport interface ContextReader {\n /**\n * Read the value of a platform header.\n * Returns undefined if header not available.\n *\n * @param header - The platform header to read\n * @returns The header value, or undefined if not present\n */\n read(header: PlatformHeader): string | undefined;\n\n /**\n * OPTIONAL: read a non-header context value (e.g. the active\n * TestCaseRecorder under RecorderKeys.RECORDER).\n *\n * Server-side readers (RequestContextReader) implement this over the\n * RequestContext; browser readers may omit it (no server-side recording\n * in browsers - same as Java). This keeps http-client free of any\n * Node-only imports while still letting it find the recorder.\n */\n // webpieces-disable no-any-unknown -- context values are heterogeneous (recorder, meta objects)\n readValue?(key: ContextKey): unknown;\n}\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { PlatformHeader } from './PlatformHeader';
|
|
2
|
+
import { ContextReader } from './ContextReader';
|
|
3
|
+
/**
|
|
4
|
+
* HeaderMethods - Utility class for working with platform headers.
|
|
5
|
+
*
|
|
6
|
+
* This class can be injected in both server (Node.js) and client (Angular/browser) environments.
|
|
7
|
+
* It provides common operations for filtering and processing headers.
|
|
8
|
+
*
|
|
9
|
+
* Pattern: Stateless utility class (pure functions, can be instantiated or injected)
|
|
10
|
+
* - Server: Can inject empty instance, use static-like methods
|
|
11
|
+
* - Client: new HeaderMethods() (no DI needed)
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Server-side (ContextFilter)
|
|
16
|
+
* constructor(@inject() headerMethods: HeaderMethods) {
|
|
17
|
+
* const allHeaders = [... flatten from extensions ...];
|
|
18
|
+
* this.transferHeaders = headerMethods.findTransferHeaders(allHeaders);
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* // Client-side (ClientFactory)
|
|
22
|
+
* const headerMethods = new HeaderMethods();
|
|
23
|
+
* const loggableHeaders = headerMethods.findLoggableHeaders(allHeaders, requestHeaders);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare class HeaderMethods {
|
|
27
|
+
/**
|
|
28
|
+
* Filter headers to only those that should be transferred (isWantTransferred=true).
|
|
29
|
+
*
|
|
30
|
+
* @param headers - Array of PlatformHeader definitions
|
|
31
|
+
* @returns Filtered array of headers with isWantTransferred=true
|
|
32
|
+
*/
|
|
33
|
+
findTransferHeaders(headers: PlatformHeader[]): PlatformHeader[];
|
|
34
|
+
/**
|
|
35
|
+
* Split headers into secure and public categories.
|
|
36
|
+
*
|
|
37
|
+
* @param headers - Array of PlatformHeader definitions
|
|
38
|
+
* @returns SplitHeaders with secureHeaders (isSecured=true) and publicHeaders (isSecured=false)
|
|
39
|
+
*/
|
|
40
|
+
secureHeaders(headers: PlatformHeader[]): PlatformHeader[];
|
|
41
|
+
/**
|
|
42
|
+
* Get all headers that should be logged.
|
|
43
|
+
* All headers are loggable - secure headers will be masked by formatHeadersForLogging.
|
|
44
|
+
*
|
|
45
|
+
* @param headers - Array of PlatformHeader definitions
|
|
46
|
+
* @returns All headers (they're all loggable, just some are masked)
|
|
47
|
+
*/
|
|
48
|
+
findLoggableHeaders(headers: PlatformHeader[]): PlatformHeader[];
|
|
49
|
+
buildSecureMapForLogs(platformHeaders: PlatformHeader[], contextReader: ContextReader): Map<string, any>;
|
|
50
|
+
/**
|
|
51
|
+
* Format headers for logging with secure masking.
|
|
52
|
+
* Takes filtered PlatformHeaders and actual header values from request.
|
|
53
|
+
*
|
|
54
|
+
* Masking rules for secure headers (isSecured=true):
|
|
55
|
+
* - Length > 15: Show first 3 and last 3 characters with "..." between
|
|
56
|
+
* - Length 8-15: Show first 2 characters with "..."
|
|
57
|
+
* - Length < 8: Show "<secure key too short to log>"
|
|
58
|
+
*
|
|
59
|
+
* @param loggableHeaders - Filtered PlatformHeaders to log
|
|
60
|
+
* @param headerMap - Map of header name (lowercase) -> array of values from request
|
|
61
|
+
* @returns Record of header name -> masked or full value for logging
|
|
62
|
+
*/
|
|
63
|
+
formatHeadersForLogging(loggableHeaders: PlatformHeader[], headerMap: Map<string, string[]>): Record<string, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Mask a secure header value based on its length.
|
|
66
|
+
*
|
|
67
|
+
* @param value - The secure header value to mask
|
|
68
|
+
* @returns Masked value
|
|
69
|
+
*/
|
|
70
|
+
private maskSecureValue;
|
|
71
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HeaderMethods = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* HeaderMethods - Utility class for working with platform headers.
|
|
6
|
+
*
|
|
7
|
+
* This class can be injected in both server (Node.js) and client (Angular/browser) environments.
|
|
8
|
+
* It provides common operations for filtering and processing headers.
|
|
9
|
+
*
|
|
10
|
+
* Pattern: Stateless utility class (pure functions, can be instantiated or injected)
|
|
11
|
+
* - Server: Can inject empty instance, use static-like methods
|
|
12
|
+
* - Client: new HeaderMethods() (no DI needed)
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Server-side (ContextFilter)
|
|
17
|
+
* constructor(@inject() headerMethods: HeaderMethods) {
|
|
18
|
+
* const allHeaders = [... flatten from extensions ...];
|
|
19
|
+
* this.transferHeaders = headerMethods.findTransferHeaders(allHeaders);
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* // Client-side (ClientFactory)
|
|
23
|
+
* const headerMethods = new HeaderMethods();
|
|
24
|
+
* const loggableHeaders = headerMethods.findLoggableHeaders(allHeaders, requestHeaders);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
class HeaderMethods {
|
|
28
|
+
/**
|
|
29
|
+
* Filter headers to only those that should be transferred (isWantTransferred=true).
|
|
30
|
+
*
|
|
31
|
+
* @param headers - Array of PlatformHeader definitions
|
|
32
|
+
* @returns Filtered array of headers with isWantTransferred=true
|
|
33
|
+
*/
|
|
34
|
+
findTransferHeaders(headers) {
|
|
35
|
+
return headers.filter(h => h.isWantTransferred);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Split headers into secure and public categories.
|
|
39
|
+
*
|
|
40
|
+
* @param headers - Array of PlatformHeader definitions
|
|
41
|
+
* @returns SplitHeaders with secureHeaders (isSecured=true) and publicHeaders (isSecured=false)
|
|
42
|
+
*/
|
|
43
|
+
secureHeaders(headers) {
|
|
44
|
+
return headers.filter(h => h.isSecured);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get all headers that should be logged.
|
|
48
|
+
* All headers are loggable - secure headers will be masked by formatHeadersForLogging.
|
|
49
|
+
*
|
|
50
|
+
* @param headers - Array of PlatformHeader definitions
|
|
51
|
+
* @returns All headers (they're all loggable, just some are masked)
|
|
52
|
+
*/
|
|
53
|
+
findLoggableHeaders(headers) {
|
|
54
|
+
return headers; // All headers are loggable, secure ones will be masked
|
|
55
|
+
}
|
|
56
|
+
buildSecureMapForLogs(platformHeaders, contextReader) {
|
|
57
|
+
const headers = new Map();
|
|
58
|
+
for (const header of platformHeaders) {
|
|
59
|
+
const value = contextReader.read(header);
|
|
60
|
+
if (value) {
|
|
61
|
+
// MDC-style key when defined (Java getLoggerMDCKey), else the raw header name
|
|
62
|
+
const logKey = header.loggerMdcKey ?? header.headerName;
|
|
63
|
+
if (!header.isSecured)
|
|
64
|
+
headers.set(logKey, value);
|
|
65
|
+
else
|
|
66
|
+
headers.set(logKey, this.maskSecureValue(value));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return headers;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Format headers for logging with secure masking.
|
|
73
|
+
* Takes filtered PlatformHeaders and actual header values from request.
|
|
74
|
+
*
|
|
75
|
+
* Masking rules for secure headers (isSecured=true):
|
|
76
|
+
* - Length > 15: Show first 3 and last 3 characters with "..." between
|
|
77
|
+
* - Length 8-15: Show first 2 characters with "..."
|
|
78
|
+
* - Length < 8: Show "<secure key too short to log>"
|
|
79
|
+
*
|
|
80
|
+
* @param loggableHeaders - Filtered PlatformHeaders to log
|
|
81
|
+
* @param headerMap - Map of header name (lowercase) -> array of values from request
|
|
82
|
+
* @returns Record of header name -> masked or full value for logging
|
|
83
|
+
*/
|
|
84
|
+
formatHeadersForLogging(loggableHeaders, headerMap) {
|
|
85
|
+
const result = {};
|
|
86
|
+
for (const platformHeader of loggableHeaders) {
|
|
87
|
+
// Look for header in the map (case-insensitive)
|
|
88
|
+
const values = headerMap.get(platformHeader.headerName.toLowerCase());
|
|
89
|
+
if (!values || values.length === 0) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const value = values[0]; // Take first value
|
|
93
|
+
if (platformHeader.isSecured) {
|
|
94
|
+
result[platformHeader.headerName] = this.maskSecureValue(value);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
result[platformHeader.headerName] = value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Mask a secure header value based on its length.
|
|
104
|
+
*
|
|
105
|
+
* @param value - The secure header value to mask
|
|
106
|
+
* @returns Masked value
|
|
107
|
+
*/
|
|
108
|
+
maskSecureValue(value) {
|
|
109
|
+
const len = value.length;
|
|
110
|
+
if (len < 8) {
|
|
111
|
+
return '<secure key too short to log>';
|
|
112
|
+
}
|
|
113
|
+
else if (len <= 15) {
|
|
114
|
+
// 8-15 characters: show first 2 + "..."
|
|
115
|
+
return `${value.substring(0, 2)}...`;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// > 15 characters: show first 3 + "..." + last 3
|
|
119
|
+
return `${value.substring(0, 3)}...${value.substring(len - 3)}`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.HeaderMethods = HeaderMethods;
|
|
124
|
+
//# sourceMappingURL=HeaderMethods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HeaderMethods.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderMethods.ts"],"names":[],"mappings":";;;AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,aAAa;IACtB;;;;;OAKG;IACH,mBAAmB,CAAC,OAAyB;QACzC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;IACpD,CAAC;IAED;;;;;OAKG;IACH,aAAa,CAAC,OAAyB;QACnC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;OAMG;IACH,mBAAmB,CAAC,OAAyB;QACzC,OAAO,OAAO,CAAC,CAAC,uDAAuD;IAC3E,CAAC;IAED,qBAAqB,CAAC,eAAiC,EAAE,aAA4B;QACjF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAe,CAAC;QAEvC,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzC,IAAG,KAAK,EAAE,CAAC;gBACP,8EAA8E;gBAC9E,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,UAAU,CAAC;gBACxD,IAAG,CAAC,MAAM,CAAC,SAAS;oBAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;;oBAE3B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;YACzD,CAAC;QACL,CAAC;QAED,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,uBAAuB,CAAC,eAAiC,EAAE,SAAgC;QACvF,MAAM,MAAM,GAA2B,EAAE,CAAC;QAE1C,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;YAC3C,gDAAgD;YAChD,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjC,SAAS;YACb,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB;YAE5C,IAAI,cAAc,CAAC,SAAS,EAAE,CAAC;gBAC3B,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YACpE,CAAC;iBAAM,CAAC;gBACJ,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC;YAC9C,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAClB,CAAC;IAED;;;;;OAKG;IACK,eAAe,CAAC,KAAa;QACjC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC;QAEzB,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACV,OAAO,+BAA+B,CAAC;QAC3C,CAAC;aAAM,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC;YACnB,wCAAwC;YACxC,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC;QACzC,CAAC;aAAM,CAAC;YACJ,iDAAiD;YACjD,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;QACpE,CAAC;IACL,CAAC;CACJ;AAxGD,sCAwGC","sourcesContent":["import { PlatformHeader } from './PlatformHeader';\n// Single source of truth for the ContextReader interface (was duplicated here)\nimport { ContextReader } from './ContextReader';\n\n/**\n * HeaderMethods - Utility class for working with platform headers.\n *\n * This class can be injected in both server (Node.js) and client (Angular/browser) environments.\n * It provides common operations for filtering and processing headers.\n *\n * Pattern: Stateless utility class (pure functions, can be instantiated or injected)\n * - Server: Can inject empty instance, use static-like methods\n * - Client: new HeaderMethods() (no DI needed)\n *\n * Usage:\n * ```typescript\n * // Server-side (ContextFilter)\n * constructor(@inject() headerMethods: HeaderMethods) {\n * const allHeaders = [... flatten from extensions ...];\n * this.transferHeaders = headerMethods.findTransferHeaders(allHeaders);\n * }\n *\n * // Client-side (ClientFactory)\n * const headerMethods = new HeaderMethods();\n * const loggableHeaders = headerMethods.findLoggableHeaders(allHeaders, requestHeaders);\n * ```\n */\nexport class HeaderMethods {\n /**\n * Filter headers to only those that should be transferred (isWantTransferred=true).\n *\n * @param headers - Array of PlatformHeader definitions\n * @returns Filtered array of headers with isWantTransferred=true\n */\n findTransferHeaders(headers: PlatformHeader[]): PlatformHeader[] {\n return headers.filter(h => h.isWantTransferred);\n }\n\n /**\n * Split headers into secure and public categories.\n *\n * @param headers - Array of PlatformHeader definitions\n * @returns SplitHeaders with secureHeaders (isSecured=true) and publicHeaders (isSecured=false)\n */\n secureHeaders(headers: PlatformHeader[]): PlatformHeader[] {\n return headers.filter(h => h.isSecured);\n }\n\n /**\n * Get all headers that should be logged.\n * All headers are loggable - secure headers will be masked by formatHeadersForLogging.\n *\n * @param headers - Array of PlatformHeader definitions\n * @returns All headers (they're all loggable, just some are masked)\n */\n findLoggableHeaders(headers: PlatformHeader[]): PlatformHeader[] {\n return headers; // All headers are loggable, secure ones will be masked\n }\n\n buildSecureMapForLogs(platformHeaders: PlatformHeader[], contextReader: ContextReader): Map<string, any> {\n const headers = new Map<string, any>();\n\n for (const header of platformHeaders) {\n const value = contextReader.read(header);\n if(value) {\n // MDC-style key when defined (Java getLoggerMDCKey), else the raw header name\n const logKey = header.loggerMdcKey ?? header.headerName;\n if(!header.isSecured)\n headers.set(logKey, value);\n else\n headers.set(logKey, this.maskSecureValue(value));\n }\n }\n\n return headers;\n }\n\n /**\n * Format headers for logging with secure masking.\n * Takes filtered PlatformHeaders and actual header values from request.\n *\n * Masking rules for secure headers (isSecured=true):\n * - Length > 15: Show first 3 and last 3 characters with \"...\" between\n * - Length 8-15: Show first 2 characters with \"...\"\n * - Length < 8: Show \"<secure key too short to log>\"\n *\n * @param loggableHeaders - Filtered PlatformHeaders to log\n * @param headerMap - Map of header name (lowercase) -> array of values from request\n * @returns Record of header name -> masked or full value for logging\n */\n formatHeadersForLogging(loggableHeaders: PlatformHeader[], headerMap: Map<string, string[]>): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const platformHeader of loggableHeaders) {\n // Look for header in the map (case-insensitive)\n const values = headerMap.get(platformHeader.headerName.toLowerCase());\n if (!values || values.length === 0) {\n continue;\n }\n\n const value = values[0]; // Take first value\n\n if (platformHeader.isSecured) {\n result[platformHeader.headerName] = this.maskSecureValue(value);\n } else {\n result[platformHeader.headerName] = value;\n }\n }\n\n return result;\n }\n\n /**\n * Mask a secure header value based on its length.\n *\n * @param value - The secure header value to mask\n * @returns Masked value\n */\n private maskSecureValue(value: string): string {\n const len = value.length;\n\n if (len < 8) {\n return '<secure key too short to log>';\n } else if (len <= 15) {\n // 8-15 characters: show first 2 + \"...\"\n return `${value.substring(0, 2)}...`;\n } else {\n // > 15 characters: show first 3 + \"...\" + last 3\n return `${value.substring(0, 3)}...${value.substring(len - 3)}`;\n }\n }\n}\n\n\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { PlatformHeader } from './PlatformHeader';
|
|
2
|
+
import { PlatformHeadersExtension } from './PlatformHeadersExtension';
|
|
3
|
+
/**
|
|
4
|
+
* HeaderRegistry - The single source of truth for all PlatformHeaders known to
|
|
5
|
+
* the platform. Port of Java webpieces' HeaderTranslation.
|
|
6
|
+
*
|
|
7
|
+
* Every consumer (server filters, logging, metrics, outbound HTTP clients)
|
|
8
|
+
* reads the header set from this registry, so externally-defined headers are
|
|
9
|
+
* honored everywhere ("infinitely scalable magic context").
|
|
10
|
+
*
|
|
11
|
+
* Constructible in BOTH environments:
|
|
12
|
+
* - Server (Inversify): WebpiecesModule binds it via
|
|
13
|
+
* `toDynamicValue(ctx => new HeaderRegistry(ctx.getAll(HEADER_TYPES.PlatformHeadersExtension)))`
|
|
14
|
+
* so every module's PlatformHeadersExtension is collected automatically.
|
|
15
|
+
* - Browser (no DI): `new HeaderRegistry([new PlatformHeadersExtension([...])])`.
|
|
16
|
+
*
|
|
17
|
+
* Duplicate validation (port of Java checkForDuplicates) runs at construction,
|
|
18
|
+
* so conflicting definitions fail fast at startup:
|
|
19
|
+
* - Two headers with the same headerName must agree on ALL flags and loggerMdcKey.
|
|
20
|
+
* - Two headers with the same loggerMdcKey must agree on headerName (and therefore flags).
|
|
21
|
+
* - Exact duplicates (same name, same flags) collapse to one entry.
|
|
22
|
+
*/
|
|
23
|
+
export declare class HeaderRegistry {
|
|
24
|
+
private readonly headers;
|
|
25
|
+
constructor(extensions: PlatformHeadersExtension[]);
|
|
26
|
+
/**
|
|
27
|
+
* All registered headers (deduplicated).
|
|
28
|
+
*/
|
|
29
|
+
getHeaders(): PlatformHeader[];
|
|
30
|
+
/**
|
|
31
|
+
* Headers that transfer over the wire (inbound request -> context, and
|
|
32
|
+
* context -> outbound request). isWantTransferred=true.
|
|
33
|
+
*/
|
|
34
|
+
getTransferredHeaders(): PlatformHeader[];
|
|
35
|
+
/**
|
|
36
|
+
* Header names whose values must be masked in logs. isSecured=true.
|
|
37
|
+
*/
|
|
38
|
+
getSecuredNames(): string[];
|
|
39
|
+
/**
|
|
40
|
+
* Headers exposed as MDC/structured-log dimensions (loggerMdcKey set).
|
|
41
|
+
*/
|
|
42
|
+
getMdcHeaders(): PlatformHeader[];
|
|
43
|
+
/**
|
|
44
|
+
* Look up a header definition by its HTTP name (case-insensitive).
|
|
45
|
+
*/
|
|
46
|
+
findByName(headerName: string): PlatformHeader | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Port of Java HeaderTranslation.checkForDuplicates: collapse exact
|
|
49
|
+
* duplicates, throw on conflicting definitions sharing a name or MDC key.
|
|
50
|
+
*/
|
|
51
|
+
private checkForDuplicates;
|
|
52
|
+
/**
|
|
53
|
+
* Two headers sharing a headerName must agree on every flag and the MDC key,
|
|
54
|
+
* otherwise the platform would behave differently depending on which module's
|
|
55
|
+
* definition happened to load first.
|
|
56
|
+
*/
|
|
57
|
+
private assertSameDefinition;
|
|
58
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HeaderRegistry = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* HeaderRegistry - The single source of truth for all PlatformHeaders known to
|
|
6
|
+
* the platform. Port of Java webpieces' HeaderTranslation.
|
|
7
|
+
*
|
|
8
|
+
* Every consumer (server filters, logging, metrics, outbound HTTP clients)
|
|
9
|
+
* reads the header set from this registry, so externally-defined headers are
|
|
10
|
+
* honored everywhere ("infinitely scalable magic context").
|
|
11
|
+
*
|
|
12
|
+
* Constructible in BOTH environments:
|
|
13
|
+
* - Server (Inversify): WebpiecesModule binds it via
|
|
14
|
+
* `toDynamicValue(ctx => new HeaderRegistry(ctx.getAll(HEADER_TYPES.PlatformHeadersExtension)))`
|
|
15
|
+
* so every module's PlatformHeadersExtension is collected automatically.
|
|
16
|
+
* - Browser (no DI): `new HeaderRegistry([new PlatformHeadersExtension([...])])`.
|
|
17
|
+
*
|
|
18
|
+
* Duplicate validation (port of Java checkForDuplicates) runs at construction,
|
|
19
|
+
* so conflicting definitions fail fast at startup:
|
|
20
|
+
* - Two headers with the same headerName must agree on ALL flags and loggerMdcKey.
|
|
21
|
+
* - Two headers with the same loggerMdcKey must agree on headerName (and therefore flags).
|
|
22
|
+
* - Exact duplicates (same name, same flags) collapse to one entry.
|
|
23
|
+
*/
|
|
24
|
+
class HeaderRegistry {
|
|
25
|
+
headers;
|
|
26
|
+
constructor(extensions) {
|
|
27
|
+
const allHeaders = [];
|
|
28
|
+
for (const extension of extensions) {
|
|
29
|
+
allHeaders.push(...extension.getHeaders());
|
|
30
|
+
}
|
|
31
|
+
this.headers = this.checkForDuplicates(allHeaders);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* All registered headers (deduplicated).
|
|
35
|
+
*/
|
|
36
|
+
getHeaders() {
|
|
37
|
+
return this.headers;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Headers that transfer over the wire (inbound request -> context, and
|
|
41
|
+
* context -> outbound request). isWantTransferred=true.
|
|
42
|
+
*/
|
|
43
|
+
getTransferredHeaders() {
|
|
44
|
+
return this.headers.filter((h) => h.isWantTransferred);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Header names whose values must be masked in logs. isSecured=true.
|
|
48
|
+
*/
|
|
49
|
+
getSecuredNames() {
|
|
50
|
+
return this.headers
|
|
51
|
+
.filter((h) => h.isSecured)
|
|
52
|
+
.map((h) => h.headerName);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Headers exposed as MDC/structured-log dimensions (loggerMdcKey set).
|
|
56
|
+
*/
|
|
57
|
+
getMdcHeaders() {
|
|
58
|
+
return this.headers.filter((h) => h.loggerMdcKey !== undefined);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Look up a header definition by its HTTP name (case-insensitive).
|
|
62
|
+
*/
|
|
63
|
+
findByName(headerName) {
|
|
64
|
+
const lower = headerName.toLowerCase();
|
|
65
|
+
return this.headers.find((h) => h.headerName.toLowerCase() === lower);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Port of Java HeaderTranslation.checkForDuplicates: collapse exact
|
|
69
|
+
* duplicates, throw on conflicting definitions sharing a name or MDC key.
|
|
70
|
+
*/
|
|
71
|
+
checkForDuplicates(allHeaders) {
|
|
72
|
+
const byName = new Map();
|
|
73
|
+
const byMdcKey = new Map();
|
|
74
|
+
for (const header of allHeaders) {
|
|
75
|
+
const nameKey = header.headerName.toLowerCase();
|
|
76
|
+
const existing = byName.get(nameKey);
|
|
77
|
+
if (existing) {
|
|
78
|
+
this.assertSameDefinition(existing, header);
|
|
79
|
+
continue; // exact duplicate - collapse
|
|
80
|
+
}
|
|
81
|
+
byName.set(nameKey, header);
|
|
82
|
+
if (header.loggerMdcKey !== undefined) {
|
|
83
|
+
const mdcClash = byMdcKey.get(header.loggerMdcKey);
|
|
84
|
+
if (mdcClash) {
|
|
85
|
+
throw new Error(`Duplicate PlatformHeader loggerMdcKey '${header.loggerMdcKey}': ` +
|
|
86
|
+
`defined by header '${mdcClash.headerName}' AND header '${header.headerName}'. ` +
|
|
87
|
+
`Each MDC key must map to exactly one header.`);
|
|
88
|
+
}
|
|
89
|
+
byMdcKey.set(header.loggerMdcKey, header);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return Array.from(byName.values());
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Two headers sharing a headerName must agree on every flag and the MDC key,
|
|
96
|
+
* otherwise the platform would behave differently depending on which module's
|
|
97
|
+
* definition happened to load first.
|
|
98
|
+
*/
|
|
99
|
+
assertSameDefinition(existing, duplicate) {
|
|
100
|
+
const conflicts = [];
|
|
101
|
+
if (existing.isWantTransferred !== duplicate.isWantTransferred) {
|
|
102
|
+
conflicts.push(`isWantTransferred (${existing.isWantTransferred} vs ${duplicate.isWantTransferred})`);
|
|
103
|
+
}
|
|
104
|
+
if (existing.isSecured !== duplicate.isSecured) {
|
|
105
|
+
conflicts.push(`isSecured (${existing.isSecured} vs ${duplicate.isSecured})`);
|
|
106
|
+
}
|
|
107
|
+
if (existing.isDimensionForMetrics !== duplicate.isDimensionForMetrics) {
|
|
108
|
+
conflicts.push(`isDimensionForMetrics (${existing.isDimensionForMetrics} vs ${duplicate.isDimensionForMetrics})`);
|
|
109
|
+
}
|
|
110
|
+
if (existing.loggerMdcKey !== duplicate.loggerMdcKey) {
|
|
111
|
+
conflicts.push(`loggerMdcKey ('${existing.loggerMdcKey}' vs '${duplicate.loggerMdcKey}')`);
|
|
112
|
+
}
|
|
113
|
+
if (conflicts.length > 0) {
|
|
114
|
+
throw new Error(`Conflicting PlatformHeader definitions for '${existing.headerName}': ` +
|
|
115
|
+
`two modules registered it with different ${conflicts.join(', ')}. ` +
|
|
116
|
+
`Headers sharing a name must agree on all flags.`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.HeaderRegistry = HeaderRegistry;
|
|
121
|
+
//# sourceMappingURL=HeaderRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HeaderRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { PlatformHeader } from './PlatformHeader';\nimport { PlatformHeadersExtension } from './PlatformHeadersExtension';\n\n/**\n * HeaderRegistry - The single source of truth for all PlatformHeaders known to\n * the platform. Port of Java webpieces' HeaderTranslation.\n *\n * Every consumer (server filters, logging, metrics, outbound HTTP clients)\n * reads the header set from this registry, so externally-defined headers are\n * honored everywhere (\"infinitely scalable magic context\").\n *\n * Constructible in BOTH environments:\n * - Server (Inversify): WebpiecesModule binds it via\n * `toDynamicValue(ctx => new HeaderRegistry(ctx.getAll(HEADER_TYPES.PlatformHeadersExtension)))`\n * so every module's PlatformHeadersExtension is collected automatically.\n * - Browser (no DI): `new HeaderRegistry([new PlatformHeadersExtension([...])])`.\n *\n * Duplicate validation (port of Java checkForDuplicates) runs at construction,\n * so conflicting definitions fail fast at startup:\n * - Two headers with the same headerName must agree on ALL flags and loggerMdcKey.\n * - Two headers with the same loggerMdcKey must agree on headerName (and therefore flags).\n * - Exact duplicates (same name, same flags) collapse to one entry.\n */\nexport class HeaderRegistry {\n private readonly headers: PlatformHeader[];\n\n constructor(extensions: PlatformHeadersExtension[]) {\n const allHeaders: PlatformHeader[] = [];\n for (const extension of extensions) {\n allHeaders.push(...extension.getHeaders());\n }\n this.headers = this.checkForDuplicates(allHeaders);\n }\n\n /**\n * All registered headers (deduplicated).\n */\n getHeaders(): PlatformHeader[] {\n return this.headers;\n }\n\n /**\n * Headers that transfer over the wire (inbound request -> context, and\n * context -> outbound request). isWantTransferred=true.\n */\n getTransferredHeaders(): PlatformHeader[] {\n return this.headers.filter((h: PlatformHeader) => h.isWantTransferred);\n }\n\n /**\n * Header names whose values must be masked in logs. isSecured=true.\n */\n getSecuredNames(): string[] {\n return this.headers\n .filter((h: PlatformHeader) => h.isSecured)\n .map((h: PlatformHeader) => h.headerName);\n }\n\n /**\n * Headers exposed as MDC/structured-log dimensions (loggerMdcKey set).\n */\n getMdcHeaders(): PlatformHeader[] {\n return this.headers.filter((h: PlatformHeader) => h.loggerMdcKey !== undefined);\n }\n\n /**\n * Look up a header definition by its HTTP name (case-insensitive).\n */\n findByName(headerName: string): PlatformHeader | undefined {\n const lower = headerName.toLowerCase();\n return this.headers.find((h: PlatformHeader) => h.headerName.toLowerCase() === lower);\n }\n\n /**\n * Port of Java HeaderTranslation.checkForDuplicates: collapse exact\n * duplicates, throw on conflicting definitions sharing a name or MDC key.\n */\n private checkForDuplicates(allHeaders: PlatformHeader[]): PlatformHeader[] {\n const byName = new Map<string, PlatformHeader>();\n const byMdcKey = new Map<string, PlatformHeader>();\n\n for (const header of allHeaders) {\n const nameKey = header.headerName.toLowerCase();\n const existing = byName.get(nameKey);\n if (existing) {\n this.assertSameDefinition(existing, header);\n continue; // exact duplicate - collapse\n }\n byName.set(nameKey, header);\n\n if (header.loggerMdcKey !== undefined) {\n const mdcClash = byMdcKey.get(header.loggerMdcKey);\n if (mdcClash) {\n throw new Error(\n `Duplicate PlatformHeader loggerMdcKey '${header.loggerMdcKey}': ` +\n `defined by header '${mdcClash.headerName}' AND header '${header.headerName}'. ` +\n `Each MDC key must map to exactly one header.`,\n );\n }\n byMdcKey.set(header.loggerMdcKey, header);\n }\n }\n\n return Array.from(byName.values());\n }\n\n /**\n * Two headers sharing a headerName must agree on every flag and the MDC key,\n * otherwise the platform would behave differently depending on which module's\n * definition happened to load first.\n */\n private assertSameDefinition(existing: PlatformHeader, duplicate: PlatformHeader): void {\n const conflicts: string[] = [];\n if (existing.isWantTransferred !== duplicate.isWantTransferred) {\n conflicts.push(`isWantTransferred (${existing.isWantTransferred} vs ${duplicate.isWantTransferred})`);\n }\n if (existing.isSecured !== duplicate.isSecured) {\n conflicts.push(`isSecured (${existing.isSecured} vs ${duplicate.isSecured})`);\n }\n if (existing.isDimensionForMetrics !== duplicate.isDimensionForMetrics) {\n conflicts.push(`isDimensionForMetrics (${existing.isDimensionForMetrics} vs ${duplicate.isDimensionForMetrics})`);\n }\n if (existing.loggerMdcKey !== duplicate.loggerMdcKey) {\n conflicts.push(`loggerMdcKey ('${existing.loggerMdcKey}' vs '${duplicate.loggerMdcKey}')`);\n }\n if (conflicts.length > 0) {\n throw new Error(\n `Conflicting PlatformHeader definitions for '${existing.headerName}': ` +\n `two modules registered it with different ${conflicts.join(', ')}. ` +\n `Headers sharing a name must agree on all flags.`,\n );\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DI symbols for platform headers system.
|
|
3
|
+
*
|
|
4
|
+
* Uses Symbol.for() to create global symbols that work across module boundaries.
|
|
5
|
+
* This is important for Inversify multiInject pattern where multiple modules
|
|
6
|
+
* bind to the same symbol.
|
|
7
|
+
*/
|
|
8
|
+
export declare const HEADER_TYPES: {
|
|
9
|
+
/**
|
|
10
|
+
* Symbol for PlatformHeadersExtension instances.
|
|
11
|
+
* Multiple modules can bind PlatformHeadersExtension instances to this symbol,
|
|
12
|
+
* and consumers can use @multiInject to collect all of them.
|
|
13
|
+
*
|
|
14
|
+
* Pattern: Extension (DI-level) vs Plugin (App-level)
|
|
15
|
+
* - Extensions contribute specific capabilities to framework (headers, converters, etc.)
|
|
16
|
+
* - Plugins provide complete features with modules + routes (Hibernate, Jackson, etc.)
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // In a module
|
|
21
|
+
* const extension = new PlatformHeadersExtension([header1, header2]);
|
|
22
|
+
* bind<PlatformHeadersExtension>(HEADER_TYPES.PlatformHeadersExtension).toConstantValue(extension);
|
|
23
|
+
*
|
|
24
|
+
* // In a consumer
|
|
25
|
+
* constructor(@multiInject(HEADER_TYPES.PlatformHeadersExtension) extensions: PlatformHeadersExtension[]) {}
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
PlatformHeadersExtension: symbol;
|
|
29
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HEADER_TYPES = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* DI symbols for platform headers system.
|
|
6
|
+
*
|
|
7
|
+
* Uses Symbol.for() to create global symbols that work across module boundaries.
|
|
8
|
+
* This is important for Inversify multiInject pattern where multiple modules
|
|
9
|
+
* bind to the same symbol.
|
|
10
|
+
*/
|
|
11
|
+
exports.HEADER_TYPES = {
|
|
12
|
+
/**
|
|
13
|
+
* Symbol for PlatformHeadersExtension instances.
|
|
14
|
+
* Multiple modules can bind PlatformHeadersExtension instances to this symbol,
|
|
15
|
+
* and consumers can use @multiInject to collect all of them.
|
|
16
|
+
*
|
|
17
|
+
* Pattern: Extension (DI-level) vs Plugin (App-level)
|
|
18
|
+
* - Extensions contribute specific capabilities to framework (headers, converters, etc.)
|
|
19
|
+
* - Plugins provide complete features with modules + routes (Hibernate, Jackson, etc.)
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // In a module
|
|
24
|
+
* const extension = new PlatformHeadersExtension([header1, header2]);
|
|
25
|
+
* bind<PlatformHeadersExtension>(HEADER_TYPES.PlatformHeadersExtension).toConstantValue(extension);
|
|
26
|
+
*
|
|
27
|
+
* // In a consumer
|
|
28
|
+
* constructor(@multiInject(HEADER_TYPES.PlatformHeadersExtension) extensions: PlatformHeadersExtension[]) {}
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
PlatformHeadersExtension: Symbol.for('PlatformHeadersExtension'),
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=HeaderTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HeaderTypes.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderTypes.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACU,QAAA,YAAY,GAAG;IACxB;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAAwB,EAAE,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC;CACnE,CAAC","sourcesContent":["/**\n * DI symbols for platform headers system.\n *\n * Uses Symbol.for() to create global symbols that work across module boundaries.\n * This is important for Inversify multiInject pattern where multiple modules\n * bind to the same symbol.\n */\nexport const HEADER_TYPES = {\n /**\n * Symbol for PlatformHeadersExtension instances.\n * Multiple modules can bind PlatformHeadersExtension instances to this symbol,\n * and consumers can use @multiInject to collect all of them.\n *\n * Pattern: Extension (DI-level) vs Plugin (App-level)\n * - Extensions contribute specific capabilities to framework (headers, converters, etc.)\n * - Plugins provide complete features with modules + routes (Hibernate, Jackson, etc.)\n *\n * Usage:\n * ```typescript\n * // In a module\n * const extension = new PlatformHeadersExtension([header1, header2]);\n * bind<PlatformHeadersExtension>(HEADER_TYPES.PlatformHeadersExtension).toConstantValue(extension);\n *\n * // In a consumer\n * constructor(@multiInject(HEADER_TYPES.PlatformHeadersExtension) extensions: PlatformHeadersExtension[]) {}\n * ```\n */\n PlatformHeadersExtension: Symbol.for('PlatformHeadersExtension'),\n};\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { RouteMetadata } from "./decorators";
|
|
2
|
+
/**
|
|
3
|
+
* LogApiCall - Generic API call logging utility.
|
|
4
|
+
*
|
|
5
|
+
* Used by both server-side (LogApiFilter) and client-side (ClientFactory) for
|
|
6
|
+
* consistent logging patterns across the framework.
|
|
7
|
+
*
|
|
8
|
+
* Logging format patterns:
|
|
9
|
+
* - [API-{type}-req] ClassName.methodName request={...} headers={...}
|
|
10
|
+
* - [API-{type}-resp-SUCCESS] ClassName.methodName response={...}
|
|
11
|
+
* - [API-{type}-resp-OTHER] ClassName.methodName errorType={...} (user errors)
|
|
12
|
+
* - [API-{type}-resp-FAIL] ClassName.methodName error={...} (server errors)
|
|
13
|
+
*/
|
|
14
|
+
export declare class LogApiCall {
|
|
15
|
+
/**
|
|
16
|
+
* Execute an API call with logging around it.
|
|
17
|
+
*
|
|
18
|
+
* @param type - 'SVR' or 'CLIENT'
|
|
19
|
+
* @param meta - Route metadata with controllerClassName and methodName
|
|
20
|
+
* @param requestDto - The request DTO
|
|
21
|
+
* @param headers - Map of header name -> values
|
|
22
|
+
* @param splitHeaders - SplitHeaders with secureHeaders and publicHeaders for masking
|
|
23
|
+
* @param method - The method to execute
|
|
24
|
+
*/
|
|
25
|
+
execute(type: string, meta: RouteMetadata, requestDto: any, headers: Map<string, any>, method: (dto: any) => Promise<any>): Promise<any>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if an error is a user error (expected behavior from server perspective).
|
|
28
|
+
* User errors are NOT failures - just users making mistakes or validation issues.
|
|
29
|
+
*
|
|
30
|
+
* User errors (logged as OTHER, no stack trace):
|
|
31
|
+
* - HttpBadRequestError (400)
|
|
32
|
+
* - HttpUnauthorizedError (401)
|
|
33
|
+
* - HttpForbiddenError (403)
|
|
34
|
+
* - HttpNotFoundError (404)
|
|
35
|
+
* - HttpUserError (266)
|
|
36
|
+
*
|
|
37
|
+
* @param error - The error to check
|
|
38
|
+
* @returns true if this is a user error, false for server errors
|
|
39
|
+
*/
|
|
40
|
+
static isUserError(error: unknown): boolean;
|
|
41
|
+
}
|