@webpieces/core-util 0.4.961 → 0.4.963

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.
Files changed (34) hide show
  1. package/package.json +1 -1
  2. package/src/http/ApiMethodInfo.d.ts +7 -6
  3. package/src/http/ApiMethodInfo.js +1 -1
  4. package/src/http/ApiMethodInfo.js.map +1 -1
  5. package/src/http/CallRegistryCompileAssertions.js +6 -1
  6. package/src/http/CallRegistryCompileAssertions.js.map +1 -1
  7. package/src/http/CallScope.d.ts +2 -2
  8. package/src/http/CallScope.js +2 -2
  9. package/src/http/CallScope.js.map +1 -1
  10. package/src/http/CallStrategy.d.ts +6 -5
  11. package/src/http/CallStrategy.js +1 -1
  12. package/src/http/CallStrategy.js.map +1 -1
  13. package/src/http/EndpointCompileAssertions.js +14 -0
  14. package/src/http/EndpointCompileAssertions.js.map +1 -1
  15. package/src/http/ErrorLadderCompileAssertions.js +6 -2
  16. package/src/http/ErrorLadderCompileAssertions.js.map +1 -1
  17. package/src/http/HttpEndpointOptions.d.ts +19 -3
  18. package/src/http/HttpEndpointOptions.js.map +1 -1
  19. package/src/http/RouteMetadata.d.ts +2 -1
  20. package/src/http/RouteMetadata.js +2 -1
  21. package/src/http/RouteMetadata.js.map +1 -1
  22. package/src/http/RouteMetadataFactory.d.ts +8 -1
  23. package/src/http/RouteMetadataFactory.js +28 -7
  24. package/src/http/RouteMetadataFactory.js.map +1 -1
  25. package/src/http/decorators.d.ts +4 -3
  26. package/src/http/decorators.js.map +1 -1
  27. package/src/index.d.ts +1 -1
  28. package/src/index.js.map +1 -1
  29. package/src/ipc/IpcConnection.d.ts +7 -1
  30. package/src/ipc/IpcConnection.js +8 -2
  31. package/src/ipc/IpcConnection.js.map +1 -1
  32. package/src/ipc/IpcLogging.d.ts +3 -1
  33. package/src/ipc/IpcLogging.js +4 -2
  34. package/src/ipc/IpcLogging.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/core-util",
