@webpieces/core-util 0.4.389 → 0.4.390
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/ApiCallInfo.d.ts +24 -18
- package/src/http/ApiCallInfo.js +7 -34
- package/src/http/ApiCallInfo.js.map +1 -1
- package/src/http/ApiMethodInfo.d.ts +40 -0
- package/src/http/ApiMethodInfo.js +43 -0
- package/src/http/ApiMethodInfo.js.map +1 -0
- package/src/http/LogApiCall.d.ts +6 -21
- package/src/http/LogApiCall.js +15 -35
- package/src/http/LogApiCall.js.map +1 -1
- package/src/http/WebpiecesCoreHeaders.d.ts +13 -0
- package/src/http/WebpiecesCoreHeaders.js +15 -0
- package/src/http/WebpiecesCoreHeaders.js.map +1 -1
- package/src/index.d.ts +4 -2
- package/src/index.js +3 -2
- package/src/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ApiMethodInfo } from './ApiMethodInfo';
|
|
1
2
|
/**
|
|
2
3
|
* ApiCallInfo - the structured tag stamped into RequestContext around every API call
|
|
3
4
|
* (by {@link LogApiCall}), so ANY log line emitted during the call inherits a filterable
|
|
@@ -6,22 +7,26 @@
|
|
|
6
7
|
* The node logging backends (winston/bunyan) read this struct out of context via
|
|
7
8
|
* `RequestContext.buildStructuredLogFields()` and emit it AS AN OBJECT under `jsonPayload.api`,
|
|
8
9
|
* which unlocks GCP Cloud Logging filters like:
|
|
9
|
-
* - `jsonPayload.api.side="client"`
|
|
10
|
-
* - `jsonPayload.api.side="server"`
|
|
11
|
-
* - `jsonPayload.api.
|
|
12
|
-
* - `jsonPayload.api
|
|
10
|
+
* - `jsonPayload.api.method.side="client"` — every outbound call this process made
|
|
11
|
+
* - `jsonPayload.api.method.side="server"` — every inbound call it handled
|
|
12
|
+
* - `jsonPayload.api.method.apiClass="SaveApi"` — one logical method, BOTH sides (client + server)
|
|
13
|
+
* - `jsonPayload.api.result="failure"` — failed exchanges only
|
|
14
|
+
* - `jsonPayload.api:*` — "API traffic only" (tracing + the recorder)
|
|
13
15
|
*
|
|
14
|
-
* IMPORTANT: the field names on
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* IMPORTANT: the field names here (and on the nested {@link ApiMethodInfo}) ARE the GCP field names —
|
|
17
|
+
* rename a field and the filter renames with it. The identity lives NESTED under `api.method`
|
|
18
|
+
* (`api.method.{side,apiClass,methodName,controllerName}`); `api.type` and `api.result` sit at the top.
|
|
19
|
+
*
|
|
20
|
+
* NOTE: the request `httpMethod`/`path` are NOT here — an inbound request stamps them as the separate
|
|
21
|
+
* top-level logged keys `jsonPayload.httpMethod` / `jsonPayload.requestPath` (see
|
|
22
|
+
* {@link WebpiecesCoreHeaders} + `RequestContextHeaders.fillFromRequest`). Outbound client calls have
|
|
23
|
+
* no inbound path, so they carry only the `api` identity.
|
|
17
24
|
*
|
|
18
25
|
* Per-hop only: the underlying `API_CALL_INFO` ContextKey is NOT transferred over the wire, so a
|
|
19
26
|
* downstream server stamps its own `side:'server'` rather than inheriting the caller's `side:'client'`.
|
|
20
27
|
*
|
|
21
28
|
* Per CLAUDE.md: data-only structures are classes, not interfaces.
|
|
22
29
|
*/
|
|
23
|
-
/** Which end of the exchange this process is: the caller ('client') or the handler ('server'). */
|
|
24
|
-
export type ApiSide = 'client' | 'server';
|
|
25
30
|
/** Which half of the exchange this tag describes: the outgoing 'request' or the returning 'response'. */
|
|
26
31
|
export type ApiType = 'request' | 'response';
|
|
27
32
|
/**
|
|
@@ -29,18 +34,19 @@ export type ApiType = 'request' | 'response';
|
|
|
29
34
|
* handled "you made a mistake"); 'failure' is a genuine server error. See {@link LogApiCall.isUserError}.
|
|
30
35
|
*/
|
|
31
36
|
export type ApiResult = 'success' | 'failure';
|
|
37
|
+
/** Re-exported from {@link ApiMethodInfo} (its true home) so existing `ApiSide` imports keep working. */
|
|
38
|
+
export type { ApiSide } from './ApiMethodInfo';
|
|
32
39
|
export declare class ApiCallInfo {
|
|
33
|
-
|
|
40
|
+
/** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under
|
|
41
|
+
* `jsonPayload.api.method`. */
|
|
42
|
+
readonly method: ApiMethodInfo;
|
|
34
43
|
readonly type: ApiType;
|
|
35
44
|
/** Response only — undefined on the 'request' tag. */
|
|
36
45
|
readonly result?: ApiResult | undefined;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
constructor(side: ApiSide, type: ApiType,
|
|
46
|
+
constructor(
|
|
47
|
+
/** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under
|
|
48
|
+
* `jsonPayload.api.method`. */
|
|
49
|
+
method: ApiMethodInfo, type: ApiType,
|
|
42
50
|
/** Response only — undefined on the 'request' tag. */
|
|
43
|
-
result?: ApiResult | undefined
|
|
44
|
-
/** The controller/API class name (e.g. 'SaveController') — filter with jsonPayload.api.controller. */
|
|
45
|
-
controller?: string | undefined);
|
|
51
|
+
result?: ApiResult | undefined);
|
|
46
52
|
}
|
package/src/http/ApiCallInfo.js
CHANGED
|
@@ -1,46 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* ApiCallInfo - the structured tag stamped into RequestContext around every API call
|
|
4
|
-
* (by {@link LogApiCall}), so ANY log line emitted during the call inherits a filterable
|
|
5
|
-
* `api` object rather than only the req/resp text lines.
|
|
6
|
-
*
|
|
7
|
-
* The node logging backends (winston/bunyan) read this struct out of context via
|
|
8
|
-
* `RequestContext.buildStructuredLogFields()` and emit it AS AN OBJECT under `jsonPayload.api`,
|
|
9
|
-
* which unlocks GCP Cloud Logging filters like:
|
|
10
|
-
* - `jsonPayload.api.side="client"` — every outbound call this process made
|
|
11
|
-
* - `jsonPayload.api.side="server"` — every inbound call it handled
|
|
12
|
-
* - `jsonPayload.api.result="failure"`— failed exchanges only
|
|
13
|
-
* - `jsonPayload.api:*` — "API traffic only" (tracing + the recorder)
|
|
14
|
-
*
|
|
15
|
-
* IMPORTANT: the field names on this class ARE the GCP field names (`api.side`, `api.type`,
|
|
16
|
-
* `api.result`, `api.path`, `api.method`, `api.controller`) — rename a field here and the filter
|
|
17
|
-
* renames with it.
|
|
18
|
-
*
|
|
19
|
-
* Per-hop only: the underlying `API_CALL_INFO` ContextKey is NOT transferred over the wire, so a
|
|
20
|
-
* downstream server stamps its own `side:'server'` rather than inheriting the caller's `side:'client'`.
|
|
21
|
-
*
|
|
22
|
-
* Per CLAUDE.md: data-only structures are classes, not interfaces.
|
|
23
|
-
*/
|
|
24
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
3
|
exports.ApiCallInfo = void 0;
|
|
26
4
|
class ApiCallInfo {
|
|
27
|
-
|
|
5
|
+
method;
|
|
28
6
|
type;
|
|
29
7
|
result;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
8
|
+
constructor(
|
|
9
|
+
/** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under
|
|
10
|
+
* `jsonPayload.api.method`. */
|
|
11
|
+
method, type,
|
|
34
12
|
/** Response only — undefined on the 'request' tag. */
|
|
35
|
-
result
|
|
36
|
-
|
|
37
|
-
controller) {
|
|
38
|
-
this.side = side;
|
|
13
|
+
result) {
|
|
14
|
+
this.method = method;
|
|
39
15
|
this.type = type;
|
|
40
16
|
this.result = result;
|
|
41
|
-
this.path = path;
|
|
42
|
-
this.method = method;
|
|
43
|
-
this.controller = controller;
|
|
44
17
|
}
|
|
45
18
|
}
|
|
46
19
|
exports.ApiCallInfo = ApiCallInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiCallInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallInfo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ApiCallInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallInfo.ts"],"names":[],"mappings":";;;AA2CA,MAAa,WAAW;IAIP;IACA;IAEA;IANb;IACI;oCACgC;IACvB,MAAqB,EACrB,IAAa;IACtB,sDAAsD;IAC7C,MAAkB;QAHlB,WAAM,GAAN,MAAM,CAAe;QACrB,SAAI,GAAJ,IAAI,CAAS;QAEb,WAAM,GAAN,MAAM,CAAY;IAC5B,CAAC;CACP;AATD,kCASC","sourcesContent":["import { ApiMethodInfo } from './ApiMethodInfo';\n\n/**\n * ApiCallInfo - the structured tag stamped into RequestContext around every API call\n * (by {@link LogApiCall}), so ANY log line emitted during the call inherits a filterable\n * `api` object rather than only the req/resp text lines.\n *\n * The node logging backends (winston/bunyan) read this struct out of context via\n * `RequestContext.buildStructuredLogFields()` and emit it AS AN OBJECT under `jsonPayload.api`,\n * which unlocks GCP Cloud Logging filters like:\n * - `jsonPayload.api.method.side=\"client\"` — every outbound call this process made\n * - `jsonPayload.api.method.side=\"server\"` — every inbound call it handled\n * - `jsonPayload.api.method.apiClass=\"SaveApi\"` — one logical method, BOTH sides (client + server)\n * - `jsonPayload.api.result=\"failure\"` — failed exchanges only\n * - `jsonPayload.api:*` — \"API traffic only\" (tracing + the recorder)\n *\n * IMPORTANT: the field names here (and on the nested {@link ApiMethodInfo}) ARE the GCP field names —\n * rename a field and the filter renames with it. The identity lives NESTED under `api.method`\n * (`api.method.{side,apiClass,methodName,controllerName}`); `api.type` and `api.result` sit at the top.\n *\n * NOTE: the request `httpMethod`/`path` are NOT here — an inbound request stamps them as the separate\n * top-level logged keys `jsonPayload.httpMethod` / `jsonPayload.requestPath` (see\n * {@link WebpiecesCoreHeaders} + `RequestContextHeaders.fillFromRequest`). Outbound client calls have\n * no inbound path, so they carry only the `api` identity.\n *\n * Per-hop only: the underlying `API_CALL_INFO` ContextKey is NOT transferred over the wire, so a\n * downstream server stamps its own `side:'server'` rather than inheriting the caller's `side:'client'`.\n *\n * Per CLAUDE.md: data-only structures are classes, not interfaces.\n */\n\n/** Which half of the exchange this tag describes: the outgoing 'request' or the returning 'response'. */\nexport type ApiType = 'request' | 'response';\n\n/**\n * Response outcome. 'success' covers 2xx AND user errors (400/401/403/404/266 — a successfully\n * handled \"you made a mistake\"); 'failure' is a genuine server error. See {@link LogApiCall.isUserError}.\n */\nexport type ApiResult = 'success' | 'failure';\n\n/** Re-exported from {@link ApiMethodInfo} (its true home) so existing `ApiSide` imports keep working. */\nexport type { ApiSide } from './ApiMethodInfo';\n\nexport class ApiCallInfo {\n constructor(\n /** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under\n * `jsonPayload.api.method`. */\n readonly method: ApiMethodInfo,\n readonly type: ApiType,\n /** Response only — undefined on the 'request' tag. */\n readonly result?: ApiResult,\n ) {}\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApiMethodInfo - the transport-neutral identity of a single API method call, handed to
|
|
3
|
+
* {@link LogApiCall.execute} by every caller (server inbound, http/in-process clients, cloud tasks,
|
|
4
|
+
* and external wrapped clients like the firestore admin client).
|
|
5
|
+
*
|
|
6
|
+
* WHY generic (not RouteMetadata): LogApiCall runs deep in the stack over MANY shapes — HTTP routes,
|
|
7
|
+
* pubsub/queue enqueues, and multi-param external clients returning Promise<void>/Promise<unknown>.
|
|
8
|
+
* None of those own an `httpMethod`/`path`, so LogApiCall must not depend on the HTTP-shaped
|
|
9
|
+
* `RouteMetadata`. This carries only what identifies the call.
|
|
10
|
+
*
|
|
11
|
+
* MATCHING is the point of {@link apiClass}: a CLIENT call and the SERVER handler for the same logical
|
|
12
|
+
* method must log the SAME identity so `jsonPayload.api.method.apiClass="SaveApi"` filters both sides
|
|
13
|
+
* together. The API CONTRACT class name (e.g. 'SaveApi') is available on both sides — the client only
|
|
14
|
+
* ever knows it, and the server carries it alongside its impl name — so it, not the server's impl
|
|
15
|
+
* class, is the required key.
|
|
16
|
+
*
|
|
17
|
+
* Per CLAUDE.md: data-only structures are classes, not interfaces.
|
|
18
|
+
*/
|
|
19
|
+
/** Which end of the exchange this process is: the caller ('client') or the handler ('server'). */
|
|
20
|
+
export type ApiSide = 'client' | 'server';
|
|
21
|
+
export declare class ApiMethodInfo {
|
|
22
|
+
readonly side: ApiSide;
|
|
23
|
+
/** REQUIRED — the API CONTRACT class name (e.g. 'SaveApi'). Matches client + server so both
|
|
24
|
+
* sides of one logical call filter together via `jsonPayload.api.method.apiClass`. */
|
|
25
|
+
readonly apiClass: string;
|
|
26
|
+
/** REQUIRED — the method on the contract (e.g. 'save'). */
|
|
27
|
+
readonly methodName: string;
|
|
28
|
+
/** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no
|
|
29
|
+
* impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
|
|
30
|
+
readonly controllerName?: string | undefined;
|
|
31
|
+
constructor(side: ApiSide,
|
|
32
|
+
/** REQUIRED — the API CONTRACT class name (e.g. 'SaveApi'). Matches client + server so both
|
|
33
|
+
* sides of one logical call filter together via `jsonPayload.api.method.apiClass`. */
|
|
34
|
+
apiClass: string,
|
|
35
|
+
/** REQUIRED — the method on the contract (e.g. 'save'). */
|
|
36
|
+
methodName: string,
|
|
37
|
+
/** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no
|
|
38
|
+
* impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
|
|
39
|
+
controllerName?: string | undefined);
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ApiMethodInfo - the transport-neutral identity of a single API method call, handed to
|
|
4
|
+
* {@link LogApiCall.execute} by every caller (server inbound, http/in-process clients, cloud tasks,
|
|
5
|
+
* and external wrapped clients like the firestore admin client).
|
|
6
|
+
*
|
|
7
|
+
* WHY generic (not RouteMetadata): LogApiCall runs deep in the stack over MANY shapes — HTTP routes,
|
|
8
|
+
* pubsub/queue enqueues, and multi-param external clients returning Promise<void>/Promise<unknown>.
|
|
9
|
+
* None of those own an `httpMethod`/`path`, so LogApiCall must not depend on the HTTP-shaped
|
|
10
|
+
* `RouteMetadata`. This carries only what identifies the call.
|
|
11
|
+
*
|
|
12
|
+
* MATCHING is the point of {@link apiClass}: a CLIENT call and the SERVER handler for the same logical
|
|
13
|
+
* method must log the SAME identity so `jsonPayload.api.method.apiClass="SaveApi"` filters both sides
|
|
14
|
+
* together. The API CONTRACT class name (e.g. 'SaveApi') is available on both sides — the client only
|
|
15
|
+
* ever knows it, and the server carries it alongside its impl name — so it, not the server's impl
|
|
16
|
+
* class, is the required key.
|
|
17
|
+
*
|
|
18
|
+
* Per CLAUDE.md: data-only structures are classes, not interfaces.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.ApiMethodInfo = void 0;
|
|
22
|
+
class ApiMethodInfo {
|
|
23
|
+
side;
|
|
24
|
+
apiClass;
|
|
25
|
+
methodName;
|
|
26
|
+
controllerName;
|
|
27
|
+
constructor(side,
|
|
28
|
+
/** REQUIRED — the API CONTRACT class name (e.g. 'SaveApi'). Matches client + server so both
|
|
29
|
+
* sides of one logical call filter together via `jsonPayload.api.method.apiClass`. */
|
|
30
|
+
apiClass,
|
|
31
|
+
/** REQUIRED — the method on the contract (e.g. 'save'). */
|
|
32
|
+
methodName,
|
|
33
|
+
/** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no
|
|
34
|
+
* impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
|
|
35
|
+
controllerName) {
|
|
36
|
+
this.side = side;
|
|
37
|
+
this.apiClass = apiClass;
|
|
38
|
+
this.methodName = methodName;
|
|
39
|
+
this.controllerName = controllerName;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.ApiMethodInfo = ApiMethodInfo;
|
|
43
|
+
//# sourceMappingURL=ApiMethodInfo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiMethodInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiMethodInfo.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAKH,MAAa,aAAa;IAET;IAGA;IAEA;IAGA;IATb,YACa,IAAa;IACtB;2FACuF;IAC9E,QAAgB;IACzB,2DAA2D;IAClD,UAAkB;IAC3B;gGAC4F;IACnF,cAAuB;QARvB,SAAI,GAAJ,IAAI,CAAS;QAGb,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,eAAU,GAAV,UAAU,CAAQ;QAGlB,mBAAc,GAAd,cAAc,CAAS;IACjC,CAAC;CACP;AAZD,sCAYC","sourcesContent":["/**\n * ApiMethodInfo - the transport-neutral identity of a single API method call, handed to\n * {@link LogApiCall.execute} by every caller (server inbound, http/in-process clients, cloud tasks,\n * and external wrapped clients like the firestore admin client).\n *\n * WHY generic (not RouteMetadata): LogApiCall runs deep in the stack over MANY shapes — HTTP routes,\n * pubsub/queue enqueues, and multi-param external clients returning Promise<void>/Promise<unknown>.\n * None of those own an `httpMethod`/`path`, so LogApiCall must not depend on the HTTP-shaped\n * `RouteMetadata`. This carries only what identifies the call.\n *\n * MATCHING is the point of {@link apiClass}: a CLIENT call and the SERVER handler for the same logical\n * method must log the SAME identity so `jsonPayload.api.method.apiClass=\"SaveApi\"` filters both sides\n * together. The API CONTRACT class name (e.g. 'SaveApi') is available on both sides — the client only\n * ever knows it, and the server carries it alongside its impl name — so it, not the server's impl\n * class, is the required key.\n *\n * Per CLAUDE.md: data-only structures are classes, not interfaces.\n */\n\n/** Which end of the exchange this process is: the caller ('client') or the handler ('server'). */\nexport type ApiSide = 'client' | 'server';\n\nexport class ApiMethodInfo {\n constructor(\n readonly side: ApiSide,\n /** REQUIRED — the API CONTRACT class name (e.g. 'SaveApi'). Matches client + server so both\n * sides of one logical call filter together via `jsonPayload.api.method.apiClass`. */\n readonly apiClass: string,\n /** REQUIRED — the method on the contract (e.g. 'save'). */\n readonly methodName: string,\n /** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no\n * impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */\n readonly controllerName?: string,\n ) {}\n}\n"]}
|
package/src/http/LogApiCall.d.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ApiSide } from "./ApiCallInfo";
|
|
3
|
-
/**
|
|
4
|
-
* Options for {@link LogApiCallImpl.execute}. `allowVoidResponse` opts OUT of the strict
|
|
5
|
-
* falsy-response guard for callers that legitimately return void/undefined (e.g. a local firestore
|
|
6
|
-
* wrapper). Defaults to strict so RPC callers keep the safety net.
|
|
7
|
-
*/
|
|
8
|
-
export declare class LogApiCallOptions {
|
|
9
|
-
allowVoidResponse?: boolean;
|
|
10
|
-
constructor(allowVoidResponse?: boolean);
|
|
11
|
-
}
|
|
1
|
+
import { ApiMethodInfo } from "./ApiMethodInfo";
|
|
12
2
|
/**
|
|
13
3
|
* LogApiCall - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and
|
|
14
4
|
* client-side (ProxyClient) for one consistent logging shape across the framework.
|
|
@@ -18,7 +8,7 @@ export declare class LogApiCallOptions {
|
|
|
18
8
|
* 2. A structured {@link ApiCallInfo} tag is stamped into the ambient request context via the
|
|
19
9
|
* {@link ApiCallContextHolder} seam, so EVERY log line emitted during the call (not just the
|
|
20
10
|
* req/resp lines) inherits a filterable `api` object — surfacing in GCP as
|
|
21
|
-
* `jsonPayload.api.{side,type,result
|
|
11
|
+
* `jsonPayload.api.{method.{side,apiClass,methodName,controllerName},type,result}`.
|
|
22
12
|
*
|
|
23
13
|
* BROWSER-SAFE: this lives in core-util and runs in the browser bundle (via ProxyClient →
|
|
24
14
|
* BrowserProxyClient), so it MUST NOT import `RequestContext` (Node async_hooks, and a circular dep).
|
|
@@ -38,15 +28,10 @@ export declare class LogApiCallImpl {
|
|
|
38
28
|
/**
|
|
39
29
|
* Execute an API call with logging + `api` context-tagging around it.
|
|
40
30
|
*
|
|
41
|
-
* @param
|
|
42
|
-
*
|
|
43
|
-
* @param requestDto - The request DTO
|
|
31
|
+
* @param methodInfo - The transport-neutral call identity (side, apiClass, methodName,
|
|
32
|
+
* controllerName?). `apiClass` is what matches a client call to its server handler in the logs.
|
|
33
|
+
* @param requestDto - The request DTO (external multi-param callers synthesize a small object)
|
|
44
34
|
* @param method - The method to execute
|
|
45
|
-
* @param options - `allowVoidResponse: true` opts OUT of the strict falsy-response guard, for
|
|
46
|
-
* callers that legitimately return void/undefined (e.g. a local firestore wrapper whose
|
|
47
|
-
* `setDocument` returns void, or a `getDocById` miss returning undefined). Defaults to strict,
|
|
48
|
-
* so RPC callers (LogApiFilter, ProxyClient) keep the safety net: an HTTP endpoint returning
|
|
49
|
-
* nothing is almost always a bug.
|
|
50
35
|
*
|
|
51
36
|
* Correlation fields (requestId, tenantId, ...) are NOT stamped here — a logging BACKEND owns that,
|
|
52
37
|
* reading RequestContext on every record. What IS stamped here is the per-call `api` tag, and only
|
|
@@ -55,7 +40,7 @@ export declare class LogApiCallImpl {
|
|
|
55
40
|
* it. Cost: only the `[API-*]` req/resp lines carry `api`, not lines emitted mid-call — which is
|
|
56
41
|
* exactly what the GCP filters (`jsonPayload.api.*`) want.
|
|
57
42
|
*/
|
|
58
|
-
execute(
|
|
43
|
+
execute(methodInfo: ApiMethodInfo, requestDto: any, method: (dto: any) => Promise<any>): Promise<any>;
|
|
59
44
|
/**
|
|
60
45
|
* Is this error a NON-failure for HEALTH/METRICS — the process working CORRECTLY (log OTHER, api
|
|
61
46
|
* result:'success') — rather than a real failure to surface (log FAIL, result:'failure')?
|
package/src/http/LogApiCall.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LogApiCall = exports.LogApiCallImpl =
|
|
3
|
+
exports.LogApiCall = exports.LogApiCallImpl = void 0;
|
|
4
4
|
const errors_1 = require("./errors");
|
|
5
5
|
const errorUtils_1 = require("../lib/errorUtils");
|
|
6
6
|
const LogManager_1 = require("../logging/LogManager");
|
|
@@ -8,18 +8,6 @@ const ApiCallInfo_1 = require("./ApiCallInfo");
|
|
|
8
8
|
const ApiCallContext_1 = require("./ApiCallContext");
|
|
9
9
|
const WebpiecesCoreHeaders_1 = require("./WebpiecesCoreHeaders");
|
|
10
10
|
const log = LogManager_1.LogManager.getLogger('LogApiCall');
|
|
11
|
-
/**
|
|
12
|
-
* Options for {@link LogApiCallImpl.execute}. `allowVoidResponse` opts OUT of the strict
|
|
13
|
-
* falsy-response guard for callers that legitimately return void/undefined (e.g. a local firestore
|
|
14
|
-
* wrapper). Defaults to strict so RPC callers keep the safety net.
|
|
15
|
-
*/
|
|
16
|
-
class LogApiCallOptions {
|
|
17
|
-
allowVoidResponse;
|
|
18
|
-
constructor(allowVoidResponse) {
|
|
19
|
-
this.allowVoidResponse = allowVoidResponse;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
exports.LogApiCallOptions = LogApiCallOptions;
|
|
23
11
|
/**
|
|
24
12
|
* LogApiCall - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and
|
|
25
13
|
* client-side (ProxyClient) for one consistent logging shape across the framework.
|
|
@@ -29,7 +17,7 @@ exports.LogApiCallOptions = LogApiCallOptions;
|
|
|
29
17
|
* 2. A structured {@link ApiCallInfo} tag is stamped into the ambient request context via the
|
|
30
18
|
* {@link ApiCallContextHolder} seam, so EVERY log line emitted during the call (not just the
|
|
31
19
|
* req/resp lines) inherits a filterable `api` object — surfacing in GCP as
|
|
32
|
-
* `jsonPayload.api.{side,type,result
|
|
20
|
+
* `jsonPayload.api.{method.{side,apiClass,methodName,controllerName},type,result}`.
|
|
33
21
|
*
|
|
34
22
|
* BROWSER-SAFE: this lives in core-util and runs in the browser bundle (via ProxyClient →
|
|
35
23
|
* BrowserProxyClient), so it MUST NOT import `RequestContext` (Node async_hooks, and a circular dep).
|
|
@@ -49,15 +37,10 @@ class LogApiCallImpl {
|
|
|
49
37
|
/**
|
|
50
38
|
* Execute an API call with logging + `api` context-tagging around it.
|
|
51
39
|
*
|
|
52
|
-
* @param
|
|
53
|
-
*
|
|
54
|
-
* @param requestDto - The request DTO
|
|
40
|
+
* @param methodInfo - The transport-neutral call identity (side, apiClass, methodName,
|
|
41
|
+
* controllerName?). `apiClass` is what matches a client call to its server handler in the logs.
|
|
42
|
+
* @param requestDto - The request DTO (external multi-param callers synthesize a small object)
|
|
55
43
|
* @param method - The method to execute
|
|
56
|
-
* @param options - `allowVoidResponse: true` opts OUT of the strict falsy-response guard, for
|
|
57
|
-
* callers that legitimately return void/undefined (e.g. a local firestore wrapper whose
|
|
58
|
-
* `setDocument` returns void, or a `getDocById` miss returning undefined). Defaults to strict,
|
|
59
|
-
* so RPC callers (LogApiFilter, ProxyClient) keep the safety net: an HTTP endpoint returning
|
|
60
|
-
* nothing is almost always a bug.
|
|
61
44
|
*
|
|
62
45
|
* Correlation fields (requestId, tenantId, ...) are NOT stamped here — a logging BACKEND owns that,
|
|
63
46
|
* reading RequestContext on every record. What IS stamped here is the per-call `api` tag, and only
|
|
@@ -66,13 +49,11 @@ class LogApiCallImpl {
|
|
|
66
49
|
* it. Cost: only the `[API-*]` req/resp lines carry `api`, not lines emitted mid-call — which is
|
|
67
50
|
* exactly what the GCP filters (`jsonPayload.api.*`) want.
|
|
68
51
|
*/
|
|
69
|
-
async execute(
|
|
52
|
+
async execute(methodInfo,
|
|
70
53
|
// webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary (matches ProxyClient)
|
|
71
54
|
requestDto,
|
|
72
55
|
// webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary
|
|
73
|
-
method
|
|
74
|
-
// webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary
|
|
75
|
-
) {
|
|
56
|
+
method) {
|
|
76
57
|
// Throws if no ApiCallContext was installed at startup, or there is no active scope to stamp
|
|
77
58
|
// into (loud misconfiguration — an api call with nowhere to tag is a bug).
|
|
78
59
|
const ctx = ApiCallContext_1.ApiCallContextHolder.get();
|
|
@@ -81,8 +62,9 @@ class LogApiCallImpl {
|
|
|
81
62
|
'RequestContext.run(...) a server filter opens; in a browser, build ClientHttpBrowserFactory first.');
|
|
82
63
|
}
|
|
83
64
|
const key = WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.API_CALL_INFO;
|
|
65
|
+
const side = methodInfo.side;
|
|
84
66
|
const server = side === 'server';
|
|
85
|
-
const
|
|
67
|
+
const id = `${methodInfo.apiClass}.${methodInfo.methodName}`;
|
|
86
68
|
// set → emit → remove, as ONE synchronous span: the tag is live only while the logger reads it,
|
|
87
69
|
// never across an await, so a single browser global slot can never be clobbered by a concurrent call.
|
|
88
70
|
const stamp = (info, emit) => {
|
|
@@ -92,13 +74,11 @@ class LogApiCallImpl {
|
|
|
92
74
|
};
|
|
93
75
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- LogApiCall logs errors before re-throwing to caller
|
|
94
76
|
try {
|
|
95
|
-
stamp(new ApiCallInfo_1.ApiCallInfo(
|
|
77
|
+
stamp(new ApiCallInfo_1.ApiCallInfo(methodInfo, 'request', undefined), () => log.info(`[API-${side}-req] ${id} request=${JSON.stringify(requestDto)}`));
|
|
96
78
|
if (!requestDto)
|
|
97
|
-
throw new Error(`Request cannot be null and was from ${
|
|
79
|
+
throw new Error(`Request cannot be null and was from ${id}`);
|
|
98
80
|
const response = await method(requestDto);
|
|
99
|
-
|
|
100
|
-
throw new Error(`Response cannot be null and was from ${cls}.${meta.methodName}`);
|
|
101
|
-
stamp(new ApiCallInfo_1.ApiCallInfo(side, 'response', 'success', meta.path, meta.methodName, cls), () => log.info(`[API-${side}-resp-SUCCESS] ${cls}.${meta.methodName} response=${JSON.stringify(response)}`));
|
|
81
|
+
stamp(new ApiCallInfo_1.ApiCallInfo(methodInfo, 'response', 'success'), () => log.info(`[API-${side}-resp-SUCCESS] ${id} response=${JSON.stringify(response)}`));
|
|
102
82
|
return response;
|
|
103
83
|
}
|
|
104
84
|
catch (err) {
|
|
@@ -108,9 +88,9 @@ class LogApiCallImpl {
|
|
|
108
88
|
// Side-dependent: a 4xx the SERVER raised is a handled non-failure (OTHER); the same 4xx a
|
|
109
89
|
// CLIENT receives means its call FAILED. HttpUserError (266) is never a failure, either side.
|
|
110
90
|
const isUser = this.isUserError(error, server);
|
|
111
|
-
stamp(new ApiCallInfo_1.ApiCallInfo(
|
|
112
|
-
? log.warn(`[API-${side}-resp-OTHER] ${
|
|
113
|
-
: log.error(`[API-${side}-resp-FAIL] ${
|
|
91
|
+
stamp(new ApiCallInfo_1.ApiCallInfo(methodInfo, 'response', isUser ? 'success' : 'failure'), () => isUser
|
|
92
|
+
? log.warn(`[API-${side}-resp-OTHER] ${id} errorType=${errorType}`)
|
|
93
|
+
: log.error(`[API-${side}-resp-FAIL] ${id} errorType=${errorType} error=${errorMessage}`));
|
|
114
94
|
throw error;
|
|
115
95
|
}
|
|
116
96
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogApiCall.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/LogApiCall.ts"],"names":[],"mappings":"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","sourcesContent":["import {RouteMetadata} from \"./decorators\";\nimport {\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpUserError,\n} from './errors';\nimport {toError} from \"../lib/errorUtils\";\nimport {LogManager} from \"../logging/LogManager\";\nimport {ApiCallInfo, ApiSide} from \"./ApiCallInfo\";\nimport {ApiCallContextHolder} from \"./ApiCallContext\";\nimport {WebpiecesCoreHeaders} from \"./WebpiecesCoreHeaders\";\n\nconst log = LogManager.getLogger('LogApiCall');\n\n/**\n * Options for {@link LogApiCallImpl.execute}. `allowVoidResponse` opts OUT of the strict\n * falsy-response guard for callers that legitimately return void/undefined (e.g. a local firestore\n * wrapper). Defaults to strict so RPC callers keep the safety net.\n */\nexport class LogApiCallOptions {\n allowVoidResponse?: boolean;\n\n constructor(allowVoidResponse?: boolean) {\n this.allowVoidResponse = allowVoidResponse;\n }\n}\n\n/**\n * LogApiCall - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and\n * client-side (ProxyClient) for one consistent logging shape across the framework.\n *\n * TWO things happen around each call:\n * 1. Text lines are emitted (the human-readable `[API-...]` patterns below).\n * 2. A structured {@link ApiCallInfo} tag is stamped into the ambient request context via the\n * {@link ApiCallContextHolder} seam, so EVERY log line emitted during the call (not just the\n * req/resp lines) inherits a filterable `api` object — surfacing in GCP as\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * BROWSER-SAFE: this lives in core-util and runs in the browser bundle (via ProxyClient →\n * BrowserProxyClient), so it MUST NOT import `RequestContext` (Node async_hooks, and a circular dep).\n * It stamps through the {@link ApiCallContext} seam instead: `setupRuntime` installs a\n * RequestContext-backed impl on a Node server, and `ClientHttpBrowserFactory` a module-global impl in a\n * browser. If neither ran, {@link ApiCallContextHolder.get} throws (loud misconfiguration).\n *\n * Singleton, mirroring `RequestContext`: use the exported {@link LogApiCall} constant, not `new`.\n *\n * Logging format patterns:\n * - [API-{side}-req] ClassName.methodName request={...}\n * - [API-{side}-resp-SUCCESS] ClassName.methodName response={...}\n * - [API-{side}-resp-OTHER] ClassName.methodName errorType={...} (user errors)\n * - [API-{side}-resp-FAIL] ClassName.methodName error={...} (server errors)\n */\nexport class LogApiCallImpl {\n\n /**\n * Execute an API call with logging + `api` context-tagging around it.\n *\n * @param side - 'client' (outbound call this process made) or 'server' (inbound call it handled)\n * @param meta - Route metadata with controllerClassName and methodName\n * @param requestDto - The request DTO\n * @param method - The method to execute\n * @param options - `allowVoidResponse: true` opts OUT of the strict falsy-response guard, for\n * callers that legitimately return void/undefined (e.g. a local firestore wrapper whose\n * `setDocument` returns void, or a `getDocById` miss returning undefined). Defaults to strict,\n * so RPC callers (LogApiFilter, ProxyClient) keep the safety net: an HTTP endpoint returning\n * nothing is almost always a bug.\n *\n * Correlation fields (requestId, tenantId, ...) are NOT stamped here — a logging BACKEND owns that,\n * reading RequestContext on every record. What IS stamped here is the per-call `api` tag, and only\n * for the SYNCHRONOUS span of each log line: set → log → remove. Because the tag is never held across\n * `await method(...)`, a concurrent browser call (single-threaded, one global slot) can never clobber\n * it. Cost: only the `[API-*]` req/resp lines carry `api`, not lines emitted mid-call — which is\n * exactly what the GCP filters (`jsonPayload.api.*`) want.\n */\n public async execute(\n side: ApiSide,\n meta: RouteMetadata,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary (matches ProxyClient)\n requestDto: any,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary\n method: (dto: any) => Promise<any>,\n options?: LogApiCallOptions\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary\n ): Promise<any> {\n // Throws if no ApiCallContext was installed at startup, or there is no active scope to stamp\n // into (loud misconfiguration — an api call with nowhere to tag is a bug).\n const ctx = ApiCallContextHolder.get();\n if (!ctx.isActive()) {\n throw new Error(\n 'LogApiCall requires an ACTIVE ApiCallContext. On a Node server, run inside the ' +\n 'RequestContext.run(...) a server filter opens; in a browser, build ClientHttpBrowserFactory first.',\n );\n }\n const key = WebpiecesCoreHeaders.API_CALL_INFO;\n const server = side === 'server';\n const cls = meta.controllerClassName;\n // set → emit → remove, as ONE synchronous span: the tag is live only while the logger reads it,\n // never across an await, so a single browser global slot can never be clobbered by a concurrent call.\n const stamp = (info: ApiCallInfo, emit: () => void): void => {\n ctx.set(key, info);\n emit();\n ctx.remove(key);\n };\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- LogApiCall logs errors before re-throwing to caller\n try {\n stamp(new ApiCallInfo(side, 'request', undefined, meta.path, meta.methodName, cls), () =>\n log.info(`[API-${side}-req] ${cls}.${meta.methodName} ${meta.path} request=${JSON.stringify(requestDto)}`));\n\n if(!requestDto)\n throw new Error(`Request cannot be null and was from ${cls}.${meta.methodName}`);\n\n const response = await method(requestDto);\n\n if(!options?.allowVoidResponse && !response)\n throw new Error(`Response cannot be null and was from ${cls}.${meta.methodName}`);\n\n stamp(new ApiCallInfo(side, 'response', 'success', meta.path, meta.methodName, cls), () =>\n log.info(`[API-${side}-resp-SUCCESS] ${cls}.${meta.methodName} response=${JSON.stringify(response)}`));\n\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n const errorType = error.constructor.name;\n const errorMessage = error.message;\n // Side-dependent: a 4xx the SERVER raised is a handled non-failure (OTHER); the same 4xx a\n // CLIENT receives means its call FAILED. HttpUserError (266) is never a failure, either side.\n const isUser = this.isUserError(error, server);\n\n stamp(new ApiCallInfo(side, 'response', isUser ? 'success' : 'failure', meta.path, meta.methodName, cls), () =>\n isUser\n ? log.warn(`[API-${side}-resp-OTHER] ${cls}.${meta.methodName} errorType=${errorType}`)\n : log.error(`[API-${side}-resp-FAIL] ${cls}.${meta.methodName} errorType=${errorType} error=${errorMessage}`));\n throw error;\n }\n }\n\n /**\n * Is this error a NON-failure for HEALTH/METRICS — the process working CORRECTLY (log OTHER, api\n * result:'success') — rather than a real failure to surface (log FAIL, result:'failure')?\n *\n * The question is \"are things WORKING?\", NOT \"was it an HTTP 4xx vs 5xx\" — and the two are not the\n * same (see 408 below). Classification is by portable Error TYPE, never by transport: LogApiCall runs\n * deep in the stack (in-process calls, pubsub/queue handlers, HTTP — one code path) and only ever\n * sees a thrown Error. The Http* classes are poorly named for that (the `Http` prefix is historical),\n * but they ARE portable error types that travel with the throw anywhere, so matching the TYPE works\n * with or without any HTTP in the picture. Do NOT swap this for an `error.code` 4xx range check — that\n * both re-couples to HTTP AND would bury 408.\n *\n * SERVER — a healthy server correctly rejecting a CLIENT'S mistake is metrics NOISE, not a failure:\n * - HttpBadRequestError (400) \"your request is malformed\"\n * - HttpUnauthorizedError (401) \"you're not authenticated\"\n * - HttpForbiddenError (403) \"authenticated, but not allowed\"\n * - HttpNotFoundError (404) \"wrong url / no such entity\" (EndpointNotFoundError is a subclass)\n * → the server is fine, the caller erred → result:'success', logged OTHER.\n *\n * SERVER — something may actually be WRONG, so SURFACE it (result:'failure', logged FAIL):\n * - HttpTimeoutError (408): a 4xx, but the client may NEVER have seen the response — deliberately\n * absent from the list below so it counts as a failure.\n * - 500 / 502 / 504 / 598, and any non-Http Error: real failures.\n *\n * HttpUserError (266): ALWAYS a non-failure, server OR client — an expected \"user made a mistake\" signal.\n * CLIENT: receiving ANY error except 266 means the outbound call FAILED → result:'failure'.\n *\n * @param error - The already-normalized error (callers pass toError(err), never a raw catch value)\n * @param server - True when this side is the SERVER handling an inbound call; false for a CLIENT's outbound call\n * @returns true if this should be treated as a non-failure (OTHER / result:'success')\n */\n isUserError(error: Error, server: boolean): boolean {\n // 266 is the one error that is never a failure, on either side.\n if (error instanceof HttpUserError) {\n return true;\n }\n // A client that RECEIVED any error (except the 266 above) made a failed call.\n if (!server) {\n return false;\n }\n // SERVER: only a healthy rejection of the caller's mistake is a non-failure. Note 408\n // (HttpTimeoutError) is intentionally NOT here — the client may never have seen the response.\n return (\n error instanceof HttpBadRequestError ||\n error instanceof HttpUnauthorizedError ||\n error instanceof HttpForbiddenError ||\n error instanceof HttpNotFoundError\n );\n }\n}\n\n/**\n * The process-wide {@link LogApiCallImpl} singleton — mirrors the `RequestContext` export pattern.\n * Callers use `LogApiCall.execute(...)`, never `new`.\n */\nexport const LogApiCall = new LogApiCallImpl();\n"]}
|
|
1
|
+
{"version":3,"file":"LogApiCall.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/LogApiCall.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpUserError,\n} from './errors';\nimport {toError} from \"../lib/errorUtils\";\nimport {LogManager} from \"../logging/LogManager\";\nimport {ApiCallInfo} from \"./ApiCallInfo\";\nimport {ApiMethodInfo} from \"./ApiMethodInfo\";\nimport {ApiCallContextHolder} from \"./ApiCallContext\";\nimport {WebpiecesCoreHeaders} from \"./WebpiecesCoreHeaders\";\n\nconst log = LogManager.getLogger('LogApiCall');\n\n/**\n * LogApiCall - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and\n * client-side (ProxyClient) for one consistent logging shape across the framework.\n *\n * TWO things happen around each call:\n * 1. Text lines are emitted (the human-readable `[API-...]` patterns below).\n * 2. A structured {@link ApiCallInfo} tag is stamped into the ambient request context via the\n * {@link ApiCallContextHolder} seam, so EVERY log line emitted during the call (not just the\n * req/resp lines) inherits a filterable `api` object — surfacing in GCP as\n * `jsonPayload.api.{method.{side,apiClass,methodName,controllerName},type,result}`.\n *\n * BROWSER-SAFE: this lives in core-util and runs in the browser bundle (via ProxyClient →\n * BrowserProxyClient), so it MUST NOT import `RequestContext` (Node async_hooks, and a circular dep).\n * It stamps through the {@link ApiCallContext} seam instead: `setupRuntime` installs a\n * RequestContext-backed impl on a Node server, and `ClientHttpBrowserFactory` a module-global impl in a\n * browser. If neither ran, {@link ApiCallContextHolder.get} throws (loud misconfiguration).\n *\n * Singleton, mirroring `RequestContext`: use the exported {@link LogApiCall} constant, not `new`.\n *\n * Logging format patterns:\n * - [API-{side}-req] ClassName.methodName request={...}\n * - [API-{side}-resp-SUCCESS] ClassName.methodName response={...}\n * - [API-{side}-resp-OTHER] ClassName.methodName errorType={...} (user errors)\n * - [API-{side}-resp-FAIL] ClassName.methodName error={...} (server errors)\n */\nexport class LogApiCallImpl {\n\n /**\n * Execute an API call with logging + `api` context-tagging around it.\n *\n * @param methodInfo - The transport-neutral call identity (side, apiClass, methodName,\n * controllerName?). `apiClass` is what matches a client call to its server handler in the logs.\n * @param requestDto - The request DTO (external multi-param callers synthesize a small object)\n * @param method - The method to execute\n *\n * Correlation fields (requestId, tenantId, ...) are NOT stamped here — a logging BACKEND owns that,\n * reading RequestContext on every record. What IS stamped here is the per-call `api` tag, and only\n * for the SYNCHRONOUS span of each log line: set → log → remove. Because the tag is never held across\n * `await method(...)`, a concurrent browser call (single-threaded, one global slot) can never clobber\n * it. Cost: only the `[API-*]` req/resp lines carry `api`, not lines emitted mid-call — which is\n * exactly what the GCP filters (`jsonPayload.api.*`) want.\n */\n public async execute(\n methodInfo: ApiMethodInfo,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary (matches ProxyClient)\n requestDto: any,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary\n method: (dto: any) => Promise<any>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary\n ): Promise<any> {\n // Throws if no ApiCallContext was installed at startup, or there is no active scope to stamp\n // into (loud misconfiguration — an api call with nowhere to tag is a bug).\n const ctx = ApiCallContextHolder.get();\n if (!ctx.isActive()) {\n throw new Error(\n 'LogApiCall requires an ACTIVE ApiCallContext. On a Node server, run inside the ' +\n 'RequestContext.run(...) a server filter opens; in a browser, build ClientHttpBrowserFactory first.',\n );\n }\n const key = WebpiecesCoreHeaders.API_CALL_INFO;\n const side = methodInfo.side;\n const server = side === 'server';\n const id = `${methodInfo.apiClass}.${methodInfo.methodName}`;\n // set → emit → remove, as ONE synchronous span: the tag is live only while the logger reads it,\n // never across an await, so a single browser global slot can never be clobbered by a concurrent call.\n const stamp = (info: ApiCallInfo, emit: () => void): void => {\n ctx.set(key, info);\n emit();\n ctx.remove(key);\n };\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- LogApiCall logs errors before re-throwing to caller\n try {\n stamp(new ApiCallInfo(methodInfo, 'request', undefined), () =>\n log.info(`[API-${side}-req] ${id} request=${JSON.stringify(requestDto)}`));\n\n if(!requestDto)\n throw new Error(`Request cannot be null and was from ${id}`);\n\n const response = await method(requestDto);\n\n stamp(new ApiCallInfo(methodInfo, 'response', 'success'), () =>\n log.info(`[API-${side}-resp-SUCCESS] ${id} response=${JSON.stringify(response)}`));\n\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n const errorType = error.constructor.name;\n const errorMessage = error.message;\n // Side-dependent: a 4xx the SERVER raised is a handled non-failure (OTHER); the same 4xx a\n // CLIENT receives means its call FAILED. HttpUserError (266) is never a failure, either side.\n const isUser = this.isUserError(error, server);\n\n stamp(new ApiCallInfo(methodInfo, 'response', isUser ? 'success' : 'failure'), () =>\n isUser\n ? log.warn(`[API-${side}-resp-OTHER] ${id} errorType=${errorType}`)\n : log.error(`[API-${side}-resp-FAIL] ${id} errorType=${errorType} error=${errorMessage}`));\n throw error;\n }\n }\n\n /**\n * Is this error a NON-failure for HEALTH/METRICS — the process working CORRECTLY (log OTHER, api\n * result:'success') — rather than a real failure to surface (log FAIL, result:'failure')?\n *\n * The question is \"are things WORKING?\", NOT \"was it an HTTP 4xx vs 5xx\" — and the two are not the\n * same (see 408 below). Classification is by portable Error TYPE, never by transport: LogApiCall runs\n * deep in the stack (in-process calls, pubsub/queue handlers, HTTP — one code path) and only ever\n * sees a thrown Error. The Http* classes are poorly named for that (the `Http` prefix is historical),\n * but they ARE portable error types that travel with the throw anywhere, so matching the TYPE works\n * with or without any HTTP in the picture. Do NOT swap this for an `error.code` 4xx range check — that\n * both re-couples to HTTP AND would bury 408.\n *\n * SERVER — a healthy server correctly rejecting a CLIENT'S mistake is metrics NOISE, not a failure:\n * - HttpBadRequestError (400) \"your request is malformed\"\n * - HttpUnauthorizedError (401) \"you're not authenticated\"\n * - HttpForbiddenError (403) \"authenticated, but not allowed\"\n * - HttpNotFoundError (404) \"wrong url / no such entity\" (EndpointNotFoundError is a subclass)\n * → the server is fine, the caller erred → result:'success', logged OTHER.\n *\n * SERVER — something may actually be WRONG, so SURFACE it (result:'failure', logged FAIL):\n * - HttpTimeoutError (408): a 4xx, but the client may NEVER have seen the response — deliberately\n * absent from the list below so it counts as a failure.\n * - 500 / 502 / 504 / 598, and any non-Http Error: real failures.\n *\n * HttpUserError (266): ALWAYS a non-failure, server OR client — an expected \"user made a mistake\" signal.\n * CLIENT: receiving ANY error except 266 means the outbound call FAILED → result:'failure'.\n *\n * @param error - The already-normalized error (callers pass toError(err), never a raw catch value)\n * @param server - True when this side is the SERVER handling an inbound call; false for a CLIENT's outbound call\n * @returns true if this should be treated as a non-failure (OTHER / result:'success')\n */\n isUserError(error: Error, server: boolean): boolean {\n // 266 is the one error that is never a failure, on either side.\n if (error instanceof HttpUserError) {\n return true;\n }\n // A client that RECEIVED any error (except the 266 above) made a failed call.\n if (!server) {\n return false;\n }\n // SERVER: only a healthy rejection of the caller's mistake is a non-failure. Note 408\n // (HttpTimeoutError) is intentionally NOT here — the client may never have seen the response.\n return (\n error instanceof HttpBadRequestError ||\n error instanceof HttpUnauthorizedError ||\n error instanceof HttpForbiddenError ||\n error instanceof HttpNotFoundError\n );\n }\n}\n\n/**\n * The process-wide {@link LogApiCallImpl} singleton — mirrors the `RequestContext` export pattern.\n * Callers use `LogApiCall.execute(...)`, never `new`.\n */\nexport const LogApiCall = new LogApiCallImpl();\n"]}
|
|
@@ -42,6 +42,19 @@ export declare class WebpiecesCoreHeaders {
|
|
|
42
42
|
* `buildLogFields()` string map deliberately skips it (typeof-string guard).
|
|
43
43
|
*/
|
|
44
44
|
static readonly API_CALL_INFO: ContextKey;
|
|
45
|
+
/**
|
|
46
|
+
* The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by
|
|
47
|
+
* `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels
|
|
48
|
+
* through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every
|
|
49
|
+
* log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /
|
|
50
|
+
* `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.
|
|
51
|
+
*
|
|
52
|
+
* - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound
|
|
53
|
+
* method/path, never the caller's. Outbound client calls never set these (no inbound path).
|
|
54
|
+
* - `isLogged` TRUE → emitted by the logging backends as plain strings.
|
|
55
|
+
*/
|
|
56
|
+
static readonly HTTP_METHOD: ContextKey;
|
|
57
|
+
static readonly REQUEST_PATH: ContextKey;
|
|
45
58
|
/**
|
|
46
59
|
* NO CREDENTIAL KEYS LIVE HERE.
|
|
47
60
|
*
|
|
@@ -45,6 +45,19 @@ class WebpiecesCoreHeaders {
|
|
|
45
45
|
* `buildLogFields()` string map deliberately skips it (typeof-string guard).
|
|
46
46
|
*/
|
|
47
47
|
static API_CALL_INFO = new ContextKey_1.ContextKey('api', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);
|
|
48
|
+
/**
|
|
49
|
+
* The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by
|
|
50
|
+
* `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels
|
|
51
|
+
* through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every
|
|
52
|
+
* log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /
|
|
53
|
+
* `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.
|
|
54
|
+
*
|
|
55
|
+
* - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound
|
|
56
|
+
* method/path, never the caller's. Outbound client calls never set these (no inbound path).
|
|
57
|
+
* - `isLogged` TRUE → emitted by the logging backends as plain strings.
|
|
58
|
+
*/
|
|
59
|
+
static HTTP_METHOD = new ContextKey_1.ContextKey('httpMethod', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);
|
|
60
|
+
static REQUEST_PATH = new ContextKey_1.ContextKey('requestPath', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);
|
|
48
61
|
/**
|
|
49
62
|
* NO CREDENTIAL KEYS LIVE HERE.
|
|
50
63
|
*
|
|
@@ -72,6 +85,8 @@ class WebpiecesCoreHeaders {
|
|
|
72
85
|
WebpiecesCoreHeaders.USER_ROLES,
|
|
73
86
|
WebpiecesCoreHeaders.RECORDING,
|
|
74
87
|
WebpiecesCoreHeaders.API_CALL_INFO,
|
|
88
|
+
WebpiecesCoreHeaders.HTTP_METHOD,
|
|
89
|
+
WebpiecesCoreHeaders.REQUEST_PATH,
|
|
75
90
|
];
|
|
76
91
|
}
|
|
77
92
|
}
|
|
@@ -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,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;;;;;;;;;;;;;;;OAeG;IAEH;;OAEG;IACH,MAAM,CAAC,aAAa;QAChB,OAAO;YACH,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,OAAO;YAC5B,oBAAoB,CAAC,MAAM;YAC3B,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,SAAS;YAC9B,oBAAoB,CAAC,aAAa;
|
|
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,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,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;;AA/EL,oDAgFC","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 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.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,9 +31,11 @@ export { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';
|
|
|
31
31
|
export { ContextReader } from './http/ContextReader';
|
|
32
32
|
export type { ContextRead, StructuredContextRead } from './http/ContextReader';
|
|
33
33
|
export { ContextMgr } from './http/ContextMgr';
|
|
34
|
-
export { LogApiCall, LogApiCallImpl
|
|
34
|
+
export { LogApiCall, LogApiCallImpl } from './http/LogApiCall';
|
|
35
35
|
export { ApiCallInfo } from './http/ApiCallInfo';
|
|
36
|
-
export type {
|
|
36
|
+
export type { ApiType, ApiResult } from './http/ApiCallInfo';
|
|
37
|
+
export { ApiMethodInfo } from './http/ApiMethodInfo';
|
|
38
|
+
export type { ApiSide } from './http/ApiMethodInfo';
|
|
37
39
|
export { ApiCallContextHolder } from './http/ApiCallContext';
|
|
38
40
|
export type { ApiCallContext } from './http/ApiCallContext';
|
|
39
41
|
export { TestCaseRecorder, RecorderKeys } from './http/recorder/TestCaseRecorder';
|
package/src/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.HttpVendorError = exports.HttpInternalServerError = exports.HttpGatewayTimeoutError = exports.HttpBadGatewayError = exports.HttpTimeoutError = exports.HttpForbiddenError = exports.HttpUnauthorizedError = exports.HttpBadRequestError = exports.EndpointNotFoundError = exports.HttpNotFoundError = exports.HttpError = exports.ProtocolError = exports.Secrets = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.validateNoConflictingDecorators = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMode = exports.getAuthMeta = exports.isApiPath = exports.isFormPost = exports.getEndpointOptions = exports.getEndpoints = exports.getApiPath = exports.Queue = exports.PubSub = exports.Rpc = exports.AuthSharedSecret = exports.AuthOidc = exports.Auth = exports.AuthJwt = exports.Public = exports.AuthenticationConfig = exports.Authentication = exports.Endpoint = exports.ApiPath = exports.LogManager = exports.ConsoleLoggerFactory = exports.ConsoleLogger = exports.DESIGN_METADATA_KEYS = exports.isDocumentDesign = exports.DocumentDesign = exports.ContextTuple = exports.ContextKey = exports.toError = void 0;
|
|
12
|
-
exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.ApiCallContextHolder = exports.
|
|
12
|
+
exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.ApiCallContextHolder = exports.ApiMethodInfo = exports.ApiCallInfo = exports.LogApiCallImpl = exports.LogApiCall = exports.ContextMgr = exports.WebpiecesCoreHeaders = exports.templateDeriver = exports.ErrorWireForm = exports.ClientRegistry = exports.HeaderRegistry = exports.DateTimeUtil = exports.TimeUtil = exports.DateUtil = exports.InstantUtil = exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = exports.ENTITY_NOT_FOUND = exports.HttpUserError = void 0;
|
|
13
13
|
var errorUtils_1 = require("./lib/errorUtils");
|
|
14
14
|
Object.defineProperty(exports, "toError", { enumerable: true, get: function () { return errorUtils_1.toError; } });
|
|
15
15
|
var ContextKey_1 = require("./ContextKey");
|
|
@@ -121,11 +121,12 @@ Object.defineProperty(exports, "ContextMgr", { enumerable: true, get: function (
|
|
|
121
121
|
var LogApiCall_1 = require("./http/LogApiCall");
|
|
122
122
|
Object.defineProperty(exports, "LogApiCall", { enumerable: true, get: function () { return LogApiCall_1.LogApiCall; } });
|
|
123
123
|
Object.defineProperty(exports, "LogApiCallImpl", { enumerable: true, get: function () { return LogApiCall_1.LogApiCallImpl; } });
|
|
124
|
-
Object.defineProperty(exports, "LogApiCallOptions", { enumerable: true, get: function () { return LogApiCall_1.LogApiCallOptions; } });
|
|
125
124
|
// The structured `api` tag + the context-writer seam LogApiCall stamps through. The Node
|
|
126
125
|
// RequestContext-backed impl is installed by @webpieces/core-context; the browser gets the no-op.
|
|
127
126
|
var ApiCallInfo_1 = require("./http/ApiCallInfo");
|
|
128
127
|
Object.defineProperty(exports, "ApiCallInfo", { enumerable: true, get: function () { return ApiCallInfo_1.ApiCallInfo; } });
|
|
128
|
+
var ApiMethodInfo_1 = require("./http/ApiMethodInfo");
|
|
129
|
+
Object.defineProperty(exports, "ApiMethodInfo", { enumerable: true, get: function () { return ApiMethodInfo_1.ApiMethodInfo; } });
|
|
129
130
|
var ApiCallContext_1 = require("./http/ApiCallContext");
|
|
130
131
|
Object.defineProperty(exports, "ApiCallContextHolder", { enumerable: true, get: function () { return ApiCallContext_1.ApiCallContextHolder; } });
|
|
131
132
|
// Test-case recording contract (impl lives in http-server; hooks in http-client)
|
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;AAEnB,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,0CAAyC;AAAhC,kGAAA,OAAO,OAAA;AAKhB,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;AAEvB,0FAA0F;AAC1F,4FAA4F;AAC5F,4DAAwD;AAA/C,iHAAA,aAAa,OAAA;AAEtB,0DAAyD;AAAhD,kHAAA,eAAe,OAAA;AACxB,oEAAmE;AAA1D,4HAAA,oBAAoB,OAAA;AAI7B,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';\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 } 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// 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';\nexport type { ContextRead, StructuredContextRead } 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"]}
|