@webpieces/core-util 0.4.956 → 0.4.958
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 +71 -11
- package/src/http/ApiCallInfo.js +81 -7
- package/src/http/ApiCallInfo.js.map +1 -1
- package/src/http/ApiMethodInfo.d.ts +14 -0
- package/src/http/ApiMethodInfo.js +3 -0
- package/src/http/ApiMethodInfo.js.map +1 -1
- package/src/http/CallRegistry.d.ts +5 -10
- package/src/http/CallRegistry.js +21 -43
- package/src/http/CallRegistry.js.map +1 -1
- package/src/http/CallRegistryCompileAssertions.js +1 -1
- package/src/http/CallRegistryCompileAssertions.js.map +1 -1
- package/src/http/CallScope.d.ts +33 -0
- package/src/http/CallScope.js +70 -0
- package/src/http/CallScope.js.map +1 -0
- package/src/http/CallStrategy.d.ts +13 -1
- package/src/http/CallStrategy.js +4 -1
- package/src/http/CallStrategy.js.map +1 -1
- package/src/http/ClientRegistry.d.ts +54 -58
- package/src/http/ClientRegistry.js +72 -73
- package/src/http/ClientRegistry.js.map +1 -1
- package/src/http/ContextMgr.js +12 -2
- package/src/http/ContextMgr.js.map +1 -1
- package/src/http/DeletedAuthCompileAssertions.js +3 -1
- package/src/http/DeletedAuthCompileAssertions.js.map +1 -1
- package/src/http/DestinationTrust.d.ts +33 -4
- package/src/http/DestinationTrust.js +54 -11
- package/src/http/DestinationTrust.js.map +1 -1
- package/src/http/EndpointCompileAssertions.js +12 -6
- package/src/http/EndpointCompileAssertions.js.map +1 -1
- package/src/http/ErrorClassifier.d.ts +20 -27
- package/src/http/ErrorClassifier.js +1 -15
- package/src/http/ErrorClassifier.js.map +1 -1
- package/src/http/ErrorLadderCompileAssertions.d.ts +1 -0
- package/src/http/ErrorLadderCompileAssertions.js +42 -0
- package/src/http/ErrorLadderCompileAssertions.js.map +1 -0
- package/src/http/ErrorTranslator.d.ts +8 -4
- package/src/http/ErrorTranslator.js.map +1 -1
- package/src/http/ExternalContextKeys.d.ts +31 -0
- package/src/http/ExternalContextKeys.js +77 -0
- package/src/http/ExternalContextKeys.js.map +1 -0
- package/src/http/HttpContract.d.ts +45 -7
- package/src/http/HttpContract.js +84 -6
- package/src/http/HttpContract.js.map +1 -1
- package/src/http/HttpEndpointOptions.d.ts +22 -3
- package/src/http/HttpEndpointOptions.js +8 -1
- package/src/http/HttpEndpointOptions.js.map +1 -1
- package/src/http/LogApiCall.d.ts +4 -1
- package/src/http/LogApiCall.js +9 -7
- package/src/http/LogApiCall.js.map +1 -1
- package/src/http/QueryParams.d.ts +47 -0
- package/src/http/QueryParams.js +89 -0
- package/src/http/QueryParams.js.map +1 -0
- package/src/http/RetryStrategy.js +1 -1
- package/src/http/RetryStrategy.js.map +1 -1
- package/src/http/RouteMetadata.d.ts +16 -2
- package/src/http/RouteMetadata.js +19 -3
- package/src/http/RouteMetadata.js.map +1 -1
- package/src/http/RouteMetadataFactory.d.ts +25 -0
- package/src/http/RouteMetadataFactory.js +97 -12
- package/src/http/RouteMetadataFactory.js.map +1 -1
- package/src/http/StreamErrorControl.d.ts +9 -1
- package/src/http/StreamErrorControl.js +14 -3
- package/src/http/StreamErrorControl.js.map +1 -1
- package/src/http/WebpiecesDefaultErrorClassifier.d.ts +2 -2
- package/src/http/WebpiecesDefaultErrorClassifier.js +2 -2
- package/src/http/WebpiecesDefaultErrorClassifier.js.map +1 -1
- package/src/http/WebpiecesDefaultErrorTranslator.d.ts +2 -1
- package/src/http/WebpiecesDefaultErrorTranslator.js +2 -1
- package/src/http/WebpiecesDefaultErrorTranslator.js.map +1 -1
- package/src/http/WpAuthApiKeyCompileAssertions.js +2 -2
- package/src/http/WpAuthApiKeyCompileAssertions.js.map +1 -1
- package/src/http/api-kind.d.ts +3 -2
- package/src/http/api-kind.js +5 -4
- package/src/http/api-kind.js.map +1 -1
- package/src/http/auth-mode.d.ts +8 -2
- package/src/http/auth-mode.js +11 -5
- package/src/http/auth-mode.js.map +1 -1
- package/src/http/decorators.d.ts +39 -21
- package/src/http/decorators.js +69 -37
- package/src/http/decorators.js.map +1 -1
- package/src/http/external-caller.d.ts +12 -17
- package/src/http/external-caller.js +12 -17
- package/src/http/external-caller.js.map +1 -1
- package/src/http/http-parameter-decorators.d.ts +17 -3
- package/src/http/http-parameter-decorators.js +27 -6
- package/src/http/http-parameter-decorators.js.map +1 -1
- package/src/index.d.ts +14 -9
- package/src/index.js +32 -13
- package/src/index.js.map +1 -1
- package/src/ipc/IpcConnection.js +1 -1
- package/src/ipc/IpcConnection.js.map +1 -1
- package/src/ipc/IpcLogging.js +1 -1
- package/src/ipc/IpcLogging.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiMethodInfo } from './ApiMethodInfo';
|
|
2
|
+
import { MaskSpec } from './LogFieldMask';
|
|
2
3
|
/**
|
|
3
4
|
* ApiCallInfo - the structured tag stamped into RequestContext around every API call
|
|
4
5
|
* (by {@link LogApiCallImpl}), so ANY log line emitted during the call inherits a filterable
|
|
@@ -15,6 +16,7 @@ import { ApiMethodInfo } from './ApiMethodInfo';
|
|
|
15
16
|
* - `jsonPayload.api.responseSize>100000` — the fat responses (the ones that get chunked)
|
|
16
17
|
* - `jsonPayload.api.responseCount>100` — responses containing many logical items
|
|
17
18
|
* - `jsonPayload.api:*` — "API traffic only" (tracing + the recorder)
|
|
19
|
+
* - `jsonPayload.api.http.status=404` — an HTTP client call the vendor answered 404 (#1339)
|
|
18
20
|
*
|
|
19
21
|
* IMPORTANT: the field names here (and on the nested {@link ApiMethodInfo}) ARE the GCP field names —
|
|
20
22
|
* rename a field and the filter renames with it. The identity lives NESTED under `api.method`
|
|
@@ -47,6 +49,46 @@ export type ApiCallDirection = 'request' | 'response';
|
|
|
47
49
|
export type ApiResult = 'success' | 'failure';
|
|
48
50
|
/** Re-exported from {@link ApiMethodInfo} (its true home) so existing `ApiSide` imports keep working. */
|
|
49
51
|
export type { ApiSide } from './ApiMethodInfo';
|
|
52
|
+
/**
|
|
53
|
+
* The HTTP facts of ONE client call's last transmission — surfaces as `jsonPayload.api.http.*`.
|
|
54
|
+
* Filled ONLY by the HTTP client (node and browser), which is the one layer that has a verb, a url
|
|
55
|
+
* and a status; in-process, pubsub and cloud-task calls leave {@link ApiCallInfo.http} unset. It
|
|
56
|
+
* exists because a vendor's 404 and its 500 were indistinguishable in the logs when only the DTOs
|
|
57
|
+
* were recorded.
|
|
58
|
+
*
|
|
59
|
+
* Never headers or tokens: the url is MASKED ({@link maskUrl}) before it gets here.
|
|
60
|
+
*/
|
|
61
|
+
export declare class HttpCallFacts {
|
|
62
|
+
/** The HTTP verb, e.g. `GET`. */
|
|
63
|
+
readonly method: string;
|
|
64
|
+
/** The final absolute url, masked: no userinfo, and `@MaskLog` field names' query values redacted. */
|
|
65
|
+
readonly url: string;
|
|
66
|
+
/** The status of the last response received. */
|
|
67
|
+
readonly status: number;
|
|
68
|
+
constructor(
|
|
69
|
+
/** The HTTP verb, e.g. `GET`. */
|
|
70
|
+
method: string,
|
|
71
|
+
/** The final absolute url, masked: no userinfo, and `@MaskLog` field names' query values redacted. */
|
|
72
|
+
url: string,
|
|
73
|
+
/** The status of the last response received. */
|
|
74
|
+
status: number);
|
|
75
|
+
/**
|
|
76
|
+
* The url as it may be logged: any `user:password@` userinfo is dropped, and the value of every
|
|
77
|
+
* query key the endpoint's `@MaskLog` names is replaced with `*****` (a `?token=` the DTO mask
|
|
78
|
+
* already hides must not reappear in the url).
|
|
79
|
+
*/
|
|
80
|
+
static maskUrl(url: string, mask: MaskSpec | undefined): string;
|
|
81
|
+
private static decode;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The per-call slot the HTTP client fills with {@link HttpCallFacts} as each response arrives, and
|
|
85
|
+
* {@link LogApiCallImpl} reads when it stamps the response tag. A holder rather than a return value
|
|
86
|
+
* because the facts are known DEEP in the transport while the tag is written at the top of the call;
|
|
87
|
+
* the last transmission (after redirects, a 401 refresh or a retry) wins.
|
|
88
|
+
*/
|
|
89
|
+
export declare class HttpCallFactsHolder {
|
|
90
|
+
facts: HttpCallFacts | undefined;
|
|
91
|
+
}
|
|
50
92
|
export declare class ApiCallInfo {
|
|
51
93
|
/** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under
|
|
52
94
|
* `jsonPayload.api.method`. */
|
|
@@ -59,11 +101,13 @@ export declare class ApiCallInfo {
|
|
|
59
101
|
* request has no duration yet. Present on BOTH the success and failure paths, so
|
|
60
102
|
* `jsonPayload.api.durationMs>1000 AND api.result="failure"` finds slow failures.
|
|
61
103
|
*
|
|
62
|
-
* There is deliberately no `statusCode` beside this. LogApiCall runs deep in the
|
|
63
|
-
* in-process calls, pubsub handlers, and cloud-task enqueues — none of which have
|
|
64
|
-
* status — and business logic must not know about HTTP. `result` (see {@link ApiResult})
|
|
65
|
-
* the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
|
|
66
|
-
* portable Error TYPE rather than by status code.
|
|
104
|
+
* There is deliberately no top-level `statusCode` beside this. LogApiCall runs deep in the
|
|
105
|
+
* stack over in-process calls, pubsub handlers, and cloud-task enqueues — none of which have
|
|
106
|
+
* an HTTP status — and business logic must not know about HTTP. `result` (see {@link ApiResult})
|
|
107
|
+
* is the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
|
|
108
|
+
* portable Error TYPE rather than by status code. That decision STANDS (#1339): the HTTP
|
|
109
|
+
* client's status lives in the optional, nested {@link http} — present only where there IS an
|
|
110
|
+
* HTTP exchange, so nothing transport-neutral ever reads it.
|
|
67
111
|
*/
|
|
68
112
|
readonly durationMs?: number | undefined;
|
|
69
113
|
/**
|
|
@@ -81,6 +125,13 @@ export declare class ApiCallInfo {
|
|
|
81
125
|
readonly responseSize?: number | undefined;
|
|
82
126
|
/** Number of logical items in a successful response. RESPONSE tag only, opt-in per call. */
|
|
83
127
|
readonly responseCount?: number | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* The HTTP verb, masked url and status of the call's last transmission — `jsonPayload.api.http.*`.
|
|
130
|
+
* RESPONSE tag only, and only for a call made by the HTTP client (success AND failure, so
|
|
131
|
+
* `api.result="failure" AND api.http.status=429` finds a vendor throttling us). Unset for
|
|
132
|
+
* in-process, pubsub and cloud-task calls. See {@link HttpCallFacts}.
|
|
133
|
+
*/
|
|
134
|
+
readonly http?: HttpCallFacts | undefined;
|
|
84
135
|
constructor(
|
|
85
136
|
/** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under
|
|
86
137
|
* `jsonPayload.api.method`. */
|
|
@@ -92,11 +143,13 @@ export declare class ApiCallInfo {
|
|
|
92
143
|
* request has no duration yet. Present on BOTH the success and failure paths, so
|
|
93
144
|
* `jsonPayload.api.durationMs>1000 AND api.result="failure"` finds slow failures.
|
|
94
145
|
*
|
|
95
|
-
* There is deliberately no `statusCode` beside this. LogApiCall runs deep in the
|
|
96
|
-
* in-process calls, pubsub handlers, and cloud-task enqueues — none of which have
|
|
97
|
-
* status — and business logic must not know about HTTP. `result` (see {@link ApiResult})
|
|
98
|
-
* the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
|
|
99
|
-
* portable Error TYPE rather than by status code.
|
|
146
|
+
* There is deliberately no top-level `statusCode` beside this. LogApiCall runs deep in the
|
|
147
|
+
* stack over in-process calls, pubsub handlers, and cloud-task enqueues — none of which have
|
|
148
|
+
* an HTTP status — and business logic must not know about HTTP. `result` (see {@link ApiResult})
|
|
149
|
+
* is the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
|
|
150
|
+
* portable Error TYPE rather than by status code. That decision STANDS (#1339): the HTTP
|
|
151
|
+
* client's status lives in the optional, nested {@link http} — present only where there IS an
|
|
152
|
+
* HTTP exchange, so nothing transport-neutral ever reads it.
|
|
100
153
|
*/
|
|
101
154
|
durationMs?: number | undefined,
|
|
102
155
|
/**
|
|
@@ -113,5 +166,12 @@ export declare class ApiCallInfo {
|
|
|
113
166
|
* a thrown error produced no response body to measure. Total size, pre-chunking. */
|
|
114
167
|
responseSize?: number | undefined,
|
|
115
168
|
/** Number of logical items in a successful response. RESPONSE tag only, opt-in per call. */
|
|
116
|
-
responseCount?: number | undefined
|
|
169
|
+
responseCount?: number | undefined,
|
|
170
|
+
/**
|
|
171
|
+
* The HTTP verb, masked url and status of the call's last transmission — `jsonPayload.api.http.*`.
|
|
172
|
+
* RESPONSE tag only, and only for a call made by the HTTP client (success AND failure, so
|
|
173
|
+
* `api.result="failure" AND api.http.status=429` finds a vendor throttling us). Unset for
|
|
174
|
+
* in-process, pubsub and cloud-task calls. See {@link HttpCallFacts}.
|
|
175
|
+
*/
|
|
176
|
+
http?: HttpCallFacts | undefined);
|
|
117
177
|
}
|
package/src/http/ApiCallInfo.js
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ApiCallInfo = void 0;
|
|
3
|
+
exports.ApiCallInfo = exports.HttpCallFactsHolder = exports.HttpCallFacts = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The HTTP facts of ONE client call's last transmission — surfaces as `jsonPayload.api.http.*`.
|
|
6
|
+
* Filled ONLY by the HTTP client (node and browser), which is the one layer that has a verb, a url
|
|
7
|
+
* and a status; in-process, pubsub and cloud-task calls leave {@link ApiCallInfo.http} unset. It
|
|
8
|
+
* exists because a vendor's 404 and its 500 were indistinguishable in the logs when only the DTOs
|
|
9
|
+
* were recorded.
|
|
10
|
+
*
|
|
11
|
+
* Never headers or tokens: the url is MASKED ({@link maskUrl}) before it gets here.
|
|
12
|
+
*/
|
|
13
|
+
class HttpCallFacts {
|
|
14
|
+
method;
|
|
15
|
+
url;
|
|
16
|
+
status;
|
|
17
|
+
constructor(
|
|
18
|
+
/** The HTTP verb, e.g. `GET`. */
|
|
19
|
+
method,
|
|
20
|
+
/** The final absolute url, masked: no userinfo, and `@MaskLog` field names' query values redacted. */
|
|
21
|
+
url,
|
|
22
|
+
/** The status of the last response received. */
|
|
23
|
+
status) {
|
|
24
|
+
this.method = method;
|
|
25
|
+
this.url = url;
|
|
26
|
+
this.status = status;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The url as it may be logged: any `user:password@` userinfo is dropped, and the value of every
|
|
30
|
+
* query key the endpoint's `@MaskLog` names is replaced with `*****` (a `?token=` the DTO mask
|
|
31
|
+
* already hides must not reappear in the url).
|
|
32
|
+
*/
|
|
33
|
+
// webpieces-disable no-function-outside-class -- pure static helper beside the data it builds
|
|
34
|
+
static maskUrl(url, mask) {
|
|
35
|
+
const withoutUserInfo = url.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/@?#]*@/i, '$1');
|
|
36
|
+
const queryAt = withoutUserInfo.indexOf('?');
|
|
37
|
+
if (queryAt < 0 || mask === undefined)
|
|
38
|
+
return withoutUserInfo;
|
|
39
|
+
const query = withoutUserInfo
|
|
40
|
+
.slice(queryAt + 1)
|
|
41
|
+
.split('&')
|
|
42
|
+
.map((pair) => {
|
|
43
|
+
const equals = pair.indexOf('=');
|
|
44
|
+
const key = equals < 0 ? pair : pair.slice(0, equals);
|
|
45
|
+
return mask.modeFor(HttpCallFacts.decode(key)) === undefined ? pair : `${key}=*****`;
|
|
46
|
+
})
|
|
47
|
+
.join('&');
|
|
48
|
+
return `${withoutUserInfo.slice(0, queryAt)}?${query}`;
|
|
49
|
+
}
|
|
50
|
+
// webpieces-disable no-function-outside-class -- pure static helper beside the data it builds
|
|
51
|
+
static decode(key) {
|
|
52
|
+
// A malformed escape is logged as written; it can never be a masked field name anyway.
|
|
53
|
+
return /%(?![0-9a-f]{2})/i.test(key) ? key : decodeURIComponent(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.HttpCallFacts = HttpCallFacts;
|
|
57
|
+
/**
|
|
58
|
+
* The per-call slot the HTTP client fills with {@link HttpCallFacts} as each response arrives, and
|
|
59
|
+
* {@link LogApiCallImpl} reads when it stamps the response tag. A holder rather than a return value
|
|
60
|
+
* because the facts are known DEEP in the transport while the tag is written at the top of the call;
|
|
61
|
+
* the last transmission (after redirects, a 401 refresh or a retry) wins.
|
|
62
|
+
*/
|
|
63
|
+
class HttpCallFactsHolder {
|
|
64
|
+
facts = undefined;
|
|
65
|
+
}
|
|
66
|
+
exports.HttpCallFactsHolder = HttpCallFactsHolder;
|
|
4
67
|
class ApiCallInfo {
|
|
5
68
|
method;
|
|
6
69
|
type;
|
|
@@ -9,6 +72,7 @@ class ApiCallInfo {
|
|
|
9
72
|
requestSize;
|
|
10
73
|
responseSize;
|
|
11
74
|
responseCount;
|
|
75
|
+
http;
|
|
12
76
|
constructor(
|
|
13
77
|
/** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under
|
|
14
78
|
* `jsonPayload.api.method`. */
|
|
@@ -20,11 +84,13 @@ class ApiCallInfo {
|
|
|
20
84
|
* request has no duration yet. Present on BOTH the success and failure paths, so
|
|
21
85
|
* `jsonPayload.api.durationMs>1000 AND api.result="failure"` finds slow failures.
|
|
22
86
|
*
|
|
23
|
-
* There is deliberately no `statusCode` beside this. LogApiCall runs deep in the
|
|
24
|
-
* in-process calls, pubsub handlers, and cloud-task enqueues — none of which have
|
|
25
|
-
* status — and business logic must not know about HTTP. `result` (see {@link ApiResult})
|
|
26
|
-
* the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
|
|
27
|
-
* portable Error TYPE rather than by status code.
|
|
87
|
+
* There is deliberately no top-level `statusCode` beside this. LogApiCall runs deep in the
|
|
88
|
+
* stack over in-process calls, pubsub handlers, and cloud-task enqueues — none of which have
|
|
89
|
+
* an HTTP status — and business logic must not know about HTTP. `result` (see {@link ApiResult})
|
|
90
|
+
* is the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
|
|
91
|
+
* portable Error TYPE rather than by status code. That decision STANDS (#1339): the HTTP
|
|
92
|
+
* client's status lives in the optional, nested {@link http} — present only where there IS an
|
|
93
|
+
* HTTP exchange, so nothing transport-neutral ever reads it.
|
|
28
94
|
*/
|
|
29
95
|
durationMs,
|
|
30
96
|
/**
|
|
@@ -41,7 +107,14 @@ class ApiCallInfo {
|
|
|
41
107
|
* a thrown error produced no response body to measure. Total size, pre-chunking. */
|
|
42
108
|
responseSize,
|
|
43
109
|
/** Number of logical items in a successful response. RESPONSE tag only, opt-in per call. */
|
|
44
|
-
responseCount
|
|
110
|
+
responseCount,
|
|
111
|
+
/**
|
|
112
|
+
* The HTTP verb, masked url and status of the call's last transmission — `jsonPayload.api.http.*`.
|
|
113
|
+
* RESPONSE tag only, and only for a call made by the HTTP client (success AND failure, so
|
|
114
|
+
* `api.result="failure" AND api.http.status=429` finds a vendor throttling us). Unset for
|
|
115
|
+
* in-process, pubsub and cloud-task calls. See {@link HttpCallFacts}.
|
|
116
|
+
*/
|
|
117
|
+
http) {
|
|
45
118
|
this.method = method;
|
|
46
119
|
this.type = type;
|
|
47
120
|
this.result = result;
|
|
@@ -49,6 +122,7 @@ class ApiCallInfo {
|
|
|
49
122
|
this.requestSize = requestSize;
|
|
50
123
|
this.responseSize = responseSize;
|
|
51
124
|
this.responseCount = responseCount;
|
|
125
|
+
this.http = http;
|
|
52
126
|
}
|
|
53
127
|
}
|
|
54
128
|
exports.ApiCallInfo = ApiCallInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiCallInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallInfo.ts"],"names":[],"mappings":";;;AAsDA,MAAa,WAAW;IAIP;IACA;IAEA;IAYA;IAUA;IAGA;IAEA;IAjCb;IACI;oCACgC;IACvB,MAAqB,EACrB,IAAsB;IAC/B,sDAAsD;IAC7C,MAAkB;IAC3B;;;;;;;;;;OAUG;IACM,UAAmB;IAC5B;;;;;;;;OAQG;IACM,WAAoB;IAC7B;yFACqF;IAC5E,YAAqB;IAC9B,4FAA4F;IACnF,aAAsB;QA9BtB,WAAM,GAAN,MAAM,CAAe;QACrB,SAAI,GAAJ,IAAI,CAAkB;QAEtB,WAAM,GAAN,MAAM,CAAY;QAYlB,eAAU,GAAV,UAAU,CAAS;QAUnB,gBAAW,GAAX,WAAW,CAAS;QAGpB,iBAAY,GAAZ,YAAY,CAAS;QAErB,kBAAa,GAAb,aAAa,CAAS;IAChC,CAAC;CACP;AApCD,kCAoCC","sourcesContent":["import { ApiMethodInfo } from './ApiMethodInfo';\n\n/**\n * ApiCallInfo - the structured tag stamped into RequestContext around every API call\n * (by {@link LogApiCallImpl}), 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.durationMs>1000` — slow calls, either side\n * - `jsonPayload.api.responseSize>100000` — the fat responses (the ones that get chunked)\n * - `jsonPayload.api.responseCount>100` — responses containing many logical items\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'. */\n/**\n * Which HALF of one api call a log line describes.\n *\n * Renamed from `ApiType`, which collided with the `@ApiType(...)` CONTRACT decorator and read as if\n * it were about the kind of API rather than the direction of one line. Per\n * `.claude/rules/no-backwards-compat.md` the old spelling is deleted rather than aliased, so a caller\n * on the old name stops compiling and the error names this one.\n */\nexport type ApiCallDirection = '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. `success` = {@link CallOutcome.EXPECTED} from {@link ClientRegistry.classify}.\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: ApiCallDirection,\n /** Response only — undefined on the 'request' tag. */\n readonly result?: ApiResult,\n /**\n * Wall-clock milliseconds the call took. RESPONSE tag only (undefined on 'request') — a\n * request has no duration yet. Present on BOTH the success and failure paths, so\n * `jsonPayload.api.durationMs>1000 AND api.result=\"failure\"` finds slow failures.\n *\n * There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over\n * in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP\n * status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is\n * the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by\n * portable Error TYPE rather than by status code.\n */\n readonly durationMs?: number,\n /**\n * Bytes of the serialized request DTO. Stamped on BOTH tags: the 'request' tag reports it as\n * soon as it is known, and the 'response' tag repeats it so one record shows the whole\n * exchange (`api.requestSize` + `api.responseSize` without a join).\n *\n * This is the TOTAL size of the body, measured BEFORE any log chunking — chunking is a\n * transport concern handled by the GCP backends, and a body split across 3 records still\n * reports its one true size here.\n */\n readonly requestSize?: number,\n /** Bytes of the serialized response. RESPONSE tag only, and only when the call succeeded —\n * a thrown error produced no response body to measure. Total size, pre-chunking. */\n readonly responseSize?: number,\n /** Number of logical items in a successful response. RESPONSE tag only, opt-in per call. */\n readonly responseCount?: number,\n ) {}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ApiCallInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallInfo.ts"],"names":[],"mappings":";;;AAwDA;;;;;;;;GAQG;AACH,MAAa,aAAa;IAGT;IAEA;IAEA;IANb;IACI,iCAAiC;IACxB,MAAc;IACvB,sGAAsG;IAC7F,GAAW;IACpB,gDAAgD;IACvC,MAAc;QAJd,WAAM,GAAN,MAAM,CAAQ;QAEd,QAAG,GAAH,GAAG,CAAQ;QAEX,WAAM,GAAN,MAAM,CAAQ;IACxB,CAAC;IAEJ;;;;OAIG;IACH,8FAA8F;IAC9F,MAAM,CAAC,OAAO,CAAC,GAAW,EAAE,IAA0B;QAClD,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,CAAC,qCAAqC,EAAE,IAAI,CAAC,CAAC;QACjF,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,OAAO,GAAG,CAAC,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,eAAe,CAAC;QAC9D,MAAM,KAAK,GAAG,eAAe;aACxB,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;aAClB,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,IAAY,EAAE,EAAE;YAClB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YACtD,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC;QACzF,CAAC,CAAC;aACD,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;IAC3D,CAAC;IAED,8FAA8F;IACtF,MAAM,CAAC,MAAM,CAAC,GAAW;QAC7B,uFAAuF;QACvF,OAAO,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACzE,CAAC;CACJ;AArCD,sCAqCC;AAED;;;;;GAKG;AACH,MAAa,mBAAmB;IAC5B,KAAK,GAA8B,SAAS,CAAC;CAChD;AAFD,kDAEC;AAED,MAAa,WAAW;IAIP;IACA;IAEA;IAcA;IAUA;IAGA;IAEA;IAOA;IA1Cb;IACI;oCACgC;IACvB,MAAqB,EACrB,IAAsB;IAC/B,sDAAsD;IAC7C,MAAkB;IAC3B;;;;;;;;;;;;OAYG;IACM,UAAmB;IAC5B;;;;;;;;OAQG;IACM,WAAoB;IAC7B;yFACqF;IAC5E,YAAqB;IAC9B,4FAA4F;IACnF,aAAsB;IAC/B;;;;;OAKG;IACM,IAAoB;QAvCpB,WAAM,GAAN,MAAM,CAAe;QACrB,SAAI,GAAJ,IAAI,CAAkB;QAEtB,WAAM,GAAN,MAAM,CAAY;QAclB,eAAU,GAAV,UAAU,CAAS;QAUnB,gBAAW,GAAX,WAAW,CAAS;QAGpB,iBAAY,GAAZ,YAAY,CAAS;QAErB,kBAAa,GAAb,aAAa,CAAS;QAOtB,SAAI,GAAJ,IAAI,CAAgB;IAC9B,CAAC;CACP;AA7CD,kCA6CC","sourcesContent":["import { ApiMethodInfo } from './ApiMethodInfo';\nimport { MaskSpec } from './LogFieldMask';\n\n/**\n * ApiCallInfo - the structured tag stamped into RequestContext around every API call\n * (by {@link LogApiCallImpl}), 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.durationMs>1000` — slow calls, either side\n * - `jsonPayload.api.responseSize>100000` — the fat responses (the ones that get chunked)\n * - `jsonPayload.api.responseCount>100` — responses containing many logical items\n * - `jsonPayload.api:*` — \"API traffic only\" (tracing + the recorder)\n * - `jsonPayload.api.http.status=404` — an HTTP client call the vendor answered 404 (#1339)\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'. */\n/**\n * Which HALF of one api call a log line describes.\n *\n * Renamed from `ApiType`, which collided with the `@ApiType(...)` CONTRACT decorator and read as if\n * it were about the kind of API rather than the direction of one line. Per\n * `.claude/rules/no-backwards-compat.md` the old spelling is deleted rather than aliased, so a caller\n * on the old name stops compiling and the error names this one.\n */\nexport type ApiCallDirection = '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. `success` = {@link CallOutcome.EXPECTED} from {@link ClientRegistry.classify}.\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\n/**\n * The HTTP facts of ONE client call's last transmission — surfaces as `jsonPayload.api.http.*`.\n * Filled ONLY by the HTTP client (node and browser), which is the one layer that has a verb, a url\n * and a status; in-process, pubsub and cloud-task calls leave {@link ApiCallInfo.http} unset. It\n * exists because a vendor's 404 and its 500 were indistinguishable in the logs when only the DTOs\n * were recorded.\n *\n * Never headers or tokens: the url is MASKED ({@link maskUrl}) before it gets here.\n */\nexport class HttpCallFacts {\n constructor(\n /** The HTTP verb, e.g. `GET`. */\n readonly method: string,\n /** The final absolute url, masked: no userinfo, and `@MaskLog` field names' query values redacted. */\n readonly url: string,\n /** The status of the last response received. */\n readonly status: number,\n ) {}\n\n /**\n * The url as it may be logged: any `user:password@` userinfo is dropped, and the value of every\n * query key the endpoint's `@MaskLog` names is replaced with `*****` (a `?token=` the DTO mask\n * already hides must not reappear in the url).\n */\n // webpieces-disable no-function-outside-class -- pure static helper beside the data it builds\n static maskUrl(url: string, mask: MaskSpec | undefined): string {\n const withoutUserInfo = url.replace(/^([a-z][a-z0-9+.-]*:\\/\\/)[^/@?#]*@/i, '$1');\n const queryAt = withoutUserInfo.indexOf('?');\n if (queryAt < 0 || mask === undefined) return withoutUserInfo;\n const query = withoutUserInfo\n .slice(queryAt + 1)\n .split('&')\n .map((pair: string) => {\n const equals = pair.indexOf('=');\n const key = equals < 0 ? pair : pair.slice(0, equals);\n return mask.modeFor(HttpCallFacts.decode(key)) === undefined ? pair : `${key}=*****`;\n })\n .join('&');\n return `${withoutUserInfo.slice(0, queryAt)}?${query}`;\n }\n\n // webpieces-disable no-function-outside-class -- pure static helper beside the data it builds\n private static decode(key: string): string {\n // A malformed escape is logged as written; it can never be a masked field name anyway.\n return /%(?![0-9a-f]{2})/i.test(key) ? key : decodeURIComponent(key);\n }\n}\n\n/**\n * The per-call slot the HTTP client fills with {@link HttpCallFacts} as each response arrives, and\n * {@link LogApiCallImpl} reads when it stamps the response tag. A holder rather than a return value\n * because the facts are known DEEP in the transport while the tag is written at the top of the call;\n * the last transmission (after redirects, a 401 refresh or a retry) wins.\n */\nexport class HttpCallFactsHolder {\n facts: HttpCallFacts | undefined = undefined;\n}\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: ApiCallDirection,\n /** Response only — undefined on the 'request' tag. */\n readonly result?: ApiResult,\n /**\n * Wall-clock milliseconds the call took. RESPONSE tag only (undefined on 'request') — a\n * request has no duration yet. Present on BOTH the success and failure paths, so\n * `jsonPayload.api.durationMs>1000 AND api.result=\"failure\"` finds slow failures.\n *\n * There is deliberately no top-level `statusCode` beside this. LogApiCall runs deep in the\n * stack over in-process calls, pubsub handlers, and cloud-task enqueues — none of which have\n * an HTTP status — and business logic must not know about HTTP. `result` (see {@link ApiResult})\n * is the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by\n * portable Error TYPE rather than by status code. That decision STANDS (#1339): the HTTP\n * client's status lives in the optional, nested {@link http} — present only where there IS an\n * HTTP exchange, so nothing transport-neutral ever reads it.\n */\n readonly durationMs?: number,\n /**\n * Bytes of the serialized request DTO. Stamped on BOTH tags: the 'request' tag reports it as\n * soon as it is known, and the 'response' tag repeats it so one record shows the whole\n * exchange (`api.requestSize` + `api.responseSize` without a join).\n *\n * This is the TOTAL size of the body, measured BEFORE any log chunking — chunking is a\n * transport concern handled by the GCP backends, and a body split across 3 records still\n * reports its one true size here.\n */\n readonly requestSize?: number,\n /** Bytes of the serialized response. RESPONSE tag only, and only when the call succeeded —\n * a thrown error produced no response body to measure. Total size, pre-chunking. */\n readonly responseSize?: number,\n /** Number of logical items in a successful response. RESPONSE tag only, opt-in per call. */\n readonly responseCount?: number,\n /**\n * The HTTP verb, masked url and status of the call's last transmission — `jsonPayload.api.http.*`.\n * RESPONSE tag only, and only for a call made by the HTTP client (success AND failure, so\n * `api.result=\"failure\" AND api.http.status=429` finds a vendor throttling us). Unset for\n * in-process, pubsub and cloud-task calls. See {@link HttpCallFacts}.\n */\n readonly http?: HttpCallFacts,\n ) {}\n}\n"]}
|
|
@@ -48,12 +48,26 @@ export declare class ApiMethodInfo {
|
|
|
48
48
|
* whole call identity travels together. webpieces itself does not act on it; an app's upgrade
|
|
49
49
|
* logic does. Default false. */
|
|
50
50
|
readonly allowUpgradeInFlight: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* The contract CLASS (the `@ApiPath` contract, or an SDK-wrapping api-client's abstract
|
|
53
|
+
* `XxxApi`) — the key {@link ClientRegistry.classify} resolves the `CallScope` ladder on
|
|
54
|
+
* (method -> API -> ALL). {@link apiClass} stays the LOGGED name. REQUIRED as a constructor
|
|
55
|
+
* argument so every caller states it: an SDK-wrapping client passes its abstract `XxxApi`, and
|
|
56
|
+
* only a call with no contract class at all (an IPC hop, an MCP protocol step) passes an explicit
|
|
57
|
+
* `undefined`, which reaches only the ALL rung.
|
|
58
|
+
*
|
|
59
|
+
* NON-enumerable on purpose: this object is nested into every `[API-*]` log record as
|
|
60
|
+
* `jsonPayload.api.method`, and a class reference is not log data.
|
|
61
|
+
*/
|
|
62
|
+
readonly contract: Function | undefined;
|
|
51
63
|
constructor(side: ApiSide,
|
|
52
64
|
/** REQUIRED — the API CONTRACT class name (e.g. 'SaveApi'). Matches client + server so both
|
|
53
65
|
* sides of one logical call filter together via `jsonPayload.api.method.apiClass`. */
|
|
54
66
|
apiClass: string,
|
|
55
67
|
/** REQUIRED — the method on the contract (e.g. 'save'). */
|
|
56
68
|
methodName: string,
|
|
69
|
+
/** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link ApiMethodInfo.contract}. */
|
|
70
|
+
contract: Function | undefined,
|
|
57
71
|
/** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no
|
|
58
72
|
* impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
|
|
59
73
|
controllerName?: string | undefined,
|
|
@@ -34,6 +34,8 @@ class ApiMethodInfo {
|
|
|
34
34
|
apiClass,
|
|
35
35
|
/** REQUIRED — the method on the contract (e.g. 'save'). */
|
|
36
36
|
methodName,
|
|
37
|
+
/** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link ApiMethodInfo.contract}. */
|
|
38
|
+
contract,
|
|
37
39
|
/** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no
|
|
38
40
|
* impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
|
|
39
41
|
controllerName,
|
|
@@ -64,6 +66,7 @@ class ApiMethodInfo {
|
|
|
64
66
|
this.noLogging = noLogging;
|
|
65
67
|
this.hideProgress = hideProgress;
|
|
66
68
|
this.allowUpgradeInFlight = allowUpgradeInFlight;
|
|
69
|
+
Object.defineProperty(this, 'contract', { value: contract, enumerable: false });
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
exports.ApiMethodInfo = ApiMethodInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiMethodInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiMethodInfo.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAOH,MAAa,aAAa;
|
|
1
|
+
{"version":3,"file":"ApiMethodInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiMethodInfo.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAOH,MAAa,aAAa;IAeT;IAGA;IAEA;IAKA;IAMA;IAKA;IAIA;IAIA;IA9Bb,YACa,IAAa;IACtB;2FACuF;IAC9E,QAAgB;IACzB,2DAA2D;IAClD,UAAkB;IAC3B,kGAAkG;IAClG,QAA8B;IAC9B;gGAC4F;IACnF,cAAuB;IAChC;;;;wGAIoG;IAC3F,IAAe;IACxB;;;sGAGkG;IACzF,YAAqB,KAAK;IACnC;;+BAE2B;IAClB,eAAwB,KAAK;IACtC;;qCAEiC;IACxB,uBAAgC,KAAK;QA7BrC,SAAI,GAAJ,IAAI,CAAS;QAGb,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,eAAU,GAAV,UAAU,CAAQ;QAKlB,mBAAc,GAAd,cAAc,CAAS;QAMvB,SAAI,GAAJ,IAAI,CAAW;QAKf,cAAS,GAAT,SAAS,CAAiB;QAI1B,iBAAY,GAAZ,YAAY,CAAiB;QAI7B,yBAAoB,GAApB,oBAAoB,CAAiB;QAE9C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IACpF,CAAC;CACJ;AAhDD,sCAgDC","sourcesContent":["/**\n * ApiMethodInfo - the transport-neutral identity of a single API method call, handed to\n * {@link LogApiCallImpl.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\nimport {MaskSpec} from \"./LogFieldMask\";\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 /**\n * The contract CLASS (the `@ApiPath` contract, or an SDK-wrapping api-client's abstract\n * `XxxApi`) — the key {@link ClientRegistry.classify} resolves the `CallScope` ladder on\n * (method -> API -> ALL). {@link apiClass} stays the LOGGED name. REQUIRED as a constructor\n * argument so every caller states it: an SDK-wrapping client passes its abstract `XxxApi`, and\n * only a call with no contract class at all (an IPC hop, an MCP protocol step) passes an explicit\n * `undefined`, which reaches only the ALL rung.\n *\n * NON-enumerable on purpose: this object is nested into every `[API-*]` log record as\n * `jsonPayload.api.method`, and a class reference is not log data.\n */\n declare readonly contract: Function | undefined;\n\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 /** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link ApiMethodInfo.contract}. */\n contract: Function | undefined,\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 /** OPTIONAL — which DTO fields to mask in the log lines, and how. Absent = log the DTO\n * verbatim exactly as before (the fast path, no per-field walk). Present = {@link LogApiCallImpl}\n * runs {@link maskedStringify} so a declared-sensitive field (an OAuth refresh token, an\n * id-token JWT) is masked in the log while the REAL value still travels on the wire untouched.\n * Rides the call identity so the spec travels with the api definition, not a global app config. */\n readonly mask?: MaskSpec,\n /** OPTIONAL — true when the route declared `@Endpoint(..., { noLogging: true })` (a log\n * shipper, a log-ingest endpoint): {@link LogApiCallImpl} emits NO `[API-*-req]`/`[API-*-resp-*]`\n * line and stamps no {@link ApiCallInfo} tag for it. Read once at route-build time and handed\n * here, exactly as {@link mask} is. Default (absent/false) = the ordinary, fully-logged call. */\n readonly noLogging: boolean = false,\n /** OPTIONAL — `@Endpoint(..., { hideProgress: true })`, carried from the route so the whole\n * call identity travels together. webpieces itself does not act on it; an app's progress UI\n * does. Default false. */\n readonly hideProgress: boolean = false,\n /** OPTIONAL — `@Endpoint(..., { allowUpgradeInFlight: true })`, carried from the route so the\n * whole call identity travels together. webpieces itself does not act on it; an app's upgrade\n * logic does. Default false. */\n readonly allowUpgradeInFlight: boolean = false,\n ) {\n Object.defineProperty(this, 'contract', { value: contract, enumerable: false });\n }\n}\n"]}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { Attempt, CallContext, CallStrategy } from './CallStrategy';
|
|
2
2
|
import { TransportDefault } from './TransportDefault';
|
|
3
|
-
|
|
4
|
-
* Where a policy applies: every contract (`'ALL'`, which has no method), one contract, or one method
|
|
5
|
-
* of one contract. Exported so other per-call registries (e.g. cloudtasks-client's `GcpTaskRegistry`)
|
|
6
|
-
* scope their settings with the SAME ladder.
|
|
7
|
-
*/
|
|
8
|
-
export type CallScope = [api: 'ALL'] | [api: Function, methodName?: string];
|
|
3
|
+
import { CallScope } from './CallScope';
|
|
9
4
|
/**
|
|
10
5
|
* Shared startup registry for browser RPC, node RPC, and Cloud Tasks ENQUEUE — ONE implementation,
|
|
11
6
|
* every transport calls {@link CallRegistry.execute}.
|
|
@@ -22,14 +17,15 @@ export type CallScope = [api: 'ALL'] | [api: Function, methodName?: string];
|
|
|
22
17
|
* retries a `WRITE`, even registered at ALL.
|
|
23
18
|
*/
|
|
24
19
|
export declare class CallRegistry {
|
|
25
|
-
private static
|
|
26
|
-
private static readonly apis;
|
|
20
|
+
private static readonly ladder;
|
|
27
21
|
static setTimeout(timeoutMs: number | undefined, ...scope: CallScope): void;
|
|
28
22
|
static setStrategy(strategy: CallStrategy<unknown> | undefined, ...scope: CallScope): void;
|
|
29
23
|
/**
|
|
30
24
|
* Run one call under its registered policy. `context` names the method (its `methodName` selects
|
|
31
25
|
* the per-method policy) and carries the endpoint's declared operation, so the SAME context a
|
|
32
|
-
* strategy sees is the one the transport's deadline puts on an `ApiCallTimeoutError`.
|
|
26
|
+
* strategy sees is the one the transport's deadline puts on an `ApiCallTimeoutError`. The context
|
|
27
|
+
* a strategy sees always carries `api` as its {@link CallContext.contract}, so the error
|
|
28
|
+
* classification inside a strategy resolves on the same ladder.
|
|
33
29
|
*
|
|
34
30
|
* Every attempt is wrapped so a retried TRANSIENT failure logs one
|
|
35
31
|
* `WARN [API-client-attempt-TRANSIENT]` line (see {@link LoggedAttempts}).
|
|
@@ -43,5 +39,4 @@ export declare class CallRegistry {
|
|
|
43
39
|
*/
|
|
44
40
|
static resolveTimeout(api: Function, methodName: string, defaultMs: number): number;
|
|
45
41
|
static clear(): void;
|
|
46
|
-
private static policy;
|
|
47
42
|
}
|
package/src/http/CallRegistry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CallRegistry = void 0;
|
|
4
|
+
const CallStrategy_1 = require("./CallStrategy");
|
|
4
5
|
const CallDeadline_1 = require("./CallDeadline");
|
|
5
6
|
const ClientRegistry_1 = require("./ClientRegistry");
|
|
6
7
|
const ErrorClassifier_1 = require("./ErrorClassifier");
|
|
@@ -8,6 +9,7 @@ const ApiMethodInfo_1 = require("./ApiMethodInfo");
|
|
|
8
9
|
const LogManager_1 = require("../logging/LogManager");
|
|
9
10
|
const ApiCallLogName_1 = require("./ApiCallLogName");
|
|
10
11
|
const errorUtils_1 = require("../lib/errorUtils");
|
|
12
|
+
const CallScope_1 = require("./CallScope");
|
|
11
13
|
// The same logger LogApiCall writes its [API-*] lines to, so an attempt line sits beside the final one.
|
|
12
14
|
const log = LogManager_1.LogManager.getLogger(ApiCallLogName_1.LOG_API_CALL_LOGGER_NAME);
|
|
13
15
|
class CallPolicy {
|
|
@@ -15,10 +17,6 @@ class CallPolicy {
|
|
|
15
17
|
// webpieces-disable no-any-unknown -- global policy spans heterogeneous response DTOs
|
|
16
18
|
strategy;
|
|
17
19
|
}
|
|
18
|
-
class ApiCallPolicies {
|
|
19
|
-
policy = new CallPolicy();
|
|
20
|
-
methods = new Map();
|
|
21
|
-
}
|
|
22
20
|
/**
|
|
23
21
|
* Counts the attempts of ONE call and logs each retried TRANSIENT failure. It wraps the attempt
|
|
24
22
|
* BEFORE any strategy sees it, so an app-written strategy gets the same lines as `RetryStrategy`.
|
|
@@ -48,7 +46,7 @@ class LoggedAttempts {
|
|
|
48
46
|
}
|
|
49
47
|
catch (err) {
|
|
50
48
|
const error = (0, errorUtils_1.toError)(err);
|
|
51
|
-
const methodInfo = new ApiMethodInfo_1.ApiMethodInfo('client', this.context.apiName, this.context.methodName);
|
|
49
|
+
const methodInfo = new ApiMethodInfo_1.ApiMethodInfo('client', this.context.apiName, this.context.methodName, this.context.contract);
|
|
52
50
|
this.pendingTransient =
|
|
53
51
|
ClientRegistry_1.ClientRegistry.classify(error, methodInfo) === ErrorClassifier_1.CallOutcome.TRANSIENT_FAILURE
|
|
54
52
|
? error
|
|
@@ -82,43 +80,46 @@ class LoggedAttempts {
|
|
|
82
80
|
* retries a `WRITE`, even registered at ALL.
|
|
83
81
|
*/
|
|
84
82
|
class CallRegistry {
|
|
85
|
-
static
|
|
86
|
-
static apis = new Map();
|
|
83
|
+
static ladder = new CallScope_1.CallScopeLadder(() => new CallPolicy());
|
|
87
84
|
// webpieces-disable no-function-outside-class -- process-global startup registry
|
|
88
85
|
static setTimeout(timeoutMs, ...scope) {
|
|
89
86
|
if (timeoutMs !== undefined)
|
|
90
87
|
CallDeadline_1.CallDeadline.validate(timeoutMs);
|
|
91
|
-
CallRegistry.
|
|
88
|
+
CallRegistry.ladder.at(...scope).timeoutMs = timeoutMs;
|
|
92
89
|
}
|
|
93
90
|
// webpieces-disable no-function-outside-class -- process-global startup registry
|
|
94
91
|
static setStrategy(
|
|
95
92
|
// webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs
|
|
96
93
|
strategy, ...scope) {
|
|
97
|
-
CallRegistry.
|
|
94
|
+
CallRegistry.ladder.at(...scope).strategy = strategy;
|
|
98
95
|
}
|
|
99
96
|
/**
|
|
100
97
|
* Run one call under its registered policy. `context` names the method (its `methodName` selects
|
|
101
98
|
* the per-method policy) and carries the endpoint's declared operation, so the SAME context a
|
|
102
|
-
* strategy sees is the one the transport's deadline puts on an `ApiCallTimeoutError`.
|
|
99
|
+
* strategy sees is the one the transport's deadline puts on an `ApiCallTimeoutError`. The context
|
|
100
|
+
* a strategy sees always carries `api` as its {@link CallContext.contract}, so the error
|
|
101
|
+
* classification inside a strategy resolves on the same ladder.
|
|
103
102
|
*
|
|
104
103
|
* Every attempt is wrapped so a retried TRANSIENT failure logs one
|
|
105
104
|
* `WARN [API-client-attempt-TRANSIENT]` line (see {@link LoggedAttempts}).
|
|
106
105
|
*/
|
|
107
106
|
// webpieces-disable no-function-outside-class -- transport-independent policy execution
|
|
108
107
|
static async execute(api, context, attempt, fallback) {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
108
|
+
const scoped = context.contract === api
|
|
109
|
+
? context
|
|
110
|
+
: new CallStrategy_1.CallContext(context.apiName, context.methodName, context.operation, api);
|
|
111
|
+
const logged = new LoggedAttempts(scoped, attempt).run;
|
|
112
|
+
const rungs = CallRegistry.ladder.rungs(api, scoped.methodName);
|
|
113
|
+
const strategy = rungs.find((p) => p.strategy !== undefined)?.strategy;
|
|
113
114
|
if (strategy !== undefined) {
|
|
114
|
-
return (await strategy(logged,
|
|
115
|
+
return (await strategy(logged, scoped));
|
|
115
116
|
}
|
|
116
|
-
const timeoutMs =
|
|
117
|
+
const timeoutMs = rungs.find((p) => p.timeoutMs !== undefined)?.timeoutMs;
|
|
117
118
|
if (timeoutMs !== undefined) {
|
|
118
119
|
return logged(timeoutMs);
|
|
119
120
|
}
|
|
120
121
|
if (fallback.strategy !== undefined) {
|
|
121
|
-
return (await fallback.strategy(logged,
|
|
122
|
+
return (await fallback.strategy(logged, scoped));
|
|
122
123
|
}
|
|
123
124
|
return logged(fallback.timeoutMs);
|
|
124
125
|
}
|
|
@@ -130,35 +131,12 @@ class CallRegistry {
|
|
|
130
131
|
*/
|
|
131
132
|
// webpieces-disable no-function-outside-class -- process-global registry lookup
|
|
132
133
|
static resolveTimeout(api, methodName, defaultMs) {
|
|
133
|
-
const
|
|
134
|
-
return
|
|
135
|
-
policies?.policy.timeoutMs ??
|
|
136
|
-
CallRegistry.all.timeoutMs ??
|
|
137
|
-
defaultMs);
|
|
134
|
+
const rungs = CallRegistry.ladder.rungs(api, methodName);
|
|
135
|
+
return rungs.find((p) => p.timeoutMs !== undefined)?.timeoutMs ?? defaultMs;
|
|
138
136
|
}
|
|
139
137
|
// webpieces-disable no-function-outside-class -- process-global registry reset for tests
|
|
140
138
|
static clear() {
|
|
141
|
-
CallRegistry.
|
|
142
|
-
CallRegistry.apis.clear();
|
|
143
|
-
}
|
|
144
|
-
// webpieces-disable no-function-outside-class -- process-global registry storage
|
|
145
|
-
static policy(api, methodName) {
|
|
146
|
-
if (api === 'ALL') {
|
|
147
|
-
return CallRegistry.all;
|
|
148
|
-
}
|
|
149
|
-
let policies = CallRegistry.apis.get(api);
|
|
150
|
-
if (!policies) {
|
|
151
|
-
policies = new ApiCallPolicies();
|
|
152
|
-
CallRegistry.apis.set(api, policies);
|
|
153
|
-
}
|
|
154
|
-
if (methodName === undefined)
|
|
155
|
-
return policies.policy;
|
|
156
|
-
let policy = policies.methods.get(methodName);
|
|
157
|
-
if (!policy) {
|
|
158
|
-
policy = new CallPolicy();
|
|
159
|
-
policies.methods.set(methodName, policy);
|
|
160
|
-
}
|
|
161
|
-
return policy;
|
|
139
|
+
CallRegistry.ladder.clear();
|
|
162
140
|
}
|
|
163
141
|
}
|
|
164
142
|
exports.CallRegistry = CallRegistry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CallRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/CallRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { Attempt, CallContext, CallStrategy } from './CallStrategy';\nimport { CallDeadline } from './CallDeadline';\nimport { TransportDefault } from './TransportDefault';\nimport { ClientRegistry } from './ClientRegistry';\nimport { CallOutcome } from './ErrorClassifier';\nimport { ApiMethodInfo } from './ApiMethodInfo';\nimport { LogManager } from '../logging/LogManager';\nimport { LOG_API_CALL_LOGGER_NAME } from './ApiCallLogName';\nimport { toError } from '../lib/errorUtils';\n\n// The same logger LogApiCall writes its [API-*] lines to, so an attempt line sits beside the final one.\nconst log = LogManager.getLogger(LOG_API_CALL_LOGGER_NAME);\n\n/**\n * Where a policy applies: every contract (`'ALL'`, which has no method), one contract, or one method\n * of one contract. Exported so other per-call registries (e.g. cloudtasks-client's `GcpTaskRegistry`)\n * scope their settings with the SAME ladder.\n */\nexport type CallScope = [api: 'ALL'] | [api: Function, methodName?: string];\n\nclass CallPolicy {\n timeoutMs?: number;\n // webpieces-disable no-any-unknown -- global policy spans heterogeneous response DTOs\n strategy?: CallStrategy<unknown>;\n}\n\nclass ApiCallPolicies {\n readonly policy = new CallPolicy();\n readonly methods = new Map<string, CallPolicy>();\n}\n\n/**\n * Counts the attempts of ONE call and logs each retried TRANSIENT failure. It wraps the attempt\n * BEFORE any strategy sees it, so an app-written strategy gets the same lines as `RetryStrategy`.\n *\n * The line for attempt N is written when attempt N+1 STARTS — i.e. only for an attempt that was\n * actually retried. The last attempt's failure is what escapes, and `LogApiCall`'s final\n * `[API-client-resp-FAIL]` line covers it, so a call that gave up after 4 attempts logs 3 attempt\n * lines and 1 FAIL line, never the last failure twice. An EXPECTED or FAILURE attempt is not logged\n * here either; the final line covers it.\n */\nclass LoggedAttempts<T> {\n private attempts = 0;\n private pendingTransient?: Error;\n\n constructor(\n private readonly context: CallContext,\n private readonly attempt: Attempt<T>,\n ) {}\n\n readonly run: Attempt<T> = async (timeoutMs: number): Promise<T> => {\n this.flushRetried();\n this.attempts += 1;\n // webpieces-disable no-unmanaged-exceptions -- observe the attempt's failure, rethrow it unchanged\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return await this.attempt(timeoutMs);\n } catch (err: unknown) {\n const error = toError(err);\n const methodInfo = new ApiMethodInfo('client', this.context.apiName, this.context.methodName);\n this.pendingTransient =\n ClientRegistry.classify(error, methodInfo) === CallOutcome.TRANSIENT_FAILURE\n ? error\n : undefined;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrows the attempt's own failure unchanged\n throw err;\n }\n };\n\n private flushRetried(): void {\n const error = this.pendingTransient;\n if (error === undefined) return;\n this.pendingTransient = undefined;\n log.warn(\n `[API-client-attempt-TRANSIENT] ${this.context.apiName}.${this.context.methodName} ` +\n `attempt ${this.attempts} errorType=${error.constructor.name} error=${error.message}`,\n );\n }\n}\n\n/**\n * Shared startup registry for browser RPC, node RPC, and Cloud Tasks ENQUEUE — ONE implementation,\n * every transport calls {@link CallRegistry.execute}.\n *\n * Ladder: strategy method -> API -> ALL first; ANY strategy replaces the ENTIRE timeout ladder.\n * Otherwise timeout method -> API -> ALL. Otherwise the transport's {@link TransportDefault} (its\n * timeout, and its strategy if it has one). A registered TIMEOUT is therefore a decision for a single\n * attempt and also replaces a transport's default strategy; to keep retrying with a different attempt\n * deadline, register a `RetryStrategy` instead.\n *\n * RPC has no default retry (only the application knows whether repeating a POST is safe); Cloud Tasks\n * enqueue does (see `TransportDefault`). An app opts RPC in with\n * `CallRegistry.setStrategy(new RetryStrategy(...).run, CatalogApi)` — and `RetryStrategy` never\n * retries a `WRITE`, even registered at ALL.\n */\nexport class CallRegistry {\n private static all = new CallPolicy();\n private static readonly apis = new Map<Function, ApiCallPolicies>();\n\n // webpieces-disable no-function-outside-class -- process-global startup registry\n static setTimeout(timeoutMs: number | undefined, ...scope: CallScope): void {\n if (timeoutMs !== undefined) CallDeadline.validate(timeoutMs);\n CallRegistry.policy(scope[0], scope[1]).timeoutMs = timeoutMs;\n }\n\n // webpieces-disable no-function-outside-class -- process-global startup registry\n static setStrategy(\n // webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs\n strategy: CallStrategy<unknown> | undefined,\n ...scope: CallScope\n ): void {\n CallRegistry.policy(scope[0], scope[1]).strategy = strategy;\n }\n\n /**\n * Run one call under its registered policy. `context` names the method (its `methodName` selects\n * the per-method policy) and carries the endpoint's declared operation, so the SAME context a\n * strategy sees is the one the transport's deadline puts on an `ApiCallTimeoutError`.\n *\n * Every attempt is wrapped so a retried TRANSIENT failure logs one\n * `WARN [API-client-attempt-TRANSIENT]` line (see {@link LoggedAttempts}).\n */\n // webpieces-disable no-function-outside-class -- transport-independent policy execution\n static async execute<T>(\n api: Function,\n context: CallContext,\n attempt: Attempt<T>,\n fallback: TransportDefault,\n ): Promise<T> {\n const logged = new LoggedAttempts<T>(context, attempt).run;\n const policies = CallRegistry.apis.get(api);\n const method = policies?.methods.get(context.methodName);\n const strategy = method?.strategy ?? policies?.policy.strategy ?? CallRegistry.all.strategy;\n if (strategy !== undefined) {\n return (await strategy(logged, context)) as T;\n }\n const timeoutMs = method?.timeoutMs ?? policies?.policy.timeoutMs ?? CallRegistry.all.timeoutMs;\n if (timeoutMs !== undefined) {\n return logged(timeoutMs);\n }\n if (fallback.strategy !== undefined) {\n return (await fallback.strategy(logged, context)) as T;\n }\n return logged(fallback.timeoutMs);\n }\n\n /**\n * The registered TIMEOUT for a call — method -> API -> ALL -> `defaultMs` — ignoring every\n * strategy. For a transport that hands timing to someone else for this call (Cloud Tasks enqueue\n * with caller-supplied gax `CallOptions`) and still needs ONE safety cap so a hung channel cannot\n * block forever. Raise it with {@link CallRegistry.setTimeout}.\n */\n // webpieces-disable no-function-outside-class -- process-global registry lookup\n static resolveTimeout(api: Function, methodName: string, defaultMs: number): number {\n const policies = CallRegistry.apis.get(api);\n return (\n policies?.methods.get(methodName)?.timeoutMs ??\n policies?.policy.timeoutMs ??\n CallRegistry.all.timeoutMs ??\n defaultMs\n );\n }\n\n // webpieces-disable no-function-outside-class -- process-global registry reset for tests\n static clear(): void {\n CallRegistry.all = new CallPolicy();\n CallRegistry.apis.clear();\n }\n\n // webpieces-disable no-function-outside-class -- process-global registry storage\n private static policy(api: Function | 'ALL', methodName?: string): CallPolicy {\n if (api === 'ALL') {\n return CallRegistry.all;\n }\n let policies = CallRegistry.apis.get(api);\n if (!policies) {\n policies = new ApiCallPolicies();\n CallRegistry.apis.set(api, policies);\n }\n if (methodName === undefined) return policies.policy;\n let policy = policies.methods.get(methodName);\n if (!policy) {\n policy = new CallPolicy();\n policies.methods.set(methodName, policy);\n }\n return policy;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"CallRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/CallRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { Attempt, CallContext, CallStrategy } from './CallStrategy';\nimport { CallDeadline } from './CallDeadline';\nimport { TransportDefault } from './TransportDefault';\nimport { ClientRegistry } from './ClientRegistry';\nimport { CallOutcome } from './ErrorClassifier';\nimport { ApiMethodInfo } from './ApiMethodInfo';\nimport { LogManager } from '../logging/LogManager';\nimport { LOG_API_CALL_LOGGER_NAME } from './ApiCallLogName';\nimport { toError } from '../lib/errorUtils';\nimport { CallScope, CallScopeLadder } from './CallScope';\n\n// The same logger LogApiCall writes its [API-*] lines to, so an attempt line sits beside the final one.\nconst log = LogManager.getLogger(LOG_API_CALL_LOGGER_NAME);\n\nclass CallPolicy {\n timeoutMs?: number;\n // webpieces-disable no-any-unknown -- global policy spans heterogeneous response DTOs\n strategy?: CallStrategy<unknown>;\n}\n\n/**\n * Counts the attempts of ONE call and logs each retried TRANSIENT failure. It wraps the attempt\n * BEFORE any strategy sees it, so an app-written strategy gets the same lines as `RetryStrategy`.\n *\n * The line for attempt N is written when attempt N+1 STARTS — i.e. only for an attempt that was\n * actually retried. The last attempt's failure is what escapes, and `LogApiCall`'s final\n * `[API-client-resp-FAIL]` line covers it, so a call that gave up after 4 attempts logs 3 attempt\n * lines and 1 FAIL line, never the last failure twice. An EXPECTED or FAILURE attempt is not logged\n * here either; the final line covers it.\n */\nclass LoggedAttempts<T> {\n private attempts = 0;\n private pendingTransient?: Error;\n\n constructor(\n private readonly context: CallContext,\n private readonly attempt: Attempt<T>,\n ) {}\n\n readonly run: Attempt<T> = async (timeoutMs: number): Promise<T> => {\n this.flushRetried();\n this.attempts += 1;\n // webpieces-disable no-unmanaged-exceptions -- observe the attempt's failure, rethrow it unchanged\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return await this.attempt(timeoutMs);\n } catch (err: unknown) {\n const error = toError(err);\n const methodInfo = new ApiMethodInfo(\n 'client',\n this.context.apiName,\n this.context.methodName,\n this.context.contract,\n );\n this.pendingTransient =\n ClientRegistry.classify(error, methodInfo) === CallOutcome.TRANSIENT_FAILURE\n ? error\n : undefined;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrows the attempt's own failure unchanged\n throw err;\n }\n };\n\n private flushRetried(): void {\n const error = this.pendingTransient;\n if (error === undefined) return;\n this.pendingTransient = undefined;\n log.warn(\n `[API-client-attempt-TRANSIENT] ${this.context.apiName}.${this.context.methodName} ` +\n `attempt ${this.attempts} errorType=${error.constructor.name} error=${error.message}`,\n );\n }\n}\n\n/**\n * Shared startup registry for browser RPC, node RPC, and Cloud Tasks ENQUEUE — ONE implementation,\n * every transport calls {@link CallRegistry.execute}.\n *\n * Ladder: strategy method -> API -> ALL first; ANY strategy replaces the ENTIRE timeout ladder.\n * Otherwise timeout method -> API -> ALL. Otherwise the transport's {@link TransportDefault} (its\n * timeout, and its strategy if it has one). A registered TIMEOUT is therefore a decision for a single\n * attempt and also replaces a transport's default strategy; to keep retrying with a different attempt\n * deadline, register a `RetryStrategy` instead.\n *\n * RPC has no default retry (only the application knows whether repeating a POST is safe); Cloud Tasks\n * enqueue does (see `TransportDefault`). An app opts RPC in with\n * `CallRegistry.setStrategy(new RetryStrategy(...).run, CatalogApi)` — and `RetryStrategy` never\n * retries a `WRITE`, even registered at ALL.\n */\nexport class CallRegistry {\n private static readonly ladder = new CallScopeLadder<CallPolicy>(() => new CallPolicy());\n\n // webpieces-disable no-function-outside-class -- process-global startup registry\n static setTimeout(timeoutMs: number | undefined, ...scope: CallScope): void {\n if (timeoutMs !== undefined) CallDeadline.validate(timeoutMs);\n CallRegistry.ladder.at(...scope).timeoutMs = timeoutMs;\n }\n\n // webpieces-disable no-function-outside-class -- process-global startup registry\n static setStrategy(\n // webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs\n strategy: CallStrategy<unknown> | undefined,\n ...scope: CallScope\n ): void {\n CallRegistry.ladder.at(...scope).strategy = strategy;\n }\n\n /**\n * Run one call under its registered policy. `context` names the method (its `methodName` selects\n * the per-method policy) and carries the endpoint's declared operation, so the SAME context a\n * strategy sees is the one the transport's deadline puts on an `ApiCallTimeoutError`. The context\n * a strategy sees always carries `api` as its {@link CallContext.contract}, so the error\n * classification inside a strategy resolves on the same ladder.\n *\n * Every attempt is wrapped so a retried TRANSIENT failure logs one\n * `WARN [API-client-attempt-TRANSIENT]` line (see {@link LoggedAttempts}).\n */\n // webpieces-disable no-function-outside-class -- transport-independent policy execution\n static async execute<T>(\n api: Function,\n context: CallContext,\n attempt: Attempt<T>,\n fallback: TransportDefault,\n ): Promise<T> {\n const scoped =\n context.contract === api\n ? context\n : new CallContext(context.apiName, context.methodName, context.operation, api);\n const logged = new LoggedAttempts<T>(scoped, attempt).run;\n const rungs = CallRegistry.ladder.rungs(api, scoped.methodName);\n const strategy = rungs.find((p: CallPolicy) => p.strategy !== undefined)?.strategy;\n if (strategy !== undefined) {\n return (await strategy(logged, scoped)) as T;\n }\n const timeoutMs = rungs.find((p: CallPolicy) => p.timeoutMs !== undefined)?.timeoutMs;\n if (timeoutMs !== undefined) {\n return logged(timeoutMs);\n }\n if (fallback.strategy !== undefined) {\n return (await fallback.strategy(logged, scoped)) as T;\n }\n return logged(fallback.timeoutMs);\n }\n\n /**\n * The registered TIMEOUT for a call — method -> API -> ALL -> `defaultMs` — ignoring every\n * strategy. For a transport that hands timing to someone else for this call (Cloud Tasks enqueue\n * with caller-supplied gax `CallOptions`) and still needs ONE safety cap so a hung channel cannot\n * block forever. Raise it with {@link CallRegistry.setTimeout}.\n */\n // webpieces-disable no-function-outside-class -- process-global registry lookup\n static resolveTimeout(api: Function, methodName: string, defaultMs: number): number {\n const rungs = CallRegistry.ladder.rungs(api, methodName);\n return rungs.find((p: CallPolicy) => p.timeoutMs !== undefined)?.timeoutMs ?? defaultMs;\n }\n\n // webpieces-disable no-function-outside-class -- process-global registry reset for tests\n static clear(): void {\n CallRegistry.ladder.clear();\n }\n}\n"]}
|
|
@@ -13,7 +13,7 @@ class CallRegistryCompileAssertions {
|
|
|
13
13
|
}
|
|
14
14
|
/** The transport fallback is a TransportDefault (timeout + optional strategy), never a bare number. */
|
|
15
15
|
fallbackIsATransportDefault() {
|
|
16
|
-
const context = new CallStrategy_1.CallContext('Api', 'work', HttpEndpointOptions_1.WRITE);
|
|
16
|
+
const context = new CallStrategy_1.CallContext('Api', 'work', HttpEndpointOptions_1.WRITE, undefined);
|
|
17
17
|
void CallRegistry_1.CallRegistry.execute(CallRegistryCompileAssertions, context, (ms) => Promise.resolve(ms),
|
|
18
18
|
// @ts-expect-error execute(..., defaultMs: number) became execute(..., fallback: TransportDefault)
|
|
19
19
|
30_000);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CallRegistryCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/CallRegistryCompileAssertions.ts"],"names":[],"mappings":";;AAAA,iDAA8C;AAC9C,iDAA6C;AAC7C,+DAA8C;AAE9C,4DAA4D;AAC5D,MAAM,6BAA6B;IAC/B,KAAK;QACD,8CAA8C;QAC9C,2BAAY,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC5C,8CAA8C;QAC9C,2BAAY,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACvD,CAAC;IAED,uGAAuG;IACvG,2BAA2B;QACvB,MAAM,OAAO,GAAG,IAAI,0BAAW,CAAC,KAAK,EAAE,MAAM,EAAE,2BAAK,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"CallRegistryCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/CallRegistryCompileAssertions.ts"],"names":[],"mappings":";;AAAA,iDAA8C;AAC9C,iDAA6C;AAC7C,+DAA8C;AAE9C,4DAA4D;AAC5D,MAAM,6BAA6B;IAC/B,KAAK;QACD,8CAA8C;QAC9C,2BAAY,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC5C,8CAA8C;QAC9C,2BAAY,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACvD,CAAC;IAED,uGAAuG;IACvG,2BAA2B;QACvB,MAAM,OAAO,GAAG,IAAI,0BAAW,CAAC,KAAK,EAAE,MAAM,EAAE,2BAAK,EAAE,SAAS,CAAC,CAAC;QACjE,KAAK,2BAAY,CAAC,OAAO,CACrB,6BAA6B,EAC7B,OAAO,EACP,CAAC,EAAU,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,mGAAmG;QACnG,MAAM,CACT,CAAC;IACN,CAAC;CACJ","sourcesContent":["import { CallRegistry } from './CallRegistry';\nimport { CallContext } from './CallStrategy';\nimport { WRITE } from './HttpEndpointOptions';\n\n/** Compiled by tsc (spec files are not), never executed. */\nclass CallRegistryCompileAssertions {\n check(): void {\n // @ts-expect-error ALL cannot select a method\n CallRegistry.setTimeout(100, 'ALL', 'work');\n // @ts-expect-error ALL cannot select a method\n CallRegistry.setStrategy(undefined, 'ALL', 'work');\n }\n\n /** The transport fallback is a TransportDefault (timeout + optional strategy), never a bare number. */\n fallbackIsATransportDefault(): void {\n const context = new CallContext('Api', 'work', WRITE, undefined);\n void CallRegistry.execute(\n CallRegistryCompileAssertions,\n context,\n (ms: number) => Promise.resolve(ms),\n // @ts-expect-error execute(..., defaultMs: number) became execute(..., fallback: TransportDefault)\n 30_000,\n );\n }\n}\n"]}
|