@webpieces/core-context 0.4.439 → 0.4.440
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/HttpRequest.d.ts +3 -3
- package/src/HttpRequest.js.map +1 -1
- package/src/RequestContext.d.ts +4 -4
- package/src/RequestContext.js +1 -1
- package/src/RequestContext.js.map +1 -1
- package/src/RequestContextApiCallContext.d.ts +3 -3
- package/src/RequestContextApiCallContext.js.map +1 -1
- package/src/RequestContextHeaders.js +1 -1
- package/src/RequestContextHeaders.js.map +1 -1
- package/src/RequestContextReader.d.ts +3 -3
- package/src/RequestContextReader.js +1 -1
- package/src/RequestContextReader.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.440",
|
|
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.440",
|
|
26
26
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
27
27
|
"inversify": "7.10.4",
|
|
28
28
|
"reflect-metadata": "0.2.2"
|
package/src/HttpRequest.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyContextKey } from '@webpieces/core-util';
|
|
2
2
|
/**
|
|
3
3
|
* HttpRequest - webpieces' TRANSPORT-NEUTRAL inbound request.
|
|
4
4
|
*
|
|
@@ -21,7 +21,7 @@ export declare class HttpRequest {
|
|
|
21
21
|
/** Header name (lowercased) -> values (HTTP allows multiple values per name). */
|
|
22
22
|
headers: Map<string, string[]>);
|
|
23
23
|
/** First value of a header, looked up by ContextKey.httpHeader (or a raw lowercased name). */
|
|
24
|
-
getHeader(key:
|
|
24
|
+
getHeader(key: AnyContextKey | string): string | undefined;
|
|
25
25
|
/** All values of a header. */
|
|
26
|
-
getHeaderValues(key:
|
|
26
|
+
getHeaderValues(key: AnyContextKey | string): string[] | undefined;
|
|
27
27
|
}
|
package/src/HttpRequest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HttpRequest.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/HttpRequest.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACH,MAAa,WAAW;IAEA;IACA;IAEA;IAJpB,YACoB,MAAc,EACd,IAAY;IAC5B,iFAAiF;IACjE,OAA8B;QAH9B,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAEZ,YAAO,GAAP,OAAO,CAAuB;IAC/C,CAAC;IAEJ,8FAA8F;IAC9F,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"HttpRequest.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/HttpRequest.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACH,MAAa,WAAW;IAEA;IACA;IAEA;IAJpB,YACoB,MAAc,EACd,IAAY;IAC5B,iFAAiF;IACjE,OAA8B;QAH9B,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAEZ,YAAO,GAAP,OAAO,CAAuB;IAC/C,CAAC;IAEJ,8FAA8F;IAC9F,SAAS,CAAC,GAA2B;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QACzC,OAAO,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,CAAC;IAED,8BAA8B;IAC9B,eAAe,CAAC,GAA2B;QACvC,MAAM,IAAI,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACxF,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;CACJ;AAnBD,kCAmBC","sourcesContent":["import { ContextKey, AnyContextKey } from '@webpieces/core-util';\n\n/**\n * HttpRequest - webpieces' TRANSPORT-NEUTRAL inbound request.\n *\n * This is @webpieces/http-routing's own request type (http-routing re-exports it), NOT\n * express's `req`. Each transport adapter (the express adapter in @webpieces/http-server, or\n * any other TypeScript web framework) builds an HttpRequest from its native request and hands\n * it to the router. Filters and the auth layer read it via `RequestContext.getRequest()`\n * instead of touching express — which is what lets the SAME filter chain run over HTTP and\n * in-process (tests build an HttpRequest carrying their credential).\n *\n * It lives in core-context (alongside RequestContext, which stores it in AsyncLocalStorage)\n * to avoid a core-context → http-routing cycle; it is a pure data holder (no express, no DI).\n */\nexport class HttpRequest {\n constructor(\n public readonly method: string,\n public readonly path: string,\n /** Header name (lowercased) -> values (HTTP allows multiple values per name). */\n public readonly headers: Map<string, string[]>,\n ) {}\n\n /** First value of a header, looked up by ContextKey.httpHeader (or a raw lowercased name). */\n getHeader(key: AnyContextKey | string): string | undefined {\n const values = this.getHeaderValues(key);\n return values && values.length > 0 ? values[0] : undefined;\n }\n\n /** All values of a header. */\n getHeaderValues(key: AnyContextKey | string): string[] | undefined {\n const name = (typeof key === 'string' ? key : key.httpHeader ?? key.name).toLowerCase();\n return this.headers.get(name);\n }\n}\n"]}
|
package/src/RequestContext.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContextKey } from '@webpieces/core-util';
|
|
1
|
+
import { ContextKey, AnyContextKey } from '@webpieces/core-util';
|
|
2
2
|
import { HttpRequest } from './HttpRequest';
|
|
3
3
|
/**
|
|
4
4
|
* Context management using AsyncLocalStorage.
|
|
@@ -48,8 +48,8 @@ declare class RequestContextImpl {
|
|
|
48
48
|
*/
|
|
49
49
|
putHeader<V>(key: ContextKey<V>, value: V): void;
|
|
50
50
|
/** Clear one context key. Used by the api-tag seam's set → log → remove span (see LogApiCall). */
|
|
51
|
-
removeHeader(key:
|
|
52
|
-
hasHeader(key:
|
|
51
|
+
removeHeader(key: AnyContextKey): void;
|
|
52
|
+
hasHeader(key: AnyContextKey): boolean;
|
|
53
53
|
/**
|
|
54
54
|
/**
|
|
55
55
|
* Build the masked field map for LOGGING: every logged key in the global
|
|
@@ -92,7 +92,7 @@ declare class RequestContextImpl {
|
|
|
92
92
|
setRequest(request: HttpRequest): void;
|
|
93
93
|
/** The current {@link HttpRequest}, or undefined if none was set for this context. */
|
|
94
94
|
getRequest(): HttpRequest | undefined;
|
|
95
|
-
getHeaders(keys:
|
|
95
|
+
getHeaders(keys: AnyContextKey[]): unknown[];
|
|
96
96
|
/**
|
|
97
97
|
* Store a value in the current context.
|
|
98
98
|
*/
|
package/src/RequestContext.js
CHANGED
|
@@ -101,7 +101,7 @@ class RequestContextImpl {
|
|
|
101
101
|
// typeof-string check; objects ride buildStructuredLogFields instead. (Was a HeaderRegistry
|
|
102
102
|
// method taking a read callback; only the server ever called it, so the seam was dead weight.)
|
|
103
103
|
for (const key of core_util_1.HeaderRegistry.get().getLoggedKeys()) {
|
|
104
|
-
// getLoggedKeys() is
|
|
104
|
+
// getLoggedKeys() is AnyContextKey[] (mixed value types), so read by name and
|
|
105
105
|
// narrow with the typeof-string guard rather than asserting a value type per key.
|
|
106
106
|
const value = this.get(key.name);
|
|
107
107
|
if (typeof value === 'string' && value) {
|
|
@@ -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, 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: ContextKey): void {\n this.remove(key.name);\n }\n\n hasHeader(key: ContextKey): 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 ContextKey<unknown>[] (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 * Returns an EMPTY map outside a `run(...)` block (a log line is never worth crashing over) — same\n * as {@link buildLogFields}.\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 so every request log line of BOTH node backends\n * (winston/bunyan read this one map) says which build emitted it, with no per-backend duplication.\n * Read via the non-throwing {@link ServiceInfo.getVersion}, so it is simply ABSENT until\n * `setInfo` has run — logging keeps working before the service is identified, then `version`\n * starts appearing. A caller-set `version` header (there is none by convention) would be\n * overwritten here; that is intentional — the build version is authoritative.\n */\n buildStructuredLogFields(): Map<string, string | object> {\n const fields = new Map<string, string | object>();\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 // PLUS this build's `version` from ServiceInfo — NOT a ContextKey, a process-global identity\n // fact, added HERE so every request log line of BOTH node backends (they read this one map)\n // says which build emitted it, with no per-backend duplication. Non-throwing read: simply\n // ABSENT until setInfo has run, so logging works before the service is identified, then\n // `version` starts appearing.\n const version = ServiceInfo.getVersion();\n if (version) {\n fields.set('version', version);\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: ContextKey[]): 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":"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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 * Returns an EMPTY map outside a `run(...)` block (a log line is never worth crashing over) — same\n * as {@link buildLogFields}.\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 so every request log line of BOTH node backends\n * (winston/bunyan read this one map) says which build emitted it, with no per-backend duplication.\n * Read via the non-throwing {@link ServiceInfo.getVersion}, so it is simply ABSENT until\n * `setInfo` has run — logging keeps working before the service is identified, then `version`\n * starts appearing. A caller-set `version` header (there is none by convention) would be\n * overwritten here; that is intentional — the build version is authoritative.\n */\n buildStructuredLogFields(): Map<string, string | object> {\n const fields = new Map<string, string | object>();\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 // PLUS this build's `version` from ServiceInfo — NOT a ContextKey, a process-global identity\n // fact, added HERE so every request log line of BOTH node backends (they read this one map)\n // says which build emitted it, with no per-backend duplication. Non-throwing read: simply\n // ABSENT until setInfo has run, so logging works before the service is identified, then\n // `version` starts appearing.\n const version = ServiceInfo.getVersion();\n if (version) {\n fields.set('version', version);\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,4 +1,4 @@
|
|
|
1
|
-
import { ApiCallContext,
|
|
1
|
+
import { ApiCallContext, AnyContextKey } from '@webpieces/core-util';
|
|
2
2
|
/**
|
|
3
3
|
* RequestContextApiCallContext - the SERVER (Node) implementation of the {@link ApiCallContext} seam,
|
|
4
4
|
* backing it with the ambient {@link RequestContext} (AsyncLocalStorage). {@link LogApiCall} — which
|
|
@@ -16,6 +16,6 @@ import { ApiCallContext, ContextKey } from '@webpieces/core-util';
|
|
|
16
16
|
export declare class RequestContextApiCallContext implements ApiCallContext {
|
|
17
17
|
/** A live request scope is required to stamp; LogApiCall checks this and throws when false. */
|
|
18
18
|
isActive(): boolean;
|
|
19
|
-
set(contextKey:
|
|
20
|
-
remove(contextKey:
|
|
19
|
+
set(contextKey: AnyContextKey, value: unknown): void;
|
|
20
|
+
remove(contextKey: AnyContextKey): void;
|
|
21
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestContextApiCallContext.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContextApiCallContext.ts"],"names":[],"mappings":";;;AACA,qDAAkD;AAElD;;;;;;;;;;;;;GAaG;AACH,MAAa,4BAA4B;IACrC,+FAA+F;IAC/F,QAAQ;QACJ,OAAO,+BAAc,CAAC,QAAQ,EAAE,CAAC;IACrC,CAAC;IAED,gHAAgH;IAChH,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"RequestContextApiCallContext.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContextApiCallContext.ts"],"names":[],"mappings":";;;AACA,qDAAkD;AAElD;;;;;;;;;;;;;GAaG;AACH,MAAa,4BAA4B;IACrC,+FAA+F;IAC/F,QAAQ;QACJ,OAAO,+BAAc,CAAC,QAAQ,EAAE,CAAC;IACrC,CAAC;IAED,gHAAgH;IAChH,GAAG,CAAC,UAAyB,EAAE,KAAc;QACzC,+BAAc,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,CAAC,UAAyB;QAC5B,+BAAc,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAC5C,CAAC;CACJ;AAdD,oEAcC","sourcesContent":["import { ApiCallContext, AnyContextKey } from '@webpieces/core-util';\nimport { RequestContext } from './RequestContext';\n\n/**\n * RequestContextApiCallContext - the SERVER (Node) implementation of the {@link ApiCallContext} seam,\n * backing it with the ambient {@link RequestContext} (AsyncLocalStorage). {@link LogApiCall} — which\n * lives in browser-safe core-util and cannot import RequestContext — stamps its `api` tag through this.\n *\n * Installed ONCE at server startup by `setupRuntime` (http-routing), beside `HeaderRegistry.configure`\n * and `LogManager.setFactory` — the one place that runs on every server, so BOTH inbound (LogApiFilter)\n * and outbound (clients) get the tag. A browser never runs setupRuntime, so it installs its own\n * module-global impl. This is the same \"impl in the env-specific package, bound to a core-util seam at\n * startup\" pattern as LogManager.setFactory + the winston/bunyan backends.\n *\n * WRITE-ONLY: the logging backends read the stamped key back off RequestContext\n * (`buildStructuredLogFields`) on every record, so this seam only needs to set it.\n */\nexport class RequestContextApiCallContext implements ApiCallContext {\n /** A live request scope is required to stamp; LogApiCall checks this and throws when false. */\n isActive(): boolean {\n return RequestContext.isActive();\n }\n\n // webpieces-disable no-any-unknown -- a context value is heterogeneous (the api struct here; strings elsewhere)\n set(contextKey: AnyContextKey, value: unknown): void {\n RequestContext.putHeader(contextKey, value);\n }\n\n remove(contextKey: AnyContextKey): void {\n RequestContext.removeHeader(contextKey);\n }\n}\n"]}
|
|
@@ -39,7 +39,7 @@ let RequestContextHeaders = class RequestContextHeaders {
|
|
|
39
39
|
const headers = new Map();
|
|
40
40
|
// getTransferredKeys() is precomputed at configure() time.
|
|
41
41
|
for (const key of core_util_1.HeaderRegistry.get().getTransferredKeys()) {
|
|
42
|
-
// getTransferredKeys() is
|
|
42
|
+
// getTransferredKeys() is AnyContextKey[]; every transferred value is a wire
|
|
43
43
|
// string, so read by name rather than asserting a value type on a generically-typed key.
|
|
44
44
|
const value = RequestContext_1.RequestContext.get(key.name);
|
|
45
45
|
if (value !== undefined && value !== null && value !== '') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestContextHeaders.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContextHeaders.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n HeaderRegistry,\n RecorderKeys,\n ServiceInfo,\n TestCaseRecorder,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { provideFrameworkSingleton } from './frameworkProvide';\nimport { HttpRequest } from './HttpRequest';\nimport { RequestContext } from './RequestContext';\n\n/**\n * RequestContextHeaders - the magic context ↔ the wire, for a SERVER. Both directions live here:\n *\n * inbound {@link fillFromRequest} the published HttpRequest's headers -> the context\n * outbound {@link buildOutboundHeaders} the context -> the next hop's headers\n *\n * Reads the AsyncLocalStorage-backed {@link RequestContext} straight through — no ContextReader,\n * no ContextMgr, no abstract base. A server has exactly one place its context lives, and the\n * indirection only hid the failure below. (The browser's answer is `ContextMgr` in\n * @webpieces/core-util, which reads an app-held store because a browser has no ambient scope.)\n *\n * FAILS FAST outside a RequestContext. Silently sending an outbound call with NO request id or\n * tenant is far worse than a loud error — the trace just disappears and you find out in production. Every server-side client (RPC and Cloud Tasks) therefore only works\n * inside `RequestContext.run(...)`, which a top-level server filter normally establishes for you.\n *\n * Stateless once built, so it binds as a framework singleton every server-side client shares.\n */\n@provideFrameworkSingleton()\nexport class RequestContextHeaders {\n /**\n * EVERY transferred key with a non-empty value, under its wire name. Nothing is rewritten.\n *\n * That includes `x-request-id`, which propagates unchanged: one id correlates the whole call\n * tree, so the callee keeps ours rather than minting its own. ({@link fillFromRequest} only\n * generates an id when the inbound request carries none.)\n *\n * Values are RAW (unmasked) — this map goes on the wire, not in logs.\n *\n * @throws Error when called outside `RequestContext.run(...)` — see the class doc.\n */\n buildOutboundHeaders(): Map<string, string> {\n this.requireActiveContext();\n\n const headers = new Map<string, string>();\n // getTransferredKeys() is precomputed at configure() time.\n for (const key of HeaderRegistry.get().getTransferredKeys()) {\n // getTransferredKeys() is ContextKey<unknown>[]; every transferred value is a wire\n // string, so read by name rather than asserting a value type on a generically-typed key.\n const value = RequestContext.get<string>(key.name);\n if (value !== undefined && value !== null && value !== '') {\n headers.set(key.httpHeader!, value);\n }\n }\n\n // CLIENT_VERSION is transferred, but each hop sends ITS OWN build version (not the inherited\n // one) so a downstream server logs which build actually called it. Overwrite whatever the loop\n // copied from an inbound clientVersion with ours; if THIS service has no version, drop it\n // rather than forward the caller's as if it were ours. Non-throwing read — absent before setInfo.\n const myVersion = ServiceInfo.getVersion();\n const clientVersionHeader = WebpiecesCoreHeaders.CLIENT_VERSION.httpHeader!;\n if (myVersion) {\n headers.set(clientVersionHeader, myVersion);\n } else {\n headers.delete(clientVersionHeader);\n }\n\n return headers;\n }\n\n /**\n * INBOUND — the exact inverse of {@link buildOutboundHeaders}. Publish the request, move every\n * transferrable header off it into the context (read by wire name, stored under the key's\n * `name`), and mint an `x-request-id` if the caller sent none.\n *\n * The request is a PARAMETER, not something we fish back out of the context. Publishing and\n * filling are therefore one atomic step that cannot be half-done or done out of order — the\n * older `setRequest()` + `fillContext()` pair could silently skip the transfer entirely when a\n * caller forgot the first half.\n *\n * This is a PRECONDITION of calling into http-routing, and it belongs ABOVE the api boundary.\n * `WebpiecesMiddleware` does it for every HTTP request; a non-webpieces transport (or a test\n * driving `createApiClient` directly) must do the same. The api proxy only checks that a\n * request scope exists — it never builds one.\n *\n * @throws Error when called outside `RequestContext.run(...)`.\n */\n fillFromRequest(request: HttpRequest): void {\n this.requireActiveContext();\n\n RequestContext.setRequest(request);\n\n // Stamp the inbound method+path as top-level logged keys (jsonPayload.httpMethod / requestPath)\n // so EVERY log line of this request carries them. Sourced from the just-published HttpRequest;\n // NOT transferred over the wire, so a downstream hop stamps its own inbound values.\n RequestContext.putHeader(WebpiecesCoreHeaders.HTTP_METHOD, request.method);\n RequestContext.putHeader(WebpiecesCoreHeaders.REQUEST_PATH, request.path);\n\n // getTransferredKeys() is precomputed at configure() time.\n for (const key of HeaderRegistry.get().getTransferredKeys()) {\n const values = request.getHeaderValues(key);\n if (values && values.length > 0) {\n RequestContext.putHeader(key, values[0]);\n }\n }\n\n if (!RequestContext.hasHeader(WebpiecesCoreHeaders.REQUEST_ID)) {\n RequestContext.putHeader(WebpiecesCoreHeaders.REQUEST_ID, this.generateRequestId());\n this.stampRequestIdSource();\n }\n }\n\n /**\n * Record that WE minted the id — only ever called from the generate branch above, so the key is\n * ABSENT on a hop that inherited the caller's id. Present == this service is the trace's origin.\n *\n * Uses the non-throwing `getName()`: this runs PER REQUEST, and a missing log field must not 500\n * live traffic. A server that booted already ran `setupRuntime`, which calls `ServiceInfo.setInfo`\n * with its required name+version, so the name is always there in practice; only a test driving the\n * context directly sees undefined.\n */\n private stampRequestIdSource(): void {\n const svcName = ServiceInfo.getName();\n if (svcName) {\n RequestContext.putHeader(WebpiecesCoreHeaders.REQUEST_ID_SOURCE, svcName);\n }\n }\n\n /** The id every log line of this request, and every downstream hop, will carry. */\n private generateRequestId(): string {\n return `svrGenReqId-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;\n }\n\n /**\n * The recorder travelling in the context, when a test is recording this call. Absent in normal\n * operation, and ALWAYS absent in a browser — which is why recording lives on the server-side\n * client and never in the isomorphic core.\n */\n findRecorder(): TestCaseRecorder | undefined {\n if (!RequestContext.isActive()) {\n return undefined;\n }\n return RequestContext.getHeader<TestCaseRecorder>(RecorderKeys.RECORDER);\n }\n\n /** Guard both directions: no ambient request scope means there is no context to fill or read. */\n private requireActiveContext(): void {\n if (!RequestContext.isActive()) {\n throw new Error(\n 'No active RequestContext. A webpieces server-side client only works inside ' +\n 'RequestContext.run(...), which a top-level server filter normally establishes. ' +\n 'In a test, wrap the call: await RequestContext.run(async () => client.foo(req));',\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RequestContextHeaders.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContextHeaders.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n HeaderRegistry,\n RecorderKeys,\n ServiceInfo,\n TestCaseRecorder,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { provideFrameworkSingleton } from './frameworkProvide';\nimport { HttpRequest } from './HttpRequest';\nimport { RequestContext } from './RequestContext';\n\n/**\n * RequestContextHeaders - the magic context ↔ the wire, for a SERVER. Both directions live here:\n *\n * inbound {@link fillFromRequest} the published HttpRequest's headers -> the context\n * outbound {@link buildOutboundHeaders} the context -> the next hop's headers\n *\n * Reads the AsyncLocalStorage-backed {@link RequestContext} straight through — no ContextReader,\n * no ContextMgr, no abstract base. A server has exactly one place its context lives, and the\n * indirection only hid the failure below. (The browser's answer is `ContextMgr` in\n * @webpieces/core-util, which reads an app-held store because a browser has no ambient scope.)\n *\n * FAILS FAST outside a RequestContext. Silently sending an outbound call with NO request id or\n * tenant is far worse than a loud error — the trace just disappears and you find out in production. Every server-side client (RPC and Cloud Tasks) therefore only works\n * inside `RequestContext.run(...)`, which a top-level server filter normally establishes for you.\n *\n * Stateless once built, so it binds as a framework singleton every server-side client shares.\n */\n@provideFrameworkSingleton()\nexport class RequestContextHeaders {\n /**\n * EVERY transferred key with a non-empty value, under its wire name. Nothing is rewritten.\n *\n * That includes `x-request-id`, which propagates unchanged: one id correlates the whole call\n * tree, so the callee keeps ours rather than minting its own. ({@link fillFromRequest} only\n * generates an id when the inbound request carries none.)\n *\n * Values are RAW (unmasked) — this map goes on the wire, not in logs.\n *\n * @throws Error when called outside `RequestContext.run(...)` — see the class doc.\n */\n buildOutboundHeaders(): Map<string, string> {\n this.requireActiveContext();\n\n const headers = new Map<string, string>();\n // getTransferredKeys() is precomputed at configure() time.\n for (const key of HeaderRegistry.get().getTransferredKeys()) {\n // getTransferredKeys() is AnyContextKey[]; every transferred value is a wire\n // string, so read by name rather than asserting a value type on a generically-typed key.\n const value = RequestContext.get<string>(key.name);\n if (value !== undefined && value !== null && value !== '') {\n headers.set(key.httpHeader!, value);\n }\n }\n\n // CLIENT_VERSION is transferred, but each hop sends ITS OWN build version (not the inherited\n // one) so a downstream server logs which build actually called it. Overwrite whatever the loop\n // copied from an inbound clientVersion with ours; if THIS service has no version, drop it\n // rather than forward the caller's as if it were ours. Non-throwing read — absent before setInfo.\n const myVersion = ServiceInfo.getVersion();\n const clientVersionHeader = WebpiecesCoreHeaders.CLIENT_VERSION.httpHeader!;\n if (myVersion) {\n headers.set(clientVersionHeader, myVersion);\n } else {\n headers.delete(clientVersionHeader);\n }\n\n return headers;\n }\n\n /**\n * INBOUND — the exact inverse of {@link buildOutboundHeaders}. Publish the request, move every\n * transferrable header off it into the context (read by wire name, stored under the key's\n * `name`), and mint an `x-request-id` if the caller sent none.\n *\n * The request is a PARAMETER, not something we fish back out of the context. Publishing and\n * filling are therefore one atomic step that cannot be half-done or done out of order — the\n * older `setRequest()` + `fillContext()` pair could silently skip the transfer entirely when a\n * caller forgot the first half.\n *\n * This is a PRECONDITION of calling into http-routing, and it belongs ABOVE the api boundary.\n * `WebpiecesMiddleware` does it for every HTTP request; a non-webpieces transport (or a test\n * driving `createApiClient` directly) must do the same. The api proxy only checks that a\n * request scope exists — it never builds one.\n *\n * @throws Error when called outside `RequestContext.run(...)`.\n */\n fillFromRequest(request: HttpRequest): void {\n this.requireActiveContext();\n\n RequestContext.setRequest(request);\n\n // Stamp the inbound method+path as top-level logged keys (jsonPayload.httpMethod / requestPath)\n // so EVERY log line of this request carries them. Sourced from the just-published HttpRequest;\n // NOT transferred over the wire, so a downstream hop stamps its own inbound values.\n RequestContext.putHeader(WebpiecesCoreHeaders.HTTP_METHOD, request.method);\n RequestContext.putHeader(WebpiecesCoreHeaders.REQUEST_PATH, request.path);\n\n // getTransferredKeys() is precomputed at configure() time.\n for (const key of HeaderRegistry.get().getTransferredKeys()) {\n const values = request.getHeaderValues(key);\n if (values && values.length > 0) {\n RequestContext.putHeader(key, values[0]);\n }\n }\n\n if (!RequestContext.hasHeader(WebpiecesCoreHeaders.REQUEST_ID)) {\n RequestContext.putHeader(WebpiecesCoreHeaders.REQUEST_ID, this.generateRequestId());\n this.stampRequestIdSource();\n }\n }\n\n /**\n * Record that WE minted the id — only ever called from the generate branch above, so the key is\n * ABSENT on a hop that inherited the caller's id. Present == this service is the trace's origin.\n *\n * Uses the non-throwing `getName()`: this runs PER REQUEST, and a missing log field must not 500\n * live traffic. A server that booted already ran `setupRuntime`, which calls `ServiceInfo.setInfo`\n * with its required name+version, so the name is always there in practice; only a test driving the\n * context directly sees undefined.\n */\n private stampRequestIdSource(): void {\n const svcName = ServiceInfo.getName();\n if (svcName) {\n RequestContext.putHeader(WebpiecesCoreHeaders.REQUEST_ID_SOURCE, svcName);\n }\n }\n\n /** The id every log line of this request, and every downstream hop, will carry. */\n private generateRequestId(): string {\n return `svrGenReqId-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;\n }\n\n /**\n * The recorder travelling in the context, when a test is recording this call. Absent in normal\n * operation, and ALWAYS absent in a browser — which is why recording lives on the server-side\n * client and never in the isomorphic core.\n */\n findRecorder(): TestCaseRecorder | undefined {\n if (!RequestContext.isActive()) {\n return undefined;\n }\n return RequestContext.getHeader<TestCaseRecorder>(RecorderKeys.RECORDER);\n }\n\n /** Guard both directions: no ambient request scope means there is no context to fill or read. */\n private requireActiveContext(): void {\n if (!RequestContext.isActive()) {\n throw new Error(\n 'No active RequestContext. A webpieces server-side client only works inside ' +\n 'RequestContext.run(...), which a top-level server filter normally establishes. ' +\n 'In a test, wrap the call: await RequestContext.run(async () => client.foo(req));',\n );\n }\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyContextKey, ContextReader } from '@webpieces/core-util';
|
|
2
2
|
/**
|
|
3
3
|
* RequestContextReader - the NODE/server ContextReader. Reads context-key values
|
|
4
4
|
* from the AsyncLocalStorage-backed RequestContext.
|
|
@@ -12,11 +12,11 @@ import { ContextKey, ContextReader } from '@webpieces/core-util';
|
|
|
12
12
|
*/
|
|
13
13
|
export declare class RequestContextReader implements ContextReader {
|
|
14
14
|
/** Read a string context-key value from the active RequestContext. */
|
|
15
|
-
read(key:
|
|
15
|
+
read(key: AnyContextKey): string | undefined;
|
|
16
16
|
/**
|
|
17
17
|
* Read a non-string context value (e.g. the active TestCaseRecorder under
|
|
18
18
|
* RecorderKeys.RECORDER). Lets the isomorphic http-client find server-side
|
|
19
19
|
* context without importing core-context itself.
|
|
20
20
|
*/
|
|
21
|
-
readValue(key:
|
|
21
|
+
readValue(key: AnyContextKey): unknown;
|
|
22
22
|
}
|
|
@@ -16,7 +16,7 @@ const RequestContext_1 = require("./RequestContext");
|
|
|
16
16
|
class RequestContextReader {
|
|
17
17
|
/** Read a string context-key value from the active RequestContext. */
|
|
18
18
|
read(key) {
|
|
19
|
-
// `key` is a generically-typed
|
|
19
|
+
// `key` is a generically-typed AnyContextKey here (the reader is key-agnostic), and
|
|
20
20
|
// this method's contract is string-only, so read by name rather than via the typed getHeader.
|
|
21
21
|
return RequestContext_1.RequestContext.get(key.name);
|
|
22
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestContextReader.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContextReader.ts"],"names":[],"mappings":";;;AACA,qDAAkD;AAElD;;;;;;;;;;GAUG;AACH,MAAa,oBAAoB;IAC7B,sEAAsE;IACtE,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"RequestContextReader.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContextReader.ts"],"names":[],"mappings":";;;AACA,qDAAkD;AAElD;;;;;;;;;;GAUG;AACH,MAAa,oBAAoB;IAC7B,sEAAsE;IACtE,IAAI,CAAC,GAAkB;QACnB,oFAAoF;QACpF,8FAA8F;QAC9F,OAAO,+BAAc,CAAC,GAAG,CAAS,GAAG,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,gGAAgG;IAChG,SAAS,CAAC,GAAkB;QACxB,OAAO,+BAAc,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;CACJ;AAjBD,oDAiBC","sourcesContent":["import { ContextKey, AnyContextKey, ContextReader } from '@webpieces/core-util';\nimport { RequestContext } from './RequestContext';\n\n/**\n * RequestContextReader - the NODE/server ContextReader. Reads context-key values\n * from the AsyncLocalStorage-backed RequestContext.\n *\n * Only works in Node.js with an active RequestContext. Lives in\n * @webpieces/core-context (Node-only) alongside the RequestContext it reads from,\n * so libraries can build a context-propagating ContextMgr without pulling in\n * @webpieces/http-routing.\n *\n * For browsers, use MutableContextStore from @webpieces/http-client.\n */\nexport class RequestContextReader implements ContextReader {\n /** Read a string context-key value from the active RequestContext. */\n read(key: AnyContextKey): string | undefined {\n // `key` is a generically-typed AnyContextKey here (the reader is key-agnostic), and\n // this method's contract is string-only, so read by name rather than via the typed getHeader.\n return RequestContext.get<string>(key.name);\n }\n\n /**\n * Read a non-string context value (e.g. the active TestCaseRecorder under\n * RecorderKeys.RECORDER). Lets the isomorphic http-client find server-side\n * context without importing core-context itself.\n */\n // webpieces-disable no-any-unknown -- context values are heterogeneous (recorder, meta objects)\n readValue(key: AnyContextKey): unknown {\n return RequestContext.getHeader(key);\n }\n}\n"]}
|