@webpieces/core-context 0.4.446 → 0.4.447
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 +2 -2
- package/src/RequestContext.d.ts +13 -11
- package/src/RequestContext.js +24 -17
- package/src/RequestContext.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/core-context",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.447",
|
|
4
4
|
"description": "AsyncLocalStorage-based context management for request-scoped data",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@webpieces/core-util": "0.4.
|
|
25
|
+
"@webpieces/core-util": "0.4.447",
|
|
26
26
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
27
27
|
"inversify": "7.10.4",
|
|
28
28
|
"reflect-metadata": "0.2.2"
|
package/src/RequestContext.d.ts
CHANGED
|
@@ -71,18 +71,20 @@ declare class RequestContextImpl {
|
|
|
71
71
|
* an {@link ApiCallInfo}) survives as an object and the winston/bunyan backends nest it into
|
|
72
72
|
* `jsonPayload.api`. Reads values UNTYPED (not `<string>`) so the object comes through intact.
|
|
73
73
|
*
|
|
74
|
-
* Outside a `run(...)` block it returns just the `version`
|
|
75
|
-
* log line is never worth crashing over, and startup/background lines must still say
|
|
76
|
-
* emitted them.
|
|
74
|
+
* Outside a `run(...)` block it returns just the `svcName` + `version` entries below (not a fully
|
|
75
|
+
* empty map): a log line is never worth crashing over, and startup/background lines must still say
|
|
76
|
+
* which service and build emitted them.
|
|
77
77
|
*
|
|
78
|
-
* PLUS this build's `version` from {@link ServiceInfo}.
|
|
79
|
-
* process-global identity
|
|
80
|
-
* BOTH node backends (winston/bunyan read this one map) says which
|
|
81
|
-
* startup, and background jobs alike — with no
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
78
|
+
* PLUS this service's `svcName` and this build's `version` from {@link ServiceInfo}. Neither is a
|
|
79
|
+
* {@link ContextKey} — they are process-global identity facts, added HERE (BEFORE the active-context
|
|
80
|
+
* check) so EVERY log line of BOTH node backends (winston/bunyan read this one map) says which
|
|
81
|
+
* service and build emitted it — request path, startup, and background jobs alike — with no
|
|
82
|
+
* per-backend duplication. This is the SINGLE place both are stamped, keeping the two backends
|
|
83
|
+
* symmetrical (jsonPayload.svcName + jsonPayload.version). Read via the non-throwing
|
|
84
|
+
* {@link ServiceInfo.getName} / {@link ServiceInfo.getVersion}, so each is simply ABSENT until
|
|
85
|
+
* `setInfo` has run — logging keeps working before the service is identified, then the fields start
|
|
86
|
+
* appearing. Caller-set `svcName`/`version` headers (there are none by convention) would be
|
|
87
|
+
* overwritten here; that is intentional — the ServiceInfo identity is authoritative.
|
|
86
88
|
*/
|
|
87
89
|
buildStructuredLogFields(): Map<string, string | object>;
|
|
88
90
|
/**
|
package/src/RequestContext.js
CHANGED
|
@@ -116,27 +116,34 @@ class RequestContextImpl {
|
|
|
116
116
|
* an {@link ApiCallInfo}) survives as an object and the winston/bunyan backends nest it into
|
|
117
117
|
* `jsonPayload.api`. Reads values UNTYPED (not `<string>`) so the object comes through intact.
|
|
118
118
|
*
|
|
119
|
-
* Outside a `run(...)` block it returns just the `version`
|
|
120
|
-
* log line is never worth crashing over, and startup/background lines must still say
|
|
121
|
-
* emitted them.
|
|
119
|
+
* Outside a `run(...)` block it returns just the `svcName` + `version` entries below (not a fully
|
|
120
|
+
* empty map): a log line is never worth crashing over, and startup/background lines must still say
|
|
121
|
+
* which service and build emitted them.
|
|
122
122
|
*
|
|
123
|
-
* PLUS this build's `version` from {@link ServiceInfo}.
|
|
124
|
-
* process-global identity
|
|
125
|
-
* BOTH node backends (winston/bunyan read this one map) says which
|
|
126
|
-
* startup, and background jobs alike — with no
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
123
|
+
* PLUS this service's `svcName` and this build's `version` from {@link ServiceInfo}. Neither is a
|
|
124
|
+
* {@link ContextKey} — they are process-global identity facts, added HERE (BEFORE the active-context
|
|
125
|
+
* check) so EVERY log line of BOTH node backends (winston/bunyan read this one map) says which
|
|
126
|
+
* service and build emitted it — request path, startup, and background jobs alike — with no
|
|
127
|
+
* per-backend duplication. This is the SINGLE place both are stamped, keeping the two backends
|
|
128
|
+
* symmetrical (jsonPayload.svcName + jsonPayload.version). Read via the non-throwing
|
|
129
|
+
* {@link ServiceInfo.getName} / {@link ServiceInfo.getVersion}, so each is simply ABSENT until
|
|
130
|
+
* `setInfo` has run — logging keeps working before the service is identified, then the fields start
|
|
131
|
+
* appearing. Caller-set `svcName`/`version` headers (there are none by convention) would be
|
|
132
|
+
* overwritten here; that is intentional — the ServiceInfo identity is authoritative.
|
|
131
133
|
*/
|
|
132
134
|
buildStructuredLogFields() {
|
|
133
135
|
const fields = new Map();
|
|
134
|
-
// This build's `version` from ServiceInfo — NOT
|
|
135
|
-
// Added FIRST, BEFORE the active-context check, so
|
|
136
|
-
// (they read this one map) — including startup and background-job lines
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
136
|
+
// This service's `svcName` + this build's `version` from ServiceInfo — NOT ContextKeys, they are
|
|
137
|
+
// process-global identity facts. Added FIRST, BEFORE the active-context check, so they ride EVERY
|
|
138
|
+
// line of both node backends (they read this one map) — including startup and background-job lines
|
|
139
|
+
// emitted with NO active RequestContext. Treated identically and read per-record via the
|
|
140
|
+
// non-throwing getters, so each is simply ABSENT until setInfo has run, then starts appearing —
|
|
141
|
+
// even if setInfo runs after a backend was constructed. This is the ONE place both facts are
|
|
142
|
+
// stamped, so winston and bunyan stay symmetrical (jsonPayload.svcName + jsonPayload.version).
|
|
143
|
+
const svcName = core_util_1.ServiceInfo.getName();
|
|
144
|
+
if (svcName) {
|
|
145
|
+
fields.set('svcName', svcName);
|
|
146
|
+
}
|
|
140
147
|
const version = core_util_1.ServiceInfo.getVersion();
|
|
141
148
|
if (version) {
|
|
142
149
|
fields.set('version', version);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestContext.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContext.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\nimport { ContextKey, AnyContextKey, HeaderRegistry, ServiceInfo } from '@webpieces/core-util';\nimport { HttpRequest } from './HttpRequest';\n\n/** Reserved context key under which the current HttpRequest is stored. */\nconst HTTP_REQUEST_KEY = '__webpieces_http_request__';\n\n/**\n * Context management using AsyncLocalStorage.\n * Similar to Java WebPieces Context class that uses ThreadLocal.\n *\n * This allows storing request-scoped data that is automatically available\n * throughout the async call chain, similar to MDC (Mapped Diagnostic Context).\n *\n * Example usage:\n * ```typescript\n * Context.put('REQUEST_ID', '12345');\n * await someAsyncOperation();\n * const id = Context.get('REQUEST_ID'); // Still available!\n * ```\n */\nclass RequestContextImpl {\n private storage: AsyncLocalStorage<Map<string, any>>;\n\n constructor() {\n this.storage = new AsyncLocalStorage<Map<string, any>>();\n }\n\n /**\n * Open THE request scope. A transport calls this once, at the beginning of a request.\n *\n * Nesting is a bug, not a feature, so it throws. AsyncLocalStorage would happily let a second\n * `run()` install a fresh empty Map that SHADOWS the outer one: every value the outer scope\n * holds becomes invisible, `fillFromRequest` mints a second request id, and the two halves of a\n * request end up in different traces. Nothing would tell you.\n *\n * With this guard the setup is right or it is loud. It mirrors\n * `RequestContextHeaders.fillFromRequest()`, which throws when there is NO active scope.\n *\n * @throws Error when a RequestContext is already active.\n */\n run<T>(fn: () => T): T {\n if (this.isActive()) {\n throw new Error(\n 'RequestContext.run(...) called inside an active RequestContext. Nesting installs a ' +\n 'fresh empty context that shadows the outer one: its values go invisible and a second ' +\n 'request id is minted. Exactly ONE scope per request — the transport opens it.',\n );\n }\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n const store = new Map<string, any>();\n return this.storage.run(store, fn);\n }\n\n /**\n * Run a function with a specific context.\n */\n runWithContext<T>(context: Map<string, any>, fn: () => T): T {\n return this.storage.run(context, fn);\n }\n\n /**\n * Read the value stored under a {@link ContextKey}. The return type is the key's OWN value type\n * `V` — `string` for wire/log keys, `ApiCallInfo` for the api tag, `TestCaseRecorder` for the\n * recorder — INFERRED from the key, never asserted by the caller. This is the typed public\n * surface over the deliberately type-erased backing Map.\n */\n getHeader<V>(key: ContextKey<V>): V | undefined {\n return this.get<V>(key.name);\n }\n\n /**\n * Store a value under a {@link ContextKey}. `value` is type-checked against the key's value type\n * `V`, so you cannot put a number under a `ContextKey<string>` or a raw object under a typed key.\n */\n putHeader<V>(key: ContextKey<V>, value: V): void {\n this.put(key.name, value);\n }\n\n /** Clear one context key. Used by the api-tag seam's set → log → remove span (see LogApiCall). */\n removeHeader(key: AnyContextKey): void {\n this.remove(key.name);\n }\n\n hasHeader(key: AnyContextKey): boolean {\n return this.has(key.name);\n }\n\n /**\n /**\n * Build the masked field map for LOGGING: every logged key in the global\n * {@link HeaderRegistry} read straight from this context, secured values\n * masked (via {@link ContextKey.maskIfSecured}), keyed by each key's `name`.\n *\n * Callers: RecordingFilter + NodeProxyClient.recordCall, which snapshot the context into a\n * test FIXTURE. The @webpieces/winston and @webpieces/bunyan backends also stamp these fields\n * onto every record, and they own the \"log emitted outside RequestContext.run(...)\" complaint —\n * reporting it HERE would recurse (the error line itself re-enters buildLogFields).\n *\n * Returns an EMPTY map outside a `run(...)` block rather than throwing: a fixture snapshot or a\n * log line is never worth crashing a request over.\n */\n buildLogFields(): Map<string, string> {\n const fields = new Map<string, string>();\n if (!this.isActive()) {\n return fields;\n }\n // The registry owns WHICH keys log (getLoggedKeys); we read each straight from THIS context\n // and each ContextKey masks its own secured value. String-only — this map feeds wire/MDC +\n // recorder fixtures — so an object-valued key (API_CALL_INFO) is guarded out by the\n // typeof-string check; objects ride buildStructuredLogFields instead. (Was a HeaderRegistry\n // method taking a read callback; only the server ever called it, so the seam was dead weight.)\n for (const key of HeaderRegistry.get().getLoggedKeys()) {\n // getLoggedKeys() is AnyContextKey[] (mixed value types), so read by name and\n // narrow with the typeof-string guard rather than asserting a value type per key.\n const value = this.get<string>(key.name);\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 field map for the node logging backends: like {@link buildLogFields}, but values\n * may be OBJECTS, so an object-valued logged key ({@link WebpiecesCoreHeaders.API_CALL_INFO} holding\n * an {@link ApiCallInfo}) survives as an object and the winston/bunyan backends nest it into\n * `jsonPayload.api`. Reads values UNTYPED (not `<string>`) so the object comes through intact.\n *\n * Outside a `run(...)` block it returns just the `version` entry below (not a fully empty map): a\n * log line is never worth crashing over, and startup/background lines must still say which build\n * emitted them.\n *\n * PLUS this build's `version` from {@link ServiceInfo}. It is NOT a {@link ContextKey} — it is a\n * process-global identity fact, added HERE (BEFORE the active-context check) so EVERY log line of\n * BOTH node backends (winston/bunyan read this one map) says which build emitted it — request path,\n * startup, and background jobs alike — with no per-backend duplication. Read via the non-throwing\n * {@link ServiceInfo.getVersion}, so it is simply ABSENT until `setInfo` has run — logging keeps\n * working before the service is identified, then `version` starts appearing. A caller-set `version`\n * header (there is none by convention) would be overwritten here; that is intentional — the build\n * version is authoritative.\n */\n buildStructuredLogFields(): Map<string, string | object> {\n const fields = new Map<string, string | object>();\n // This build's `version` from ServiceInfo — NOT a ContextKey, a process-global identity fact.\n // Added FIRST, BEFORE the active-context check, so it rides EVERY line of both node backends\n // (they read this one map) — including startup and background-job lines emitted with NO active\n // RequestContext. Was appended AFTER the early-return below, so those out-of-context lines\n // silently shipped without `version`. Non-throwing read: simply ABSENT until setInfo has run,\n // so logging works before the service is identified, then `version` starts appearing.\n const version = ServiceInfo.getVersion();\n if (version) {\n fields.set('version', version);\n }\n if (!this.isActive()) {\n return fields;\n }\n // Like buildLogFields, but values may be OBJECTS (API_CALL_INFO): read UNTYPED so the object\n // survives and winston/bunyan nest it into jsonPayload.<name>. Secured STRING values are still\n // masked per key; non-string primitives are ignored rather than String()-flattened. (Inlined\n // from HeaderRegistry for the same reason as buildLogFields — only the server called it.)\n for (const key of HeaderRegistry.get().getLoggedKeys()) {\n const value = this.getHeader(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 }\n return fields;\n }\n\n\n /**\n * Store the transport-neutral {@link HttpRequest} for this request. Called once, above the\n * api boundary, by whichever transport is driving the router (the express adapter, or the\n * in-process client). Filters/auth read it back via {@link getRequest} so they never touch\n * express — the same chain then runs over HTTP and in-process.\n */\n setRequest(request: HttpRequest): void {\n this.put(HTTP_REQUEST_KEY, request);\n }\n\n /** The current {@link HttpRequest}, or undefined if none was set for this context. */\n getRequest(): HttpRequest | undefined {\n return this.get<HttpRequest>(HTTP_REQUEST_KEY);\n }\n\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n getHeaders(keys: AnyContextKey[]): unknown[] {\n return keys.map(key => this.getHeader(key));\n }\n\n /**\n * Store a value in the current context.\n */\n put(key: string, value: any): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error('No context available. Did you call Context.run() first?');\n }\n store.set(key, value);\n }\n\n /**\n * Retrieve a value from the current context.\n */\n get<T = any>(key: string): T | undefined {\n const store = this.storage.getStore();\n return store?.get(key);\n }\n\n /**\n * Remove a value from the current context.\n */\n remove(key: string): void {\n const store = this.storage.getStore();\n store?.delete(key);\n }\n\n /**\n * Clear all values from the current context.\n */\n clear(): void {\n const store = this.storage.getStore();\n store?.clear();\n }\n\n /**\n * Copy the current context to a new Map.\n * Used by XPromise to preserve context across async boundaries.\n */\n copyContext(): Map<string, any> {\n const store = this.storage.getStore();\n if (!store) {\n return new Map();\n }\n return new Map(store);\n }\n\n /**\n * Set the entire context from a Map.\n * Used by XPromise to restore context.\n */\n setContext(context: Map<string, any>): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error('No context available. Did you call Context.run() first?');\n }\n store.clear();\n context.forEach((value, key) => {\n store.set(key, value);\n });\n }\n\n /**\n * Get all context entries.\n */\n getAll(): Map<string, any> {\n const store = this.storage.getStore();\n return store ? new Map(store) : new Map();\n }\n\n /**\n * Check if a key exists in the context.\n */\n has(key: string): boolean {\n const store = this.storage.getStore();\n return store?.has(key) ?? false;\n }\n\n /**\n * Check if RequestContext is currently active.\n * Returns true if we're inside a RequestContext.run() block, false otherwise.\n *\n * Useful for tests to verify context is set up before making API calls.\n */\n isActive(): boolean {\n return this.storage.getStore() !== undefined;\n }\n\n}\n\n\n\n/**\n * Global singleton instance of RequestContext.\n * Use this throughout your application.\n */\nexport const RequestContext = new RequestContextImpl();\n"]}
|
|
1
|
+
{"version":3,"file":"RequestContext.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContext.ts"],"names":[],"mappings":";;;AAAA,6CAAgD;AAChD,oDAA8F;AAG9F,0EAA0E;AAC1E,MAAM,gBAAgB,GAAG,4BAA4B,CAAC;AAEtD;;;;;;;;;;;;;GAaG;AACH,MAAM,kBAAkB;IACZ,OAAO,CAAsC;IAErD;QACI,IAAI,CAAC,OAAO,GAAG,IAAI,+BAAiB,EAAoB,CAAC;IAC7D,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,GAAG,CAAI,EAAW;QACd,IAAI,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACX,qFAAqF;gBACrF,uFAAuF;gBACvF,+EAA+E,CAClF,CAAC;QACN,CAAC;QACD,yGAAyG;QACzG,MAAM,KAAK,GAAG,IAAI,GAAG,EAAe,CAAC;QACrC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,cAAc,CAAI,OAAyB,EAAE,EAAW;QACpD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAI,GAAkB;QAC3B,OAAO,IAAI,CAAC,GAAG,CAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED;;;OAGG;IACH,SAAS,CAAI,GAAkB,EAAE,KAAQ;QACrC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,kGAAkG;IAClG,YAAY,CAAC,GAAkB;QAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,SAAS,CAAC,GAAkB;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,cAAc;QACV,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC;QAClB,CAAC;QACD,4FAA4F;QAC5F,2FAA2F;QAC3F,oFAAoF;QACpF,4FAA4F;QAC5F,+FAA+F;QAC/F,KAAK,MAAM,GAAG,IAAI,0BAAc,CAAC,GAAG,EAAE,CAAC,aAAa,EAAE,EAAE,CAAC;YACrD,8EAA8E;YAC9E,kFAAkF;YAClF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAS,GAAG,CAAC,IAAI,CAAC,CAAC;YACzC,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;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,wBAAwB;QACpB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;QAClD,iGAAiG;QACjG,kGAAkG;QAClG,mGAAmG;QACnG,yFAAyF;QACzF,gGAAgG;QAChG,6FAA6F;QAC7F,+FAA+F;QAC/F,MAAM,OAAO,GAAG,uBAAW,CAAC,OAAO,EAAE,CAAC;QACtC,IAAI,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,OAAO,GAAG,uBAAW,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC;QAClB,CAAC;QACD,6FAA6F;QAC7F,+FAA+F;QAC/F,6FAA6F;QAC7F,0FAA0F;QAC1F,KAAK,MAAM,GAAG,IAAI,0BAAc,CAAC,GAAG,EAAE,CAAC,aAAa,EAAE,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAClC,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;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAGD;;;;;OAKG;IACH,UAAU,CAAC,OAAoB;QAC3B,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,sFAAsF;IACtF,UAAU;QACN,OAAO,IAAI,CAAC,GAAG,CAAc,gBAAgB,CAAC,CAAC;IACnD,CAAC;IAED,yGAAyG;IACzG,UAAU,CAAC,IAAqB;QAC5B,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,GAAW,EAAE,KAAU;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC/E,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,GAAG,CAAU,GAAW;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,GAAW;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,KAAK;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtC,KAAK,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED;;;OAGG;IACH,WAAW;QACP,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,IAAI,GAAG,EAAE,CAAC;QACrB,CAAC;QACD,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,OAAyB;QAChC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC/E,CAAC;QACD,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC3B,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;OAEG;IACH,MAAM;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;IAC9C,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,GAAW;QACX,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC;IACpC,CAAC;IAED;;;;;OAKG;IACH,QAAQ;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,SAAS,CAAC;IACjD,CAAC;CAEJ;AAID;;;GAGG;AACU,QAAA,cAAc,GAAG,IAAI,kBAAkB,EAAE,CAAC","sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\nimport { ContextKey, AnyContextKey, HeaderRegistry, ServiceInfo } from '@webpieces/core-util';\nimport { HttpRequest } from './HttpRequest';\n\n/** Reserved context key under which the current HttpRequest is stored. */\nconst HTTP_REQUEST_KEY = '__webpieces_http_request__';\n\n/**\n * Context management using AsyncLocalStorage.\n * Similar to Java WebPieces Context class that uses ThreadLocal.\n *\n * This allows storing request-scoped data that is automatically available\n * throughout the async call chain, similar to MDC (Mapped Diagnostic Context).\n *\n * Example usage:\n * ```typescript\n * Context.put('REQUEST_ID', '12345');\n * await someAsyncOperation();\n * const id = Context.get('REQUEST_ID'); // Still available!\n * ```\n */\nclass RequestContextImpl {\n private storage: AsyncLocalStorage<Map<string, any>>;\n\n constructor() {\n this.storage = new AsyncLocalStorage<Map<string, any>>();\n }\n\n /**\n * Open THE request scope. A transport calls this once, at the beginning of a request.\n *\n * Nesting is a bug, not a feature, so it throws. AsyncLocalStorage would happily let a second\n * `run()` install a fresh empty Map that SHADOWS the outer one: every value the outer scope\n * holds becomes invisible, `fillFromRequest` mints a second request id, and the two halves of a\n * request end up in different traces. Nothing would tell you.\n *\n * With this guard the setup is right or it is loud. It mirrors\n * `RequestContextHeaders.fillFromRequest()`, which throws when there is NO active scope.\n *\n * @throws Error when a RequestContext is already active.\n */\n run<T>(fn: () => T): T {\n if (this.isActive()) {\n throw new Error(\n 'RequestContext.run(...) called inside an active RequestContext. Nesting installs a ' +\n 'fresh empty context that shadows the outer one: its values go invisible and a second ' +\n 'request id is minted. Exactly ONE scope per request — the transport opens it.',\n );\n }\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n const store = new Map<string, any>();\n return this.storage.run(store, fn);\n }\n\n /**\n * Run a function with a specific context.\n */\n runWithContext<T>(context: Map<string, any>, fn: () => T): T {\n return this.storage.run(context, fn);\n }\n\n /**\n * Read the value stored under a {@link ContextKey}. The return type is the key's OWN value type\n * `V` — `string` for wire/log keys, `ApiCallInfo` for the api tag, `TestCaseRecorder` for the\n * recorder — INFERRED from the key, never asserted by the caller. This is the typed public\n * surface over the deliberately type-erased backing Map.\n */\n getHeader<V>(key: ContextKey<V>): V | undefined {\n return this.get<V>(key.name);\n }\n\n /**\n * Store a value under a {@link ContextKey}. `value` is type-checked against the key's value type\n * `V`, so you cannot put a number under a `ContextKey<string>` or a raw object under a typed key.\n */\n putHeader<V>(key: ContextKey<V>, value: V): void {\n this.put(key.name, value);\n }\n\n /** Clear one context key. Used by the api-tag seam's set → log → remove span (see LogApiCall). */\n removeHeader(key: AnyContextKey): void {\n this.remove(key.name);\n }\n\n hasHeader(key: AnyContextKey): boolean {\n return this.has(key.name);\n }\n\n /**\n /**\n * Build the masked field map for LOGGING: every logged key in the global\n * {@link HeaderRegistry} read straight from this context, secured values\n * masked (via {@link ContextKey.maskIfSecured}), keyed by each key's `name`.\n *\n * Callers: RecordingFilter + NodeProxyClient.recordCall, which snapshot the context into a\n * test FIXTURE. The @webpieces/winston and @webpieces/bunyan backends also stamp these fields\n * onto every record, and they own the \"log emitted outside RequestContext.run(...)\" complaint —\n * reporting it HERE would recurse (the error line itself re-enters buildLogFields).\n *\n * Returns an EMPTY map outside a `run(...)` block rather than throwing: a fixture snapshot or a\n * log line is never worth crashing a request over.\n */\n buildLogFields(): Map<string, string> {\n const fields = new Map<string, string>();\n if (!this.isActive()) {\n return fields;\n }\n // The registry owns WHICH keys log (getLoggedKeys); we read each straight from THIS context\n // and each ContextKey masks its own secured value. String-only — this map feeds wire/MDC +\n // recorder fixtures — so an object-valued key (API_CALL_INFO) is guarded out by the\n // typeof-string check; objects ride buildStructuredLogFields instead. (Was a HeaderRegistry\n // method taking a read callback; only the server ever called it, so the seam was dead weight.)\n for (const key of HeaderRegistry.get().getLoggedKeys()) {\n // getLoggedKeys() is AnyContextKey[] (mixed value types), so read by name and\n // narrow with the typeof-string guard rather than asserting a value type per key.\n const value = this.get<string>(key.name);\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 field map for the node logging backends: like {@link buildLogFields}, but values\n * may be OBJECTS, so an object-valued logged key ({@link WebpiecesCoreHeaders.API_CALL_INFO} holding\n * an {@link ApiCallInfo}) survives as an object and the winston/bunyan backends nest it into\n * `jsonPayload.api`. Reads values UNTYPED (not `<string>`) so the object comes through intact.\n *\n * Outside a `run(...)` block it returns just the `svcName` + `version` entries below (not a fully\n * empty map): a log line is never worth crashing over, and startup/background lines must still say\n * which service and build emitted them.\n *\n * PLUS this service's `svcName` and this build's `version` from {@link ServiceInfo}. Neither is a\n * {@link ContextKey} — they are process-global identity facts, added HERE (BEFORE the active-context\n * check) so EVERY log line of BOTH node backends (winston/bunyan read this one map) says which\n * service and build emitted it — request path, startup, and background jobs alike — with no\n * per-backend duplication. This is the SINGLE place both are stamped, keeping the two backends\n * symmetrical (jsonPayload.svcName + jsonPayload.version). Read via the non-throwing\n * {@link ServiceInfo.getName} / {@link ServiceInfo.getVersion}, so each is simply ABSENT until\n * `setInfo` has run — logging keeps working before the service is identified, then the fields start\n * appearing. Caller-set `svcName`/`version` headers (there are none by convention) would be\n * overwritten here; that is intentional — the ServiceInfo identity is authoritative.\n */\n buildStructuredLogFields(): Map<string, string | object> {\n const fields = new Map<string, string | object>();\n // This service's `svcName` + this build's `version` from ServiceInfo — NOT ContextKeys, they are\n // process-global identity facts. Added FIRST, BEFORE the active-context check, so they ride EVERY\n // line of both node backends (they read this one map) — including startup and background-job lines\n // emitted with NO active RequestContext. Treated identically and read per-record via the\n // non-throwing getters, so each is simply ABSENT until setInfo has run, then starts appearing —\n // even if setInfo runs after a backend was constructed. This is the ONE place both facts are\n // stamped, so winston and bunyan stay symmetrical (jsonPayload.svcName + jsonPayload.version).\n const svcName = ServiceInfo.getName();\n if (svcName) {\n fields.set('svcName', svcName);\n }\n const version = ServiceInfo.getVersion();\n if (version) {\n fields.set('version', version);\n }\n if (!this.isActive()) {\n return fields;\n }\n // Like buildLogFields, but values may be OBJECTS (API_CALL_INFO): read UNTYPED so the object\n // survives and winston/bunyan nest it into jsonPayload.<name>. Secured STRING values are still\n // masked per key; non-string primitives are ignored rather than String()-flattened. (Inlined\n // from HeaderRegistry for the same reason as buildLogFields — only the server called it.)\n for (const key of HeaderRegistry.get().getLoggedKeys()) {\n const value = this.getHeader(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 }\n return fields;\n }\n\n\n /**\n * Store the transport-neutral {@link HttpRequest} for this request. Called once, above the\n * api boundary, by whichever transport is driving the router (the express adapter, or the\n * in-process client). Filters/auth read it back via {@link getRequest} so they never touch\n * express — the same chain then runs over HTTP and in-process.\n */\n setRequest(request: HttpRequest): void {\n this.put(HTTP_REQUEST_KEY, request);\n }\n\n /** The current {@link HttpRequest}, or undefined if none was set for this context. */\n getRequest(): HttpRequest | undefined {\n return this.get<HttpRequest>(HTTP_REQUEST_KEY);\n }\n\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n getHeaders(keys: AnyContextKey[]): unknown[] {\n return keys.map(key => this.getHeader(key));\n }\n\n /**\n * Store a value in the current context.\n */\n put(key: string, value: any): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error('No context available. Did you call Context.run() first?');\n }\n store.set(key, value);\n }\n\n /**\n * Retrieve a value from the current context.\n */\n get<T = any>(key: string): T | undefined {\n const store = this.storage.getStore();\n return store?.get(key);\n }\n\n /**\n * Remove a value from the current context.\n */\n remove(key: string): void {\n const store = this.storage.getStore();\n store?.delete(key);\n }\n\n /**\n * Clear all values from the current context.\n */\n clear(): void {\n const store = this.storage.getStore();\n store?.clear();\n }\n\n /**\n * Copy the current context to a new Map.\n * Used by XPromise to preserve context across async boundaries.\n */\n copyContext(): Map<string, any> {\n const store = this.storage.getStore();\n if (!store) {\n return new Map();\n }\n return new Map(store);\n }\n\n /**\n * Set the entire context from a Map.\n * Used by XPromise to restore context.\n */\n setContext(context: Map<string, any>): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error('No context available. Did you call Context.run() first?');\n }\n store.clear();\n context.forEach((value, key) => {\n store.set(key, value);\n });\n }\n\n /**\n * Get all context entries.\n */\n getAll(): Map<string, any> {\n const store = this.storage.getStore();\n return store ? new Map(store) : new Map();\n }\n\n /**\n * Check if a key exists in the context.\n */\n has(key: string): boolean {\n const store = this.storage.getStore();\n return store?.has(key) ?? false;\n }\n\n /**\n * Check if RequestContext is currently active.\n * Returns true if we're inside a RequestContext.run() block, false otherwise.\n *\n * Useful for tests to verify context is set up before making API calls.\n */\n isActive(): boolean {\n return this.storage.getStore() !== undefined;\n }\n\n}\n\n\n\n/**\n * Global singleton instance of RequestContext.\n * Use this throughout your application.\n */\nexport const RequestContext = new RequestContextImpl();\n"]}
|