3
- "version": "0.4.961",
3
+ "version": "0.4.963",
4
4
  "description": "Utility functions for WebPieces - works in browser and Node.js",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -52,22 +52,23 @@ export declare class ApiMethodInfo {
52
52
  * The contract CLASS (the `@ApiPath` contract, or an SDK-wrapping api-client's abstract
53
53
  * `XxxApi`) — the key {@link ClientRegistry.classify} resolves the `CallScope` ladder on
54
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.
55
+ * argument so every caller states it, and never `undefined`: an SDK-wrapping client passes its
56
+ * abstract `XxxApi`, an IPC hop its `@WpInternal` class, and an MCP protocol step (which has no
57
+ * user contract) the framework-owned `McpProtocol` class — which reaches the ALL rung unless
58
+ * somebody registers for that class.
58
59
  *
59
60
  * NON-enumerable on purpose: this object is nested into every `[API-*]` log record as
60
61
  * `jsonPayload.api.method`, and a class reference is not log data.
61
62
  */
62
- readonly contract: Function | undefined;
63
+ readonly contract: Function;
63
64
  constructor(side: ApiSide,
64
65
  /** REQUIRED — the API CONTRACT class name (e.g. 'SaveApi'). Matches client + server so both
65
66
  * sides of one logical call filter together via `jsonPayload.api.method.apiClass`. */
66
67
  apiClass: string,
67
68
  /** REQUIRED — the method on the contract (e.g. 'save'). */
68
69
  methodName: string,
69
- /** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link ApiMethodInfo.contract}. */
70
- contract: Function | undefined,
70
+ /** REQUIRED — the contract CLASS; see {@link ApiMethodInfo.contract}. */
71
+ contract: Function,
71
72
  /** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no
72
73
  * impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
73
74
  controllerName?: string | undefined,
@@ -34,7 +34,7 @@ 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}. */
37
+ /** REQUIRED — the contract CLASS; see {@link ApiMethodInfo.contract}. */
38
38
  contract,
39
39
  /** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no
40
40
  * impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
@@ -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;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
+ {"version":3,"file":"ApiMethodInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiMethodInfo.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAOH,MAAa,aAAa;IAgBT;IAGA;IAEA;IAKA;IAMA;IAKA;IAIA;IAIA;IA9Bb,YACa,IAAa;IACtB;2FACuF;IAC9E,QAAgB;IACzB,2DAA2D;IAClD,UAAkB;IAC3B,yEAAyE;IACzE,QAAkB;IAClB;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;AAjDD,sCAiDC","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, and never `undefined`: an SDK-wrapping client passes its\n * abstract `XxxApi`, an IPC hop its `@WpInternal` class, and an MCP protocol step (which has no\n * user contract) the framework-owned `McpProtocol` class — which reaches the ALL rung unless\n * somebody registers for that class.\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;\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; see {@link ApiMethodInfo.contract}. */\n contract: Function,\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"]}
@@ -11,9 +11,14 @@ class CallRegistryCompileAssertions {
11
11
  // @ts-expect-error ALL cannot select a method
12
12
  CallRegistry_1.CallRegistry.setStrategy(undefined, 'ALL', 'work');
13
13
  }
14
+ /** A CallContext's contract is the CLASS, never undefined (#1350). */
15
+ contractIsNeverUndefined() {
16
+ // @ts-expect-error CallContext's contract is a CLASS, never undefined — pass the real contract class
17
+ void new CallStrategy_1.CallContext('Api', 'work', HttpEndpointOptions_1.WRITE, undefined);
18
+ }
14
19
  /** The transport fallback is a TransportDefault (timeout + optional strategy), never a bare number. */
15
20
  fallbackIsATransportDefault() {
16
- const context = new CallStrategy_1.CallContext('Api', 'work', HttpEndpointOptions_1.WRITE, undefined);
21
+ const context = new CallStrategy_1.CallContext('Api', 'work', HttpEndpointOptions_1.WRITE, CallRegistryCompileAssertions);
17
22
  void CallRegistry_1.CallRegistry.execute(CallRegistryCompileAssertions, context, (ms) => Promise.resolve(ms),
18
23
  // @ts-expect-error execute(..., defaultMs: number) became execute(..., fallback: TransportDefault)
19
24
  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,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"]}
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,sEAAsE;IACtE,wBAAwB;QACpB,qGAAqG;QACrG,KAAK,IAAI,0BAAW,CAAC,KAAK,EAAE,MAAM,EAAE,2BAAK,EAAE,SAAS,CAAC,CAAC;IAC1D,CAAC;IAED,uGAAuG;IACvG,2BAA2B;QACvB,MAAM,OAAO,GAAG,IAAI,0BAAW,CAAC,KAAK,EAAE,MAAM,EAAE,2BAAK,EAAE,6BAA6B,CAAC,CAAC;QACrF,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 /** A CallContext's contract is the CLASS, never undefined (#1350). */\n contractIsNeverUndefined(): void {\n // @ts-expect-error CallContext's contract is a CLASS, never undefined — pass the real contract class\n void new CallContext('Api', 'work', WRITE, undefined);\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, CallRegistryCompileAssertions);\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"]}
@@ -25,8 +25,8 @@ export declare class CallScopeLadder<T> {
25
25
  at(...scope: CallScope): T;
26
26
  /**
27
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.
28
+ * no slot is skipped. `api` undefined (a failure before routing, which has no route and so no
29
+ * contract class) reaches only the ALL rung.
30
30
  */
31
31
  rungs(api: Function | undefined, methodName: string | undefined): T[];
32
32
  clear(): void;
@@ -46,8 +46,8 @@ class CallScopeLadder {
46
46
  }
47
47
  /**
48
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.
49
+ * no slot is skipped. `api` undefined (a failure before routing, which has no route and so no
50
+ * contract class) reaches only the ALL rung.
51
51
  */
52
52
  rungs(api, methodName) {
53
53
  const found = [];
@@ -1 +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"]}
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 failure before routing, which has no route and so no\n * contract class) 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"]}
@@ -17,16 +17,17 @@ export declare class CallContext {
17
17
  /**
18
18
  * The contract CLASS the call belongs to — the key of the `CallScope` ladder, so a strategy's
19
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.
20
+ * REQUIRED and never `undefined`: every transport knows its contract class (HTTP and Cloud Tasks
21
+ * clients hold it, the IPC client proxy holds its `@WpInternal` class), and
22
+ * `CallRegistry.execute` re-keys the context a strategy sees onto the class it was given.
22
23
  *
23
24
  * NON-enumerable on purpose: this context rides on `ApiCallTimeoutError`, which is logged and
24
25
  * compared as data, and a class reference is not data.
25
26
  */
26
- readonly contract: Function | undefined;
27
+ readonly contract: Function;
27
28
  constructor(apiName: string, methodName: string, operation: EndpointOperation,
28
- /** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link CallContext.contract}. */
29
- contract: Function | undefined);
29
+ /** REQUIRED — the contract CLASS; see {@link CallContext.contract}. */
30
+ contract: Function);
30
31
  }
31
32
  /** Owns all timing and retry decisions. No public cancellation signal. */
32
33
  export type CallStrategy<T> = (call: Attempt<T>, ctx: CallContext) => Promise<T>;
@@ -15,7 +15,7 @@ class CallContext {
15
15
  methodName;
16
16
  operation;
17
17
  constructor(apiName, methodName, operation,
18
- /** REQUIRED — the contract CLASS, or explicit `undefined`; see {@link CallContext.contract}. */
18
+ /** REQUIRED — the contract CLASS; see {@link CallContext.contract}. */
19
19
  contract) {
20
20
  this.apiName = apiName;
21
21
  this.methodName = methodName;
@@ -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;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
+ {"version":3,"file":"CallStrategy.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/CallStrategy.ts"],"names":[],"mappings":";;;AAKA;;;;;;;;GAQG;AACH,MAAa,WAAW;IAcA;IACA;IACA;IAHpB,YACoB,OAAe,EACf,UAAkB,EAClB,SAA4B;IAC5C,uEAAuE;IACvE,QAAkB;QAJF,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;AAtBD,kCAsBC","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 * REQUIRED and never `undefined`: every transport knows its contract class (HTTP and Cloud Tasks\n * clients hold it, the IPC client proxy holds its `@WpInternal` class), and\n * `CallRegistry.execute` re-keys the context a strategy sees onto the class it was given.\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;\n\n constructor(\n public readonly apiName: string,\n public readonly methodName: string,\n public readonly operation: EndpointOperation,\n /** REQUIRED — the contract CLASS; see {@link CallContext.contract}. */\n contract: Function,\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
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const HttpEndpointOptions_1 = require("./HttpEndpointOptions");
3
4
  const decorators_1 = require("./decorators");
4
5
  (0, decorators_1.Endpoint)(decorators_1.POST, '/valid-rpc', decorators_1.WRITE, decorators_1.RPC);
5
6
  (0, decorators_1.Endpoint)(decorators_1.PUT, '/valid-put', decorators_1.WRITE_IDEMPOTENT, decorators_1.RPC);
@@ -24,4 +25,17 @@ const decorators_1 = require("./decorators");
24
25
  (0, decorators_1.Endpoint)(decorators_1.POST, '/raw-trigger', decorators_1.WRITE, 'rpc');
25
26
  // @ts-expect-error callers must import the enum-backed HTTP method constant, for the new verbs too
26
27
  (0, decorators_1.Endpoint)('PUT', '/raw-put', decorators_1.WRITE, decorators_1.RPC);
28
+ // A CLOUDTASKS / CRON endpoint has no response body BY KIND, an EXTERNAL_OUTBOUND one is shaped by the
29
+ // vendor (#1348); NONE stays a declaration only where OUR server answers (RPC, EXTERNAL_INBOUND).
30
+ (0, decorators_1.Endpoint)(decorators_1.POST, '/valid-task', decorators_1.WRITE, decorators_1.CLOUDTASKS);
31
+ (0, decorators_1.Endpoint)(decorators_1.POST, '/valid-cron', decorators_1.WRITE, decorators_1.CRON, { noLogging: true });
32
+ (0, decorators_1.Endpoint)(decorators_1.POST, '/valid-rpc-void', decorators_1.WRITE, decorators_1.RPC, { responseBody: HttpEndpointOptions_1.NONE });
33
+ (0, decorators_1.Endpoint)(decorators_1.DELETE, '/valid-outbound-void', decorators_1.WRITE_IDEMPOTENT, decorators_1.EXTERNAL_OUTBOUND);
34
+ (0, decorators_1.Endpoint)(decorators_1.POST, '/valid-inbound-void', decorators_1.WRITE, decorators_1.EXTERNAL_INBOUND, { calledBy: 'twilio', responseBody: HttpEndpointOptions_1.NONE });
35
+ // @ts-expect-error CLOUDTASKS already implies no response body; responseBody: NONE is a second spelling
36
+ (0, decorators_1.Endpoint)(decorators_1.POST, '/redundant-task-none', decorators_1.WRITE, decorators_1.CLOUDTASKS, { responseBody: HttpEndpointOptions_1.NONE });
37
+ // @ts-expect-error CRON already implies no response body; responseBody: NONE is a second spelling
38
+ (0, decorators_1.Endpoint)(decorators_1.POST, '/redundant-cron-none', decorators_1.WRITE, decorators_1.CRON, { responseBody: HttpEndpointOptions_1.NONE });
39
+ // @ts-expect-error the vendor shapes an EXTERNAL_OUTBOUND response; responseBody: NONE describes OUR server
40
+ (0, decorators_1.Endpoint)(decorators_1.DELETE, '/outbound-none', decorators_1.WRITE_IDEMPOTENT, decorators_1.EXTERNAL_OUTBOUND, { responseBody: HttpEndpointOptions_1.NONE });
27
41
  //# sourceMappingURL=EndpointCompileAssertions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EndpointCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/EndpointCompileAssertions.ts"],"names":[],"mappings":";;AAAA,6CAAgJ;AAEhJ,IAAA,qBAAQ,EAAC,iBAAI,EAAE,YAAY,EAAE,kBAAK,EAAE,gBAAG,CAAC,CAAC;AACzC,IAAA,qBAAQ,EAAC,gBAAG,EAAE,YAAY,EAAE,6BAAgB,EAAE,gBAAG,CAAC,CAAC;AACnD,IAAA,qBAAQ,EAAC,kBAAK,EAAE,cAAc,EAAE,kBAAK,EAAE,gBAAG,CAAC,CAAC;AAC5C,IAAA,qBAAQ,EAAC,mBAAM,EAAE,eAAe,EAAE,6BAAgB,EAAE,gBAAG,CAAC,CAAC;AACzD,IAAA,qBAAQ,EAAC,iBAAI,EAAE,iBAAiB,EAAE,kBAAK,EAAE,6BAAgB,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;AACnF,IAAA,qBAAQ,EAAC,gBAAG,EAAE,iBAAiB,EAAE,iBAAI,EAAE,8BAAiB,CAAC,CAAC;AAE1D,kFAAkF;AAClF,IAAA,qBAAQ,EAAC,iBAAI,EAAE,2BAA2B,EAAE,kBAAK,EAAE,6BAAgB,CAAC,CAAC;AAErE,sEAAsE;AACtE,IAAA,qBAAQ,EAAC,iBAAI,EAAE,0BAA0B,EAAE,kBAAK,EAAE,6BAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AAExF,kFAAkF;AAClF,IAAA,qBAAQ,EAAC,iBAAI,EAAE,sBAAsB,EAAE,kBAAK,EAAE,6BAAgB,EAAE;IAC5D,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,QAAQ;CACvB,CAAC,CAAC;AAEH,0EAA0E;AAC1E,IAAA,qBAAQ,EAAC,iBAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAG,CAAC,CAAC;AAE/C,4EAA4E;AAC5E,IAAA,qBAAQ,EAAC,MAAM,EAAE,aAAa,EAAE,kBAAK,EAAE,gBAAG,CAAC,CAAC;AAE5C,6EAA6E;AAC7E,IAAA,qBAAQ,EAAC,iBAAI,EAAE,cAAc,EAAE,kBAAK,EAAE,KAAK,CAAC,CAAC;AAE7C,mGAAmG;AACnG,IAAA,qBAAQ,EAAC,KAAK,EAAE,UAAU,EAAE,kBAAK,EAAE,gBAAG,CAAC,CAAC","sourcesContent":["import { DELETE, GET, READ, Endpoint, EXTERNAL_INBOUND, EXTERNAL_OUTBOUND, PATCH, POST, PUT, RPC, WRITE, WRITE_IDEMPOTENT } from './decorators';\n\nEndpoint(POST, '/valid-rpc', WRITE, RPC);\nEndpoint(PUT, '/valid-put', WRITE_IDEMPOTENT, RPC);\nEndpoint(PATCH, '/valid-patch', WRITE, RPC);\nEndpoint(DELETE, '/valid-delete', WRITE_IDEMPOTENT, RPC);\nEndpoint(POST, '/valid-external', WRITE, EXTERNAL_INBOUND, { calledBy: 'twilio' });\nEndpoint(GET, '/valid-outbound', READ, EXTERNAL_OUTBOUND);\n\n// @ts-expect-error inbound external endpoints require options containing calledBy\nEndpoint(POST, '/missing-external-options', WRITE, EXTERNAL_INBOUND);\n\n// @ts-expect-error inbound external endpoint options require calledBy\nEndpoint(POST, '/missing-external-caller', WRITE, EXTERNAL_INBOUND, { formPost: true });\n\n// @ts-expect-error callerKind is a closed enum-backed union rather than free text\nEndpoint(POST, '/invalid-caller-kind', WRITE, EXTERNAL_INBOUND, {\n calledBy: 'twilio',\n callerKind: 'vendor',\n});\n\n// @ts-expect-error callers must import the enum-backed operation constant\nEndpoint(POST, '/raw-operation', 'write', RPC);\n\n// @ts-expect-error callers must import the enum-backed HTTP method constant\nEndpoint('POST', '/raw-method', WRITE, RPC);\n\n// @ts-expect-error callers must import the enum-backed trigger-kind constant\nEndpoint(POST, '/raw-trigger', WRITE, 'rpc');\n\n// @ts-expect-error callers must import the enum-backed HTTP method constant, for the new verbs too\nEndpoint('PUT', '/raw-put', WRITE, RPC);\n"]}
1
+ {"version":3,"file":"EndpointCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/EndpointCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { NONE } from './HttpEndpointOptions';\nimport { CLOUDTASKS, CRON, DELETE, GET, READ, Endpoint, EXTERNAL_INBOUND, EXTERNAL_OUTBOUND, PATCH, POST, PUT, RPC, WRITE, WRITE_IDEMPOTENT } from './decorators';\n\nEndpoint(POST, '/valid-rpc', WRITE, RPC);\nEndpoint(PUT, '/valid-put', WRITE_IDEMPOTENT, RPC);\nEndpoint(PATCH, '/valid-patch', WRITE, RPC);\nEndpoint(DELETE, '/valid-delete', WRITE_IDEMPOTENT, RPC);\nEndpoint(POST, '/valid-external', WRITE, EXTERNAL_INBOUND, { calledBy: 'twilio' });\nEndpoint(GET, '/valid-outbound', READ, EXTERNAL_OUTBOUND);\n\n// @ts-expect-error inbound external endpoints require options containing calledBy\nEndpoint(POST, '/missing-external-options', WRITE, EXTERNAL_INBOUND);\n\n// @ts-expect-error inbound external endpoint options require calledBy\nEndpoint(POST, '/missing-external-caller', WRITE, EXTERNAL_INBOUND, { formPost: true });\n\n// @ts-expect-error callerKind is a closed enum-backed union rather than free text\nEndpoint(POST, '/invalid-caller-kind', WRITE, EXTERNAL_INBOUND, {\n calledBy: 'twilio',\n callerKind: 'vendor',\n});\n\n// @ts-expect-error callers must import the enum-backed operation constant\nEndpoint(POST, '/raw-operation', 'write', RPC);\n\n// @ts-expect-error callers must import the enum-backed HTTP method constant\nEndpoint('POST', '/raw-method', WRITE, RPC);\n\n// @ts-expect-error callers must import the enum-backed trigger-kind constant\nEndpoint(POST, '/raw-trigger', WRITE, 'rpc');\n\n// @ts-expect-error callers must import the enum-backed HTTP method constant, for the new verbs too\nEndpoint('PUT', '/raw-put', WRITE, RPC);\n\n// A CLOUDTASKS / CRON endpoint has no response body BY KIND, an EXTERNAL_OUTBOUND one is shaped by the\n// vendor (#1348); NONE stays a declaration only where OUR server answers (RPC, EXTERNAL_INBOUND).\nEndpoint(POST, '/valid-task', WRITE, CLOUDTASKS);\nEndpoint(POST, '/valid-cron', WRITE, CRON, { noLogging: true });\nEndpoint(POST, '/valid-rpc-void', WRITE, RPC, { responseBody: NONE });\nEndpoint(DELETE, '/valid-outbound-void', WRITE_IDEMPOTENT, EXTERNAL_OUTBOUND);\nEndpoint(POST, '/valid-inbound-void', WRITE, EXTERNAL_INBOUND, { calledBy: 'twilio', responseBody: NONE });\n\n// @ts-expect-error CLOUDTASKS already implies no response body; responseBody: NONE is a second spelling\nEndpoint(POST, '/redundant-task-none', WRITE, CLOUDTASKS, { responseBody: NONE });\n\n// @ts-expect-error CRON already implies no response body; responseBody: NONE is a second spelling\nEndpoint(POST, '/redundant-cron-none', WRITE, CRON, { responseBody: NONE });\n\n// @ts-expect-error the vendor shapes an EXTERNAL_OUTBOUND response; responseBody: NONE describes OUR server\nEndpoint(DELETE, '/outbound-none', WRITE_IDEMPOTENT, EXTERNAL_OUTBOUND, { responseBody: NONE });\n"]}
@@ -29,10 +29,14 @@ class ErrorLadderCompileAssertions {
29
29
  ClientRegistry_1.ClientRegistry.setErrorClassifier(classifier, 'ALL', 'work');
30
30
  }
31
31
  callIdentityStatesItsContract() {
32
- // @ts-expect-error ApiMethodInfo's contract CLASS is required (pass undefined only when there is none)
32
+ // @ts-expect-error ApiMethodInfo's contract CLASS is required
33
33
  void new ApiMethodInfo_1.ApiMethodInfo('client', 'SaveApi', 'save');
34
- // @ts-expect-error CallContext's contract CLASS is required (pass undefined only when there is none)
34
+ // @ts-expect-error CallContext's contract CLASS is required
35
35
  void new CallStrategy_1.CallContext('SaveApi', 'save', HttpEndpointOptions_1.WRITE);
36
+ // @ts-expect-error ApiMethodInfo's contract is a CLASS, never undefined — pass the real contract class
37
+ void new ApiMethodInfo_1.ApiMethodInfo('client', 'SaveApi', 'save', undefined);
38
+ // @ts-expect-error CallContext's contract is a CLASS, never undefined — pass the real contract class
39
+ void new CallStrategy_1.CallContext('SaveApi', 'save', HttpEndpointOptions_1.WRITE, undefined);
36
40
  }
37
41
  keyedByClassNeverByName(classifier) {
38
42
  // @ts-expect-error the ladder is keyed by the contract CLASS, never its name
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorLadderCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ErrorLadderCompileAssertions.ts"],"names":[],"mappings":";;AAAA,qDAAkD;AAGlD,mDAAgD;AAChD,iDAA6C;AAC7C,+DAA8C;AAE9C;;;;GAIG;AACH,MAAM,4BAA4B;IAC9B,eAAe,CAAC,UAA2B,EAAE,UAA2B;QACpE,qGAAqG;QACrG,+BAAc,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC9C,oFAAoF;QACpF,+BAAc,CAAC,yBAAyB,CAAC,UAAU,CAAC,CAAC;QACrD,8GAA8G;QAC9G,+BAAc,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC9C,wEAAwE;QACxE,+BAAc,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC9C,mFAAmF;QACnF,+BAAc,CAAC,kBAAkB,EAAE,CAAC;IACxC,CAAC;IAED,cAAc,CAAC,UAA2B,EAAE,UAA2B;QACnE,8CAA8C;QAC9C,+BAAc,CAAC,kBAAkB,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC7D,8CAA8C;QAC9C,+BAAc,CAAC,kBAAkB,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACjE,CAAC;IAED,6BAA6B;QACzB,uGAAuG;QACvG,KAAK,IAAI,6BAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QACpD,qGAAqG;QACrG,KAAK,IAAI,0BAAW,CAAC,SAAS,EAAE,MAAM,EAAE,2BAAK,CAAC,CAAC;IACnD,CAAC;IAED,uBAAuB,CAAC,UAA2B;QAC/C,6EAA6E;QAC7E,+BAAc,CAAC,kBAAkB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC7D,CAAC;CACJ","sourcesContent":["import { ClientRegistry } from './ClientRegistry';\nimport { ErrorClassifier } from './ErrorClassifier';\nimport { ErrorTranslator } from './ErrorTranslator';\nimport { ApiMethodInfo } from './ApiMethodInfo';\nimport { CallContext } from './CallStrategy';\nimport { WRITE } from './HttpEndpointOptions';\n\n/**\n * Compiled by tsc (spec files are not), never executed. Pins the ONE spelling of the error ladder:\n * every registration states its `CallScope`, and the unscoped spellings it replaced do not compile.\n * tsc fails the build with TS2578 if any of these ever starts compiling again.\n */\nclass ErrorLadderCompileAssertions {\n scopeIsRequired(translator: ErrorTranslator, classifier: ErrorClassifier): void {\n // @ts-expect-error setErrorTranslator(t) became setErrorTranslator(t, 'ALL') — the scope is required\n ClientRegistry.setErrorTranslator(translator);\n // @ts-expect-error setDefaultErrorClassifier(c) became setErrorClassifier(c, 'ALL')\n ClientRegistry.setDefaultErrorClassifier(classifier);\n // @ts-expect-error addErrorClassifier(new KeyedErrorClassifier('XApi', c)) became setErrorClassifier(c, XApi)\n ClientRegistry.addErrorClassifier(classifier);\n // @ts-expect-error the scope is required — setErrorClassifier(c, 'ALL')\n ClientRegistry.setErrorClassifier(classifier);\n // @ts-expect-error getErrorTranslator() became getErrorTranslator(api, methodName)\n ClientRegistry.getErrorTranslator();\n }\n\n allHasNoMethod(translator: ErrorTranslator, classifier: ErrorClassifier): void {\n // @ts-expect-error ALL cannot select a method\n ClientRegistry.setErrorTranslator(translator, 'ALL', 'work');\n // @ts-expect-error ALL cannot select a method\n ClientRegistry.setErrorClassifier(classifier, 'ALL', 'work');\n }\n\n callIdentityStatesItsContract(): void {\n // @ts-expect-error ApiMethodInfo's contract CLASS is required (pass undefined only when there is none)\n void new ApiMethodInfo('client', 'SaveApi', 'save');\n // @ts-expect-error CallContext's contract CLASS is required (pass undefined only when there is none)\n void new CallContext('SaveApi', 'save', WRITE);\n }\n\n keyedByClassNeverByName(classifier: ErrorClassifier): void {\n // @ts-expect-error the ladder is keyed by the contract CLASS, never its name\n ClientRegistry.setErrorClassifier(classifier, 'SaveApi');\n }\n}\n"]}
1
+ {"version":3,"file":"ErrorLadderCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ErrorLadderCompileAssertions.ts"],"names":[],"mappings":";;AAAA,qDAAkD;AAGlD,mDAAgD;AAChD,iDAA6C;AAC7C,+DAA8C;AAE9C;;;;GAIG;AACH,MAAM,4BAA4B;IAC9B,eAAe,CAAC,UAA2B,EAAE,UAA2B;QACpE,qGAAqG;QACrG,+BAAc,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC9C,oFAAoF;QACpF,+BAAc,CAAC,yBAAyB,CAAC,UAAU,CAAC,CAAC;QACrD,8GAA8G;QAC9G,+BAAc,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC9C,wEAAwE;QACxE,+BAAc,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC9C,mFAAmF;QACnF,+BAAc,CAAC,kBAAkB,EAAE,CAAC;IACxC,CAAC;IAED,cAAc,CAAC,UAA2B,EAAE,UAA2B;QACnE,8CAA8C;QAC9C,+BAAc,CAAC,kBAAkB,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC7D,8CAA8C;QAC9C,+BAAc,CAAC,kBAAkB,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACjE,CAAC;IAED,6BAA6B;QACzB,8DAA8D;QAC9D,KAAK,IAAI,6BAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QACpD,4DAA4D;QAC5D,KAAK,IAAI,0BAAW,CAAC,SAAS,EAAE,MAAM,EAAE,2BAAK,CAAC,CAAC;QAC/C,uGAAuG;QACvG,KAAK,IAAI,6BAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAC/D,qGAAqG;QACrG,KAAK,IAAI,0BAAW,CAAC,SAAS,EAAE,MAAM,EAAE,2BAAK,EAAE,SAAS,CAAC,CAAC;IAC9D,CAAC;IAED,uBAAuB,CAAC,UAA2B;QAC/C,6EAA6E;QAC7E,+BAAc,CAAC,kBAAkB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC7D,CAAC;CACJ","sourcesContent":["import { ClientRegistry } from './ClientRegistry';\nimport { ErrorClassifier } from './ErrorClassifier';\nimport { ErrorTranslator } from './ErrorTranslator';\nimport { ApiMethodInfo } from './ApiMethodInfo';\nimport { CallContext } from './CallStrategy';\nimport { WRITE } from './HttpEndpointOptions';\n\n/**\n * Compiled by tsc (spec files are not), never executed. Pins the ONE spelling of the error ladder:\n * every registration states its `CallScope`, and the unscoped spellings it replaced do not compile.\n * tsc fails the build with TS2578 if any of these ever starts compiling again.\n */\nclass ErrorLadderCompileAssertions {\n scopeIsRequired(translator: ErrorTranslator, classifier: ErrorClassifier): void {\n // @ts-expect-error setErrorTranslator(t) became setErrorTranslator(t, 'ALL') — the scope is required\n ClientRegistry.setErrorTranslator(translator);\n // @ts-expect-error setDefaultErrorClassifier(c) became setErrorClassifier(c, 'ALL')\n ClientRegistry.setDefaultErrorClassifier(classifier);\n // @ts-expect-error addErrorClassifier(new KeyedErrorClassifier('XApi', c)) became setErrorClassifier(c, XApi)\n ClientRegistry.addErrorClassifier(classifier);\n // @ts-expect-error the scope is required — setErrorClassifier(c, 'ALL')\n ClientRegistry.setErrorClassifier(classifier);\n // @ts-expect-error getErrorTranslator() became getErrorTranslator(api, methodName)\n ClientRegistry.getErrorTranslator();\n }\n\n allHasNoMethod(translator: ErrorTranslator, classifier: ErrorClassifier): void {\n // @ts-expect-error ALL cannot select a method\n ClientRegistry.setErrorTranslator(translator, 'ALL', 'work');\n // @ts-expect-error ALL cannot select a method\n ClientRegistry.setErrorClassifier(classifier, 'ALL', 'work');\n }\n\n callIdentityStatesItsContract(): void {\n // @ts-expect-error ApiMethodInfo's contract CLASS is required\n void new ApiMethodInfo('client', 'SaveApi', 'save');\n // @ts-expect-error CallContext's contract CLASS is required\n void new CallContext('SaveApi', 'save', WRITE);\n // @ts-expect-error ApiMethodInfo's contract is a CLASS, never undefined — pass the real contract class\n void new ApiMethodInfo('client', 'SaveApi', 'save', undefined);\n // @ts-expect-error CallContext's contract is a CLASS, never undefined — pass the real contract class\n void new CallContext('SaveApi', 'save', WRITE, undefined);\n }\n\n keyedByClassNeverByName(classifier: ErrorClassifier): void {\n // @ts-expect-error the ladder is keyed by the contract CLASS, never its name\n ClientRegistry.setErrorClassifier(classifier, 'SaveApi');\n }\n}\n"]}
@@ -25,14 +25,21 @@ export interface EndpointOptions {
25
25
  /** Return only a JSON body (default), or the full transport-neutral status/header/body value. */
26
26
  responseType?: EndpointResponseType;
27
27
  /**
28
- * `NONE`: this method returns `Promise<void>` and its success response is 0 BYTES. Our server
28
+ * `NONE`: this method returns `Promise<void>` and OUR SERVER's success response is 0 BYTES: it
29
29
  * answers `204 No Content` with no body and no Content-Type, and every client (node, browser,
30
- * in-process) resolves `undefined` — while still ACCEPTING `{}`, an empty `200` or a `204`,
31
- * because a vendor whose spec documents no body may still send one.
30
+ * in-process) resolves `undefined` — while still ACCEPTING `{}`, an empty `200` or a `204`.
32
31
  *
33
32
  * WHY a declaration: `design:returntype` of an async method is just `Promise`, so the runtime
34
33
  * cannot see `void`. The architecture scan checks this option agrees with the declared
35
34
  * `Promise<void>` in both directions, so it cannot drift from the signature.
35
+ *
36
+ * Only for `RPC` and `EXTERNAL_INBOUND`, where our server answers and a body is a real
37
+ * question (#1348). Every other kind refuses it (see {@link NoResponseBodyOptionEndpointOptions}):
38
+ * - `CLOUDTASKS` / `CRON` are fire-and-forget BY KIND — the caller reads nothing but the status —
39
+ * so the kind itself implies no response body; such a method must return `Promise<void>`.
40
+ * - `EXTERNAL_OUTBOUND` is served by the VENDOR, which shapes its own responses; no server of
41
+ * ours implements it. Its client accepts any 2xx: 204 / empty -> `undefined`, a JSON body is
42
+ * parsed, a non-JSON body -> `undefined`.
36
43
  */
37
44
  responseBody?: ResponseBodyKind;
38
45
  /**
@@ -160,4 +167,13 @@ export interface ExternalEndpointOptions extends EndpointOptions {
160
167
  /** What that caller is; picks its graph shape. Defaults to `'saas'`. */
161
168
  callerKind?: ExternalSystemKind;
162
169
  }
170
+ /**
171
+ * The options of a `CLOUDTASKS`, `CRON` or `EXTERNAL_OUTBOUND` endpoint, where `responseBody: NONE`
172
+ * cannot be written (#1348): the CLOUDTASKS / CRON kind already says the response has no body (one
173
+ * spelling per decision), and an EXTERNAL_OUTBOUND response is shaped by the vendor, not by us.
174
+ */
175
+ export interface NoResponseBodyOptionEndpointOptions extends EndpointOptions {
176
+ /** Implied by CLOUDTASKS / CRON, the vendor's to shape on EXTERNAL_OUTBOUND: `NONE` does not compile. */
177
+ responseBody?: never;
178
+ }
163
179
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"HttpEndpointOptions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HttpEndpointOptions.ts"],"names":[],"mappings":";;;AAGA,8EAA8E;AAC9E,IAAK,sBAIJ;AAJD,WAAK,sBAAsB;IACvB,uCAAa,CAAA;IACb,+DAAqC,CAAA;IACrC,yCAAe,CAAA;AACnB,CAAC,EAJI,sBAAsB,KAAtB,sBAAsB,QAI1B;AAED,wDAAwD;AAC3C,QAAA,IAAI,GAAG,sBAAsB,CAAC,IAAI,CAAC;AACnC,QAAA,gBAAgB,GAAG,sBAAsB,CAAC,gBAAgB,CAAC;AAC3D,QAAA,KAAK,GAAG,sBAAsB,CAAC,KAAK,CAAC;AAKlD,gEAAgE;AAChE,IAAK,iBAEJ;AAFD,WAAK,iBAAiB;IAClB,kCAAa,CAAA;AACjB,CAAC,EAFI,iBAAiB,KAAjB,iBAAiB,QAErB;AAED,uFAAuF;AAC1E,QAAA,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC","sourcesContent":["import { ExternalSystemKind } from './external-caller';\nimport { EndpointResponseType } from './HttpContract';\n\n/** Nominal backing keeps raw string literals out of endpoint declarations. */\nenum EndpointOperationValue {\n READ = 'read',\n WRITE_IDEMPOTENT = 'write-idempotent',\n WRITE = 'write',\n}\n\n/** Short, statically importable decorator arguments. */\nexport const READ = EndpointOperationValue.READ;\nexport const WRITE_IDEMPOTENT = EndpointOperationValue.WRITE_IDEMPOTENT;\nexport const WRITE = EndpointOperationValue.WRITE;\n\n/** The side-effect contract callers use to decide whether repeating an operation is safe. */\nexport type EndpointOperation = typeof READ | typeof WRITE_IDEMPOTENT | typeof WRITE;\n\n/** Nominal backing for {@link EndpointOptions.responseBody}. */\nenum ResponseBodyValue {\n NONE = 'none',\n}\n\n/** `@Endpoint(..., { responseBody: NONE })` — the success response carries 0 bytes. */\nexport const NONE = ResponseBodyValue.NONE;\n\n/** The one response-body declaration an endpoint can make; absent means a JSON body. */\nexport type ResponseBodyKind = typeof NONE;\n\n/** Options for one `@Endpoint`, stored parallel to the method-to-path metadata. */\nexport interface EndpointOptions {\n /** Return only a JSON body (default), or the full transport-neutral status/header/body value. */\n responseType?: EndpointResponseType;\n /**\n * `NONE`: this method returns `Promise<void>` and its success response is 0 BYTES. Our server\n * answers `204 No Content` with no body and no Content-Type, and every client (node, browser,\n * in-process) resolves `undefined` — while still ACCEPTING `{}`, an empty `200` or a `204`,\n * because a vendor whose spec documents no body may still send one.\n *\n * WHY a declaration: `design:returntype` of an async method is just `Promise`, so the runtime\n * cannot see `void`. The architecture scan checks this option agrees with the declared\n * `Promise<void>` in both directions, so it cannot drift from the signature.\n */\n responseBody?: ResponseBodyKind;\n /**\n * Parse the request body as application/x-www-form-urlencoded instead of JSON. The request\n * DTO must be flat; urlencoded has no nesting. Default false = JSON.\n */\n formPost?: boolean;\n /**\n * Retain the verbatim request bytes and absolute URL for a `vendorAuth(...)` hook to verify.\n * This is retention, not new buffering: the Express adapter already accumulates the body.\n *\n * A served `vendorAuth(...)` endpoint requires this option at wiring time. It combines with `formPost` for vendors\n * that sign flat form bodies, e.g. `{ formPost: true, rawBody: true }`.\n */\n rawBody?: boolean;\n /**\n * The app should show NO progress UI for this call — it is not something the USER is waiting on\n * (a background download, a log shipper, a heartbeat, a telemetry flush).\n *\n * webpieces draws no progress bar itself; it DECLARES the fact and carries it on\n * `RouteMetadata.hideProgress`, which an app's `RequestLifecycleListener` reads to keep the call\n * out of its bar. WHY a declaration on the endpoint: the alternative is the app matching path\n * strings in its listener, which silently drifts the first time a route is renamed. The ROUTE is\n * the stable fact, so the decision belongs to the route's author.\n *\n * Independent of {@link noLogging} and {@link allowUpgradeInFlight}: a background download hides\n * its progress yet still wants its `[API-*]` lines (exactly what you need when a sync is slow)\n * and still must finish before an app upgrades under it.\n *\n * Default false: absent means the call drives the app's progress UI, like any ordinary route.\n * `grep -rn hideProgress` lists every endpoint the user is not shown waiting on.\n */\n hideProgress?: boolean;\n /**\n * webpieces writes NO `[API-*-req]` / `[API-*-resp-*]` line for this call, on either side —\n * {@link LogApiCallImpl} reads it via `ApiMethodInfo.noLogging`, handed in by both the client\n * proxy and the server's `LogApiFilter` — and stamps no per-call `api` tag.\n *\n * WHY a declaration and not a suppression switch: shipping a log over a logged transport logs\n * about shipping logs — the line describing the call IS the thing being shipped, and emitting it\n * feeds the next batch its own payload. The two mechanisms that do not know the endpoint both\n * failed in prod (see #976) — a wall-clock \"we are shipping\" boolean swallowed unrelated lines\n * including an error alarm, and a logger-name allowlist was incomplete by construction the moment\n * anyone added a logger, producing a steady-state 4-lines-per-second request loop on an idle page.\n * The ENDPOINT is the only stable, declarative fact, so it is where the decision belongs.\n *\n * Rides the route metadata for the same reason {@link formPost} and {@link rawBody} do: the\n * consumer branches on the ROUTE, without knowing the apiClass/methodName.\n *\n * Default false: absent means the ordinary, fully-logged route. The quiet path is the one you\n * have to say out loud, so `grep -rn noLogging` lists every endpoint webpieces stops logging.\n */\n noLogging?: boolean;\n /**\n * An app MAY upgrade (reload onto a newer deployed bundle) while this call is in flight, and the\n * call neither blocks nor arms that upgrade.\n *\n * By default every RPC participates in the app's upgrade decision: the app must not upgrade while\n * it is in flight, and it counts toward the \"finished with a SUCCESS result\" boundary at which an\n * app typically arms its seamless upgrade. That default is the safe one — reloading under an\n * in-flight write or download loses it. A call nobody asked for, and that is harmless to cut off\n * (a log shipper, a heartbeat), declares `true` so it can neither hold an upgrade hostage nor be\n * the success that triggers one.\n *\n * WHY separate from {@link hideProgress}: apps commonly arm an upgrade off their progress bar's\n * request group, so hiding a call from the bar used to drop it out of the upgrade decision as a\n * side effect. A hidden background download must still block an upgrade until it succeeds; only\n * this flag, said out loud, lets a call stop participating.\n *\n * webpieces only declares and carries it (`RouteMetadata.allowUpgradeInFlight`); the app's\n * `RequestLifecycleListener` / upgrade logic decides what to do with it. Default false.\n * `grep -rn allowUpgradeInFlight` lists every endpoint an upgrade may cut off.\n *\n * THE SERVER'S HALF of the upgrade story is `ApiClientTooOldError` (HTTP 426,\n * `errorCode: 'client-too-old'`). This flag is the CLIENT deciding WHEN it may upgrade; that\n * error is a SERVER saying the client MUST. Who throws it: a server gate that compares the build\n * the caller declared (a header, a version in the request) against the minimum it still\n * supports. Receivers decode it back into `ApiClientTooOldError` on every hop, server-to-server\n * and browser, HTTP and IPC, and treat it as an upgrade, never as a bug: the app's upgrade logic\n * reloads or installs the new build, typically honouring this flag for the calls still in flight.\n */\n allowUpgradeInFlight?: boolean;\n /**\n * This route is NOT part of the CUSTOMER-facing contract. `public-openapi.json` — the document a\n * customer gets, and the only one a publish path uploads — contains no path, no operation, no\n * schema and no prose for it. It is not \"rendered with a flag set\", it is absent.\n *\n * It is still in `full-private-openapi.json`, and still in `mcp-openapi.json` when it carries\n * `@WpMcpTool`. The reason it is withheld belongs in the method's JSDoc, which is where every\n * other piece of its documentation already lives.\n *\n * WHY A BOOLEAN HERE IS NOT SHIM SHAPE #5, given that `@ApiType` exists precisely to avoid a\n * falsy default meaning \"publish\": the two operate at different levels and the default here is\n * NOT the permissive one. A contract reaches customers only by declaring\n * `@ApiType(..., EXTERNAL_CUSTOMER)` — one deliberate token per contract — and `hidden` only ever\n * SUBTRACTS from what that token already granted. Omitting it on a contract nobody published to\n * customers publishes nothing. The per-method case it exists for is real and cannot be said at\n * class level: a NEW endpoint on an already-published API, built on main before it is announced.\n *\n * Default false, and `grep -rn \"hidden: true\"` lists every method withheld from customers.\n *\n * HIDING IS A DOCUMENTATION DECISION AND NEVER AN ACCESS-CONTROL ONE. The route is served exactly\n * as before and is still reachable by anything holding its credential — including an agent, if it\n * also carries `@WpMcpTool`. Declare its authorization as loudly as a published one.\n */\n hidden?: boolean;\n /**\n * This operation may touch systems OUTSIDE this service — a payment processor, a partner's API,\n * the physical world. It feeds MCP's `openWorldHint`, and it is rendered as a sentence in the\n * operation's description so a human integrating against the document sees it too.\n *\n * DEFAULT FALSE, deliberately unlike the MCP specification's own `true` default. MCP defaults to\n * \"open world\" because it cannot know; webpieces CAN know, because the author of the endpoint is\n * right here. Defaulting to `true` would make the interesting case the silent one and the boring\n * case the one you have to type, which is backwards — and an agent told that every endpoint might\n * reach the outside world has been told nothing. `grep -rn \"openWorld: true\"` lists the ones that\n * genuinely do.\n */\n openWorld?: boolean;\n}\n\n/** An EXTERNAL_INBOUND endpoint must also identify the outside system that calls it. */\nexport interface ExternalEndpointOptions extends EndpointOptions {\n /** The outside system (`'twilio'`) — graph identity, not display text. */\n calledBy: string;\n /** What that caller is; picks its graph shape. Defaults to `'saas'`. */\n callerKind?: ExternalSystemKind;\n}\n"]}
1
+ {"version":3,"file":"HttpEndpointOptions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HttpEndpointOptions.ts"],"names":[],"mappings":";;;AAGA,8EAA8E;AAC9E,IAAK,sBAIJ;AAJD,WAAK,sBAAsB;IACvB,uCAAa,CAAA;IACb,+DAAqC,CAAA;IACrC,yCAAe,CAAA;AACnB,CAAC,EAJI,sBAAsB,KAAtB,sBAAsB,QAI1B;AAED,wDAAwD;AAC3C,QAAA,IAAI,GAAG,sBAAsB,CAAC,IAAI,CAAC;AACnC,QAAA,gBAAgB,GAAG,sBAAsB,CAAC,gBAAgB,CAAC;AAC3D,QAAA,KAAK,GAAG,sBAAsB,CAAC,KAAK,CAAC;AAKlD,gEAAgE;AAChE,IAAK,iBAEJ;AAFD,WAAK,iBAAiB;IAClB,kCAAa,CAAA;AACjB,CAAC,EAFI,iBAAiB,KAAjB,iBAAiB,QAErB;AAED,uFAAuF;AAC1E,QAAA,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC","sourcesContent":["import { ExternalSystemKind } from './external-caller';\nimport { EndpointResponseType } from './HttpContract';\n\n/** Nominal backing keeps raw string literals out of endpoint declarations. */\nenum EndpointOperationValue {\n READ = 'read',\n WRITE_IDEMPOTENT = 'write-idempotent',\n WRITE = 'write',\n}\n\n/** Short, statically importable decorator arguments. */\nexport const READ = EndpointOperationValue.READ;\nexport const WRITE_IDEMPOTENT = EndpointOperationValue.WRITE_IDEMPOTENT;\nexport const WRITE = EndpointOperationValue.WRITE;\n\n/** The side-effect contract callers use to decide whether repeating an operation is safe. */\nexport type EndpointOperation = typeof READ | typeof WRITE_IDEMPOTENT | typeof WRITE;\n\n/** Nominal backing for {@link EndpointOptions.responseBody}. */\nenum ResponseBodyValue {\n NONE = 'none',\n}\n\n/** `@Endpoint(..., { responseBody: NONE })` — the success response carries 0 bytes. */\nexport const NONE = ResponseBodyValue.NONE;\n\n/** The one response-body declaration an endpoint can make; absent means a JSON body. */\nexport type ResponseBodyKind = typeof NONE;\n\n/** Options for one `@Endpoint`, stored parallel to the method-to-path metadata. */\nexport interface EndpointOptions {\n /** Return only a JSON body (default), or the full transport-neutral status/header/body value. */\n responseType?: EndpointResponseType;\n /**\n * `NONE`: this method returns `Promise<void>` and OUR SERVER's success response is 0 BYTES: it\n * answers `204 No Content` with no body and no Content-Type, and every client (node, browser,\n * in-process) resolves `undefined` — while still ACCEPTING `{}`, an empty `200` or a `204`.\n *\n * WHY a declaration: `design:returntype` of an async method is just `Promise`, so the runtime\n * cannot see `void`. The architecture scan checks this option agrees with the declared\n * `Promise<void>` in both directions, so it cannot drift from the signature.\n *\n * Only for `RPC` and `EXTERNAL_INBOUND`, where our server answers and a body is a real\n * question (#1348). Every other kind refuses it (see {@link NoResponseBodyOptionEndpointOptions}):\n * - `CLOUDTASKS` / `CRON` are fire-and-forget BY KIND — the caller reads nothing but the status —\n * so the kind itself implies no response body; such a method must return `Promise<void>`.\n * - `EXTERNAL_OUTBOUND` is served by the VENDOR, which shapes its own responses; no server of\n * ours implements it. Its client accepts any 2xx: 204 / empty -> `undefined`, a JSON body is\n * parsed, a non-JSON body -> `undefined`.\n */\n responseBody?: ResponseBodyKind;\n /**\n * Parse the request body as application/x-www-form-urlencoded instead of JSON. The request\n * DTO must be flat; urlencoded has no nesting. Default false = JSON.\n */\n formPost?: boolean;\n /**\n * Retain the verbatim request bytes and absolute URL for a `vendorAuth(...)` hook to verify.\n * This is retention, not new buffering: the Express adapter already accumulates the body.\n *\n * A served `vendorAuth(...)` endpoint requires this option at wiring time. It combines with `formPost` for vendors\n * that sign flat form bodies, e.g. `{ formPost: true, rawBody: true }`.\n */\n rawBody?: boolean;\n /**\n * The app should show NO progress UI for this call — it is not something the USER is waiting on\n * (a background download, a log shipper, a heartbeat, a telemetry flush).\n *\n * webpieces draws no progress bar itself; it DECLARES the fact and carries it on\n * `RouteMetadata.hideProgress`, which an app's `RequestLifecycleListener` reads to keep the call\n * out of its bar. WHY a declaration on the endpoint: the alternative is the app matching path\n * strings in its listener, which silently drifts the first time a route is renamed. The ROUTE is\n * the stable fact, so the decision belongs to the route's author.\n *\n * Independent of {@link noLogging} and {@link allowUpgradeInFlight}: a background download hides\n * its progress yet still wants its `[API-*]` lines (exactly what you need when a sync is slow)\n * and still must finish before an app upgrades under it.\n *\n * Default false: absent means the call drives the app's progress UI, like any ordinary route.\n * `grep -rn hideProgress` lists every endpoint the user is not shown waiting on.\n */\n hideProgress?: boolean;\n /**\n * webpieces writes NO `[API-*-req]` / `[API-*-resp-*]` line for this call, on either side —\n * {@link LogApiCallImpl} reads it via `ApiMethodInfo.noLogging`, handed in by both the client\n * proxy and the server's `LogApiFilter` — and stamps no per-call `api` tag.\n *\n * WHY a declaration and not a suppression switch: shipping a log over a logged transport logs\n * about shipping logs — the line describing the call IS the thing being shipped, and emitting it\n * feeds the next batch its own payload. The two mechanisms that do not know the endpoint both\n * failed in prod (see #976) — a wall-clock \"we are shipping\" boolean swallowed unrelated lines\n * including an error alarm, and a logger-name allowlist was incomplete by construction the moment\n * anyone added a logger, producing a steady-state 4-lines-per-second request loop on an idle page.\n * The ENDPOINT is the only stable, declarative fact, so it is where the decision belongs.\n *\n * Rides the route metadata for the same reason {@link formPost} and {@link rawBody} do: the\n * consumer branches on the ROUTE, without knowing the apiClass/methodName.\n *\n * Default false: absent means the ordinary, fully-logged route. The quiet path is the one you\n * have to say out loud, so `grep -rn noLogging` lists every endpoint webpieces stops logging.\n */\n noLogging?: boolean;\n /**\n * An app MAY upgrade (reload onto a newer deployed bundle) while this call is in flight, and the\n * call neither blocks nor arms that upgrade.\n *\n * By default every RPC participates in the app's upgrade decision: the app must not upgrade while\n * it is in flight, and it counts toward the \"finished with a SUCCESS result\" boundary at which an\n * app typically arms its seamless upgrade. That default is the safe one — reloading under an\n * in-flight write or download loses it. A call nobody asked for, and that is harmless to cut off\n * (a log shipper, a heartbeat), declares `true` so it can neither hold an upgrade hostage nor be\n * the success that triggers one.\n *\n * WHY separate from {@link hideProgress}: apps commonly arm an upgrade off their progress bar's\n * request group, so hiding a call from the bar used to drop it out of the upgrade decision as a\n * side effect. A hidden background download must still block an upgrade until it succeeds; only\n * this flag, said out loud, lets a call stop participating.\n *\n * webpieces only declares and carries it (`RouteMetadata.allowUpgradeInFlight`); the app's\n * `RequestLifecycleListener` / upgrade logic decides what to do with it. Default false.\n * `grep -rn allowUpgradeInFlight` lists every endpoint an upgrade may cut off.\n *\n * THE SERVER'S HALF of the upgrade story is `ApiClientTooOldError` (HTTP 426,\n * `errorCode: 'client-too-old'`). This flag is the CLIENT deciding WHEN it may upgrade; that\n * error is a SERVER saying the client MUST. Who throws it: a server gate that compares the build\n * the caller declared (a header, a version in the request) against the minimum it still\n * supports. Receivers decode it back into `ApiClientTooOldError` on every hop, server-to-server\n * and browser, HTTP and IPC, and treat it as an upgrade, never as a bug: the app's upgrade logic\n * reloads or installs the new build, typically honouring this flag for the calls still in flight.\n */\n allowUpgradeInFlight?: boolean;\n /**\n * This route is NOT part of the CUSTOMER-facing contract. `public-openapi.json` — the document a\n * customer gets, and the only one a publish path uploads — contains no path, no operation, no\n * schema and no prose for it. It is not \"rendered with a flag set\", it is absent.\n *\n * It is still in `full-private-openapi.json`, and still in `mcp-openapi.json` when it carries\n * `@WpMcpTool`. The reason it is withheld belongs in the method's JSDoc, which is where every\n * other piece of its documentation already lives.\n *\n * WHY A BOOLEAN HERE IS NOT SHIM SHAPE #5, given that `@ApiType` exists precisely to avoid a\n * falsy default meaning \"publish\": the two operate at different levels and the default here is\n * NOT the permissive one. A contract reaches customers only by declaring\n * `@ApiType(..., EXTERNAL_CUSTOMER)` — one deliberate token per contract — and `hidden` only ever\n * SUBTRACTS from what that token already granted. Omitting it on a contract nobody published to\n * customers publishes nothing. The per-method case it exists for is real and cannot be said at\n * class level: a NEW endpoint on an already-published API, built on main before it is announced.\n *\n * Default false, and `grep -rn \"hidden: true\"` lists every method withheld from customers.\n *\n * HIDING IS A DOCUMENTATION DECISION AND NEVER AN ACCESS-CONTROL ONE. The route is served exactly\n * as before and is still reachable by anything holding its credential — including an agent, if it\n * also carries `@WpMcpTool`. Declare its authorization as loudly as a published one.\n */\n hidden?: boolean;\n /**\n * This operation may touch systems OUTSIDE this service — a payment processor, a partner's API,\n * the physical world. It feeds MCP's `openWorldHint`, and it is rendered as a sentence in the\n * operation's description so a human integrating against the document sees it too.\n *\n * DEFAULT FALSE, deliberately unlike the MCP specification's own `true` default. MCP defaults to\n * \"open world\" because it cannot know; webpieces CAN know, because the author of the endpoint is\n * right here. Defaulting to `true` would make the interesting case the silent one and the boring\n * case the one you have to type, which is backwards — and an agent told that every endpoint might\n * reach the outside world has been told nothing. `grep -rn \"openWorld: true\"` lists the ones that\n * genuinely do.\n */\n openWorld?: boolean;\n}\n\n/** An EXTERNAL_INBOUND endpoint must also identify the outside system that calls it. */\nexport interface ExternalEndpointOptions extends EndpointOptions {\n /** The outside system (`'twilio'`) — graph identity, not display text. */\n calledBy: string;\n /** What that caller is; picks its graph shape. Defaults to `'saas'`. */\n callerKind?: ExternalSystemKind;\n}\n\n/**\n * The options of a `CLOUDTASKS`, `CRON` or `EXTERNAL_OUTBOUND` endpoint, where `responseBody: NONE`\n * cannot be written (#1348): the CLOUDTASKS / CRON kind already says the response has no body (one\n * spelling per decision), and an EXTERNAL_OUTBOUND response is shaped by the vendor, not by us.\n */\nexport interface NoResponseBodyOptionEndpointOptions extends EndpointOptions {\n /** Implied by CLOUDTASKS / CRON, the vendor's to shape on EXTERNAL_OUTBOUND: `NONE` does not compile. */\n responseBody?: never;\n}\n"]}
@@ -98,7 +98,8 @@ export declare class RouteMetadata {
98
98
  */
99
99
  readonly allowUpgradeInFlight: boolean;
100
100
  /**
101
- * True when @Endpoint(..., { responseBody: NONE }): the method returns `Promise<void>` and the
101
+ * True when the endpoint is CLOUDTASKS or CRON (no response body BY KIND, #1348), or declares
102
+ * @Endpoint(..., { responseBody: NONE }): the method returns `Promise<void>` and the
102
103
  * success response is 0 bytes. The server answers 204 with no body; every client accepts `{}`,
103
104
  * an empty 200 or a 204 and resolves `undefined`. Default false = a JSON body.
104
105
  */
@@ -109,7 +109,8 @@ class RouteMetadata {
109
109
  */
110
110
  allowUpgradeInFlight;
111
111
  /**
112
- * True when @Endpoint(..., { responseBody: NONE }): the method returns `Promise<void>` and the
112
+ * True when the endpoint is CLOUDTASKS or CRON (no response body BY KIND, #1348), or declares
113
+ * @Endpoint(..., { responseBody: NONE }): the method returns `Promise<void>` and the
113
114
  * success response is 0 bytes. The server answers 204 with no body; every client accepts `{}`,
114
115
  * an empty 200 or a 204 and resolves `undefined`. Default false = a JSON body.
115
116
  */
@@ -1 +1 @@
1
- {"version":3,"file":"RouteMetadata.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadata.ts"],"names":[],"mappings":"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","sourcesContent":["import { MaskSpec } from './LogFieldMask';\nimport { AuthMeta } from './auth-mode';\nimport { EndpointResponseType, HttpParameterBinding } from './HttpContract';\nimport { StreamingEndpointMetadata } from './StreamingContract';\nimport { EndpointOperation } from './HttpEndpointOptions';\nimport { CallMode } from '../errors/CallMode';\nimport { AnyContextKey } from '../ContextKey';\n\n/**\n * Route metadata stored per-method at runtime.\n * Used internally by http-routing and http-client as the runtime representation\n * of a route. Constructed from @ApiPath + @Endpoint metadata by ProxyClient\n * and ApiRoutingFactory.\n *\n * Lives in its own file (one class per file) purely for file size, exactly as `api-kind.ts` and\n * `external-caller.ts` were split off `decorators.ts` before it. Nothing about its role changed.\n */\nexport class RouteMetadata {\n apiClass?: Function;\n // webpieces-disable no-any-unknown -- erased application policy is validated by AuthorizationHook at server startup\n authorization?: import('./authorization').AuthorizationRequirement<unknown>;\n localOnly = false;\n /**\n * True when the contract is `EXTERNAL_OUTBOUND`: an outside system's API. Its client sends ONLY\n * {@link externalContextKeys} and believes no response header (`DestinationTrust.forRoute`).\n */\n externalOutbound = false;\n /** `@ExternalContextKeys(...)` resolved for this method — class-level keys plus the method's own. */\n externalContextKeys: readonly AnyContextKey[] = [];\n\n /** Clients clone metadata per call so a concrete winning mode governs both trust directions. */\n withSelectedAuth(method: import('./auth-mode').AuthMode): RouteMetadata {\n if (!this.authMeta?.methods.includes(method)) throw new Error('Selected credential is not declared by this endpoint.');\n const route = Object.assign(new RouteMetadata(this.httpMethod, this.path, this.methodName, this.operation), this);\n route.authMeta = new AuthMeta([method], method.kind === 'public' ? this.authMeta.publicReason : undefined);\n return route;\n }\n /**\n * Whether the user is waiting on THIS call (#1293). Not an endpoint declaration: a client stamps\n * it per call with {@link withCallMode}, from the `ClientConfig` it was built with. FOREGROUND on\n * every route a contract's decorators build.\n */\n callMode: CallMode = CallMode.FOREGROUND;\n\n /**\n * A per-call copy carrying `mode`. A BACKGROUND call also gets {@link hideProgress} — nobody is\n * waiting on it — so an app's `RequestLifecycleListener` keeps it out of the progress bar with\n * no endpoint declaration. FOREGROUND returns this route unchanged.\n */\n withCallMode(mode: CallMode): RouteMetadata {\n if (mode === CallMode.FOREGROUND) return this;\n const route = new RouteMetadata(\n this.httpMethod,\n this.path,\n this.methodName,\n this.operation,\n this.controllerClassName,\n this.authMeta,\n this.apiName,\n this.formPost,\n this.mask,\n this.rawBody,\n this.parameterBindings,\n this.bodyParameterIndex,\n this.responseType,\n this.streaming,\n true,\n this.noLogging,\n this.allowUpgradeInFlight,\n this.noResponseBody,\n );\n route.apiClass = this.apiClass;\n route.authorization = this.authorization;\n route.localOnly = this.localOnly;\n route.externalOutbound = this.externalOutbound;\n route.externalContextKeys = this.externalContextKeys;\n route.callMode = mode;\n return route;\n }\n httpMethod: string;\n path: string;\n methodName: string;\n controllerClassName?: string;\n authMeta?: AuthMeta;\n /** The API contract class name (e.g. 'SaveApi') — distinct from the controller name. */\n apiName?: string;\n /**\n * True when @Endpoint(..., { formPost: true }): the body is application/x-www-form-urlencoded\n * (flat key→value), not JSON. Rides the route metadata so the per-route body parse can branch\n * without knowing the apiClass/methodName. Default false = JSON.\n */\n readonly formPost: boolean;\n /**\n * The @MaskLog field-mask spec for this route, or undefined when the method declared none. Read\n * ONCE here at route-build time and handed to {@link LogApiCallImpl} via ApiMethodInfo, so the per-call\n * log path pays for masking only on routes that opted in (the rest stay on plain JSON.stringify).\n */\n readonly mask?: MaskSpec;\n /**\n * True when @Endpoint(..., { rawBody: true }): the transport must retain the verbatim bytes +\n * absolute url for `VendorAuthHook.verify` to check a vendor credential over. Rides the route\n * metadata for the same reason {@link formPost} does — the transport adapter decides how to read\n * the body from the ROUTE, without knowing the apiClass/methodName.\n */\n readonly rawBody: boolean;\n /**\n * True when @Endpoint(..., { hideProgress: true }): the app should show no progress UI for this\n * call. webpieces does not act on it; an app's `RequestLifecycleListener.onRequestStart/onRequestEnd`\n * reads it from the ROUTE, for the same reason {@link formPost} and {@link rawBody} ride here —\n * the consumer branches without knowing the apiClass/methodName. Default false = the call drives\n * the progress UI.\n */\n readonly hideProgress: boolean;\n /**\n * True when @Endpoint(..., { noLogging: true }): read by {@link LogApiCallImpl} (via\n * ApiMethodInfo, exactly as {@link mask} is) so no `[API-*-req]`/`[API-*-resp-*]` line is\n * emitted for the call, on the client or the server. Default false = an ordinary, fully-logged\n * route.\n */\n readonly noLogging: boolean;\n /**\n * True when @Endpoint(..., { allowUpgradeInFlight: true }): an app may upgrade while this call is\n * in flight, and the call neither blocks nor arms an upgrade. webpieces only carries it; the\n * app's `RequestLifecycleListener` / upgrade logic reads it. Default false = the app must not\n * upgrade while the call is in flight, and its success counts toward arming one.\n */\n readonly allowUpgradeInFlight: boolean;\n /**\n * True when @Endpoint(..., { responseBody: NONE }): the method returns `Promise<void>` and the\n * success response is 0 bytes. The server answers 204 with no body; every client accepts `{}`,\n * an empty 200 or a 204 and resolves `undefined`. Default false = a JSON body.\n */\n readonly noResponseBody: boolean;\n\n constructor(\n httpMethod: string,\n path: string,\n methodName: string,\n /** Explicit side-effect semantics; deliberately independent of the HTTP verb. */\n readonly operation: EndpointOperation,\n controllerClassName?: string,\n authMeta?: AuthMeta,\n apiName?: string,\n formPost: boolean = false,\n mask?: MaskSpec,\n rawBody: boolean = false,\n /** Explicit path/query mappings, in API declaration order. */\n readonly parameterBindings: readonly HttpParameterBinding[] = [],\n /** The sole unannotated POST parameter, or undefined for a bodyless route. */\n readonly bodyParameterIndex?: number,\n /** Whether clients receive only the body or the complete status/header/body response. */\n readonly responseType: EndpointResponseType = 'body',\n /** Present only for `(ResponseStream) => Promise<RequestStream>` contracts. */\n readonly streaming?: StreamingEndpointMetadata,\n hideProgress: boolean = false,\n noLogging: boolean = false,\n allowUpgradeInFlight: boolean = false,\n noResponseBody: boolean = false,\n ) {\n this.httpMethod = httpMethod;\n this.path = path;\n this.methodName = methodName;\n this.controllerClassName = controllerClassName;\n this.authMeta = authMeta;\n this.apiName = apiName;\n this.formPost = formPost;\n this.mask = mask;\n this.rawBody = rawBody;\n this.parameterBindings = parameterBindings;\n this.bodyParameterIndex = bodyParameterIndex;\n this.responseType = responseType;\n this.streaming = streaming;\n this.hideProgress = hideProgress;\n this.noLogging = noLogging;\n this.allowUpgradeInFlight = allowUpgradeInFlight;\n this.noResponseBody = noResponseBody;\n }\n}\n"]}
1
+ {"version":3,"file":"RouteMetadata.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadata.ts"],"names":[],"mappings":"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","sourcesContent":["import { MaskSpec } from './LogFieldMask';\nimport { AuthMeta } from './auth-mode';\nimport { EndpointResponseType, HttpParameterBinding } from './HttpContract';\nimport { StreamingEndpointMetadata } from './StreamingContract';\nimport { EndpointOperation } from './HttpEndpointOptions';\nimport { CallMode } from '../errors/CallMode';\nimport { AnyContextKey } from '../ContextKey';\n\n/**\n * Route metadata stored per-method at runtime.\n * Used internally by http-routing and http-client as the runtime representation\n * of a route. Constructed from @ApiPath + @Endpoint metadata by ProxyClient\n * and ApiRoutingFactory.\n *\n * Lives in its own file (one class per file) purely for file size, exactly as `api-kind.ts` and\n * `external-caller.ts` were split off `decorators.ts` before it. Nothing about its role changed.\n */\nexport class RouteMetadata {\n apiClass?: Function;\n // webpieces-disable no-any-unknown -- erased application policy is validated by AuthorizationHook at server startup\n authorization?: import('./authorization').AuthorizationRequirement<unknown>;\n localOnly = false;\n /**\n * True when the contract is `EXTERNAL_OUTBOUND`: an outside system's API. Its client sends ONLY\n * {@link externalContextKeys} and believes no response header (`DestinationTrust.forRoute`).\n */\n externalOutbound = false;\n /** `@ExternalContextKeys(...)` resolved for this method — class-level keys plus the method's own. */\n externalContextKeys: readonly AnyContextKey[] = [];\n\n /** Clients clone metadata per call so a concrete winning mode governs both trust directions. */\n withSelectedAuth(method: import('./auth-mode').AuthMode): RouteMetadata {\n if (!this.authMeta?.methods.includes(method)) throw new Error('Selected credential is not declared by this endpoint.');\n const route = Object.assign(new RouteMetadata(this.httpMethod, this.path, this.methodName, this.operation), this);\n route.authMeta = new AuthMeta([method], method.kind === 'public' ? this.authMeta.publicReason : undefined);\n return route;\n }\n /**\n * Whether the user is waiting on THIS call (#1293). Not an endpoint declaration: a client stamps\n * it per call with {@link withCallMode}, from the `ClientConfig` it was built with. FOREGROUND on\n * every route a contract's decorators build.\n */\n callMode: CallMode = CallMode.FOREGROUND;\n\n /**\n * A per-call copy carrying `mode`. A BACKGROUND call also gets {@link hideProgress} — nobody is\n * waiting on it — so an app's `RequestLifecycleListener` keeps it out of the progress bar with\n * no endpoint declaration. FOREGROUND returns this route unchanged.\n */\n withCallMode(mode: CallMode): RouteMetadata {\n if (mode === CallMode.FOREGROUND) return this;\n const route = new RouteMetadata(\n this.httpMethod,\n this.path,\n this.methodName,\n this.operation,\n this.controllerClassName,\n this.authMeta,\n this.apiName,\n this.formPost,\n this.mask,\n this.rawBody,\n this.parameterBindings,\n this.bodyParameterIndex,\n this.responseType,\n this.streaming,\n true,\n this.noLogging,\n this.allowUpgradeInFlight,\n this.noResponseBody,\n );\n route.apiClass = this.apiClass;\n route.authorization = this.authorization;\n route.localOnly = this.localOnly;\n route.externalOutbound = this.externalOutbound;\n route.externalContextKeys = this.externalContextKeys;\n route.callMode = mode;\n return route;\n }\n httpMethod: string;\n path: string;\n methodName: string;\n controllerClassName?: string;\n authMeta?: AuthMeta;\n /** The API contract class name (e.g. 'SaveApi') — distinct from the controller name. */\n apiName?: string;\n /**\n * True when @Endpoint(..., { formPost: true }): the body is application/x-www-form-urlencoded\n * (flat key→value), not JSON. Rides the route metadata so the per-route body parse can branch\n * without knowing the apiClass/methodName. Default false = JSON.\n */\n readonly formPost: boolean;\n /**\n * The @MaskLog field-mask spec for this route, or undefined when the method declared none. Read\n * ONCE here at route-build time and handed to {@link LogApiCallImpl} via ApiMethodInfo, so the per-call\n * log path pays for masking only on routes that opted in (the rest stay on plain JSON.stringify).\n */\n readonly mask?: MaskSpec;\n /**\n * True when @Endpoint(..., { rawBody: true }): the transport must retain the verbatim bytes +\n * absolute url for `VendorAuthHook.verify` to check a vendor credential over. Rides the route\n * metadata for the same reason {@link formPost} does — the transport adapter decides how to read\n * the body from the ROUTE, without knowing the apiClass/methodName.\n */\n readonly rawBody: boolean;\n /**\n * True when @Endpoint(..., { hideProgress: true }): the app should show no progress UI for this\n * call. webpieces does not act on it; an app's `RequestLifecycleListener.onRequestStart/onRequestEnd`\n * reads it from the ROUTE, for the same reason {@link formPost} and {@link rawBody} ride here —\n * the consumer branches without knowing the apiClass/methodName. Default false = the call drives\n * the progress UI.\n */\n readonly hideProgress: boolean;\n /**\n * True when @Endpoint(..., { noLogging: true }): read by {@link LogApiCallImpl} (via\n * ApiMethodInfo, exactly as {@link mask} is) so no `[API-*-req]`/`[API-*-resp-*]` line is\n * emitted for the call, on the client or the server. Default false = an ordinary, fully-logged\n * route.\n */\n readonly noLogging: boolean;\n /**\n * True when @Endpoint(..., { allowUpgradeInFlight: true }): an app may upgrade while this call is\n * in flight, and the call neither blocks nor arms an upgrade. webpieces only carries it; the\n * app's `RequestLifecycleListener` / upgrade logic reads it. Default false = the app must not\n * upgrade while the call is in flight, and its success counts toward arming one.\n */\n readonly allowUpgradeInFlight: boolean;\n /**\n * True when the endpoint is CLOUDTASKS or CRON (no response body BY KIND, #1348), or declares\n * @Endpoint(..., { responseBody: NONE }): the method returns `Promise<void>` and the\n * success response is 0 bytes. The server answers 204 with no body; every client accepts `{}`,\n * an empty 200 or a 204 and resolves `undefined`. Default false = a JSON body.\n */\n readonly noResponseBody: boolean;\n\n constructor(\n httpMethod: string,\n path: string,\n methodName: string,\n /** Explicit side-effect semantics; deliberately independent of the HTTP verb. */\n readonly operation: EndpointOperation,\n controllerClassName?: string,\n authMeta?: AuthMeta,\n apiName?: string,\n formPost: boolean = false,\n mask?: MaskSpec,\n rawBody: boolean = false,\n /** Explicit path/query mappings, in API declaration order. */\n readonly parameterBindings: readonly HttpParameterBinding[] = [],\n /** The sole unannotated POST parameter, or undefined for a bodyless route. */\n readonly bodyParameterIndex?: number,\n /** Whether clients receive only the body or the complete status/header/body response. */\n readonly responseType: EndpointResponseType = 'body',\n /** Present only for `(ResponseStream) => Promise<RequestStream>` contracts. */\n readonly streaming?: StreamingEndpointMetadata,\n hideProgress: boolean = false,\n noLogging: boolean = false,\n allowUpgradeInFlight: boolean = false,\n noResponseBody: boolean = false,\n ) {\n this.httpMethod = httpMethod;\n this.path = path;\n this.methodName = methodName;\n this.controllerClassName = controllerClassName;\n this.authMeta = authMeta;\n this.apiName = apiName;\n this.formPost = formPost;\n this.mask = mask;\n this.rawBody = rawBody;\n this.parameterBindings = parameterBindings;\n this.bodyParameterIndex = bodyParameterIndex;\n this.responseType = responseType;\n this.streaming = streaming;\n this.hideProgress = hideProgress;\n this.noLogging = noLogging;\n this.allowUpgradeInFlight = allowUpgradeInFlight;\n this.noResponseBody = noResponseBody;\n }\n}\n"]}
@@ -57,7 +57,14 @@ export declare class RouteMetadataFactory {
57
57
  */
58
58
  private static validateQueryDeclarations;
59
59
  /**
60
- * `responseBody: NONE` means the success response is 0 bytes, so it cannot combine with a
60
+ * Whether the success response is 0 bytes (server 204, every client resolves `undefined`).
61
+ *
62
+ * A CLOUDTASKS or CRON endpoint is fire-and-forget BY KIND — Cloud Tasks and the scheduler read
63
+ * nothing but the status — so the kind implies it (#1348), and an explicit `responseBody: NONE`
64
+ * there is a second spelling of a decision the kind already made, so it is refused. On an
65
+ * EXTERNAL_OUTBOUND endpoint the VENDOR shapes the response, so NONE (what OUR server sends) is
66
+ * refused there too and the client accepts any 2xx. On RPC / EXTERNAL_INBOUND
67
+ * `responseBody: NONE` declares it. Either way the 0-byte response cannot combine with a
61
68
  * caller-owned full response or a streaming endpoint (both of which DEFINE a response).
62
69
  */
63
70
  private static noResponseBody;
@@ -36,7 +36,7 @@ class RouteMetadataFactory {
36
36
  ? this.validateStreamingParameters(apiClass, methodName, httpMethod, parameterTypes, declarations, streaming)
37
37
  : this.validateParameters(apiClass, methodName, fullPath, httpMethod, parameterTypes, declarations);
38
38
  const bindings = this.bindingsOf(declarations, parameterTypes);
39
- const route = new RouteMetadata_1.RouteMetadata(httpMethod, fullPath, methodName, operation, controllerClassName, (0, decorators_1.getAuthMeta)(apiClass, methodName), apiClass.name || 'UnknownApi', options.formPost === true, (0, decorators_1.getMaskSpec)(apiClass, methodName), options.rawBody === true, bindings, bodyParameterIndex, this.responseType(options.responseType, apiClass, methodName), streaming, options.hideProgress === true, options.noLogging === true, options.allowUpgradeInFlight === true, this.noResponseBody(options, streaming, apiClass, methodName));
39
+ const route = new RouteMetadata_1.RouteMetadata(httpMethod, fullPath, methodName, operation, controllerClassName, (0, decorators_1.getAuthMeta)(apiClass, methodName), apiClass.name || 'UnknownApi', options.formPost === true, (0, decorators_1.getMaskSpec)(apiClass, methodName), options.rawBody === true, bindings, bodyParameterIndex, this.responseType(options.responseType, apiClass, methodName), streaming, options.hideProgress === true, options.noLogging === true, options.allowUpgradeInFlight === true, this.noResponseBody(options, streaming, apiClass, methodName, (0, decorators_1.getEndpointKind)(apiClass, methodName)));
40
40
  route.authorization = (0, authorization_1.getAuthorization)(apiClass, methodName);
41
41
  route.apiClass = apiClass;
42
42
  route.localOnly = (0, decorators_2.isLocalOnly)(apiClass, methodName);
@@ -203,19 +203,40 @@ class RouteMetadataFactory {
203
203
  }
204
204
  }
205
205
  /**
206
- * `responseBody: NONE` means the success response is 0 bytes, so it cannot combine with a
206
+ * Whether the success response is 0 bytes (server 204, every client resolves `undefined`).
207
+ *
208
+ * A CLOUDTASKS or CRON endpoint is fire-and-forget BY KIND — Cloud Tasks and the scheduler read
209
+ * nothing but the status — so the kind implies it (#1348), and an explicit `responseBody: NONE`
210
+ * there is a second spelling of a decision the kind already made, so it is refused. On an
211
+ * EXTERNAL_OUTBOUND endpoint the VENDOR shapes the response, so NONE (what OUR server sends) is
212
+ * refused there too and the client accepts any 2xx. On RPC / EXTERNAL_INBOUND
213
+ * `responseBody: NONE` declares it. Either way the 0-byte response cannot combine with a
207
214
  * caller-owned full response or a streaming endpoint (both of which DEFINE a response).
208
215
  */
209
216
  // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory
210
- static noResponseBody(options, streaming, apiClass, methodName) {
211
- if (options.responseBody === undefined)
212
- return false;
217
+ static noResponseBody(options, streaming, apiClass, methodName, kind) {
213
218
  const label = `${apiClass.name || 'Unknown'}.${methodName}`;
214
- if (options.responseBody !== HttpEndpointOptions_1.NONE) {
219
+ if (kind === decorators_2.EXTERNAL_OUTBOUND && options.responseBody !== undefined) {
220
+ throw new Error(`${label} is an EXTERNAL_OUTBOUND endpoint and declares responseBody: NONE, which does not apply: ` +
221
+ `NONE describes what OUR server sends, and the vendor shapes an outbound response. The outbound ` +
222
+ `client accepts any 2xx (204 or empty -> undefined, JSON -> parsed). Delete responseBody: NONE.`);
223
+ }
224
+ const impliedByKind = kind === decorators_2.CLOUDTASKS || kind === decorators_2.CRON;
225
+ if (impliedByKind && options.responseBody !== undefined) {
226
+ throw new Error(`${label} is a ${kind.toUpperCase()} endpoint and declares responseBody: NONE, which is redundant: ` +
227
+ `a ${kind.toUpperCase()} endpoint is fire-and-forget, so its kind already means no response body ` +
228
+ `(204, 0 bytes). Delete responseBody: NONE from its @Endpoint options.`);
229
+ }
230
+ if (!impliedByKind && options.responseBody === undefined)
231
+ return false;
232
+ if (!impliedByKind && options.responseBody !== HttpEndpointOptions_1.NONE) {
215
233
  throw new Error(`${label} declares unsupported responseBody '${String(options.responseBody)}'; the only value is NONE.`);
216
234
  }
217
235
  if (options.responseType === 'full' || streaming !== undefined) {
218
- throw new Error(`${label} declares responseBody: NONE together with ${streaming ? 'a stream' : "responseType: 'full'"}; ` +
236
+ const why = impliedByKind
237
+ ? `is a ${kind.toUpperCase()} endpoint (no response body by kind)`
238
+ : 'declares responseBody: NONE';
239
+ throw new Error(`${label} ${why} together with ${streaming ? 'a stream' : "responseType: 'full'"}; ` +
219
240
  'a 0-byte response cannot also carry a caller-owned or streamed response.');
220
241
  }
221
242
  return true;