@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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a policy applies: every contract (`'ALL'`, which has no method), one contract, or one method
|
|
3
|
+
* of one contract. The contract is always the CLASS (the `@ApiPath` contract, or an SDK-wrapping
|
|
4
|
+
* api-client's abstract `XxxApi`), never its name — a string key is one typo away from silently
|
|
5
|
+
* matching nothing.
|
|
6
|
+
*
|
|
7
|
+
* Every per-call startup registry scopes its settings with this ONE ladder: `CallRegistry`
|
|
8
|
+
* (strategy, timeout), `ClientRegistry` (error translator, error classifier) and cloudtasks-client's
|
|
9
|
+
* `GcpTaskRegistry` (customizer, gax). Resolution is always method -> API -> ALL -> the registry's
|
|
10
|
+
* own webpieces default.
|
|
11
|
+
*/
|
|
12
|
+
export type CallScope = [api: 'ALL'] | [api: Function, methodName?: string];
|
|
13
|
+
/**
|
|
14
|
+
* The ONE implementation of the {@link CallScope} ladder. It stores one slot per scope (created on
|
|
15
|
+
* first use by `create`) and answers a call's rungs most-specific-first; each registry decides what a
|
|
16
|
+
* rung's slot holds and how a rung "answers" (first value present, or first verdict that does not
|
|
17
|
+
* defer).
|
|
18
|
+
*/
|
|
19
|
+
export declare class CallScopeLadder<T> {
|
|
20
|
+
private readonly create;
|
|
21
|
+
private all;
|
|
22
|
+
private readonly apis;
|
|
23
|
+
constructor(create: () => T);
|
|
24
|
+
/** The slot for `scope`, created on first use. Registries write their setting into it. */
|
|
25
|
+
at(...scope: CallScope): T;
|
|
26
|
+
/**
|
|
27
|
+
* The registered slots for one call, most specific first: method, then API, then ALL. A rung with
|
|
28
|
+
* no slot is skipped. `api` undefined (a call with no contract class — an IPC hop, an MCP protocol
|
|
29
|
+
* step, a failure before routing) reaches only the ALL rung.
|
|
30
|
+
*/
|
|
31
|
+
rungs(api: Function | undefined, methodName: string | undefined): T[];
|
|
32
|
+
clear(): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CallScopeLadder = void 0;
|
|
4
|
+
class ApiRungs {
|
|
5
|
+
api;
|
|
6
|
+
methods = new Map();
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The ONE implementation of the {@link CallScope} ladder. It stores one slot per scope (created on
|
|
10
|
+
* first use by `create`) and answers a call's rungs most-specific-first; each registry decides what a
|
|
11
|
+
* rung's slot holds and how a rung "answers" (first value present, or first verdict that does not
|
|
12
|
+
* defer).
|
|
13
|
+
*/
|
|
14
|
+
class CallScopeLadder {
|
|
15
|
+
create;
|
|
16
|
+
all;
|
|
17
|
+
apis = new Map();
|
|
18
|
+
constructor(create) {
|
|
19
|
+
this.create = create;
|
|
20
|
+
}
|
|
21
|
+
/** The slot for `scope`, created on first use. Registries write their setting into it. */
|
|
22
|
+
at(...scope) {
|
|
23
|
+
const api = scope[0];
|
|
24
|
+
if (api === 'ALL') {
|
|
25
|
+
if (this.all === undefined)
|
|
26
|
+
this.all = this.create();
|
|
27
|
+
return this.all;
|
|
28
|
+
}
|
|
29
|
+
let rungs = this.apis.get(api);
|
|
30
|
+
if (!rungs) {
|
|
31
|
+
rungs = new ApiRungs();
|
|
32
|
+
this.apis.set(api, rungs);
|
|
33
|
+
}
|
|
34
|
+
const methodName = scope[1];
|
|
35
|
+
if (methodName === undefined) {
|
|
36
|
+
if (rungs.api === undefined)
|
|
37
|
+
rungs.api = this.create();
|
|
38
|
+
return rungs.api;
|
|
39
|
+
}
|
|
40
|
+
let slot = rungs.methods.get(methodName);
|
|
41
|
+
if (slot === undefined) {
|
|
42
|
+
slot = this.create();
|
|
43
|
+
rungs.methods.set(methodName, slot);
|
|
44
|
+
}
|
|
45
|
+
return slot;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The registered slots for one call, most specific first: method, then API, then ALL. A rung with
|
|
49
|
+
* no slot is skipped. `api` undefined (a call with no contract class — an IPC hop, an MCP protocol
|
|
50
|
+
* step, a failure before routing) reaches only the ALL rung.
|
|
51
|
+
*/
|
|
52
|
+
rungs(api, methodName) {
|
|
53
|
+
const found = [];
|
|
54
|
+
const rungs = api === undefined ? undefined : this.apis.get(api);
|
|
55
|
+
const method = methodName === undefined ? undefined : rungs?.methods.get(methodName);
|
|
56
|
+
if (method !== undefined)
|
|
57
|
+
found.push(method);
|
|
58
|
+
if (rungs?.api !== undefined)
|
|
59
|
+
found.push(rungs.api);
|
|
60
|
+
if (this.all !== undefined)
|
|
61
|
+
found.push(this.all);
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
clear() {
|
|
65
|
+
this.all = undefined;
|
|
66
|
+
this.apis.clear();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.CallScopeLadder = CallScopeLadder;
|
|
70
|
+
//# sourceMappingURL=CallScope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CallScope.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/CallScope.ts"],"names":[],"mappings":";;;AAaA,MAAM,QAAQ;IACV,GAAG,CAAK;IACC,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;CAC3C;AAED;;;;;GAKG;AACH,MAAa,eAAe;IAIK;IAHrB,GAAG,CAAgB;IACV,IAAI,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEzD,YAA6B,MAAe;QAAf,WAAM,GAAN,MAAM,CAAS;IAAG,CAAC;IAEhD,0FAA0F;IAC1F,EAAE,CAAC,GAAG,KAAgB;QAClB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YAChB,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;gBAAE,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC,GAAG,CAAC;QACpB,CAAC;QACD,IAAI,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,KAAK,GAAG,IAAI,QAAQ,EAAK,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC9B,CAAC;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;gBAAE,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACvD,OAAO,KAAK,CAAC,GAAG,CAAC;QACrB,CAAC;QACD,IAAI,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACrB,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACrB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAyB,EAAE,UAA8B;QAC3D,MAAM,KAAK,GAAQ,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACrF,IAAI,MAAM,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,KAAK,EAAE,GAAG,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpD,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,KAAK;QACD,IAAI,CAAC,GAAG,GAAG,SAAS,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACJ;AAlDD,0CAkDC","sourcesContent":["/**\n * Where a policy applies: every contract (`'ALL'`, which has no method), one contract, or one method\n * of one contract. The contract is always the CLASS (the `@ApiPath` contract, or an SDK-wrapping\n * api-client's abstract `XxxApi`), never its name — a string key is one typo away from silently\n * matching nothing.\n *\n * Every per-call startup registry scopes its settings with this ONE ladder: `CallRegistry`\n * (strategy, timeout), `ClientRegistry` (error translator, error classifier) and cloudtasks-client's\n * `GcpTaskRegistry` (customizer, gax). Resolution is always method -> API -> ALL -> the registry's\n * own webpieces default.\n */\nexport type CallScope = [api: 'ALL'] | [api: Function, methodName?: string];\n\nclass ApiRungs<T> {\n api?: T;\n readonly methods = new Map<string, T>();\n}\n\n/**\n * The ONE implementation of the {@link CallScope} ladder. It stores one slot per scope (created on\n * first use by `create`) and answers a call's rungs most-specific-first; each registry decides what a\n * rung's slot holds and how a rung \"answers\" (first value present, or first verdict that does not\n * defer).\n */\nexport class CallScopeLadder<T> {\n private all: T | undefined;\n private readonly apis = new Map<Function, ApiRungs<T>>();\n\n constructor(private readonly create: () => T) {}\n\n /** The slot for `scope`, created on first use. Registries write their setting into it. */\n at(...scope: CallScope): T {\n const api = scope[0];\n if (api === 'ALL') {\n if (this.all === undefined) this.all = this.create();\n return this.all;\n }\n let rungs = this.apis.get(api);\n if (!rungs) {\n rungs = new ApiRungs<T>();\n this.apis.set(api, rungs);\n }\n const methodName = scope[1];\n if (methodName === undefined) {\n if (rungs.api === undefined) rungs.api = this.create();\n return rungs.api;\n }\n let slot = rungs.methods.get(methodName);\n if (slot === undefined) {\n slot = this.create();\n rungs.methods.set(methodName, slot);\n }\n return slot;\n }\n\n /**\n * The registered slots for one call, most specific first: method, then API, then ALL. A rung with\n * no slot is skipped. `api` undefined (a call with no contract class — an IPC hop, an MCP protocol\n * step, a failure before routing) reaches only the ALL rung.\n */\n rungs(api: Function | undefined, methodName: string | undefined): T[] {\n const found: T[] = [];\n const rungs = api === undefined ? undefined : this.apis.get(api);\n const method = methodName === undefined ? undefined : rungs?.methods.get(methodName);\n if (method !== undefined) found.push(method);\n if (rungs?.api !== undefined) found.push(rungs.api);\n if (this.all !== undefined) found.push(this.all);\n return found;\n }\n\n clear(): void {\n this.all = undefined;\n this.apis.clear();\n }\n}\n"]}
|
|
@@ -14,7 +14,19 @@ export declare class CallContext {
|
|
|
14
14
|
readonly apiName: string;
|
|
15
15
|
readonly methodName: string;
|
|
16
16
|
readonly operation: EndpointOperation;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The contract CLASS the call belongs to — the key of the `CallScope` ladder, so a strategy's
|
|
19
|
+
* per-attempt error classification resolves the classifier registered for THIS contract.
|
|
20
|
+
* `CallRegistry.execute` guarantees it on the context a strategy sees; undefined only for a
|
|
21
|
+
* transport with no contract class (an IPC deadline), which reaches the ALL rung alone.
|
|
22
|
+
*
|
|
23
|
+
* NON-enumerable on purpose: this context rides on `ApiCallTimeoutError`, which is logged and
|
|
24
|
+
* compared as data, and a class reference is not data.
|
|
25
|
+
*/
|
|
26
|
+
readonly contract: Function | undefined;
|
|
27
|
+
constructor(apiName: string, methodName: string, operation: EndpointOperation,
|
|
28
|
+
/** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link CallContext.contract}. */
|
|
29
|
+
contract: Function | undefined);
|
|
18
30
|
}
|
|
19
31
|
/** Owns all timing and retry decisions. No public cancellation signal. */
|
|
20
32
|
export type CallStrategy<T> = (call: Attempt<T>, ctx: CallContext) => Promise<T>;
|
package/src/http/CallStrategy.js
CHANGED
|
@@ -14,10 +14,13 @@ class CallContext {
|
|
|
14
14
|
apiName;
|
|
15
15
|
methodName;
|
|
16
16
|
operation;
|
|
17
|
-
constructor(apiName, methodName, operation
|
|
17
|
+
constructor(apiName, methodName, operation,
|
|
18
|
+
/** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link CallContext.contract}. */
|
|
19
|
+
contract) {
|
|
18
20
|
this.apiName = apiName;
|
|
19
21
|
this.methodName = methodName;
|
|
20
22
|
this.operation = operation;
|
|
23
|
+
Object.defineProperty(this, 'contract', { value: contract, enumerable: false });
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
exports.CallContext = CallContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CallStrategy.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/CallStrategy.ts"],"names":[],"mappings":";;;AAKA;;;;;;;;GAQG;AACH,MAAa,WAAW;
|
|
1
|
+
{"version":3,"file":"CallStrategy.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/CallStrategy.ts"],"names":[],"mappings":";;;AAKA;;;;;;;;GAQG;AACH,MAAa,WAAW;IAaA;IACA;IACA;IAHpB,YACoB,OAAe,EACf,UAAkB,EAClB,SAA4B;IAC5C,gGAAgG;IAChG,QAA8B;QAJd,YAAO,GAAP,OAAO,CAAQ;QACf,eAAU,GAAV,UAAU,CAAQ;QAClB,cAAS,GAAT,SAAS,CAAmB;QAI5C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IACpF,CAAC;CACJ;AArBD,kCAqBC","sourcesContent":["import { EndpointOperation } from './HttpEndpointOptions';\n\n/** One bounded attempt. Only an explicitly registered strategy may retry it. */\nexport type Attempt<T> = (timeoutMs: number) => Promise<T>;\n\n/**\n * Contract identity for diagnostics; registry keys use the class itself.\n *\n * `operation` is the endpoint's declared `@Endpoint` side-effect contract (`READ`,\n * `WRITE_IDEMPOTENT` or `WRITE`). A {@link CallStrategy} reads it to decide whether a retry is safe,\n * and `ApiCallTimeoutError` reads it to choose the text it shows an end user. A transport with\n * no declared operation (IPC contracts declare none) passes `WRITE`: the conservative answer, since\n * it never invites a blind retry of something that may already have happened.\n */\nexport class CallContext {\n /**\n * The contract CLASS the call belongs to — the key of the `CallScope` ladder, so a strategy's\n * per-attempt error classification resolves the classifier registered for THIS contract.\n * `CallRegistry.execute` guarantees it on the context a strategy sees; undefined only for a\n * transport with no contract class (an IPC deadline), which reaches the ALL rung alone.\n *\n * NON-enumerable on purpose: this context rides on `ApiCallTimeoutError`, which is logged and\n * compared as data, and a class reference is not data.\n */\n declare readonly contract: Function | undefined;\n\n constructor(\n public readonly apiName: string,\n public readonly methodName: string,\n public readonly operation: EndpointOperation,\n /** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link CallContext.contract}. */\n contract: Function | undefined,\n ) {\n Object.defineProperty(this, 'contract', { value: contract, enumerable: false });\n }\n}\n\n/** Owns all timing and retry decisions. No public cancellation signal. */\nexport type CallStrategy<T> = (call: Attempt<T>, ctx: CallContext) => Promise<T>;\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ErrorTranslator } from './ErrorTranslator';
|
|
2
|
-
import { CallOutcome, ErrorClassifier
|
|
2
|
+
import { CallOutcome, ErrorClassifier } from './ErrorClassifier';
|
|
3
|
+
import { CallScope } from './CallScope';
|
|
3
4
|
import { ApiMethodInfo } from './ApiMethodInfo';
|
|
4
5
|
/**
|
|
5
6
|
* Derives a base URL for a service name that has NO registered mapping — the pluggable half of
|
|
@@ -54,35 +55,26 @@ export declare class ClientRegistry {
|
|
|
54
55
|
/** The fallback for svcNames with no mapping. Undefined = no derivation in this environment. */
|
|
55
56
|
private static deriver;
|
|
56
57
|
/**
|
|
57
|
-
* The
|
|
58
|
-
* {@link WEBPIECES_DEFAULT_ERROR_TRANSLATOR},
|
|
59
|
-
*
|
|
60
|
-
* no-DI pattern as {@link ClientRegistry.mappings} above.
|
|
58
|
+
* The {@link ErrorTranslator} per {@link CallScope} — method -> API -> ALL, and below every rung
|
|
59
|
+
* {@link WEBPIECES_DEFAULT_ERROR_TRANSLATOR}, so {@link ClientRegistry.getErrorTranslator} is
|
|
60
|
+
* never undefined. Process-global, set once at startup on the SERVER and in the BROWSER — the
|
|
61
|
+
* same no-DI pattern as {@link ClientRegistry.mappings} above.
|
|
61
62
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* INSIDE its own `toWire`, where the precedence is written down, instead of leaving it implicit
|
|
68
|
-
* in the order two unrelated startup paths happened to register.
|
|
69
|
-
*/
|
|
70
|
-
private static errorTranslator;
|
|
71
|
-
/**
|
|
72
|
-
* The app/company DEFAULT {@link ErrorClassifier} — ONE per process, reads {@link ApiMethodInfo.side}
|
|
73
|
-
* so a single classifier covers the server router AND all internal clients. NEVER undefined: it
|
|
74
|
-
* starts life holding the webpieces built-in ({@link WEBPIECES_DEFAULT_ERROR_CLASSIFIER}) and
|
|
75
|
-
* {@link ClientRegistry.setDefaultErrorClassifier} replaces it. Populated once at startup on the
|
|
76
|
-
* SERVER and in the BROWSER — same no-DI pattern as {@link ClientRegistry.mappings}.
|
|
63
|
+
* ONE translator per scope, not a list, on purpose: an app that has several layers of error
|
|
64
|
+
* policy for one scope composes them INSIDE its own `toWire`, where the precedence is written
|
|
65
|
+
* down. The ladder exists because vendors put their errors on the wire in their OWN formats — a
|
|
66
|
+
* per-API translator reads one vendor's 4xx body (`fromWire`), or writes the error body an
|
|
67
|
+
* inbound vendor expects back (`toWire`), while every other contract keeps the app's ALL one.
|
|
77
68
|
*/
|
|
78
|
-
private static
|
|
69
|
+
private static readonly errorTranslators;
|
|
79
70
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
71
|
+
* The {@link ErrorClassifier} per {@link CallScope} — method -> API -> ALL, and below every rung
|
|
72
|
+
* {@link WEBPIECES_DEFAULT_ERROR_CLASSIFIER}. A classifier that DEFERS (`undefined`) falls through
|
|
73
|
+
* to the next rung. The ALL classifier reads {@link ApiMethodInfo.side}, so one classifier covers
|
|
74
|
+
* the server router AND every internal client; an SDK-wrapping api-client registers its own at
|
|
75
|
+
* its abstract `XxxApi` class. See {@link ClientRegistry.classify}.
|
|
84
76
|
*/
|
|
85
|
-
private static readonly
|
|
77
|
+
private static readonly errorClassifiers;
|
|
86
78
|
/** Map a service name to `http://localhost:<port>`. */
|
|
87
79
|
static addMapping(svcName: string, port: number): void;
|
|
88
80
|
/**
|
|
@@ -120,42 +112,46 @@ export declare class ClientRegistry {
|
|
|
120
112
|
*/
|
|
121
113
|
static resolve(svcName: string): Promise<string>;
|
|
122
114
|
/**
|
|
123
|
-
* Install
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* {@link ClientRegistry.addMapping}.
|
|
115
|
+
* Install an {@link ErrorTranslator} for one {@link CallScope}: `'ALL'` (the app's translator,
|
|
116
|
+
* server side AND every client side), one contract CLASS, or one method of one contract. The
|
|
117
|
+
* scope is REQUIRED — there is no unscoped spelling. Call at startup, on the server AND in the
|
|
118
|
+
* browser, mirroring {@link ClientRegistry.addMapping}.
|
|
127
119
|
*
|
|
128
|
-
*
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* ClientRegistry.setErrorTranslator(new AppErrorTranslator(), 'ALL');
|
|
122
|
+
* ClientRegistry.setErrorTranslator(new VendorErrorTranslator(), VendorApi); // one contract
|
|
123
|
+
* ClientRegistry.setErrorTranslator(new LegacyErrorTranslator(), VendorApi, 'sync'); // one method
|
|
124
|
+
* ```
|
|
133
125
|
*
|
|
134
|
-
*
|
|
135
|
-
* {@link
|
|
136
|
-
*
|
|
137
|
-
* so every call site in the framework is one unconditional line.
|
|
126
|
+
* `set`, not `add`: see the field doc above for why a per-scope LIST is not wanted. A translator
|
|
127
|
+
* declines an error by DELEGATING to {@link WebpiecesDefaultErrorTranslator} — not to the next
|
|
128
|
+
* rung — exactly as before.
|
|
138
129
|
*/
|
|
139
|
-
static
|
|
130
|
+
static setErrorTranslator(translator: ErrorTranslator, ...scope: CallScope): void;
|
|
140
131
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
132
|
+
* THE {@link ErrorTranslator} for one call — method -> API -> ALL ->
|
|
133
|
+
* {@link WEBPIECES_DEFAULT_ERROR_TRANSLATOR}, never `undefined`, so a caller never branches.
|
|
134
|
+
*
|
|
135
|
+
* Both arguments are required so every call site states what it knows: the client and the server
|
|
136
|
+
* pass the route's contract class and method; a failure with no route (an unmatched request, a
|
|
137
|
+
* failure before routing) passes `undefined, undefined` and reaches the ALL rung alone.
|
|
145
138
|
*/
|
|
146
|
-
static
|
|
139
|
+
static getErrorTranslator(api: Function | undefined, methodName: string | undefined): ErrorTranslator;
|
|
147
140
|
/**
|
|
148
|
-
*
|
|
149
|
-
* {@link
|
|
150
|
-
*
|
|
141
|
+
* Install an {@link ErrorClassifier} for one {@link CallScope}: `'ALL'` (the app/company
|
|
142
|
+
* classifier — it reads {@link ApiMethodInfo.side}, so it covers the server router and every
|
|
143
|
+
* internal client), one contract CLASS (an SDK-wrapping api-client keys by its abstract `XxxApi`),
|
|
144
|
+
* or one method of one contract. The scope is REQUIRED. Call at startup, on the server AND in the
|
|
145
|
+
* browser. See {@link ErrorClassifier}.
|
|
151
146
|
*/
|
|
152
|
-
static
|
|
147
|
+
static setErrorClassifier(classifier: ErrorClassifier, ...scope: CallScope): void;
|
|
153
148
|
/**
|
|
154
|
-
* The {@link CallOutcome} of a thrown API-call error. Resolved
|
|
155
|
-
* definitive:
|
|
156
|
-
* 1. the
|
|
157
|
-
* 2. else the
|
|
158
|
-
* 3. else the
|
|
149
|
+
* The {@link CallOutcome} of a thrown API-call error. Resolved on the {@link CallScope} ladder of
|
|
150
|
+
* `methodInfo.contract` / `methodInfo.methodName`, most specific first, and ALWAYS definitive:
|
|
151
|
+
* 1. the method classifier — unless it defers;
|
|
152
|
+
* 2. else the API classifier — unless it defers;
|
|
153
|
+
* 3. else the ALL classifier — unless it defers;
|
|
154
|
+
* 4. else the webpieces built-in ({@link WEBPIECES_DEFAULT_ERROR_CLASSIFIER}), which never defers.
|
|
159
155
|
*
|
|
160
156
|
* The ONE verdict read by `RetryStrategy` (retry only TRANSIENT), `CallRegistry`'s per-attempt
|
|
161
157
|
* log line, `LogApiCall` (EXPECTED → OTHER, else FAIL) and the MCP renderer's retry advice.
|
|
@@ -165,10 +161,10 @@ export declare class ClientRegistry {
|
|
|
165
161
|
* Put the process-globals back to the state a fresh process starts in. For TESTS, so one spec
|
|
166
162
|
* cannot leak its registrations into the next.
|
|
167
163
|
*
|
|
168
|
-
* It
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
164
|
+
* It empties every translator and classifier rung, which RESTORES the webpieces defaults:
|
|
165
|
+
* `getErrorTranslator(...)` and `classify(...)` fall through to them below every rung, so
|
|
166
|
+
* "cleared" is not a state they have. A spec that wants webpieces' own behaviour back asks for
|
|
167
|
+
* exactly that.
|
|
172
168
|
*/
|
|
173
169
|
static resetForTests(): void;
|
|
174
170
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ClientRegistry = void 0;
|
|
4
|
+
const CallScope_1 = require("./CallScope");
|
|
4
5
|
const WebpiecesDefaultErrorClassifier_1 = require("./WebpiecesDefaultErrorClassifier");
|
|
5
6
|
const WebpiecesDefaultErrorTranslator_1 = require("./WebpiecesDefaultErrorTranslator");
|
|
7
|
+
/** One {@link CallScope} rung of the error-translator ladder. Data only, so a class. */
|
|
8
|
+
class TranslatorSlot {
|
|
9
|
+
translator;
|
|
10
|
+
}
|
|
11
|
+
/** One {@link CallScope} rung of the error-classifier ladder. Data only, so a class. */
|
|
12
|
+
class ClassifierSlot {
|
|
13
|
+
classifier;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* ClientRegistry - the ONE place a `svcName` becomes a base URL, for every outbound client in every
|
|
8
17
|
* environment. A client is built once but a URL is per-environment, so the URL belongs here rather
|
|
@@ -43,35 +52,26 @@ class ClientRegistry {
|
|
|
43
52
|
/** The fallback for svcNames with no mapping. Undefined = no derivation in this environment. */
|
|
44
53
|
static deriver;
|
|
45
54
|
/**
|
|
46
|
-
* The
|
|
47
|
-
* {@link WEBPIECES_DEFAULT_ERROR_TRANSLATOR},
|
|
48
|
-
*
|
|
49
|
-
* no-DI pattern as {@link ClientRegistry.mappings} above.
|
|
50
|
-
*
|
|
51
|
-
* There is no "did anyone register one" question anywhere in the framework because this field
|
|
52
|
-
* cannot be empty: every caller makes ONE unconditional call, and an app translator declines an
|
|
53
|
-
* error by DELEGATING to {@link WebpiecesDefaultErrorTranslator}.
|
|
55
|
+
* The {@link ErrorTranslator} per {@link CallScope} — method -> API -> ALL, and below every rung
|
|
56
|
+
* {@link WEBPIECES_DEFAULT_ERROR_TRANSLATOR}, so {@link ClientRegistry.getErrorTranslator} is
|
|
57
|
+
* never undefined. Process-global, set once at startup on the SERVER and in the BROWSER — the
|
|
58
|
+
* same no-DI pattern as {@link ClientRegistry.mappings} above.
|
|
54
59
|
*
|
|
55
|
-
* ONE, not a list, on purpose: an app that has several layers of error
|
|
56
|
-
* INSIDE its own `toWire`, where the precedence is written
|
|
57
|
-
*
|
|
60
|
+
* ONE translator per scope, not a list, on purpose: an app that has several layers of error
|
|
61
|
+
* policy for one scope composes them INSIDE its own `toWire`, where the precedence is written
|
|
62
|
+
* down. The ladder exists because vendors put their errors on the wire in their OWN formats — a
|
|
63
|
+
* per-API translator reads one vendor's 4xx body (`fromWire`), or writes the error body an
|
|
64
|
+
* inbound vendor expects back (`toWire`), while every other contract keeps the app's ALL one.
|
|
58
65
|
*/
|
|
59
|
-
static
|
|
66
|
+
static errorTranslators = new CallScope_1.CallScopeLadder(() => new TranslatorSlot());
|
|
60
67
|
/**
|
|
61
|
-
* The
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
68
|
+
* The {@link ErrorClassifier} per {@link CallScope} — method -> API -> ALL, and below every rung
|
|
69
|
+
* {@link WEBPIECES_DEFAULT_ERROR_CLASSIFIER}. A classifier that DEFERS (`undefined`) falls through
|
|
70
|
+
* to the next rung. The ALL classifier reads {@link ApiMethodInfo.side}, so one classifier covers
|
|
71
|
+
* the server router AND every internal client; an SDK-wrapping api-client registers its own at
|
|
72
|
+
* its abstract `XxxApi` class. See {@link ClientRegistry.classify}.
|
|
66
73
|
*/
|
|
67
|
-
static
|
|
68
|
-
/**
|
|
69
|
-
* Per-EXTERNAL-client {@link ErrorClassifier}s, keyed by {@link ApiMethodInfo.apiClass}
|
|
70
|
-
* ('FirestoreAdminClient', 'ClaudeApi', ...). Consulted BEFORE the default tier, so an external
|
|
71
|
-
* client overrides the default for its own apiClass only. Internal clients / the server register
|
|
72
|
-
* nothing here and fall through to the default. See {@link ClientRegistry.classify}.
|
|
73
|
-
*/
|
|
74
|
-
static errorClassifiersByApiClass = new Map();
|
|
74
|
+
static errorClassifiers = new CallScope_1.CallScopeLadder(() => new ClassifierSlot());
|
|
75
75
|
/** Map a service name to `http://localhost:<port>`. */
|
|
76
76
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
|
|
77
77
|
static addMapping(svcName, port) {
|
|
@@ -153,89 +153,88 @@ class ClientRegistry {
|
|
|
153
153
|
return url;
|
|
154
154
|
}
|
|
155
155
|
/**
|
|
156
|
-
* Install
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* {@link ClientRegistry.addMapping}.
|
|
156
|
+
* Install an {@link ErrorTranslator} for one {@link CallScope}: `'ALL'` (the app's translator,
|
|
157
|
+
* server side AND every client side), one contract CLASS, or one method of one contract. The
|
|
158
|
+
* scope is REQUIRED — there is no unscoped spelling. Call at startup, on the server AND in the
|
|
159
|
+
* browser, mirroring {@link ClientRegistry.addMapping}.
|
|
160
160
|
*
|
|
161
|
-
*
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* THE {@link ErrorTranslator} for this process — never `undefined`, so a caller never branches.
|
|
161
|
+
* ```ts
|
|
162
|
+
* ClientRegistry.setErrorTranslator(new AppErrorTranslator(), 'ALL');
|
|
163
|
+
* ClientRegistry.setErrorTranslator(new VendorErrorTranslator(), VendorApi); // one contract
|
|
164
|
+
* ClientRegistry.setErrorTranslator(new LegacyErrorTranslator(), VendorApi, 'sync'); // one method
|
|
165
|
+
* ```
|
|
169
166
|
*
|
|
170
|
-
*
|
|
171
|
-
* {@link
|
|
172
|
-
*
|
|
173
|
-
* so every call site in the framework is one unconditional line.
|
|
167
|
+
* `set`, not `add`: see the field doc above for why a per-scope LIST is not wanted. A translator
|
|
168
|
+
* declines an error by DELEGATING to {@link WebpiecesDefaultErrorTranslator} — not to the next
|
|
169
|
+
* rung — exactly as before.
|
|
174
170
|
*/
|
|
175
171
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
|
|
176
|
-
static
|
|
177
|
-
|
|
172
|
+
static setErrorTranslator(translator, ...scope) {
|
|
173
|
+
ClientRegistry.errorTranslators.at(...scope).translator = translator;
|
|
178
174
|
}
|
|
179
175
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
176
|
+
* THE {@link ErrorTranslator} for one call — method -> API -> ALL ->
|
|
177
|
+
* {@link WEBPIECES_DEFAULT_ERROR_TRANSLATOR}, never `undefined`, so a caller never branches.
|
|
178
|
+
*
|
|
179
|
+
* Both arguments are required so every call site states what it knows: the client and the server
|
|
180
|
+
* pass the route's contract class and method; a failure with no route (an unmatched request, a
|
|
181
|
+
* failure before routing) passes `undefined, undefined` and reaches the ALL rung alone.
|
|
184
182
|
*/
|
|
185
183
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
|
|
186
|
-
static
|
|
187
|
-
|
|
184
|
+
static getErrorTranslator(api, methodName) {
|
|
185
|
+
const registered = ClientRegistry.errorTranslators
|
|
186
|
+
.rungs(api, methodName)
|
|
187
|
+
.find((slot) => slot.translator !== undefined)?.translator;
|
|
188
|
+
return registered ?? WebpiecesDefaultErrorTranslator_1.WEBPIECES_DEFAULT_ERROR_TRANSLATOR;
|
|
188
189
|
}
|
|
189
190
|
/**
|
|
190
|
-
*
|
|
191
|
-
* {@link
|
|
192
|
-
*
|
|
191
|
+
* Install an {@link ErrorClassifier} for one {@link CallScope}: `'ALL'` (the app/company
|
|
192
|
+
* classifier — it reads {@link ApiMethodInfo.side}, so it covers the server router and every
|
|
193
|
+
* internal client), one contract CLASS (an SDK-wrapping api-client keys by its abstract `XxxApi`),
|
|
194
|
+
* or one method of one contract. The scope is REQUIRED. Call at startup, on the server AND in the
|
|
195
|
+
* browser. See {@link ErrorClassifier}.
|
|
193
196
|
*/
|
|
194
197
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
|
|
195
|
-
static
|
|
196
|
-
ClientRegistry.
|
|
198
|
+
static setErrorClassifier(classifier, ...scope) {
|
|
199
|
+
ClientRegistry.errorClassifiers.at(...scope).classifier = classifier;
|
|
197
200
|
}
|
|
198
201
|
/**
|
|
199
|
-
* The {@link CallOutcome} of a thrown API-call error. Resolved
|
|
200
|
-
* definitive:
|
|
201
|
-
* 1. the
|
|
202
|
-
* 2. else the
|
|
203
|
-
* 3. else the
|
|
202
|
+
* The {@link CallOutcome} of a thrown API-call error. Resolved on the {@link CallScope} ladder of
|
|
203
|
+
* `methodInfo.contract` / `methodInfo.methodName`, most specific first, and ALWAYS definitive:
|
|
204
|
+
* 1. the method classifier — unless it defers;
|
|
205
|
+
* 2. else the API classifier — unless it defers;
|
|
206
|
+
* 3. else the ALL classifier — unless it defers;
|
|
207
|
+
* 4. else the webpieces built-in ({@link WEBPIECES_DEFAULT_ERROR_CLASSIFIER}), which never defers.
|
|
204
208
|
*
|
|
205
209
|
* The ONE verdict read by `RetryStrategy` (retry only TRANSIENT), `CallRegistry`'s per-attempt
|
|
206
210
|
* log line, `LogApiCall` (EXPECTED → OTHER, else FAIL) and the MCP renderer's retry advice.
|
|
207
211
|
*/
|
|
208
212
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
|
|
209
213
|
static classify(error, methodInfo) {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
const verdict =
|
|
214
|
+
const rungs = ClientRegistry.errorClassifiers.rungs(methodInfo.contract, methodInfo.methodName);
|
|
215
|
+
for (const slot of rungs) {
|
|
216
|
+
const verdict = slot.classifier?.classify(error, methodInfo);
|
|
213
217
|
if (verdict !== undefined) {
|
|
214
218
|
return verdict;
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
|
-
const verdict = ClientRegistry.appDefaultErrorClassifier.classify(error, methodInfo);
|
|
218
|
-
if (verdict !== undefined) {
|
|
219
|
-
return verdict;
|
|
220
|
-
}
|
|
221
221
|
return WebpiecesDefaultErrorClassifier_1.WEBPIECES_DEFAULT_ERROR_CLASSIFIER.classify(error, methodInfo);
|
|
222
222
|
}
|
|
223
223
|
/**
|
|
224
224
|
* Put the process-globals back to the state a fresh process starts in. For TESTS, so one spec
|
|
225
225
|
* cannot leak its registrations into the next.
|
|
226
226
|
*
|
|
227
|
-
* It
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
227
|
+
* It empties every translator and classifier rung, which RESTORES the webpieces defaults:
|
|
228
|
+
* `getErrorTranslator(...)` and `classify(...)` fall through to them below every rung, so
|
|
229
|
+
* "cleared" is not a state they have. A spec that wants webpieces' own behaviour back asks for
|
|
230
|
+
* exactly that.
|
|
231
231
|
*/
|
|
232
232
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
|
|
233
233
|
static resetForTests() {
|
|
234
234
|
ClientRegistry.mappings.clear();
|
|
235
235
|
ClientRegistry.deriver = undefined;
|
|
236
|
-
ClientRegistry.
|
|
237
|
-
ClientRegistry.
|
|
238
|
-
ClientRegistry.errorClassifiersByApiClass.clear();
|
|
236
|
+
ClientRegistry.errorTranslators.clear();
|
|
237
|
+
ClientRegistry.errorClassifiers.clear();
|
|
239
238
|
}
|
|
240
239
|
}
|
|
241
240
|
exports.ClientRegistry = ClientRegistry;
|