@webpieces/core-util 0.4.399 → 0.4.400
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/ContextMgr.js +13 -0
- package/src/http/ContextMgr.js.map +1 -1
- package/src/http/ContextReader.d.ts +0 -14
- package/src/http/ContextReader.js.map +1 -1
- package/src/http/HeaderRegistry.d.ts +5 -24
- package/src/http/HeaderRegistry.js +6 -56
- package/src/http/HeaderRegistry.js.map +1 -1
- package/src/http/ServiceInfo.d.ts +30 -38
- package/src/http/ServiceInfo.js +35 -50
- package/src/http/ServiceInfo.js.map +1 -1
- package/src/http/WebpiecesCoreHeaders.d.ts +14 -0
- package/src/http/WebpiecesCoreHeaders.js +15 -0
- package/src/http/WebpiecesCoreHeaders.js.map +1 -1
- package/src/index.d.ts +0 -1
- package/src/index.js.map +1 -1
package/package.json
CHANGED
package/src/http/ContextMgr.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ContextMgr = void 0;
|
|
4
4
|
const HeaderRegistry_1 = require("./HeaderRegistry");
|
|
5
|
+
const ServiceInfo_1 = require("./ServiceInfo");
|
|
6
|
+
const WebpiecesCoreHeaders_1 = require("./WebpiecesCoreHeaders");
|
|
5
7
|
/**
|
|
6
8
|
* ContextMgr - propagates the magic context onto outbound BROWSER requests.
|
|
7
9
|
*
|
|
@@ -51,6 +53,17 @@ class ContextMgr {
|
|
|
51
53
|
outbound.set(key.httpHeader, value);
|
|
52
54
|
}
|
|
53
55
|
}
|
|
56
|
+
// CLIENT_VERSION: a browser ORIGINATES a call, so it sends its OWN app build version (from
|
|
57
|
+
// ServiceInfo) and the server logs which client build called it — same rule as the server-side
|
|
58
|
+
// RequestContextHeaders. Our version wins; absent if this app was never identified via setInfo.
|
|
59
|
+
const myVersion = ServiceInfo_1.ServiceInfo.getVersion();
|
|
60
|
+
const clientVersionHeader = WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.CLIENT_VERSION.httpHeader;
|
|
61
|
+
if (myVersion) {
|
|
62
|
+
outbound.set(clientVersionHeader, myVersion);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
outbound.delete(clientVersionHeader);
|
|
66
|
+
}
|
|
54
67
|
return outbound;
|
|
55
68
|
}
|
|
56
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextMgr.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextMgr.ts"],"names":[],"mappings":";;;AAEA,qDAAkD;
|
|
1
|
+
{"version":3,"file":"ContextMgr.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextMgr.ts"],"names":[],"mappings":";;;AAEA,qDAAkD;AAClD,+CAA4C;AAC5C,iEAA8D;AAE9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,UAAU;IAIC;IAFpB;IACI,2DAA2D;IAC3C,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAC7C,CAAC;IAEJ;;;;;;;;;;OAUG;IACH,oBAAoB;QAChB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE3C,KAAK,MAAM,GAAG,IAAI,+BAAc,CAAC,GAAG,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;gBACxD,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,UAAW,EAAE,KAAK,CAAC,CAAC;YACzC,CAAC;QACL,CAAC;QAED,2FAA2F;QAC3F,+FAA+F;QAC/F,gGAAgG;QAChG,MAAM,SAAS,GAAG,yBAAW,CAAC,UAAU,EAAE,CAAC;QAC3C,MAAM,mBAAmB,GAAG,2CAAoB,CAAC,cAAc,CAAC,UAAW,CAAC;QAC5E,IAAI,SAAS,EAAE,CAAC;YACZ,QAAQ,CAAC,GAAG,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACJ,QAAQ,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;QACzC,CAAC;QAED,OAAO,QAAQ,CAAC;IACpB,CAAC;CACJ;AAzCD,gCAyCC","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { ContextReader } from './ContextReader';\nimport { HeaderRegistry } from './HeaderRegistry';\nimport { ServiceInfo } from './ServiceInfo';\nimport { WebpiecesCoreHeaders } from './WebpiecesCoreHeaders';\n\n/**\n * ContextMgr - propagates the magic context onto outbound BROWSER requests.\n *\n * BROWSER-ONLY. Only @webpieces/http-client-browser may name this class. The server reads\n * `RequestContext` directly through `RequestContextHeaders` (in @webpieces/core-context) — a\n * `ContextReader` indirection buys a server nothing, because there is exactly one right answer there.\n *\n * Browsers have no AsyncLocalStorage, so the app holds a `MutableContextStore` and sets values as\n * they become known (login token, tenant). Every transferred key (httpHeader set) in the GLOBAL\n * {@link HeaderRegistry} is read from it and added to outbound requests. The registry is a process\n * global configured once at startup (like LogManager) and is browser-safe: it is the key SCHEMA,\n * not the value store.\n *\n * Example usage:\n * ```typescript\n * // startup, before bootstrap:\n * HeaderRegistry.configure(CompanyHeaders.getAllHeaders(), true);\n *\n * const store = new MutableContextStore();\n * const factory = new ClientHttpBrowserFactory(store);\n * const client = factory.createRpcClient(SaveApi, new ClientConfig('http://api.example.com'));\n * ```\n */\nexport class ContextMgr {\n\n constructor(\n /** The app-held store that provides context-key values. */\n public readonly contextReader: ContextReader,\n ) {}\n\n /**\n * Build the headers to send on an outbound request: every transferred key (httpHeader set)\n * with a non-empty value, emitted under its `httpHeader` wire name.\n *\n * NO request-id chaining. A browser ORIGINATES a trace — it has no inbound request to point\n * back at. If the app puts an `x-request-id` on the store it goes out as-is, and the server's\n * inbound transfer adopts it as hop 1's own id. Chaining is a server concern; see\n * RequestContextHeaders.\n *\n * Values are RAW (unmasked) — this map goes on the wire, not in logs.\n */\n buildOutboundHeaders(): Map<string, string> {\n const outbound = new Map<string, string>();\n\n for (const key of HeaderRegistry.get().getTransferredKeys()) {\n const value = this.contextReader.read(key);\n if (value !== undefined && value !== null && value !== '') {\n outbound.set(key.httpHeader!, value);\n }\n }\n\n // CLIENT_VERSION: a browser ORIGINATES a call, so it sends its OWN app build version (from\n // ServiceInfo) and the server logs which client build called it — same rule as the server-side\n // RequestContextHeaders. Our version wins; absent if this app was never identified via setInfo.\n const myVersion = ServiceInfo.getVersion();\n const clientVersionHeader = WebpiecesCoreHeaders.CLIENT_VERSION.httpHeader!;\n if (myVersion) {\n outbound.set(clientVersionHeader, myVersion);\n } else {\n outbound.delete(clientVersionHeader);\n }\n\n return outbound;\n }\n}\n"]}
|
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
import { ContextKey } from '../ContextKey';
|
|
2
|
-
/**
|
|
3
|
-
* Reads one context key's string value. The ONE seam between the two environments:
|
|
4
|
-
* the server passes `RequestContext.getHeader`, a browser passes its store's read.
|
|
5
|
-
*
|
|
6
|
-
* A lambda, not an object — nothing here needs an implementation to hold.
|
|
7
|
-
*/
|
|
8
|
-
export type ContextRead = (key: ContextKey) => string | undefined;
|
|
9
|
-
/**
|
|
10
|
-
* Reads one context key's value WITHOUT narrowing to string — a key may hold an object
|
|
11
|
-
* (e.g. {@link ApiCallInfo} under API_CALL_INFO). Used only by
|
|
12
|
-
* {@link HeaderRegistry.buildStructuredLogFields} for the node logging backends, which can emit
|
|
13
|
-
* object values as nested `jsonPayload.<name>`. Server passes `RequestContext.getHeader`.
|
|
14
|
-
*/
|
|
15
|
-
export type StructuredContextRead = (key: ContextKey) => unknown;
|
|
16
2
|
/**
|
|
17
3
|
* ContextReader - reads context-key values from an app-held store.
|
|
18
4
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextReader.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextReader.ts"],"names":[],"mappings":"","sourcesContent":["import { ContextKey } from '../ContextKey';\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"ContextReader.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextReader.ts"],"names":[],"mappings":"","sourcesContent":["import { ContextKey } from '../ContextKey';\n\n/**\n * ContextReader - reads context-key values from an app-held store.\n *\n * BROWSER-ONLY. Browsers have no AsyncLocalStorage and therefore no ambient request scope, so the\n * app holds a `MutableContextStore` (in @webpieces/http-client-browser) and sets values as they\n * become known (login token, tenant, ...).\n *\n * The server has no use for this: there is exactly one right answer there, so `RequestContextHeaders`\n * (in @webpieces/core-context) reads `RequestContext` directly rather than through a reader object.\n *\n * Note this is only about where VALUES live. The key SCHEMA — which keys exist, which transfer, which\n * are secured — is the global {@link HeaderRegistry}, and that is browser-safe and shared by both.\n *\n * This is a business-logic interface (per CLAUDE.md: behavior = interface).\n */\nexport interface ContextReader {\n /**\n * Read the string value of a context key. Returns undefined if not present.\n */\n read(key: ContextKey): string | undefined;\n\n /**\n * OPTIONAL: read a non-string context value (e.g. the active TestCaseRecorder\n * under RecorderKeys.RECORDER). Server-side readers implement this over the\n * RequestContext; browser readers may omit it (no server-side recording in\n * browsers — same as Java).\n */\n // webpieces-disable no-any-unknown -- context values are heterogeneous (recorder, meta objects)\n readValue?(key: ContextKey): unknown;\n}\n"]}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ContextKey } from '../ContextKey';
|
|
2
|
-
import { ContextRead, StructuredContextRead } from './ContextReader';
|
|
3
2
|
/**
|
|
4
3
|
* HeaderRegistry - the single, GLOBAL source of truth for every ContextKey the
|
|
5
4
|
* platform knows about. Port of Java webpieces' HeaderTranslation.
|
|
@@ -52,31 +51,13 @@ export declare class HeaderRegistry {
|
|
|
52
51
|
getTransferredKeys(): ContextKey[];
|
|
53
52
|
/** Names (log keys) whose values must be masked in logs. isSecured=true. */
|
|
54
53
|
getSecuredNames(): string[];
|
|
55
|
-
/** Keys that appear in logs. isLogged=true. */
|
|
56
|
-
getLoggedKeys(): ContextKey[];
|
|
57
|
-
/**
|
|
58
|
-
* The log/MDC field map: every logged key with a value, under its `name`, secured values masked.
|
|
59
|
-
*
|
|
60
|
-
* The ONE implementation, because the registry owns the keys and each {@link ContextKey} knows
|
|
61
|
-
* how to mask its own value. Callers differ only in WHERE a value is read from:
|
|
62
|
-
* `RequestContext.buildLogFields()` passes its own getHeader (server), and `ContextMgr` passes
|
|
63
|
-
* the app-held store's read (browser).
|
|
64
|
-
*
|
|
65
|
-
* getLoggedKeys() is precomputed at configure() time, so this is hot-path safe.
|
|
66
|
-
*/
|
|
67
|
-
buildLogFields(read: ContextRead): Map<string, string>;
|
|
68
54
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* filterable `jsonPayload.api.side`, `.result`, ...). String values are still masked per key.
|
|
74
|
-
*
|
|
75
|
-
* The flat {@link buildLogFields} stays string-only for wire/MDC/recorder-fixture callers, which
|
|
76
|
-
* must not carry heterogeneous objects. This is the deliberate second builder (registry stays the
|
|
77
|
-
* single source of truth for WHICH keys log; the two builders differ only in value SHAPE).
|
|
55
|
+
* Keys that appear in logs. isLogged=true. The node logging backends read THESE and build the log
|
|
56
|
+
* field map directly from the active context — see `RequestContext.buildLogFields()` /
|
|
57
|
+
* `buildStructuredLogFields()`. (The registry used to own those two builders behind a read
|
|
58
|
+
* callback, but only the server ever called them, so the seam was inlined away.)
|
|
78
59
|
*/
|
|
79
|
-
|
|
60
|
+
getLoggedKeys(): ContextKey[];
|
|
80
61
|
/** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */
|
|
81
62
|
findByHttpHeader(httpHeader: string): ContextKey | undefined;
|
|
82
63
|
/**
|
|
@@ -88,64 +88,14 @@ class HeaderRegistry {
|
|
|
88
88
|
getSecuredNames() {
|
|
89
89
|
return this.securedNames;
|
|
90
90
|
}
|
|
91
|
-
/** Keys that appear in logs. isLogged=true. */
|
|
92
|
-
getLoggedKeys() {
|
|
93
|
-
return this.loggedKeys;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* The log/MDC field map: every logged key with a value, under its `name`, secured values masked.
|
|
97
|
-
*
|
|
98
|
-
* The ONE implementation, because the registry owns the keys and each {@link ContextKey} knows
|
|
99
|
-
* how to mask its own value. Callers differ only in WHERE a value is read from:
|
|
100
|
-
* `RequestContext.buildLogFields()` passes its own getHeader (server), and `ContextMgr` passes
|
|
101
|
-
* the app-held store's read (browser).
|
|
102
|
-
*
|
|
103
|
-
* getLoggedKeys() is precomputed at configure() time, so this is hot-path safe.
|
|
104
|
-
*/
|
|
105
|
-
buildLogFields(read) {
|
|
106
|
-
const fields = new Map();
|
|
107
|
-
for (const key of this.getLoggedKeys()) {
|
|
108
|
-
const value = read(key);
|
|
109
|
-
// typeof-string guard: an object-valued logged key (API_CALL_INFO) must NOT leak into the
|
|
110
|
-
// flat string map — this map feeds wire/MDC + recorder fixtures, which are string-only.
|
|
111
|
-
// `read` is typed string-returning, but a key like API_CALL_INFO actually holds an object at
|
|
112
|
-
// runtime; the guard keeps it out. Object-valued keys ride buildStructuredLogFields instead.
|
|
113
|
-
if (typeof value === 'string' && value) {
|
|
114
|
-
fields.set(key.name, key.maskIfSecured(value));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return fields;
|
|
118
|
-
}
|
|
119
91
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* filterable `jsonPayload.api.side`, `.result`, ...). String values are still masked per key.
|
|
125
|
-
*
|
|
126
|
-
* The flat {@link buildLogFields} stays string-only for wire/MDC/recorder-fixture callers, which
|
|
127
|
-
* must not carry heterogeneous objects. This is the deliberate second builder (registry stays the
|
|
128
|
-
* single source of truth for WHICH keys log; the two builders differ only in value SHAPE).
|
|
92
|
+
* Keys that appear in logs. isLogged=true. The node logging backends read THESE and build the log
|
|
93
|
+
* field map directly from the active context — see `RequestContext.buildLogFields()` /
|
|
94
|
+
* `buildStructuredLogFields()`. (The registry used to own those two builders behind a read
|
|
95
|
+
* callback, but only the server ever called them, so the seam was inlined away.)
|
|
129
96
|
*/
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
for (const key of this.getLoggedKeys()) {
|
|
133
|
-
const value = read(key);
|
|
134
|
-
if (value === undefined || value === null) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (typeof value === 'string') {
|
|
138
|
-
if (value) {
|
|
139
|
-
fields.set(key.name, key.maskIfSecured(value));
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
else if (typeof value === 'object') {
|
|
143
|
-
fields.set(key.name, value);
|
|
144
|
-
}
|
|
145
|
-
// Non-string primitives (number/boolean/bigint) are not expected for logged context keys;
|
|
146
|
-
// ignore them here rather than String()-flattening, keeping the shape honest.
|
|
147
|
-
}
|
|
148
|
-
return fields;
|
|
97
|
+
getLoggedKeys() {
|
|
98
|
+
return this.loggedKeys;
|
|
149
99
|
}
|
|
150
100
|
/** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */
|
|
151
101
|
findByHttpHeader(httpHeader) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeaderRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderRegistry.ts"],"names":[],"mappings":";;;AAEA,iEAA8D;AAE9D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAa,cAAc;IACvB,8EAA8E;IAC9E,MAAM,CAAU,eAAe,GAAiB,2CAAoB,CAAC,aAAa,EAAE,CAAC;IAE7E,MAAM,CAAC,QAAQ,CAA6B;IAEnC,IAAI,CAAe;IAEpC,yEAAyE;IACzE,gFAAgF;IAChF,8EAA8E;IAC9E,8EAA8E;IAC9E,6EAA6E;IAC5D,eAAe,CAAe;IAC9B,YAAY,CAAW;IACvB,UAAU,CAAe;IACzB,YAAY,CAA0B;IAEvD,YAAoB,IAAkB;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC;QACvF,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI;aACxB,MAAM,CAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;aACtC,GAAG,CAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAClE,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CACvB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAa,EAAwB,EAAE,CAAC,CAAC,CAAC,CAAC,UAAW,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CACtG,CAAC;IACN,CAAC;IAED;;;OAGG;IACH,2KAA2K;IAC3K,MAAM,CAAC,SAAS,CAAC,UAAwB,EAAE,eAAwB;QAC/D,MAAM,GAAG,GAAiB;YACtB,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,GAAG,UAAU;SAChB,CAAC;QACF,cAAc,CAAC,QAAQ,GAAG,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IAED,0EAA0E;IAC1E,MAAM,CAAC,GAAG;QACN,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACX,4EAA4E;gBAC5E,qFAAqF,CACxF,CAAC;QACN,CAAC;QACD,OAAO,cAAc,CAAC,QAAQ,CAAC;IACnC,CAAC;IAED,iFAAiF;IACjF,MAAM,CAAC,YAAY;QACf,OAAO,cAAc,CAAC,QAAQ,KAAK,SAAS,CAAC;IACjD,CAAC;IAED,0CAA0C;IAC1C,OAAO;QACH,OAAO,IAAI,CAAC,IAAI,CAAC;IACrB,CAAC;IAED;;;OAGG;IACH,kBAAkB;QACd,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAED,4EAA4E;IAC5E,eAAe;QACX,OAAO,IAAI,CAAC,YAAY,CAAC;IAC7B,CAAC;IAED,+CAA+C;IAC/C,aAAa;QACT,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED;;;;;;;;;OASG;IACH,cAAc,CAAC,IAAiB;QAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YACxB,0FAA0F;YAC1F,wFAAwF;YACxF,6FAA6F;YAC7F,6FAA6F;YAC7F,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,EAAE,CAAC;gBACrC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;YACnD,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED;;;;;;;;;;OAUG;IACH,wBAAwB,CAAC,IAA2B;QAChD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;QAClD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YACxB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACxC,SAAS;YACb,CAAC;YACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC5B,IAAI,KAAK,EAAE,CAAC;oBACR,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;gBACnD,CAAC;YACL,CAAC;iBAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACnC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAChC,CAAC;YACD,0FAA0F;YAC1F,8EAA8E;QAClF,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,8FAA8F;IAC9F,gBAAgB,CAAC,UAAkB;QAC/B,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED;;;OAGG;IACK,kBAAkB,CAAC,OAAqB;QAC5C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC7C,MAAM,YAAY,GAAG,IAAI,GAAG,EAAsB,CAAC;QAEnD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACrC,IAAI,QAAQ,EAAE,CAAC;gBACX,IAAI,CAAC,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;gBACzC,SAAS,CAAC,6BAA6B;YAC3C,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAEzB,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;gBAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC1C,IAAI,KAAK,EAAE,CAAC;oBACR,MAAM,IAAI,KAAK,CACX,oCAAoC,GAAG,CAAC,UAAU,KAAK;wBACvD,mBAAmB,KAAK,CAAC,IAAI,cAAc,GAAG,CAAC,IAAI,KAAK;wBACxD,uDAAuD,CAC1D,CAAC;gBACN,CAAC;gBACD,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YACrC,CAAC;QACL,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACvC,CAAC;IAED;;;;OAIG;IACK,oBAAoB,CAAC,QAAoB,EAAE,SAAqB;QACpE,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,EAAE,CAAC;YAC/C,SAAS,CAAC,IAAI,CAAC,gBAAgB,QAAQ,CAAC,UAAU,SAAS,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,QAAQ,CAAC,SAAS,KAAK,SAAS,CAAC,SAAS,EAAE,CAAC;YAC7C,SAAS,CAAC,IAAI,CAAC,cAAc,QAAQ,CAAC,SAAS,OAAO,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ,EAAE,CAAC;YAC3C,SAAS,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,QAAQ,OAAO,SAAS,CAAC,QAAQ,GAAG,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACX,2CAA2C,QAAQ,CAAC,IAAI,KAAK;gBAC7D,4CAA4C,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBACpE,8CAA8C,CACjD,CAAC;QACN,CAAC;IACL,CAAC;;AAxML,wCAyMC","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { ContextRead, StructuredContextRead } from './ContextReader';\nimport { WebpiecesCoreHeaders } from './WebpiecesCoreHeaders';\n\n/**\n * HeaderRegistry - the single, GLOBAL source of truth for every ContextKey the\n * platform knows about. Port of Java webpieces' HeaderTranslation.\n *\n * Configured exactly like {@link LogManager} — once, at process startup — and then\n * globally accessible. There is NO DI wiring: filters/clients call\n * `HeaderRegistry.get()` instead of injecting it.\n *\n * ```ts\n * // startup (server AND browser), BEFORE LogManager.setFactory(...):\n * HeaderRegistry.configure(CompanyHeaders.getAllHeaders(), true);\n * ```\n *\n * - `svrHeaders` the context keys this process registers — by convention the whole\n * company-wide set (all keys across all servers), e.g. CompanyHeaders.\n * - `platformHeaders` when true, also include {@link HeaderRegistry.DEFAULT_HEADERS}\n * (the webpieces common keys: request-id, correlation-id, ...).\n *\n * Duplicate validation (port of Java checkForDuplicates) runs at configure() time,\n * so conflicting definitions fail fast at startup:\n * - Two keys with the same `name` must agree on httpHeader/isSecured/isLogged.\n * - Two keys with the same `httpHeader` must agree on `name`.\n * - Exact duplicates collapse to one entry.\n */\nexport class HeaderRegistry {\n /** The webpieces-supplied common keys; included when platformHeaders=true. */\n static readonly DEFAULT_HEADERS: ContextKey[] = WebpiecesCoreHeaders.getAllHeaders();\n\n private static instance: HeaderRegistry | undefined;\n\n private readonly keys: ContextKey[];\n\n // Derived collections precomputed ONCE, here in the constructor (i.e. at\n // configure() time). The hot path — every log line calls getLoggedKeys(), every\n // outbound request calls getTransferredKeys() — then returns the cached array\n // instead of re-filtering the full key list on each call. These are reachable\n // only through HeaderRegistry.get(), which throws until configure() has run.\n private readonly transferredKeys: ContextKey[];\n private readonly securedNames: string[];\n private readonly loggedKeys: ContextKey[];\n private readonly byHttpHeader: Map<string, ContextKey>;\n\n private constructor(keys: ContextKey[]) {\n this.keys = this.checkForDuplicates(keys);\n this.transferredKeys = this.keys.filter((k: ContextKey) => k.httpHeader !== undefined);\n this.securedNames = this.keys\n .filter((k: ContextKey) => k.isSecured)\n .map((k: ContextKey) => k.name);\n this.loggedKeys = this.keys.filter((k: ContextKey) => k.isLogged);\n this.byHttpHeader = new Map(\n this.transferredKeys.map((k: ContextKey): [string, ContextKey] => [k.httpHeader!.toLowerCase(), k]),\n );\n }\n\n /**\n * Install the process-wide registry. Call once at startup, BEFORE\n * LogManager.setFactory(...) (logging masks/keys off this registry).\n */\n // webpieces-disable no-function-outside-class -- HeaderRegistry is a deliberately static global singleton (like LogManager); configured once at startup, never DI-injected\n static configure(svrHeaders: ContextKey[], platformHeaders: boolean): void {\n const all: ContextKey[] = [\n ...(platformHeaders ? HeaderRegistry.DEFAULT_HEADERS : []),\n ...svrHeaders,\n ];\n HeaderRegistry.instance = new HeaderRegistry(all);\n }\n\n /** The configured registry. Throws if configure() has not been called. */\n static get(): HeaderRegistry {\n if (!HeaderRegistry.instance) {\n throw new Error(\n 'HeaderRegistry.configure(...) has not been called. Configure the registry ' +\n 'at startup (before LogManager.setFactory) so filters/logging know the context keys.',\n );\n }\n return HeaderRegistry.instance;\n }\n\n /** True once configure() has run. Used by LogManager.setFactory to fail fast. */\n static isConfigured(): boolean {\n return HeaderRegistry.instance !== undefined;\n }\n\n /** All registered keys (deduplicated). */\n getKeys(): ContextKey[] {\n return this.keys;\n }\n\n /**\n * Keys that transfer over the wire (inbound request -> context, and context ->\n * outbound request): those with an httpHeader set.\n */\n getTransferredKeys(): ContextKey[] {\n return this.transferredKeys;\n }\n\n /** Names (log keys) whose values must be masked in logs. isSecured=true. */\n getSecuredNames(): string[] {\n return this.securedNames;\n }\n\n /** Keys that appear in logs. isLogged=true. */\n getLoggedKeys(): ContextKey[] {\n return this.loggedKeys;\n }\n\n /**\n * The log/MDC field map: every logged key with a value, under its `name`, secured values masked.\n *\n * The ONE implementation, because the registry owns the keys and each {@link ContextKey} knows\n * how to mask its own value. Callers differ only in WHERE a value is read from:\n * `RequestContext.buildLogFields()` passes its own getHeader (server), and `ContextMgr` passes\n * the app-held store's read (browser).\n *\n * getLoggedKeys() is precomputed at configure() time, so this is hot-path safe.\n */\n buildLogFields(read: ContextRead): Map<string, string> {\n const fields = new Map<string, string>();\n for (const key of this.getLoggedKeys()) {\n const value = read(key);\n // typeof-string guard: an object-valued logged key (API_CALL_INFO) must NOT leak into the\n // flat string map — this map feeds wire/MDC + recorder fixtures, which are string-only.\n // `read` is typed string-returning, but a key like API_CALL_INFO actually holds an object at\n // runtime; the guard keeps it out. Object-valued keys ride buildStructuredLogFields instead.\n if (typeof value === 'string' && value) {\n fields.set(key.name, key.maskIfSecured(value));\n }\n }\n return fields;\n }\n\n /**\n * The STRUCTURED log-field map: like {@link buildLogFields}, but values may be objects, so an\n * object-valued logged key (e.g. {@link ApiCallInfo} under API_CALL_INFO) survives as an object\n * instead of being dropped. The node logging backends (winston/bunyan) call THIS — they\n * JSON-serialize the whole record, so an object value nests into `jsonPayload.<name>` (→\n * filterable `jsonPayload.api.side`, `.result`, ...). String values are still masked per key.\n *\n * The flat {@link buildLogFields} stays string-only for wire/MDC/recorder-fixture callers, which\n * must not carry heterogeneous objects. This is the deliberate second builder (registry stays the\n * single source of truth for WHICH keys log; the two builders differ only in value SHAPE).\n */\n buildStructuredLogFields(read: StructuredContextRead): Map<string, string | object> {\n const fields = new Map<string, string | object>();\n for (const key of this.getLoggedKeys()) {\n const value = read(key);\n if (value === undefined || value === null) {\n continue;\n }\n if (typeof value === 'string') {\n if (value) {\n fields.set(key.name, key.maskIfSecured(value));\n }\n } else if (typeof value === 'object') {\n fields.set(key.name, value);\n }\n // Non-string primitives (number/boolean/bigint) are not expected for logged context keys;\n // ignore them here rather than String()-flattening, keeping the shape honest.\n }\n return fields;\n }\n\n /** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */\n findByHttpHeader(httpHeader: string): ContextKey | undefined {\n return this.byHttpHeader.get(httpHeader.toLowerCase());\n }\n\n /**\n * Collapse exact duplicates, throw on conflicting definitions sharing a `name`\n * or an `httpHeader`.\n */\n private checkForDuplicates(allKeys: ContextKey[]): ContextKey[] {\n const byName = new Map<string, ContextKey>();\n const byHttpHeader = new Map<string, ContextKey>();\n\n for (const key of allKeys) {\n const nameKey = key.name.toLowerCase();\n const existing = byName.get(nameKey);\n if (existing) {\n this.assertSameDefinition(existing, key);\n continue; // exact duplicate - collapse\n }\n byName.set(nameKey, key);\n\n if (key.httpHeader !== undefined) {\n const headerKey = key.httpHeader.toLowerCase();\n const clash = byHttpHeader.get(headerKey);\n if (clash) {\n throw new Error(\n `Duplicate ContextKey httpHeader '${key.httpHeader}': ` +\n `defined by key '${clash.name}' AND key '${key.name}'. ` +\n `Each HTTP header must map to exactly one context key.`,\n );\n }\n byHttpHeader.set(headerKey, key);\n }\n }\n\n return Array.from(byName.values());\n }\n\n /**\n * Two keys sharing a `name` must agree on httpHeader/isSecured/isLogged,\n * otherwise the platform would behave differently depending on which module's\n * definition happened to load first.\n */\n private assertSameDefinition(existing: ContextKey, duplicate: ContextKey): void {\n const conflicts: string[] = [];\n if (existing.httpHeader !== duplicate.httpHeader) {\n conflicts.push(`httpHeader ('${existing.httpHeader}' vs '${duplicate.httpHeader}')`);\n }\n if (existing.isSecured !== duplicate.isSecured) {\n conflicts.push(`isSecured (${existing.isSecured} vs ${duplicate.isSecured})`);\n }\n if (existing.isLogged !== duplicate.isLogged) {\n conflicts.push(`isLogged (${existing.isLogged} vs ${duplicate.isLogged})`);\n }\n if (conflicts.length > 0) {\n throw new Error(\n `Conflicting ContextKey definitions for '${existing.name}': ` +\n `two modules registered it with different ${conflicts.join(', ')}. ` +\n `Keys sharing a name must agree on all flags.`,\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"HeaderRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { WebpiecesCoreHeaders } from './WebpiecesCoreHeaders';\n\n/**\n * HeaderRegistry - the single, GLOBAL source of truth for every ContextKey the\n * platform knows about. Port of Java webpieces' HeaderTranslation.\n *\n * Configured exactly like {@link LogManager} — once, at process startup — and then\n * globally accessible. There is NO DI wiring: filters/clients call\n * `HeaderRegistry.get()` instead of injecting it.\n *\n * ```ts\n * // startup (server AND browser), BEFORE LogManager.setFactory(...):\n * HeaderRegistry.configure(CompanyHeaders.getAllHeaders(), true);\n * ```\n *\n * - `svrHeaders` the context keys this process registers — by convention the whole\n * company-wide set (all keys across all servers), e.g. CompanyHeaders.\n * - `platformHeaders` when true, also include {@link HeaderRegistry.DEFAULT_HEADERS}\n * (the webpieces common keys: request-id, correlation-id, ...).\n *\n * Duplicate validation (port of Java checkForDuplicates) runs at configure() time,\n * so conflicting definitions fail fast at startup:\n * - Two keys with the same `name` must agree on httpHeader/isSecured/isLogged.\n * - Two keys with the same `httpHeader` must agree on `name`.\n * - Exact duplicates collapse to one entry.\n */\nexport class HeaderRegistry {\n /** The webpieces-supplied common keys; included when platformHeaders=true. */\n static readonly DEFAULT_HEADERS: ContextKey[] = WebpiecesCoreHeaders.getAllHeaders();\n\n private static instance: HeaderRegistry | undefined;\n\n private readonly keys: ContextKey[];\n\n // Derived collections precomputed ONCE, here in the constructor (i.e. at\n // configure() time). The hot path — every log line calls getLoggedKeys(), every\n // outbound request calls getTransferredKeys() — then returns the cached array\n // instead of re-filtering the full key list on each call. These are reachable\n // only through HeaderRegistry.get(), which throws until configure() has run.\n private readonly transferredKeys: ContextKey[];\n private readonly securedNames: string[];\n private readonly loggedKeys: ContextKey[];\n private readonly byHttpHeader: Map<string, ContextKey>;\n\n private constructor(keys: ContextKey[]) {\n this.keys = this.checkForDuplicates(keys);\n this.transferredKeys = this.keys.filter((k: ContextKey) => k.httpHeader !== undefined);\n this.securedNames = this.keys\n .filter((k: ContextKey) => k.isSecured)\n .map((k: ContextKey) => k.name);\n this.loggedKeys = this.keys.filter((k: ContextKey) => k.isLogged);\n this.byHttpHeader = new Map(\n this.transferredKeys.map((k: ContextKey): [string, ContextKey] => [k.httpHeader!.toLowerCase(), k]),\n );\n }\n\n /**\n * Install the process-wide registry. Call once at startup, BEFORE\n * LogManager.setFactory(...) (logging masks/keys off this registry).\n */\n // webpieces-disable no-function-outside-class -- HeaderRegistry is a deliberately static global singleton (like LogManager); configured once at startup, never DI-injected\n static configure(svrHeaders: ContextKey[], platformHeaders: boolean): void {\n const all: ContextKey[] = [\n ...(platformHeaders ? HeaderRegistry.DEFAULT_HEADERS : []),\n ...svrHeaders,\n ];\n HeaderRegistry.instance = new HeaderRegistry(all);\n }\n\n /** The configured registry. Throws if configure() has not been called. */\n static get(): HeaderRegistry {\n if (!HeaderRegistry.instance) {\n throw new Error(\n 'HeaderRegistry.configure(...) has not been called. Configure the registry ' +\n 'at startup (before LogManager.setFactory) so filters/logging know the context keys.',\n );\n }\n return HeaderRegistry.instance;\n }\n\n /** True once configure() has run. Used by LogManager.setFactory to fail fast. */\n static isConfigured(): boolean {\n return HeaderRegistry.instance !== undefined;\n }\n\n /** All registered keys (deduplicated). */\n getKeys(): ContextKey[] {\n return this.keys;\n }\n\n /**\n * Keys that transfer over the wire (inbound request -> context, and context ->\n * outbound request): those with an httpHeader set.\n */\n getTransferredKeys(): ContextKey[] {\n return this.transferredKeys;\n }\n\n /** Names (log keys) whose values must be masked in logs. isSecured=true. */\n getSecuredNames(): string[] {\n return this.securedNames;\n }\n\n /**\n * Keys that appear in logs. isLogged=true. The node logging backends read THESE and build the log\n * field map directly from the active context — see `RequestContext.buildLogFields()` /\n * `buildStructuredLogFields()`. (The registry used to own those two builders behind a read\n * callback, but only the server ever called them, so the seam was inlined away.)\n */\n getLoggedKeys(): ContextKey[] {\n return this.loggedKeys;\n }\n\n /** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */\n findByHttpHeader(httpHeader: string): ContextKey | undefined {\n return this.byHttpHeader.get(httpHeader.toLowerCase());\n }\n\n /**\n * Collapse exact duplicates, throw on conflicting definitions sharing a `name`\n * or an `httpHeader`.\n */\n private checkForDuplicates(allKeys: ContextKey[]): ContextKey[] {\n const byName = new Map<string, ContextKey>();\n const byHttpHeader = new Map<string, ContextKey>();\n\n for (const key of allKeys) {\n const nameKey = key.name.toLowerCase();\n const existing = byName.get(nameKey);\n if (existing) {\n this.assertSameDefinition(existing, key);\n continue; // exact duplicate - collapse\n }\n byName.set(nameKey, key);\n\n if (key.httpHeader !== undefined) {\n const headerKey = key.httpHeader.toLowerCase();\n const clash = byHttpHeader.get(headerKey);\n if (clash) {\n throw new Error(\n `Duplicate ContextKey httpHeader '${key.httpHeader}': ` +\n `defined by key '${clash.name}' AND key '${key.name}'. ` +\n `Each HTTP header must map to exactly one context key.`,\n );\n }\n byHttpHeader.set(headerKey, key);\n }\n }\n\n return Array.from(byName.values());\n }\n\n /**\n * Two keys sharing a `name` must agree on httpHeader/isSecured/isLogged,\n * otherwise the platform would behave differently depending on which module's\n * definition happened to load first.\n */\n private assertSameDefinition(existing: ContextKey, duplicate: ContextKey): void {\n const conflicts: string[] = [];\n if (existing.httpHeader !== duplicate.httpHeader) {\n conflicts.push(`httpHeader ('${existing.httpHeader}' vs '${duplicate.httpHeader}')`);\n }\n if (existing.isSecured !== duplicate.isSecured) {\n conflicts.push(`isSecured (${existing.isSecured} vs ${duplicate.isSecured})`);\n }\n if (existing.isLogged !== duplicate.isLogged) {\n conflicts.push(`isLogged (${existing.isLogged} vs ${duplicate.isLogged})`);\n }\n if (conflicts.length > 0) {\n throw new Error(\n `Conflicting ContextKey definitions for '${existing.name}': ` +\n `two modules registered it with different ${conflicts.join(', ')}. ` +\n `Keys sharing a name must agree on all flags.`,\n );\n }\n }\n}\n"]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* deps), which is why it lives in core-util beside {@link ClientRegistry}.
|
|
8
8
|
*
|
|
9
9
|
* ```ts
|
|
10
|
-
* // startup, FIRST
|
|
10
|
+
* // startup, FIRST:
|
|
11
11
|
* ServiceInfo.setInfo('my-service', '2.1.0');
|
|
12
12
|
* ```
|
|
13
13
|
*
|
|
@@ -21,20 +21,20 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Switching backends silently changed which fields your logs carried. Several unrelated readers
|
|
23
23
|
* need these same two facts, so they belong to the framework, not to a backend:
|
|
24
|
-
* - the bunyan backend, to satisfy bunyan's mandatory root-logger `name`, and to stamp `version`;
|
|
25
24
|
* - the winston backend, to stamp `svcName` + `version`;
|
|
26
|
-
* -
|
|
25
|
+
* - the bunyan backend, to stamp `version` (its root-logger `name` is a fixed constant now);
|
|
26
|
+
* - {@link WebpiecesCoreHeaders.REQUEST_ID_SOURCE}, to record WHO minted a request id;
|
|
27
|
+
* - {@link WebpiecesCoreHeaders.CLIENT_VERSION}, so a downstream server can log which build called it.
|
|
27
28
|
*
|
|
28
29
|
* VERSION IS OPAQUE. It is whatever string identifies THIS build — a git SHA, a semver tag, a CI
|
|
29
30
|
* build number. webpieces neither parses nor derives it; the app decides where it comes from (a
|
|
30
31
|
* generated file, an env var, a Docker build arg) and passes it here.
|
|
31
32
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* omit the field.
|
|
33
|
+
* DOES NOT THROW ON READ. {@link getName} / {@link getVersion} return `undefined` when unset, so a
|
|
34
|
+
* log line emitted before `setInfo` (early boot) still ships — it simply omits the version. Logging
|
|
35
|
+
* never blocks on identity. The "a deployed build MUST identify itself" guarantee is enforced ONCE,
|
|
36
|
+
* loudly, at startup by whoever requires it (`setupRuntime` calls {@link assertIdentified}), instead
|
|
37
|
+
* of by every reader throwing.
|
|
38
38
|
*/
|
|
39
39
|
export declare class ServiceInfo {
|
|
40
40
|
/** This service's name. Process-global; set once at startup. */
|
|
@@ -42,14 +42,11 @@ export declare class ServiceInfo {
|
|
|
42
42
|
/** This build's version — an opaque app-chosen string. Process-global; set once at startup. */
|
|
43
43
|
private static svcVersion;
|
|
44
44
|
/**
|
|
45
|
-
* Identify this service. Call it
|
|
46
|
-
* which reads both values during its own constructor.
|
|
45
|
+
* Identify this service. Call it at startup.
|
|
47
46
|
*
|
|
48
47
|
* LAST CALL WINS, deliberately. A real deployment identifies itself once, but an in-process test
|
|
49
48
|
* can legitimately boot TWO services back-to-back (see the app-example e2e two-server flow), so a
|
|
50
|
-
* "one process = one service" rule would reject a case that genuinely exists.
|
|
51
|
-
* factory captures both values in its own constructor, each server built that way still keeps
|
|
52
|
-
* what was set when IT was built.
|
|
49
|
+
* "one process = one service" rule would reject a case that genuinely exists.
|
|
53
50
|
*
|
|
54
51
|
* @param name - this service's name, e.g. 'my-service'.
|
|
55
52
|
* @param version - the opaque identifier of THIS build (git SHA, semver, CI build number).
|
|
@@ -57,38 +54,33 @@ export declare class ServiceInfo {
|
|
|
57
54
|
*/
|
|
58
55
|
static setInfo(name: string, version: string): void;
|
|
59
56
|
/**
|
|
60
|
-
* This service's name,
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*/
|
|
65
|
-
static getName(): string;
|
|
66
|
-
/**
|
|
67
|
-
* This build's version, REQUIRED. For STARTUP callers only (the logger factories, setupRuntime),
|
|
68
|
-
* for the same reason as {@link getName}.
|
|
69
|
-
*
|
|
70
|
-
* @throws Error if {@link setInfo} was never called.
|
|
57
|
+
* This service's name, or `undefined` when {@link setInfo} has not been called. Does NOT throw:
|
|
58
|
+
* readers (logging backends, requestIdSource, outbound CLIENT_VERSION) simply omit the field when
|
|
59
|
+
* it is missing, so a pre-`setInfo` log line still emits. Callers that REQUIRE identity call
|
|
60
|
+
* {@link assertIdentified} at startup instead.
|
|
71
61
|
*/
|
|
72
|
-
static
|
|
62
|
+
static getName(): string | undefined;
|
|
73
63
|
/**
|
|
74
|
-
* This
|
|
75
|
-
* server that booted has already passed the {@link getName} check in `setupRuntime`, so a real
|
|
76
|
-
* request always finds a name here. Only a unit test driving the context directly can see
|
|
77
|
-
* undefined — and there, omitting a log field beats exploding.
|
|
64
|
+
* This build's version, or `undefined` when unset — same non-throwing contract as {@link getName}.
|
|
78
65
|
*/
|
|
79
|
-
static
|
|
66
|
+
static getVersion(): string | undefined;
|
|
80
67
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
68
|
+
* FAIL FAST, AT STARTUP. Throws unless BOTH name and version are set. Whoever wants the "a
|
|
69
|
+
* deployed build must be able to say which build it is" guarantee calls this while booting
|
|
70
|
+
* (`setupRuntime` does) — so a forgotten `setInfo` kills the deploy (the revision never goes
|
|
71
|
+
* healthy) instead of quietly shipping logs that cannot say which build emitted them. Nothing on
|
|
72
|
+
* the REQUEST path calls this: a missing log field must never 500 live traffic.
|
|
73
|
+
*
|
|
74
|
+
* @throws Error if {@link setInfo} was never called.
|
|
83
75
|
*/
|
|
84
|
-
static
|
|
76
|
+
static assertIdentified(): void;
|
|
85
77
|
/** Reset — for tests, mirroring {@link ClientRegistry.clear}. */
|
|
86
78
|
static clear(): void;
|
|
87
79
|
/**
|
|
88
|
-
* The one actionable "you forgot to call setInfo" message
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
80
|
+
* The one actionable "you forgot to call setInfo" message, thrown by {@link assertIdentified}:
|
|
81
|
+
* setInfo sets BOTH, so either being missing has the identical cause and the identical fix —
|
|
82
|
+
* telling the caller only about the half they happened to read first would send them back for a
|
|
83
|
+
* second round.
|
|
92
84
|
*/
|
|
93
85
|
private static notSetMessage;
|
|
94
86
|
}
|
package/src/http/ServiceInfo.js
CHANGED
|
@@ -10,7 +10,7 @@ exports.ServiceInfo = void 0;
|
|
|
10
10
|
* deps), which is why it lives in core-util beside {@link ClientRegistry}.
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
|
-
* // startup, FIRST
|
|
13
|
+
* // startup, FIRST:
|
|
14
14
|
* ServiceInfo.setInfo('my-service', '2.1.0');
|
|
15
15
|
* ```
|
|
16
16
|
*
|
|
@@ -24,20 +24,20 @@ exports.ServiceInfo = void 0;
|
|
|
24
24
|
*
|
|
25
25
|
* Switching backends silently changed which fields your logs carried. Several unrelated readers
|
|
26
26
|
* need these same two facts, so they belong to the framework, not to a backend:
|
|
27
|
-
* - the bunyan backend, to satisfy bunyan's mandatory root-logger `name`, and to stamp `version`;
|
|
28
27
|
* - the winston backend, to stamp `svcName` + `version`;
|
|
29
|
-
* -
|
|
28
|
+
* - the bunyan backend, to stamp `version` (its root-logger `name` is a fixed constant now);
|
|
29
|
+
* - {@link WebpiecesCoreHeaders.REQUEST_ID_SOURCE}, to record WHO minted a request id;
|
|
30
|
+
* - {@link WebpiecesCoreHeaders.CLIENT_VERSION}, so a downstream server can log which build called it.
|
|
30
31
|
*
|
|
31
32
|
* VERSION IS OPAQUE. It is whatever string identifies THIS build — a git SHA, a semver tag, a CI
|
|
32
33
|
* build number. webpieces neither parses nor derives it; the app decides where it comes from (a
|
|
33
34
|
* generated file, an env var, a Docker build arg) and passes it here.
|
|
34
35
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* omit the field.
|
|
36
|
+
* DOES NOT THROW ON READ. {@link getName} / {@link getVersion} return `undefined` when unset, so a
|
|
37
|
+
* log line emitted before `setInfo` (early boot) still ships — it simply omits the version. Logging
|
|
38
|
+
* never blocks on identity. The "a deployed build MUST identify itself" guarantee is enforced ONCE,
|
|
39
|
+
* loudly, at startup by whoever requires it (`setupRuntime` calls {@link assertIdentified}), instead
|
|
40
|
+
* of by every reader throwing.
|
|
41
41
|
*/
|
|
42
42
|
class ServiceInfo {
|
|
43
43
|
/** This service's name. Process-global; set once at startup. */
|
|
@@ -45,14 +45,11 @@ class ServiceInfo {
|
|
|
45
45
|
/** This build's version — an opaque app-chosen string. Process-global; set once at startup. */
|
|
46
46
|
static svcVersion;
|
|
47
47
|
/**
|
|
48
|
-
* Identify this service. Call it
|
|
49
|
-
* which reads both values during its own constructor.
|
|
48
|
+
* Identify this service. Call it at startup.
|
|
50
49
|
*
|
|
51
50
|
* LAST CALL WINS, deliberately. A real deployment identifies itself once, but an in-process test
|
|
52
51
|
* can legitimately boot TWO services back-to-back (see the app-example e2e two-server flow), so a
|
|
53
|
-
* "one process = one service" rule would reject a case that genuinely exists.
|
|
54
|
-
* factory captures both values in its own constructor, each server built that way still keeps
|
|
55
|
-
* what was set when IT was built.
|
|
52
|
+
* "one process = one service" rule would reject a case that genuinely exists.
|
|
56
53
|
*
|
|
57
54
|
* @param name - this service's name, e.g. 'my-service'.
|
|
58
55
|
* @param version - the opaque identifier of THIS build (git SHA, semver, CI build number).
|
|
@@ -72,48 +69,36 @@ class ServiceInfo {
|
|
|
72
69
|
ServiceInfo.svcVersion = version;
|
|
73
70
|
}
|
|
74
71
|
/**
|
|
75
|
-
* This service's name,
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
72
|
+
* This service's name, or `undefined` when {@link setInfo} has not been called. Does NOT throw:
|
|
73
|
+
* readers (logging backends, requestIdSource, outbound CLIENT_VERSION) simply omit the field when
|
|
74
|
+
* it is missing, so a pre-`setInfo` log line still emits. Callers that REQUIRE identity call
|
|
75
|
+
* {@link assertIdentified} at startup instead.
|
|
79
76
|
*/
|
|
80
77
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
81
78
|
static getName() {
|
|
82
|
-
if (!ServiceInfo.svcName) {
|
|
83
|
-
throw new Error(ServiceInfo.notSetMessage());
|
|
84
|
-
}
|
|
85
79
|
return ServiceInfo.svcName;
|
|
86
80
|
}
|
|
87
81
|
/**
|
|
88
|
-
* This build's version,
|
|
89
|
-
* for the same reason as {@link getName}.
|
|
90
|
-
*
|
|
91
|
-
* @throws Error if {@link setInfo} was never called.
|
|
82
|
+
* This build's version, or `undefined` when unset — same non-throwing contract as {@link getName}.
|
|
92
83
|
*/
|
|
93
84
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
94
85
|
static getVersion() {
|
|
95
|
-
if (!ServiceInfo.svcVersion) {
|
|
96
|
-
throw new Error(ServiceInfo.notSetMessage());
|
|
97
|
-
}
|
|
98
86
|
return ServiceInfo.svcVersion;
|
|
99
87
|
}
|
|
100
88
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return ServiceInfo.svcName;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* This build's version, or undefined when unset. For the REQUEST path, which must NOT throw —
|
|
112
|
-
* same reasoning as {@link tryGetName}.
|
|
89
|
+
* FAIL FAST, AT STARTUP. Throws unless BOTH name and version are set. Whoever wants the "a
|
|
90
|
+
* deployed build must be able to say which build it is" guarantee calls this while booting
|
|
91
|
+
* (`setupRuntime` does) — so a forgotten `setInfo` kills the deploy (the revision never goes
|
|
92
|
+
* healthy) instead of quietly shipping logs that cannot say which build emitted them. Nothing on
|
|
93
|
+
* the REQUEST path calls this: a missing log field must never 500 live traffic.
|
|
94
|
+
*
|
|
95
|
+
* @throws Error if {@link setInfo} was never called.
|
|
113
96
|
*/
|
|
114
97
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
115
|
-
static
|
|
116
|
-
|
|
98
|
+
static assertIdentified() {
|
|
99
|
+
if (!ServiceInfo.svcName || !ServiceInfo.svcVersion) {
|
|
100
|
+
throw new Error(ServiceInfo.notSetMessage());
|
|
101
|
+
}
|
|
117
102
|
}
|
|
118
103
|
/** Reset — for tests, mirroring {@link ClientRegistry.clear}. */
|
|
119
104
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
@@ -122,19 +107,19 @@ class ServiceInfo {
|
|
|
122
107
|
ServiceInfo.svcVersion = undefined;
|
|
123
108
|
}
|
|
124
109
|
/**
|
|
125
|
-
* The one actionable "you forgot to call setInfo" message
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
110
|
+
* The one actionable "you forgot to call setInfo" message, thrown by {@link assertIdentified}:
|
|
111
|
+
* setInfo sets BOTH, so either being missing has the identical cause and the identical fix —
|
|
112
|
+
* telling the caller only about the half they happened to read first would send them back for a
|
|
113
|
+
* second round.
|
|
129
114
|
*/
|
|
130
115
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
131
116
|
static notSetMessage() {
|
|
132
|
-
return ('ServiceInfo.setInfo(...) has not been called. Identify this service at startup
|
|
133
|
-
'BEFORE constructing the logger factory (it reads name+version in its constructor):\n' +
|
|
117
|
+
return ('ServiceInfo.setInfo(...) has not been called. Identify this service at startup:\n' +
|
|
134
118
|
" ServiceInfo.setInfo('my-service', '2.1.0');\n" +
|
|
135
119
|
'The name+version stamp every log line (so you can tell WHICH BUILD emitted a line), ' +
|
|
136
|
-
'
|
|
137
|
-
'is opaque — a git SHA, a
|
|
120
|
+
'the name records which service minted a request id (requestIdSource), and the version ' +
|
|
121
|
+
'travels to downstream servers as clientVersion. The version is opaque — a git SHA, a ' +
|
|
122
|
+
'semver tag, a CI build number, whatever identifies your build.');
|
|
138
123
|
}
|
|
139
124
|
}
|
|
140
125
|
exports.ServiceInfo = ServiceInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServiceInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ServiceInfo.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAa,WAAW;IACpB,gEAAgE;IACxD,MAAM,CAAC,OAAO,CAAqB;IAE3C,+FAA+F;IACvF,MAAM,CAAC,UAAU,CAAqB;IAE9C
|
|
1
|
+
{"version":3,"file":"ServiceInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ServiceInfo.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAa,WAAW;IACpB,gEAAgE;IACxD,MAAM,CAAC,OAAO,CAAqB;IAE3C,+FAA+F;IACvF,MAAM,CAAC,UAAU,CAAqB;IAE9C;;;;;;;;;;OAUG;IACH,4JAA4J;IAC5J,MAAM,CAAC,OAAO,CAAC,IAAY,EAAE,OAAe;QACxC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACX,qFAAqF;gBACrF,qFAAqF;gBACrF,2BAA2B,CAC9B,CAAC;QACN,CAAC;QACD,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3B,WAAW,CAAC,UAAU,GAAG,OAAO,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,4JAA4J;IAC5J,MAAM,CAAC,OAAO;QACV,OAAO,WAAW,CAAC,OAAO,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,4JAA4J;IAC5J,MAAM,CAAC,UAAU;QACb,OAAO,WAAW,CAAC,UAAU,CAAC;IAClC,CAAC;IAED;;;;;;;;OAQG;IACH,4JAA4J;IAC5J,MAAM,CAAC,gBAAgB;QACnB,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC;QACjD,CAAC;IACL,CAAC;IAED,iEAAiE;IACjE,4JAA4J;IAC5J,MAAM,CAAC,KAAK;QACR,WAAW,CAAC,OAAO,GAAG,SAAS,CAAC;QAChC,WAAW,CAAC,UAAU,GAAG,SAAS,CAAC;IACvC,CAAC;IAED;;;;;OAKG;IACH,4JAA4J;IACpJ,MAAM,CAAC,aAAa;QACxB,OAAO,CACH,mFAAmF;YACnF,mDAAmD;YACnD,sFAAsF;YACtF,wFAAwF;YACxF,uFAAuF;YACvF,gEAAgE,CACnE,CAAC;IACN,CAAC;CACJ;AA7FD,kCA6FC","sourcesContent":["/**\n * ServiceInfo - the ONE answer to \"what service am I, and which build of it?\", for every part of\n * webpieces that needs it.\n *\n * Configured like {@link HeaderRegistry} / {@link ClientRegistry} / LogManager — populated once at\n * startup, then globally accessible with NO DI wiring. Browser-safe (no `process.env`, no node-only\n * deps), which is why it lives in core-util beside {@link ClientRegistry}.\n *\n * ```ts\n * // startup, FIRST:\n * ServiceInfo.setInfo('my-service', '2.1.0');\n * ```\n *\n * WHY THIS EXISTS. Both facts used to be artifacts of WHICH LOGGING LIBRARY YOU PICKED rather than\n * facts about the service:\n * - the name lived on `BunyanFactoryOptions`, because bunyan REQUIRES a root-logger name\n * (`options.name (string) is required` — bunyan's own TypeError). winston has no such concept, so\n * a winston app shipped its logs UNNAMED.\n * - the version lived on `WinstonFactoryOptions` as `svcGitHash`, so a bunyan app could not stamp a\n * version AT ALL — and the name presumed a git SHA, which not every project deploys from.\n *\n * Switching backends silently changed which fields your logs carried. Several unrelated readers\n * need these same two facts, so they belong to the framework, not to a backend:\n * - the winston backend, to stamp `svcName` + `version`;\n * - the bunyan backend, to stamp `version` (its root-logger `name` is a fixed constant now);\n * - {@link WebpiecesCoreHeaders.REQUEST_ID_SOURCE}, to record WHO minted a request id;\n * - {@link WebpiecesCoreHeaders.CLIENT_VERSION}, so a downstream server can log which build called it.\n *\n * VERSION IS OPAQUE. It is whatever string identifies THIS build — a git SHA, a semver tag, a CI\n * build number. webpieces neither parses nor derives it; the app decides where it comes from (a\n * generated file, an env var, a Docker build arg) and passes it here.\n *\n * DOES NOT THROW ON READ. {@link getName} / {@link getVersion} return `undefined` when unset, so a\n * log line emitted before `setInfo` (early boot) still ships — it simply omits the version. Logging\n * never blocks on identity. The \"a deployed build MUST identify itself\" guarantee is enforced ONCE,\n * loudly, at startup by whoever requires it (`setupRuntime` calls {@link assertIdentified}), instead\n * of by every reader throwing.\n */\nexport class ServiceInfo {\n /** This service's name. Process-global; set once at startup. */\n private static svcName: string | undefined;\n\n /** This build's version — an opaque app-chosen string. Process-global; set once at startup. */\n private static svcVersion: string | undefined;\n\n /**\n * Identify this service. Call it at startup.\n *\n * LAST CALL WINS, deliberately. A real deployment identifies itself once, but an in-process test\n * can legitimately boot TWO services back-to-back (see the app-example e2e two-server flow), so a\n * \"one process = one service\" rule would reject a case that genuinely exists.\n *\n * @param name - this service's name, e.g. 'my-service'.\n * @param version - the opaque identifier of THIS build (git SHA, semver, CI build number).\n * @throws Error on a blank name or version — that is always a bug, never a use case.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static setInfo(name: string, version: string): void {\n if (!name || !name.trim()) {\n throw new Error('ServiceInfo.setInfo(...) requires a non-blank service name.');\n }\n if (!version || !version.trim()) {\n throw new Error(\n 'ServiceInfo.setInfo(...) requires a non-blank version. It is opaque to webpieces — ' +\n 'a git SHA, a semver tag, a CI build number — but every deployed build must be able ' +\n 'to say which build it is.',\n );\n }\n ServiceInfo.svcName = name;\n ServiceInfo.svcVersion = version;\n }\n\n /**\n * This service's name, or `undefined` when {@link setInfo} has not been called. Does NOT throw:\n * readers (logging backends, requestIdSource, outbound CLIENT_VERSION) simply omit the field when\n * it is missing, so a pre-`setInfo` log line still emits. Callers that REQUIRE identity call\n * {@link assertIdentified} at startup instead.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static getName(): string | undefined {\n return ServiceInfo.svcName;\n }\n\n /**\n * This build's version, or `undefined` when unset — same non-throwing contract as {@link getName}.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static getVersion(): string | undefined {\n return ServiceInfo.svcVersion;\n }\n\n /**\n * FAIL FAST, AT STARTUP. Throws unless BOTH name and version are set. Whoever wants the \"a\n * deployed build must be able to say which build it is\" guarantee calls this while booting\n * (`setupRuntime` does) — so a forgotten `setInfo` kills the deploy (the revision never goes\n * healthy) instead of quietly shipping logs that cannot say which build emitted them. Nothing on\n * the REQUEST path calls this: a missing log field must never 500 live traffic.\n *\n * @throws Error if {@link setInfo} was never called.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static assertIdentified(): void {\n if (!ServiceInfo.svcName || !ServiceInfo.svcVersion) {\n throw new Error(ServiceInfo.notSetMessage());\n }\n }\n\n /** Reset — for tests, mirroring {@link ClientRegistry.clear}. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static clear(): void {\n ServiceInfo.svcName = undefined;\n ServiceInfo.svcVersion = undefined;\n }\n\n /**\n * The one actionable \"you forgot to call setInfo\" message, thrown by {@link assertIdentified}:\n * setInfo sets BOTH, so either being missing has the identical cause and the identical fix —\n * telling the caller only about the half they happened to read first would send them back for a\n * second round.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n private static notSetMessage(): string {\n return (\n 'ServiceInfo.setInfo(...) has not been called. Identify this service at startup:\\n' +\n \" ServiceInfo.setInfo('my-service', '2.1.0');\\n\" +\n 'The name+version stamp every log line (so you can tell WHICH BUILD emitted a line), ' +\n 'the name records which service minted a request id (requestIdSource), and the version ' +\n 'travels to downstream servers as clientVersion. The version is opaque — a git SHA, a ' +\n 'semver tag, a CI build number, whatever identifies your build.'\n );\n }\n}\n"]}
|
|
@@ -36,6 +36,20 @@ export declare class WebpiecesCoreHeaders {
|
|
|
36
36
|
* - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.
|
|
37
37
|
*/
|
|
38
38
|
static readonly REQUEST_ID_SOURCE: ContextKey;
|
|
39
|
+
/**
|
|
40
|
+
* The CALLER's build version — so a downstream server's logs record which build of the client
|
|
41
|
+
* called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`
|
|
42
|
+
* (this service's build): `version` answers "which build wrote this line?", `clientVersion`
|
|
43
|
+
* answers "which build asked us to?".
|
|
44
|
+
*
|
|
45
|
+
* - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT
|
|
46
|
+
* copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`
|
|
47
|
+
* as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server
|
|
48
|
+
* `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.
|
|
49
|
+
* - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log
|
|
50
|
+
* field map; no backend change needed.
|
|
51
|
+
*/
|
|
52
|
+
static readonly CLIENT_VERSION: ContextKey;
|
|
39
53
|
static readonly ORG_ID: ContextKey;
|
|
40
54
|
static readonly USER_ID: ContextKey;
|
|
41
55
|
static readonly USER_ROLES: ContextKey;
|
|
@@ -40,6 +40,20 @@ class WebpiecesCoreHeaders {
|
|
|
40
40
|
*/
|
|
41
41
|
static REQUEST_ID_SOURCE = new ContextKey_1.ContextKey('requestIdSource',
|
|
42
42
|
/*httpHeader*/ undefined);
|
|
43
|
+
/**
|
|
44
|
+
* The CALLER's build version — so a downstream server's logs record which build of the client
|
|
45
|
+
* called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`
|
|
46
|
+
* (this service's build): `version` answers "which build wrote this line?", `clientVersion`
|
|
47
|
+
* answers "which build asked us to?".
|
|
48
|
+
*
|
|
49
|
+
* - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT
|
|
50
|
+
* copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`
|
|
51
|
+
* as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server
|
|
52
|
+
* `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.
|
|
53
|
+
* - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log
|
|
54
|
+
* field map; no backend change needed.
|
|
55
|
+
*/
|
|
56
|
+
static CLIENT_VERSION = new ContextKey_1.ContextKey('clientVersion', 'x-webpieces-client-version');
|
|
43
57
|
static ORG_ID = new ContextKey_1.ContextKey('orgId', 'x-org-id');
|
|
44
58
|
static USER_ID = new ContextKey_1.ContextKey('userId', 'x-user-id');
|
|
45
59
|
static USER_ROLES = new ContextKey_1.ContextKey('roles', 'x-webpieces-roles');
|
|
@@ -97,6 +111,7 @@ class WebpiecesCoreHeaders {
|
|
|
97
111
|
return [
|
|
98
112
|
WebpiecesCoreHeaders.REQUEST_ID,
|
|
99
113
|
WebpiecesCoreHeaders.REQUEST_ID_SOURCE,
|
|
114
|
+
WebpiecesCoreHeaders.CLIENT_VERSION,
|
|
100
115
|
WebpiecesCoreHeaders.USER_ID,
|
|
101
116
|
WebpiecesCoreHeaders.ORG_ID,
|
|
102
117
|
WebpiecesCoreHeaders.USER_ROLES,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,8CAA2C;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,MAAa,oBAAoB;IAC7B;;;OAGG;IACH,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAEzE;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAU,iBAAiB,GAAG,IAAI,uBAAU,CAC9C,iBAAiB;IACjB,cAAc,CAAC,SAAS,CAC3B,CAAC;IAEF,MAAM,CAAU,MAAM,GAAG,IAAI,uBAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAE7D,MAAM,CAAU,OAAO,GAAG,IAAI,uBAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAEhE,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAE1E;;;OAGG;IACH,MAAM,CAAU,SAAS,GAAG,IAAI,uBAAU,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC;IAEjF;;;;;;;;;;;OAWG;IACH,MAAM,CAAU,aAAa,GAAG,IAAI,uBAAU,CAAC,KAAK,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAExH;;;;;;;;;;OAUG;IACH,MAAM,CAAU,WAAW,GAAG,IAAI,uBAAU,CAAC,YAAY,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE7H,MAAM,CAAU,YAAY,GAAG,IAAI,uBAAU,CAAC,aAAa,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE/H;;;;;;;;;;;;;;;OAeG;IAEH;;OAEG;IACH,MAAM,CAAC,aAAa;QAChB,OAAO;YACH,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,iBAAiB;YACtC,oBAAoB,CAAC,OAAO;YAC5B,oBAAoB,CAAC,MAAM;YAC3B,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,SAAS;YAC9B,oBAAoB,CAAC,aAAa;YAClC,oBAAoB,CAAC,WAAW;YAChC,oBAAoB,CAAC,YAAY;SACpC,CAAC;IACN,CAAC;;
|
|
1
|
+
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,8CAA2C;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,MAAa,oBAAoB;IAC7B;;;OAGG;IACH,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAEzE;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAU,iBAAiB,GAAG,IAAI,uBAAU,CAC9C,iBAAiB;IACjB,cAAc,CAAC,SAAS,CAC3B,CAAC;IAEF;;;;;;;;;;;;OAYG;IACH,MAAM,CAAU,cAAc,GAAG,IAAI,uBAAU,CAAC,eAAe,EAAE,4BAA4B,CAAC,CAAC;IAE/F,MAAM,CAAU,MAAM,GAAG,IAAI,uBAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAE7D,MAAM,CAAU,OAAO,GAAG,IAAI,uBAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAEhE,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAE1E;;;OAGG;IACH,MAAM,CAAU,SAAS,GAAG,IAAI,uBAAU,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC;IAEjF;;;;;;;;;;;OAWG;IACH,MAAM,CAAU,aAAa,GAAG,IAAI,uBAAU,CAAC,KAAK,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAExH;;;;;;;;;;OAUG;IACH,MAAM,CAAU,WAAW,GAAG,IAAI,uBAAU,CAAC,YAAY,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE7H,MAAM,CAAU,YAAY,GAAG,IAAI,uBAAU,CAAC,aAAa,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE/H;;;;;;;;;;;;;;;OAeG;IAEH;;OAEG;IACH,MAAM,CAAC,aAAa;QAChB,OAAO;YACH,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,iBAAiB;YACtC,oBAAoB,CAAC,cAAc;YACnC,oBAAoB,CAAC,OAAO;YAC5B,oBAAoB,CAAC,MAAM;YAC3B,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,SAAS;YAC9B,oBAAoB,CAAC,aAAa;YAClC,oBAAoB,CAAC,WAAW;YAChC,oBAAoB,CAAC,YAAY;SACpC,CAAC;IACN,CAAC;;AAnHL,oDAoHC","sourcesContent":["import { ContextKey } from '../ContextKey';\n\n/**\n * Core framework context keys — the minimum the WebPieces framework needs to correlate one\n * request across every service it touches, and across every log line each of them writes.\n *\n * ONE id, propagated unchanged. The first service to see a request without an `x-request-id`\n * generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you\n * have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must\n * stitch back together buys nothing a single shared id does not already give you.\n *\n * Lives in core-util (browser-safe) so both the http clients and http-server can reference it.\n *\n * Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by\n * passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.\n *\n * Each key's `name` is the logical/log name; `httpHeader` is the wire name.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * The id that correlates every hop of one request, and every log line of every hop.\n * Generated by the first service to see a request without one; propagated unchanged after that.\n */\n static readonly REQUEST_ID = new ContextKey('requestId', 'x-request-id');\n\n /**\n * WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by\n * `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when\n * the inbound request carried no `x-request-id`). It answers the question the id alone cannot:\n * \"this trace starts here — is that right?\" An id appearing with no source means it came from\n * outside; an id sourced by a service that should never be an entry point is a routing bug.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is the WHOLE POINT. If it\n * travelled, hop 2 would inherit it, hop 3 would inherit it, and \"who started this trace\"\n * would be indistinguishable from \"who passed it along\" — the origin, the one fact this key\n * carries, would be lost. It is absent on every hop that did NOT mint the id, which is exactly\n * the signal: present == I am the origin.\n * - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.\n */\n static readonly REQUEST_ID_SOURCE = new ContextKey(\n 'requestIdSource',\n /*httpHeader*/ undefined\n );\n\n /**\n * The CALLER's build version — so a downstream server's logs record which build of the client\n * called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`\n * (this service's build): `version` answers \"which build wrote this line?\", `clientVersion`\n * answers \"which build asked us to?\".\n *\n * - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT\n * copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`\n * as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server\n * `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.\n * - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log\n * field map; no backend change needed.\n */\n static readonly CLIENT_VERSION = new ContextKey('clientVersion', 'x-webpieces-client-version');\n\n static readonly ORG_ID = new ContextKey('orgId', 'x-org-id');\n\n static readonly USER_ID = new ContextKey('userId', 'x-user-id');\n\n static readonly USER_ROLES = new ContextKey('roles', 'x-webpieces-roles');\n\n /**\n * Turns on test-case recording for this request (Java: x-webpieces-recording).\n * Transferred so recording follows the request across service hops.\n */\n static readonly RECORDING = new ContextKey('recording', 'x-webpieces-recording');\n\n /**\n * The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCall} around every\n * outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted\n * during the call inherits a filterable `api` object, surfacing in GCP as nested\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire. Per-hop only: each server/client hop\n * stamps its own tag, so a downstream server records `side:'server'`, never the caller's `side:'client'`.\n * - `isLogged` TRUE → emitted by the logging backends. It carries an OBJECT value, so the backends\n * read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat\n * `buildLogFields()` string map deliberately skips it (typeof-string guard).\n */\n static readonly API_CALL_INFO = new ContextKey('api', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);\n\n /**\n * The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by\n * `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels\n * through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every\n * log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /\n * `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound\n * method/path, never the caller's. Outbound client calls never set these (no inbound path).\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly HTTP_METHOD = new ContextKey('httpMethod', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);\n\n static readonly REQUEST_PATH = new ContextKey('requestPath', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);\n\n /**\n * NO CREDENTIAL KEYS LIVE HERE.\n *\n * `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them\n * TRANSFERRED keys, so the inbound transfer copied them off the request into the\n * RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the\n * caller's credential onward — to services that had no business seeing it.\n *\n * A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}\n * by the framework AuthFilter, and written straight onto the outbound request by the client\n * that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the\n * magic context, so nothing can propagate it by accident.\n *\n * An app that genuinely wants a credential to travel can still register its own ContextKey for\n * it — but that is now an explicit, visible decision rather than the default.\n */\n\n /**\n * Get all core context keys as an array (the platform DEFAULT_HEADERS set).\n */\n static getAllHeaders(): ContextKey[] {\n return [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.REQUEST_ID_SOURCE,\n WebpiecesCoreHeaders.CLIENT_VERSION,\n WebpiecesCoreHeaders.USER_ID,\n WebpiecesCoreHeaders.ORG_ID,\n WebpiecesCoreHeaders.USER_ROLES,\n WebpiecesCoreHeaders.RECORDING,\n WebpiecesCoreHeaders.API_CALL_INFO,\n WebpiecesCoreHeaders.HTTP_METHOD,\n WebpiecesCoreHeaders.REQUEST_PATH,\n ];\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -31,7 +31,6 @@ export type { ErrorTranslation } from './http/ErrorTranslation';
|
|
|
31
31
|
export { templateDeriver } from './http/templateDeriver';
|
|
32
32
|
export { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';
|
|
33
33
|
export { ContextReader } from './http/ContextReader';
|
|
34
|
-
export type { ContextRead, StructuredContextRead } from './http/ContextReader';
|
|
35
34
|
export { ContextMgr } from './http/ContextMgr';
|
|
36
35
|
export { LogApiCall, LogApiCallImpl } from './http/LogApiCall';
|
|
37
36
|
export { ApiCallInfo } from './http/ApiCallInfo';
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;AAEH,+CAA2C;AAAlC,qGAAA,OAAO,OAAA;AAChB,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,+EAA+E;AAC/E,kFAAkF;AAClF,yCAAyC;AACzC,mDAA0F;AAAjF,gHAAA,cAAc,OAAA;AAAE,kHAAA,gBAAgB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAO/D,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,uEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAC7B,mDAAkD;AAAzC,wGAAA,UAAU,OAAA;AACnB,mDAAyH;AAAhH,wGAAA,UAAU,OAAA;AAAE,4GAAA,cAAc,OAAA;AAAE,0GAAA,YAAY,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAAE,kHAAA,oBAAoB,OAAA;AAE1F,8DAA8D;AAC9D,sEAAsE;AACtE,sEAAsE;AACtE,uEAAuE;AACvE,kEAAkE;AAElE,4BAA4B;AAC5B,gDA+B2B;AA9BvB,qGAAA,OAAO,OAAA;AACP,sGAAA,QAAQ,OAAA;AACR,4GAAA,cAAc,OAAA;AACd,kHAAA,oBAAoB,OAAA;AACpB,mEAAmE;AACnE,oGAAA,MAAM,OAAA;AACN,qGAAA,OAAO,OAAA;AACP,kGAAA,IAAI,OAAA;AACJ,sGAAA,QAAQ,OAAA;AACR,8GAAA,gBAAgB,OAAA;AAChB,sDAAsD;AACtD,iGAAA,GAAG,OAAA;AACH,oGAAA,MAAM,OAAA;AACN,mGAAA,KAAK,OAAA;AACL,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,gHAAA,kBAAkB,OAAA;AAClB,wGAAA,UAAU,OAAA;AACV,uGAAA,SAAS,OAAA;AACT,yGAAA,WAAW,OAAA;AACX,yGAAA,WAAW,OAAA;AACX,4HAAA,8BAA8B,OAAA;AAC9B,wGAAA,UAAU,OAAA;AACV,2GAAA,aAAa,OAAA;AACb,qHAAA,uBAAuB,OAAA;AACvB,0GAAA,YAAY,OAAA;AACZ,6HAAA,+BAA+B,OAAA;AAC/B,sGAAA,QAAQ,OAAA;AACR,2GAAA,aAAa,OAAA;AACb,2GAAA,aAAa,OAAA;AAGjB,4FAA4F;AAC5F,0CAAkD;AAAzC,kGAAA,OAAO,OAAA;AAAE,kGAAA,OAAO,OAAA;AAKzB,cAAc;AACd,wCAuBuB;AAtBnB,uGAAA,aAAa,OAAA;AACb,mGAAA,SAAS,OAAA;AACT,2GAAA,iBAAiB,OAAA;AACjB,+GAAA,qBAAqB,OAAA;AACrB,6GAAA,mBAAmB,OAAA;AACnB,+GAAA,qBAAqB,OAAA;AACrB,4GAAA,kBAAkB,OAAA;AAClB,0GAAA,gBAAgB,OAAA;AAChB,6GAAA,mBAAmB,OAAA;AACnB,iHAAA,uBAAuB,OAAA;AACvB,iHAAA,uBAAuB,OAAA;AACvB,yGAAA,eAAe,OAAA;AACf,uGAAA,aAAa,OAAA;AACb,0BAA0B;AAC1B,0GAAA,gBAAgB,OAAA;AAChB,0GAAA,gBAAgB,OAAA;AAChB,qGAAA,WAAW,OAAA;AACX,sGAAA,YAAY,OAAA;AACZ,6GAAA,mBAAmB,OAAA;AACnB,sGAAA,YAAY,OAAA;AACZ,uGAAA,aAAa,OAAA;AACb,qGAAA,WAAW,OAAA;AAGf,iEAAiE;AACjE,4CASyB;AAJrB,uGAAA,WAAW,OAAA;AACX,oGAAA,QAAQ,OAAA;AACR,oGAAA,QAAQ,OAAA;AACR,wGAAA,YAAY,OAAA;AAGhB,mEAAmE;AACnE,wDAAuD;AAA9C,gHAAA,cAAc,OAAA;AACvB,wDAAuD;AAA9C,gHAAA,cAAc,OAAA;AAGvB,iFAAiF;AACjF,8EAA8E;AAC9E,kDAAiD;AAAxC,0GAAA,WAAW,OAAA;AACpB,0FAA0F;AAC1F,4FAA4F;AAC5F,4DAAwD;AAA/C,iHAAA,aAAa,OAAA;AAEtB,0DAAyD;AAAhD,kHAAA,eAAe,OAAA;AACxB,oEAAmE;AAA1D,4HAAA,oBAAoB,OAAA;AAG7B,iGAAiG;AACjG,gGAAgG;AAChG,kEAAkE;AAClE,gDAA+C;AAAtC,wGAAA,UAAU,OAAA;AAEnB,sGAAsG;AACtG,gDAA+D;AAAtD,wGAAA,UAAU,OAAA;AAAE,4GAAA,cAAc,OAAA;AAEnC,yFAAyF;AACzF,kGAAkG;AAClG,kDAAiD;AAAxC,0GAAA,WAAW,OAAA;AAEpB,sDAAqD;AAA5C,8GAAA,aAAa,OAAA;AAEtB,wDAA6D;AAApD,sHAAA,oBAAoB,OAAA;AAG7B,iFAAiF;AACjF,qEAAkF;AAAvD,gHAAA,YAAY,OAAA;AACvC,qEAAqG;AAA5F,oHAAA,gBAAgB,OAAA;AAAE,iHAAA,aAAa,OAAA;AAAE,oHAAA,gBAAgB,OAAA;AAC1D,2DAAgF;AAAvE,0GAAA,WAAW,OAAA;AAAE,mHAAA,oBAAoB,OAAA;AAC1C,qEAAoG;AAA3F,oHAAA,gBAAgB,OAAA;AAAE,iHAAA,aAAa,OAAA;AAAE,mHAAA,eAAe,OAAA","sourcesContent":["/**\n * @webpieces/core-util\n *\n * Utility functions for WebPieces applications.\n * This package works in both browser and Node.js environments.\n *\n * @packageDocumentation\n */\n\nexport { toError } from './lib/errorUtils';\nexport { ContextKey } from './ContextKey';\nexport { ContextTuple } from './ContextTuple';\n\n// @DocumentDesign — DI-design-root marker. Applies to ANY project kind (server\n// controllers AND library impl classes), so it lives here (browser + Node) rather\n// than in a server-only routing package.\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './DocumentDesign';\n\n// Logging (merged from former @webpieces/wp-logging).\n// Pluggable logging interface + a browser-safe console default; apps plug in\n// bunyan/winston/pino/etc. via LogManager.setFactory(...). Browser + Node.\nexport type { Logger, LogLevel } from './logging/Logger';\nexport type { LoggerFactory } from './logging/LoggerFactory';\nexport { ConsoleLogger } from './logging/ConsoleLogger';\nexport { ConsoleLoggerFactory } from './logging/ConsoleLoggerFactory';\nexport { LogManager } from './logging/LogManager';\nexport { LogChunker, LogChunkerImpl, LogChunkInfo, MAX_GCP_LOG_BYTES, GCP_LOG_BUDGET_BYTES } from './logging/LogChunker';\n\n// HTTP API contract (merged from former @webpieces/http-api).\n// Shared HTTP API definition consumed by both client and server: REST\n// decorators, the HttpError hierarchy, datetime DTOs, platform-header\n// registry/readers, ValidateImplementation, and the test-case recorder\n// contract. Pure definitions — express-free, browser + Node safe.\n\n// API definition decorators\nexport {\n ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n // Auth mode decorators (clean service-to-service + user JWT model)\n Public,\n AuthJwt,\n Auth,\n AuthOidc,\n AuthSharedSecret,\n // API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n getEndpointOptions,\n isFormPost,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n validateNoConflictingDecorators,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n} from './http/decorators';\nexport type { AuthMode, ApiKind, JwtRequirement, EndpointOptions } from './http/decorators';\n// Client-side shared-secret store (the value THIS service sends per @AuthSharedSecret key).\nexport { Secrets, SECRETS } from './http/Secrets';\n\n// Type validators\nexport { ValidateImplementation } from './http/validators';\n\n// HTTP errors\nexport {\n ProtocolError,\n HttpError,\n HttpNotFoundError,\n EndpointNotFoundError,\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpTimeoutError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n HttpInternalServerError,\n HttpVendorError,\n HttpUserError,\n // Error subtype constants\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './http/errors';\n\n// Date/Time DTOs and Utilities (inspired by Java Time / JSR-310)\nexport {\n InstantDto,\n DateDto,\n TimeDto,\n DateTimeDto,\n InstantUtil,\n DateUtil,\n TimeUtil,\n DateTimeUtil,\n} from './http/datetime';\n\n// Context keys + registry (the global magic-context header system)\nexport { HeaderRegistry } from './http/HeaderRegistry';\nexport { ClientRegistry } from './http/ClientRegistry';\nexport type { ServiceUrlDeriver } from './http/ClientRegistry';\n\n// \"What service am I\" — set once at startup, read by the logging backends and by\n// RequestContextHeaders (to stamp requestIdSource on ids this service mints).\nexport { ServiceInfo } from './http/ServiceInfo';\n// Pluggable, bidirectional error translation (app exception <-> wire form). Registered on\n// ClientRegistry at startup; consulted before the built-in webpieces mapping on BOTH sides.\nexport { ErrorWireForm } from './http/ErrorTranslation';\nexport type { ErrorTranslation } from './http/ErrorTranslation';\nexport { templateDeriver } from './http/templateDeriver';\nexport { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';\nexport { ContextReader } from './http/ContextReader';\n\n// BROWSER-ONLY outbound-header propagation (app-held store + registry -> outbound HTTP headers).\n// Only @webpieces/http-client-browser may name it; the server reads RequestContext directly via\n// RequestContextHeaders in the Node-only @webpieces/core-context.\nexport { ContextMgr } from './http/ContextMgr';\n\n// API-call logging helper (uses LogManager above). Singleton: use the LogApiCall constant, not `new`.\nexport { LogApiCall, LogApiCallImpl } from './http/LogApiCall';\n\n// The structured `api` tag + the context-writer seam LogApiCall stamps through. The Node\n// RequestContext-backed impl is installed by @webpieces/core-context; the browser gets the no-op.\nexport { ApiCallInfo } from './http/ApiCallInfo';\nexport type { ApiType, ApiResult } from './http/ApiCallInfo';\nexport { ApiMethodInfo } from './http/ApiMethodInfo';\nexport type { ApiSide } from './http/ApiMethodInfo';\nexport { ApiCallContextHolder } from './http/ApiCallContext';\nexport type { ApiCallContext } from './http/ApiCallContext';\n\n// Test-case recording contract (impl lives in http-server; hooks in http-client)\nexport { TestCaseRecorder, RecorderKeys } from './http/recorder/TestCaseRecorder';\nexport { RecordedEndpoint, RecordedError, RecordedTestCase } from './http/recorder/RecordedEndpoint';\nexport { DoNotRecord, getDoNotRecordFields } from './http/recorder/DoNotRecord';\nexport { RecordSerializer, SerializedMap, SerializedError } from './http/recorder/RecordSerializer';\n"]}
|