@webpieces/core-util 0.4.962 → 0.4.964

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 (65) hide show
  1. package/package.json +1 -1
  2. package/src/http/ApiCallInfo.d.ts +3 -3
  3. package/src/http/ApiCallInfo.js +1 -1
  4. package/src/http/ApiCallInfo.js.map +1 -1
  5. package/src/http/ApiMethodInfo.d.ts +1 -1
  6. package/src/http/ApiMethodInfo.js.map +1 -1
  7. package/src/http/ApiRegistry.d.ts +91 -0
  8. package/src/http/ApiRegistry.js +129 -0
  9. package/src/http/ApiRegistry.js.map +1 -0
  10. package/src/http/CallScope.d.ts +35 -8
  11. package/src/http/CallScope.js +59 -5
  12. package/src/http/CallScope.js.map +1 -1
  13. package/src/http/CallStrategy.d.ts +1 -1
  14. package/src/http/CallStrategy.js.map +1 -1
  15. package/src/http/ClientRegistry.d.ts +38 -71
  16. package/src/http/ClientRegistry.js +127 -97
  17. package/src/http/ClientRegistry.js.map +1 -1
  18. package/src/http/ClientRegistryCompileAssertions.js +63 -0
  19. package/src/http/ClientRegistryCompileAssertions.js.map +1 -0
  20. package/src/http/EndpointCompileAssertions.js +14 -0
  21. package/src/http/EndpointCompileAssertions.js.map +1 -1
  22. package/src/http/ErrorClassifier.d.ts +7 -7
  23. package/src/http/ErrorClassifier.js.map +1 -1
  24. package/src/http/ErrorLadderCompileAssertions.js +41 -17
  25. package/src/http/ErrorLadderCompileAssertions.js.map +1 -1
  26. package/src/http/ErrorTranslator.d.ts +6 -6
  27. package/src/http/ErrorTranslator.js.map +1 -1
  28. package/src/http/HttpEndpointOptions.d.ts +19 -3
  29. package/src/http/HttpEndpointOptions.js.map +1 -1
  30. package/src/http/LogApiCall.d.ts +1 -1
  31. package/src/http/LogApiCall.js +4 -4
  32. package/src/http/LogApiCall.js.map +1 -1
  33. package/src/http/RetryStrategy.d.ts +2 -2
  34. package/src/http/RetryStrategy.js +4 -4
  35. package/src/http/RetryStrategy.js.map +1 -1
  36. package/src/http/RouteMetadata.d.ts +2 -1
  37. package/src/http/RouteMetadata.js +2 -1
  38. package/src/http/RouteMetadata.js.map +1 -1
  39. package/src/http/RouteMetadataFactory.d.ts +8 -1
  40. package/src/http/RouteMetadataFactory.js +28 -7
  41. package/src/http/RouteMetadataFactory.js.map +1 -1
  42. package/src/http/StreamErrorControl.js +2 -2
  43. package/src/http/StreamErrorControl.js.map +1 -1
  44. package/src/http/TransportDefault.d.ts +2 -2
  45. package/src/http/TransportDefault.js +2 -2
  46. package/src/http/TransportDefault.js.map +1 -1
  47. package/src/http/WebpiecesDefaultErrorTranslator.d.ts +2 -2
  48. package/src/http/WebpiecesDefaultErrorTranslator.js +2 -2
  49. package/src/http/WebpiecesDefaultErrorTranslator.js.map +1 -1
  50. package/src/http/decorators.d.ts +4 -3
  51. package/src/http/decorators.js.map +1 -1
  52. package/src/index.d.ts +3 -4
  53. package/src/index.js +7 -5
  54. package/src/index.js.map +1 -1
  55. package/src/ipc/IpcErrorTranslator.d.ts +1 -1
  56. package/src/ipc/IpcErrorTranslator.js.map +1 -1
  57. package/src/ipc/IpcRegistry.d.ts +1 -1
  58. package/src/ipc/IpcRegistry.js +1 -1
  59. package/src/ipc/IpcRegistry.js.map +1 -1
  60. package/src/http/CallRegistry.d.ts +0 -42
  61. package/src/http/CallRegistry.js +0 -143
  62. package/src/http/CallRegistry.js.map +0 -1
  63. package/src/http/CallRegistryCompileAssertions.js +0 -27
  64. package/src/http/CallRegistryCompileAssertions.js.map +0 -1
  65. /package/src/http/{CallRegistryCompileAssertions.d.ts → ClientRegistryCompileAssertions.d.ts} +0 -0
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RetryStrategy = void 0;
4
4
  const CallDeadline_1 = require("./CallDeadline");
5
- const ClientRegistry_1 = require("./ClientRegistry");
5
+ const ApiRegistry_1 = require("./ApiRegistry");
6
6
  const ErrorClassifier_1 = require("./ErrorClassifier");
7
7
  const ApiMethodInfo_1 = require("./ApiMethodInfo");
8
8
  const HttpEndpointOptions_1 = require("./HttpEndpointOptions");
@@ -10,11 +10,11 @@ const ApiError_1 = require("../errors/ApiError");
10
10
  const errorUtils_1 = require("../lib/errorUtils");
11
11
  /**
12
12
  * THE webpieces retry strategy, shared by every transport — register its {@link RetryStrategy.run}
13
- * with `CallRegistry.setStrategy(...)`, or hand it to a `TransportDefault`.
13
+ * with `ClientRegistry.setStrategy(...)`, or hand it to a `TransportDefault`.
14
14
  *
15
15
  * Two questions, two owners, and it asks both:
16
16
  *
17
- * - **the ERROR** — `ClientRegistry.classify(error, …)`. Only `CallOutcome.TRANSIENT_FAILURE` is
17
+ * - **the ERROR** — `ApiRegistry.classify(error, …)`. Only `CallOutcome.TRANSIENT_FAILURE` is
18
18
  * retried; an EXPECTED or FAILURE outcome is a definitive answer and is rethrown unchanged.
19
19
  * - **the ENDPOINT** — `CallContext.operation`. A `WRITE` is NEVER retried, even when this strategy is
20
20
  * registered at ALL: a transient error on a non-idempotent write may already have happened.
@@ -64,7 +64,7 @@ class RetryStrategy {
64
64
  catch (err) {
65
65
  const error = (0, errorUtils_1.toError)(err);
66
66
  const retryable = ctx.operation !== HttpEndpointOptions_1.WRITE &&
67
- ClientRegistry_1.ClientRegistry.classify(error, methodInfo) === ErrorClassifier_1.CallOutcome.TRANSIENT_FAILURE;
67
+ ApiRegistry_1.ApiRegistry.classify(error, methodInfo) === ErrorClassifier_1.CallOutcome.TRANSIENT_FAILURE;
68
68
  if (!retryable) {
69
69
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a definitive answer is rethrown unchanged
70
70
  throw err;
@@ -1 +1 @@
1
- {"version":3,"file":"RetryStrategy.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RetryStrategy.ts"],"names":[],"mappings":"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","sourcesContent":["import { Attempt, CallContext, CallStrategy } from './CallStrategy';\nimport { CallDeadline } from './CallDeadline';\nimport { ClientRegistry } from './ClientRegistry';\nimport { CallOutcome } from './ErrorClassifier';\nimport { ApiMethodInfo } from './ApiMethodInfo';\nimport { WRITE } from './HttpEndpointOptions';\nimport { ApiRetriesExhaustedError } from '../errors/ApiError';\nimport { toError } from '../lib/errorUtils';\n\n/**\n * THE webpieces retry strategy, shared by every transport — register its {@link RetryStrategy.run}\n * with `CallRegistry.setStrategy(...)`, or hand it to a `TransportDefault`.\n *\n * Two questions, two owners, and it asks both:\n *\n * - **the ERROR** — `ClientRegistry.classify(error, …)`. Only `CallOutcome.TRANSIENT_FAILURE` is\n * retried; an EXPECTED or FAILURE outcome is a definitive answer and is rethrown unchanged.\n * - **the ENDPOINT** — `CallContext.operation`. A `WRITE` is NEVER retried, even when this strategy is\n * registered at ALL: a transient error on a non-idempotent write may already have happened.\n *\n * Each attempt gets its own `attemptTimeoutMs` deadline. Between attempts it waits\n * `initialDelayMs`, then multiplies by `delayMultiplier`, capped at `maxDelayMs`.\n *\n * When it RETRIED and ran out, it throws {@link ApiRetriesExhaustedError} (`cause` = the last\n * attempt's error), which is classified FAILURE so no layer above retries again. A call that made a\n * single attempt — `maxRetries` 0, a `WRITE`, a non-transient error — rethrows its own error unchanged.\n */\nexport class RetryStrategy {\n /** The {@link CallStrategy} to register. A bound field, so `strategy.run` can be passed around. */\n // webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs\n readonly run: CallStrategy<unknown>;\n\n constructor(\n readonly attemptTimeoutMs: number,\n readonly maxRetries: number,\n readonly initialDelayMs: number,\n readonly delayMultiplier: number,\n readonly maxDelayMs: number,\n ) {\n CallDeadline.validate(attemptTimeoutMs);\n RetryStrategy.requireWhole('maxRetries', maxRetries);\n RetryStrategy.requireWhole('initialDelayMs', initialDelayMs);\n RetryStrategy.requireWhole('maxDelayMs', maxDelayMs);\n if (!Number.isFinite(delayMultiplier) || delayMultiplier < 1) {\n throw new RangeError('delayMultiplier must be finite and at least 1');\n }\n // webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs\n this.run = (call: Attempt<unknown>, ctx: CallContext): Promise<unknown> => this.execute(call, ctx);\n }\n\n // webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs\n private async execute(call: Attempt<unknown>, ctx: CallContext): Promise<unknown> {\n const methodInfo = new ApiMethodInfo(\n 'client',\n ctx.apiName,\n ctx.methodName,\n ctx.contract,\n );\n let delayMs = this.initialDelayMs;\n for (let attempt = 1; ; attempt++) {\n // webpieces-disable no-unmanaged-exceptions -- a strategy decides per failure whether to retry\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return await call(this.attemptTimeoutMs);\n } catch (err: unknown) {\n const error = toError(err);\n const retryable =\n ctx.operation !== WRITE &&\n ClientRegistry.classify(error, methodInfo) === CallOutcome.TRANSIENT_FAILURE;\n if (!retryable) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a definitive answer is rethrown unchanged\n throw err;\n }\n if (attempt > this.maxRetries) {\n if (attempt === 1) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- never retried, so nothing was exhausted\n throw err;\n }\n throw new ApiRetriesExhaustedError(attempt, error);\n }\n await RetryStrategy.sleep(delayMs);\n delayMs = Math.min(delayMs * this.delayMultiplier, this.maxDelayMs);\n }\n }\n }\n\n // webpieces-disable no-function-outside-class -- constructor argument guard\n private static requireWhole(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0) {\n throw new RangeError(`${name} must be a non-negative integer`);\n }\n }\n\n // webpieces-disable no-function-outside-class -- backoff timer\n private static sleep(ms: number): Promise<void> {\n return new Promise<void>((resolve: () => void) => setTimeout(resolve, ms));\n }\n}\n"]}
1
+ {"version":3,"file":"RetryStrategy.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RetryStrategy.ts"],"names":[],"mappings":"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","sourcesContent":["import { Attempt, CallContext, CallStrategy } from './CallStrategy';\nimport { CallDeadline } from './CallDeadline';\nimport { ApiRegistry } from './ApiRegistry';\nimport { CallOutcome } from './ErrorClassifier';\nimport { ApiMethodInfo } from './ApiMethodInfo';\nimport { WRITE } from './HttpEndpointOptions';\nimport { ApiRetriesExhaustedError } from '../errors/ApiError';\nimport { toError } from '../lib/errorUtils';\n\n/**\n * THE webpieces retry strategy, shared by every transport — register its {@link RetryStrategy.run}\n * with `ClientRegistry.setStrategy(...)`, or hand it to a `TransportDefault`.\n *\n * Two questions, two owners, and it asks both:\n *\n * - **the ERROR** — `ApiRegistry.classify(error, …)`. Only `CallOutcome.TRANSIENT_FAILURE` is\n * retried; an EXPECTED or FAILURE outcome is a definitive answer and is rethrown unchanged.\n * - **the ENDPOINT** — `CallContext.operation`. A `WRITE` is NEVER retried, even when this strategy is\n * registered at ALL: a transient error on a non-idempotent write may already have happened.\n *\n * Each attempt gets its own `attemptTimeoutMs` deadline. Between attempts it waits\n * `initialDelayMs`, then multiplies by `delayMultiplier`, capped at `maxDelayMs`.\n *\n * When it RETRIED and ran out, it throws {@link ApiRetriesExhaustedError} (`cause` = the last\n * attempt's error), which is classified FAILURE so no layer above retries again. A call that made a\n * single attempt — `maxRetries` 0, a `WRITE`, a non-transient error — rethrows its own error unchanged.\n */\nexport class RetryStrategy {\n /** The {@link CallStrategy} to register. A bound field, so `strategy.run` can be passed around. */\n // webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs\n readonly run: CallStrategy<unknown>;\n\n constructor(\n readonly attemptTimeoutMs: number,\n readonly maxRetries: number,\n readonly initialDelayMs: number,\n readonly delayMultiplier: number,\n readonly maxDelayMs: number,\n ) {\n CallDeadline.validate(attemptTimeoutMs);\n RetryStrategy.requireWhole('maxRetries', maxRetries);\n RetryStrategy.requireWhole('initialDelayMs', initialDelayMs);\n RetryStrategy.requireWhole('maxDelayMs', maxDelayMs);\n if (!Number.isFinite(delayMultiplier) || delayMultiplier < 1) {\n throw new RangeError('delayMultiplier must be finite and at least 1');\n }\n // webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs\n this.run = (call: Attempt<unknown>, ctx: CallContext): Promise<unknown> => this.execute(call, ctx);\n }\n\n // webpieces-disable no-any-unknown -- registry strategies span heterogeneous response DTOs\n private async execute(call: Attempt<unknown>, ctx: CallContext): Promise<unknown> {\n const methodInfo = new ApiMethodInfo(\n 'client',\n ctx.apiName,\n ctx.methodName,\n ctx.contract,\n );\n let delayMs = this.initialDelayMs;\n for (let attempt = 1; ; attempt++) {\n // webpieces-disable no-unmanaged-exceptions -- a strategy decides per failure whether to retry\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return await call(this.attemptTimeoutMs);\n } catch (err: unknown) {\n const error = toError(err);\n const retryable =\n ctx.operation !== WRITE &&\n ApiRegistry.classify(error, methodInfo) === CallOutcome.TRANSIENT_FAILURE;\n if (!retryable) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a definitive answer is rethrown unchanged\n throw err;\n }\n if (attempt > this.maxRetries) {\n if (attempt === 1) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- never retried, so nothing was exhausted\n throw err;\n }\n throw new ApiRetriesExhaustedError(attempt, error);\n }\n await RetryStrategy.sleep(delayMs);\n delayMs = Math.min(delayMs * this.delayMultiplier, this.maxDelayMs);\n }\n }\n }\n\n // webpieces-disable no-function-outside-class -- constructor argument guard\n private static requireWhole(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0) {\n throw new RangeError(`${name} must be a non-negative integer`);\n }\n }\n\n // webpieces-disable no-function-outside-class -- backoff timer\n private static sleep(ms: number): Promise<void> {\n return new Promise<void>((resolve: () => void) => setTimeout(resolve, ms));\n }\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;
@@ -1 +1 @@
1
- {"version":3,"file":"RouteMetadataFactory.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadataFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpointHttpMethod,\n getEndpointKind,\n getEndpointOperation,\n getEndpointOptions,\n getEndpoints,\n getMaskSpec,\n} from './decorators';\nimport { getHttpParameterDeclarations } from './http-parameter-decorators';\nimport {\n EndpointResponseType,\n HttpParameterBinding,\n HttpParameterDeclaration,\n HttpParameterValueType,\n HttpMethod,\n POST,\n verbCarriesBody,\n} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { QueryParams } from './QueryParams';\nimport { EndpointOptions, NONE } from './HttpEndpointOptions';\nimport { getStreamingEndpoint } from './StreamingContract';\nimport { getAuthorization } from './authorization';\nimport {\n assertEveryEndpointHasAuthMode,\n assertExternalEndpointKinds,\n isExternalOutboundApi,\n isLocalOnly,\n} from './decorators';\nimport { declaresExternalContextKeys, getExternalContextKeys } from './ExternalContextKeys';\nimport { StreamDirection, StreamingEndpointMetadata } from './StreamingContract';\n\n/**\n * Build the one runtime route model consumed by incoming routing, in-process clients, and both\n * generated HTTP clients. Keeping this join/validation here prevents four subtly different\n * interpretations of the same decorators.\n */\nexport class RouteMetadataFactory {\n // webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes\n static create(\n apiClass: Function,\n methodName: string,\n controllerClassName?: string,\n ): RouteMetadata {\n assertEveryEndpointHasAuthMode(apiClass);\n const externalOutbound = this.externalOutbound(apiClass);\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', this.endpointPath(apiClass, methodName));\n const options = getEndpointOptions(apiClass, methodName);\n const streaming = getStreamingEndpoint(apiClass, methodName);\n const httpMethod = getEndpointHttpMethod(apiClass, methodName);\n const operation = getEndpointOperation(apiClass, methodName);\n this.validateEndpointOptions(apiClass, methodName, httpMethod, options.formPost, options.rawBody);\n this.validateTriggerVerb(apiClass, methodName, httpMethod);\n const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\n this.validateQueryDeclarations(apiClass, methodName, externalOutbound, parameterTypes, declarations);\n const bodyParameterIndex = streaming\n ? this.validateStreamingParameters(\n apiClass,\n methodName,\n httpMethod,\n parameterTypes,\n declarations,\n streaming,\n )\n : this.validateParameters(\n apiClass,\n methodName,\n fullPath,\n httpMethod,\n parameterTypes,\n declarations,\n );\n const bindings = this.bindingsOf(declarations, parameterTypes);\n const route = new RouteMetadata(\n httpMethod,\n fullPath,\n methodName,\n operation,\n controllerClassName,\n getAuthMeta(apiClass, methodName),\n apiClass.name || 'UnknownApi',\n options.formPost === true,\n getMaskSpec(apiClass, methodName),\n options.rawBody === true,\n bindings,\n bodyParameterIndex,\n this.responseType(options.responseType, apiClass, methodName),\n streaming,\n options.hideProgress === true,\n options.noLogging === true,\n options.allowUpgradeInFlight === true,\n this.noResponseBody(options, streaming, apiClass, methodName),\n );\n route.authorization = getAuthorization(apiClass, methodName);\n route.apiClass = apiClass;\n route.localOnly = isLocalOnly(apiClass, methodName);\n route.externalOutbound = externalOutbound;\n route.externalContextKeys = getExternalContextKeys(apiClass, methodName);\n return route;\n }\n /** Each declaration resolved against its argument's reflected type, in argument order. */\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static bindingsOf(\n declarations: readonly HttpParameterDeclaration[],\n parameterTypes: readonly Function[],\n ): HttpParameterBinding[] {\n return declarations\n .map(\n (declaration: HttpParameterDeclaration) =>\n new HttpParameterBinding(\n declaration.index,\n declaration.source,\n declaration.wireName,\n this.valueType(parameterTypes[declaration.index]),\n declaration.itemType,\n ),\n )\n .sort((a: HttpParameterBinding, b: HttpParameterBinding) => a.index - b.index);\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static endpointPath(apiClass: Function, methodName: string): string {\n const path = (getEndpoints(apiClass) ?? {})[methodName];\n if (path !== undefined) return path;\n throw new Error(`No @Endpoint metadata for ${apiClass.name || 'Unknown'}.${methodName}.`);\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper keeps the existing static metadata factory below the method-size limit\n private static validateEndpointOptions(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n formPost?: boolean,\n rawBody?: boolean,\n ): void {\n if (verbCarriesBody(httpMethod)) return;\n if (formPost === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is ${httpMethod} and cannot declare formPost:true because ${httpMethod} has no request body.`,\n );\n }\n if (rawBody === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is ${httpMethod} and cannot declare rawBody:true because ${httpMethod} has no request body.`,\n );\n }\n }\n\n /**\n * A CLOUDTASKS or CRON endpoint must be POST: Cloud Tasks, Cloud Scheduler and the in-memory task\n * invoker all deliver with POST, so any other verb would register a route nothing ever reaches\n * (a 404 at delivery time, not at startup).\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static validateTriggerVerb(apiClass: Function, methodName: string, httpMethod: HttpMethod): void {\n const kind = getEndpointKind(apiClass, methodName);\n if ((kind === 'cloudtasks' || kind === 'cron') && httpMethod !== POST) {\n throw new Error(\n `${apiClass.name || 'Unknown'}.${methodName} is a ${kind.toUpperCase()} endpoint declared ${httpMethod}; ` +\n `task and scheduler delivery always uses POST, so declare @Endpoint(POST, ...).`,\n );\n }\n }\n\n // webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators\n private static validateStreamingParameters(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n streaming: StreamingEndpointMetadata,\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (\n !streaming.initialRequestSchema ||\n !streaming.initialResponseSchema ||\n (streaming.direction !== StreamDirection.RESPONSE && !streaming.requestSchema) ||\n (streaming.direction !== StreamDirection.REQUEST && !streaming.responseSchema)\n ) {\n throw new Error(\n `${label} requires generated streaming schemas. Run wp-openapi and registerStreamingCatalog(${apiClass.name}, catalog) before binding.`,\n );\n }\n if (httpMethod !== POST) throw new Error(`${label} is streaming and must use POST.`);\n if (declarations.length > 0)\n throw new Error(`${label} is streaming and cannot declare path/query parameters.`);\n const expected = streaming.direction === StreamDirection.REQUEST ? 1 : 2;\n if (parameterTypes.length !== expected) {\n const form =\n streaming.direction === StreamDirection.REQUEST\n ? '(InitialRequest) => Promise<RequestStream<InitialResponse, RequestEvent>>'\n : streaming.direction === StreamDirection.RESPONSE\n ? '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<InitialResponse>'\n : '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<RequestStream<InitialResponse, RequestEvent>>';\n throw new Error(\n `Invalid @WpStream(StreamDirection.${streaming.direction.toUpperCase()}) method ${label}: ` +\n `${streaming.direction.toUpperCase()} accepts exactly ${form}.`,\n );\n }\n return 0;\n }\n\n /**\n * Refuse a contract in which two endpoints resolve to the same HTTP method + path.\n *\n * Empty paths are LEGAL (`@ApiPath('')` + `@Endpoint(POST, '', ...)` is how a contract says \"the whole\n * destination arrives per call\"), so the thing actually worth rejecting is ambiguity: two methods\n * that land on one route. On the server the second registration would silently shadow the first\n * in the route map, and on the client both would dial the same URL — neither is ever what the\n * author meant. Placeholder NAMES are ignored when comparing (`/a/{id}` and `/a/{key}` are the\n * same route to a router), `''` and `'/'` compare equal (the server serves an empty path at the\n * root), and the error names BOTH methods so the fix is obvious.\n *\n * Called by every transport that binds a whole contract (the generated clients and the server's\n * route registration), so a duplicate dies at startup however the contract is consumed.\n *\n * @throws Error naming both colliding methods and the shared route.\n */\n // webpieces-disable no-function-outside-class -- deterministic contract validation shared by DI-free browser and server runtimes\n static assertNoDuplicateRoutes(apiClass: Function): void {\n const label = apiClass.name || 'Unknown';\n const seen = new Map<string, string>();\n for (const methodName of Object.keys(getEndpoints(apiClass) ?? {})) {\n const route = this.create(apiClass, methodName);\n // '' and '/' are one route to a server (every request path starts with '/').\n const shape = (route.path === '' ? '/' : route.path).replace(/\\{[^{}]+\\}/g, '{}');\n const key = `${route.httpMethod} ${shape}`;\n const previous = seen.get(key);\n if (previous !== undefined) {\n throw new Error(\n `${label}.${previous} and ${label}.${methodName} both resolve to ` +\n `${route.httpMethod} '${route.path}'. Two endpoints of one contract cannot share ` +\n `an HTTP method + path; give one of them a distinct @Endpoint path or HTTP method.`,\n );\n }\n seen.set(key, methodName);\n }\n }\n\n /**\n * Join `@ApiPath` and `@Endpoint` into the route path.\n *\n * Both empty joins to `''`, NOT `'/'`. A contract declaring `@ApiPath('')` + `@Endpoint(POST, '', ...)`\n * is saying \"this route adds nothing to the base URL\", and a client whose base URL is a full\n * destination (host + path + query) must send it byte for byte. Before #926 the client joined with\n * plain concatenation (`'' + ''`), and forcing a `/` here broke exactly that (#944).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static joinPath(basePath: string, endpointPath: string): string {\n if (basePath === '' && endpointPath === '') return '';\n const joined = `${basePath}/${endpointPath}`.replace(/\\/{2,}/g, '/');\n return joined.startsWith('/') ? joined : `/${joined}`;\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static responseType(\n value: EndpointResponseType | undefined,\n apiClass: Function,\n methodName: string,\n ): EndpointResponseType {\n const responseType = value ?? 'body';\n if (responseType === 'body' || responseType === 'full') return responseType;\n throw new Error(\n `${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`,\n );\n }\n\n /**\n * The EXTERNAL_* rules ({@link assertExternalEndpointKinds}), then whether the contract is an\n * outside system's API. `@ExternalContextKeys` says which context an OUTSIDE system may see, so it\n * is refused on a contract this repo serves, which already receives the platform context.\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static externalOutbound(apiClass: Function): boolean {\n assertExternalEndpointKinds(apiClass);\n const externalOutbound = isExternalOutboundApi(apiClass);\n if (!externalOutbound && declaresExternalContextKeys(apiClass)) {\n throw new Error(\n `${apiClass.name || 'Unknown'} declares @ExternalContextKeys but is not an EXTERNAL_OUTBOUND contract. ` +\n 'The list says which context an OUTSIDE system may see; a contract served in this repo already ' +\n 'receives the platform context.',\n );\n }\n return externalOutbound;\n }\n\n /**\n * `@QueryString()` (a free-form {@link QueryParams}) is the query spelling of an EXTERNAL_OUTBOUND\n * contract ONLY — our own contracts keep the typed `@QueryParam` — and its argument must BE a\n * `QueryParams`. An item type (`@QueryParam('ids', Number)`) only means something on a list.\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static validateQueryDeclarations(\n apiClass: Function,\n methodName: string,\n externalOutbound: boolean,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): void {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n for (const declaration of declarations) {\n const type = parameterTypes[declaration.index];\n if (declaration.source === 'query-map' && !externalOutbound) {\n throw new Error(\n `${label} declares @QueryString(), which only an EXTERNAL_OUTBOUND contract may use — a vendor's ` +\n `query string is free-form. A contract this repo serves names each key: @QueryParam('name').`,\n );\n }\n if (declaration.source === 'query-map' && type !== undefined && type !== QueryParams) {\n throw new Error(`${label} argument ${declaration.index} is @QueryString() and must be typed QueryParams.`);\n }\n if (declaration.itemType !== undefined && type !== undefined && type !== Array) {\n throw new Error(\n `${label} @QueryParam('${declaration.wireName}', ${declaration.itemType.name}) names an item type, ` +\n `but argument ${declaration.index} is not a list.`,\n );\n }\n }\n }\n\n /**\n * `responseBody: NONE` means the success response is 0 bytes, so it cannot combine with a\n * caller-owned full response or a streaming endpoint (both of which DEFINE a response).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static noResponseBody(\n options: EndpointOptions,\n streaming: StreamingEndpointMetadata | undefined,\n apiClass: Function,\n methodName: string,\n ): boolean {\n if (options.responseBody === undefined) return false;\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (options.responseBody !== NONE) {\n throw new Error(\n `${label} declares unsupported responseBody '${String(options.responseBody)}'; the only value is NONE.`,\n );\n }\n if (options.responseType === 'full' || streaming !== undefined) {\n throw new Error(\n `${label} declares responseBody: NONE together with ${streaming ? 'a stream' : \"responseType: 'full'\"}; ` +\n 'a 0-byte response cannot also carry a caller-owned or streamed response.',\n );\n }\n return true;\n }\n\n // webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency\n private static parameterTypes(apiClass: Function, methodName: string): Function[] {\n return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];\n }\n\n // webpieces-disable no-function-outside-class -- private deterministic contract validation helper\n private static validateParameters(\n apiClass: Function,\n methodName: string,\n fullPath: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n const placeholders = [...fullPath.matchAll(/\\{([^{}]+)\\}/g)].map(\n (match: RegExpMatchArray) => match[1],\n );\n const pathNames = declarations\n .filter((declaration: HttpParameterDeclaration) => declaration.source === 'path')\n .map((declaration: HttpParameterDeclaration) => declaration.wireName);\n for (const placeholder of new Set(placeholders)) {\n if (!pathNames.includes(placeholder)) {\n throw new Error(\n `${label} path '${fullPath}' contains {${placeholder}} but no @PathParam('${placeholder}') parameter.`,\n );\n }\n }\n for (const pathName of pathNames) {\n if (!placeholders.includes(pathName)) {\n throw new Error(\n `${label} declares @PathParam('${pathName}') but '${fullPath}' has no {${pathName}} placeholder.`,\n );\n }\n }\n\n const seenWire = new Set<string>();\n const mappedIndexes = new Set<number>();\n for (const declaration of declarations) {\n const wireKey = `${declaration.source}:${declaration.wireName}`;\n if (seenWire.has(wireKey)) {\n throw new Error(`${label} maps '${wireKey}' more than once.`);\n }\n seenWire.add(wireKey);\n mappedIndexes.add(declaration.index);\n }\n\n const declaredCount = Math.max(\n parameterTypes.length,\n declarations.reduce(\n (highest: number, declaration: HttpParameterDeclaration) =>\n Math.max(highest, declaration.index + 1),\n 0,\n ),\n );\n const unbound: number[] = [];\n for (let index = 0; index < declaredCount; index++) {\n if (!mappedIndexes.has(index)) unbound.push(index);\n }\n if (!verbCarriesBody(httpMethod) && unbound.length > 0) {\n throw new Error(\n `${label} is ${httpMethod}, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`,\n );\n }\n if (unbound.length > 1) {\n throw new Error(\n `${label} has ${unbound.length} unmapped parameters. ${httpMethod} permits exactly one unannotated request body; map every other parameter explicitly.`,\n );\n }\n return unbound[0];\n }\n\n // webpieces-disable no-function-outside-class -- private conversion of reflected constructors to wire types\n private static valueType(type: Function | undefined): HttpParameterValueType {\n if (type === String) return 'string';\n if (type === Number) return 'number';\n if (type === Boolean) return 'boolean';\n if (type === Array) return 'array';\n return 'unknown';\n }\n}\n"]}
1
+ {"version":3,"file":"RouteMetadataFactory.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadataFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpointHttpMethod,\n getEndpointKind,\n getEndpointOperation,\n getEndpointOptions,\n getEndpoints,\n getMaskSpec,\n} from './decorators';\nimport { getHttpParameterDeclarations } from './http-parameter-decorators';\nimport {\n EndpointResponseType,\n HttpParameterBinding,\n HttpParameterDeclaration,\n HttpParameterValueType,\n HttpMethod,\n POST,\n verbCarriesBody,\n} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { QueryParams } from './QueryParams';\nimport { EndpointOptions, NONE } from './HttpEndpointOptions';\nimport { getStreamingEndpoint } from './StreamingContract';\nimport { getAuthorization } from './authorization';\nimport {\n CLOUDTASKS,\n CRON,\n EndpointKind,\n EXTERNAL_OUTBOUND,\n assertEveryEndpointHasAuthMode,\n assertExternalEndpointKinds,\n isExternalOutboundApi,\n isLocalOnly,\n} from './decorators';\nimport { declaresExternalContextKeys, getExternalContextKeys } from './ExternalContextKeys';\nimport { StreamDirection, StreamingEndpointMetadata } from './StreamingContract';\n\n/**\n * Build the one runtime route model consumed by incoming routing, in-process clients, and both\n * generated HTTP clients. Keeping this join/validation here prevents four subtly different\n * interpretations of the same decorators.\n */\nexport class RouteMetadataFactory {\n // webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes\n static create(\n apiClass: Function,\n methodName: string,\n controllerClassName?: string,\n ): RouteMetadata {\n assertEveryEndpointHasAuthMode(apiClass);\n const externalOutbound = this.externalOutbound(apiClass);\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', this.endpointPath(apiClass, methodName));\n const options = getEndpointOptions(apiClass, methodName);\n const streaming = getStreamingEndpoint(apiClass, methodName);\n const httpMethod = getEndpointHttpMethod(apiClass, methodName);\n const operation = getEndpointOperation(apiClass, methodName);\n this.validateEndpointOptions(apiClass, methodName, httpMethod, options.formPost, options.rawBody);\n this.validateTriggerVerb(apiClass, methodName, httpMethod);\n const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\n this.validateQueryDeclarations(apiClass, methodName, externalOutbound, parameterTypes, declarations);\n const bodyParameterIndex = streaming\n ? this.validateStreamingParameters(\n apiClass,\n methodName,\n httpMethod,\n parameterTypes,\n declarations,\n streaming,\n )\n : this.validateParameters(\n apiClass,\n methodName,\n fullPath,\n httpMethod,\n parameterTypes,\n declarations,\n );\n const bindings = this.bindingsOf(declarations, parameterTypes);\n const route = new RouteMetadata(\n httpMethod,\n fullPath,\n methodName,\n operation,\n controllerClassName,\n getAuthMeta(apiClass, methodName),\n apiClass.name || 'UnknownApi',\n options.formPost === true,\n getMaskSpec(apiClass, methodName),\n options.rawBody === true,\n bindings,\n bodyParameterIndex,\n this.responseType(options.responseType, apiClass, methodName),\n streaming,\n options.hideProgress === true,\n options.noLogging === true,\n options.allowUpgradeInFlight === true,\n this.noResponseBody(options, streaming, apiClass, methodName, getEndpointKind(apiClass, methodName)),\n );\n route.authorization = getAuthorization(apiClass, methodName);\n route.apiClass = apiClass;\n route.localOnly = isLocalOnly(apiClass, methodName);\n route.externalOutbound = externalOutbound;\n route.externalContextKeys = getExternalContextKeys(apiClass, methodName);\n return route;\n }\n /** Each declaration resolved against its argument's reflected type, in argument order. */\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static bindingsOf(\n declarations: readonly HttpParameterDeclaration[],\n parameterTypes: readonly Function[],\n ): HttpParameterBinding[] {\n return declarations\n .map(\n (declaration: HttpParameterDeclaration) =>\n new HttpParameterBinding(\n declaration.index,\n declaration.source,\n declaration.wireName,\n this.valueType(parameterTypes[declaration.index]),\n declaration.itemType,\n ),\n )\n .sort((a: HttpParameterBinding, b: HttpParameterBinding) => a.index - b.index);\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static endpointPath(apiClass: Function, methodName: string): string {\n const path = (getEndpoints(apiClass) ?? {})[methodName];\n if (path !== undefined) return path;\n throw new Error(`No @Endpoint metadata for ${apiClass.name || 'Unknown'}.${methodName}.`);\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper keeps the existing static metadata factory below the method-size limit\n private static validateEndpointOptions(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n formPost?: boolean,\n rawBody?: boolean,\n ): void {\n if (verbCarriesBody(httpMethod)) return;\n if (formPost === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is ${httpMethod} and cannot declare formPost:true because ${httpMethod} has no request body.`,\n );\n }\n if (rawBody === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is ${httpMethod} and cannot declare rawBody:true because ${httpMethod} has no request body.`,\n );\n }\n }\n\n /**\n * A CLOUDTASKS or CRON endpoint must be POST: Cloud Tasks, Cloud Scheduler and the in-memory task\n * invoker all deliver with POST, so any other verb would register a route nothing ever reaches\n * (a 404 at delivery time, not at startup).\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static validateTriggerVerb(apiClass: Function, methodName: string, httpMethod: HttpMethod): void {\n const kind = getEndpointKind(apiClass, methodName);\n if ((kind === 'cloudtasks' || kind === 'cron') && httpMethod !== POST) {\n throw new Error(\n `${apiClass.name || 'Unknown'}.${methodName} is a ${kind.toUpperCase()} endpoint declared ${httpMethod}; ` +\n `task and scheduler delivery always uses POST, so declare @Endpoint(POST, ...).`,\n );\n }\n }\n\n // webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators\n private static validateStreamingParameters(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n streaming: StreamingEndpointMetadata,\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (\n !streaming.initialRequestSchema ||\n !streaming.initialResponseSchema ||\n (streaming.direction !== StreamDirection.RESPONSE && !streaming.requestSchema) ||\n (streaming.direction !== StreamDirection.REQUEST && !streaming.responseSchema)\n ) {\n throw new Error(\n `${label} requires generated streaming schemas. Run wp-openapi and registerStreamingCatalog(${apiClass.name}, catalog) before binding.`,\n );\n }\n if (httpMethod !== POST) throw new Error(`${label} is streaming and must use POST.`);\n if (declarations.length > 0)\n throw new Error(`${label} is streaming and cannot declare path/query parameters.`);\n const expected = streaming.direction === StreamDirection.REQUEST ? 1 : 2;\n if (parameterTypes.length !== expected) {\n const form =\n streaming.direction === StreamDirection.REQUEST\n ? '(InitialRequest) => Promise<RequestStream<InitialResponse, RequestEvent>>'\n : streaming.direction === StreamDirection.RESPONSE\n ? '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<InitialResponse>'\n : '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<RequestStream<InitialResponse, RequestEvent>>';\n throw new Error(\n `Invalid @WpStream(StreamDirection.${streaming.direction.toUpperCase()}) method ${label}: ` +\n `${streaming.direction.toUpperCase()} accepts exactly ${form}.`,\n );\n }\n return 0;\n }\n\n /**\n * Refuse a contract in which two endpoints resolve to the same HTTP method + path.\n *\n * Empty paths are LEGAL (`@ApiPath('')` + `@Endpoint(POST, '', ...)` is how a contract says \"the whole\n * destination arrives per call\"), so the thing actually worth rejecting is ambiguity: two methods\n * that land on one route. On the server the second registration would silently shadow the first\n * in the route map, and on the client both would dial the same URL — neither is ever what the\n * author meant. Placeholder NAMES are ignored when comparing (`/a/{id}` and `/a/{key}` are the\n * same route to a router), `''` and `'/'` compare equal (the server serves an empty path at the\n * root), and the error names BOTH methods so the fix is obvious.\n *\n * Called by every transport that binds a whole contract (the generated clients and the server's\n * route registration), so a duplicate dies at startup however the contract is consumed.\n *\n * @throws Error naming both colliding methods and the shared route.\n */\n // webpieces-disable no-function-outside-class -- deterministic contract validation shared by DI-free browser and server runtimes\n static assertNoDuplicateRoutes(apiClass: Function): void {\n const label = apiClass.name || 'Unknown';\n const seen = new Map<string, string>();\n for (const methodName of Object.keys(getEndpoints(apiClass) ?? {})) {\n const route = this.create(apiClass, methodName);\n // '' and '/' are one route to a server (every request path starts with '/').\n const shape = (route.path === '' ? '/' : route.path).replace(/\\{[^{}]+\\}/g, '{}');\n const key = `${route.httpMethod} ${shape}`;\n const previous = seen.get(key);\n if (previous !== undefined) {\n throw new Error(\n `${label}.${previous} and ${label}.${methodName} both resolve to ` +\n `${route.httpMethod} '${route.path}'. Two endpoints of one contract cannot share ` +\n `an HTTP method + path; give one of them a distinct @Endpoint path or HTTP method.`,\n );\n }\n seen.set(key, methodName);\n }\n }\n\n /**\n * Join `@ApiPath` and `@Endpoint` into the route path.\n *\n * Both empty joins to `''`, NOT `'/'`. A contract declaring `@ApiPath('')` + `@Endpoint(POST, '', ...)`\n * is saying \"this route adds nothing to the base URL\", and a client whose base URL is a full\n * destination (host + path + query) must send it byte for byte. Before #926 the client joined with\n * plain concatenation (`'' + ''`), and forcing a `/` here broke exactly that (#944).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static joinPath(basePath: string, endpointPath: string): string {\n if (basePath === '' && endpointPath === '') return '';\n const joined = `${basePath}/${endpointPath}`.replace(/\\/{2,}/g, '/');\n return joined.startsWith('/') ? joined : `/${joined}`;\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static responseType(\n value: EndpointResponseType | undefined,\n apiClass: Function,\n methodName: string,\n ): EndpointResponseType {\n const responseType = value ?? 'body';\n if (responseType === 'body' || responseType === 'full') return responseType;\n throw new Error(\n `${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`,\n );\n }\n\n /**\n * The EXTERNAL_* rules ({@link assertExternalEndpointKinds}), then whether the contract is an\n * outside system's API. `@ExternalContextKeys` says which context an OUTSIDE system may see, so it\n * is refused on a contract this repo serves, which already receives the platform context.\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static externalOutbound(apiClass: Function): boolean {\n assertExternalEndpointKinds(apiClass);\n const externalOutbound = isExternalOutboundApi(apiClass);\n if (!externalOutbound && declaresExternalContextKeys(apiClass)) {\n throw new Error(\n `${apiClass.name || 'Unknown'} declares @ExternalContextKeys but is not an EXTERNAL_OUTBOUND contract. ` +\n 'The list says which context an OUTSIDE system may see; a contract served in this repo already ' +\n 'receives the platform context.',\n );\n }\n return externalOutbound;\n }\n\n /**\n * `@QueryString()` (a free-form {@link QueryParams}) is the query spelling of an EXTERNAL_OUTBOUND\n * contract ONLY — our own contracts keep the typed `@QueryParam` — and its argument must BE a\n * `QueryParams`. An item type (`@QueryParam('ids', Number)`) only means something on a list.\n */\n // webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory\n private static validateQueryDeclarations(\n apiClass: Function,\n methodName: string,\n externalOutbound: boolean,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): void {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n for (const declaration of declarations) {\n const type = parameterTypes[declaration.index];\n if (declaration.source === 'query-map' && !externalOutbound) {\n throw new Error(\n `${label} declares @QueryString(), which only an EXTERNAL_OUTBOUND contract may use — a vendor's ` +\n `query string is free-form. A contract this repo serves names each key: @QueryParam('name').`,\n );\n }\n if (declaration.source === 'query-map' && type !== undefined && type !== QueryParams) {\n throw new Error(`${label} argument ${declaration.index} is @QueryString() and must be typed QueryParams.`);\n }\n if (declaration.itemType !== undefined && type !== undefined && type !== Array) {\n throw new Error(\n `${label} @QueryParam('${declaration.wireName}', ${declaration.itemType.name}) names an item type, ` +\n `but argument ${declaration.index} is not a list.`,\n );\n }\n }\n }\n\n /**\n * Whether the success response is 0 bytes (server 204, every client resolves `undefined`).\n *\n * A CLOUDTASKS or CRON endpoint is fire-and-forget BY KIND — Cloud Tasks and the scheduler read\n * nothing but the status — so the kind implies it (#1348), and an explicit `responseBody: NONE`\n * there is a second spelling of a decision the kind already made, so it is refused. On an\n * EXTERNAL_OUTBOUND endpoint the VENDOR shapes the response, so NONE (what OUR server sends) is\n * refused there too and the client accepts any 2xx. On RPC / EXTERNAL_INBOUND\n * `responseBody: NONE` declares it. Either way the 0-byte response cannot combine with a\n * caller-owned full response or a streaming endpoint (both of which DEFINE a response).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static noResponseBody(\n options: EndpointOptions,\n streaming: StreamingEndpointMetadata | undefined,\n apiClass: Function,\n methodName: string,\n kind: EndpointKind | undefined,\n ): boolean {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (kind === EXTERNAL_OUTBOUND && options.responseBody !== undefined) {\n throw new Error(\n `${label} is an EXTERNAL_OUTBOUND endpoint and declares responseBody: NONE, which does not apply: ` +\n `NONE describes what OUR server sends, and the vendor shapes an outbound response. The outbound ` +\n `client accepts any 2xx (204 or empty -> undefined, JSON -> parsed). Delete responseBody: NONE.`,\n );\n }\n const impliedByKind = kind === CLOUDTASKS || kind === CRON;\n if (impliedByKind && options.responseBody !== undefined) {\n throw new Error(\n `${label} is a ${kind.toUpperCase()} endpoint and declares responseBody: NONE, which is redundant: ` +\n `a ${kind.toUpperCase()} endpoint is fire-and-forget, so its kind already means no response body ` +\n `(204, 0 bytes). Delete responseBody: NONE from its @Endpoint options.`,\n );\n }\n if (!impliedByKind && options.responseBody === undefined) return false;\n if (!impliedByKind && options.responseBody !== NONE) {\n throw new Error(\n `${label} declares unsupported responseBody '${String(options.responseBody)}'; the only value is NONE.`,\n );\n }\n if (options.responseType === 'full' || streaming !== undefined) {\n const why = impliedByKind\n ? `is a ${kind.toUpperCase()} endpoint (no response body by kind)`\n : 'declares responseBody: NONE';\n throw new Error(\n `${label} ${why} together with ${streaming ? 'a stream' : \"responseType: 'full'\"}; ` +\n 'a 0-byte response cannot also carry a caller-owned or streamed response.',\n );\n }\n return true;\n }\n\n // webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency\n private static parameterTypes(apiClass: Function, methodName: string): Function[] {\n return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];\n }\n\n // webpieces-disable no-function-outside-class -- private deterministic contract validation helper\n private static validateParameters(\n apiClass: Function,\n methodName: string,\n fullPath: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n const placeholders = [...fullPath.matchAll(/\\{([^{}]+)\\}/g)].map(\n (match: RegExpMatchArray) => match[1],\n );\n const pathNames = declarations\n .filter((declaration: HttpParameterDeclaration) => declaration.source === 'path')\n .map((declaration: HttpParameterDeclaration) => declaration.wireName);\n for (const placeholder of new Set(placeholders)) {\n if (!pathNames.includes(placeholder)) {\n throw new Error(\n `${label} path '${fullPath}' contains {${placeholder}} but no @PathParam('${placeholder}') parameter.`,\n );\n }\n }\n for (const pathName of pathNames) {\n if (!placeholders.includes(pathName)) {\n throw new Error(\n `${label} declares @PathParam('${pathName}') but '${fullPath}' has no {${pathName}} placeholder.`,\n );\n }\n }\n\n const seenWire = new Set<string>();\n const mappedIndexes = new Set<number>();\n for (const declaration of declarations) {\n const wireKey = `${declaration.source}:${declaration.wireName}`;\n if (seenWire.has(wireKey)) {\n throw new Error(`${label} maps '${wireKey}' more than once.`);\n }\n seenWire.add(wireKey);\n mappedIndexes.add(declaration.index);\n }\n\n const declaredCount = Math.max(\n parameterTypes.length,\n declarations.reduce(\n (highest: number, declaration: HttpParameterDeclaration) =>\n Math.max(highest, declaration.index + 1),\n 0,\n ),\n );\n const unbound: number[] = [];\n for (let index = 0; index < declaredCount; index++) {\n if (!mappedIndexes.has(index)) unbound.push(index);\n }\n if (!verbCarriesBody(httpMethod) && unbound.length > 0) {\n throw new Error(\n `${label} is ${httpMethod}, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`,\n );\n }\n if (unbound.length > 1) {\n throw new Error(\n `${label} has ${unbound.length} unmapped parameters. ${httpMethod} permits exactly one unannotated request body; map every other parameter explicitly.`,\n );\n }\n return unbound[0];\n }\n\n // webpieces-disable no-function-outside-class -- private conversion of reflected constructors to wire types\n private static valueType(type: Function | undefined): HttpParameterValueType {\n if (type === String) return 'string';\n if (type === Number) return 'number';\n if (type === Boolean) return 'boolean';\n if (type === Array) return 'array';\n return 'unknown';\n }\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StreamErrorControl = exports.StreamDisconnectedError = exports.StreamProtocolError = void 0;
4
- const ClientRegistry_1 = require("./ClientRegistry");
4
+ const ApiRegistry_1 = require("./ApiRegistry");
5
5
  const HttpResponseDto_1 = require("./HttpResponseDto");
6
6
  const WebpiecesDefaultErrorTranslator_1 = require("./WebpiecesDefaultErrorTranslator");
7
7
  const errorUtils_1 = require("../lib/errorUtils");
@@ -58,7 +58,7 @@ class StreamErrorControl {
58
58
  return new StreamProtocolError('Error translator returned for a JSONL error control record.');
59
59
  }
60
60
  translator() {
61
- return ClientRegistry_1.ClientRegistry.getErrorTranslator(this.route.apiClass, this.route.methodName);
61
+ return ApiRegistry_1.ApiRegistry.getErrorTranslator(this.route.apiClass, this.route.methodName);
62
62
  }
63
63
  response(line) {
64
64
  let control;
@@ -1 +1 @@
1
- {"version":3,"file":"StreamErrorControl.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/StreamErrorControl.ts"],"names":[],"mappings":"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","sourcesContent":["import { ClientRole } from '../errors/ClientRole';\nimport { ClientRegistry } from './ClientRegistry';\nimport { HttpHeader, HttpResponseDto, HttpResponseStatus } from './HttpResponseDto';\nimport { WEBPIECES_DEFAULT_ERROR_TRANSLATOR } from './WebpiecesDefaultErrorTranslator';\nimport { DtoValue } from '../mcp/DtoSchema';\nimport { toError } from '../lib/errorUtils';\nimport { StreamTransportError } from './StreamingContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { ErrorTranslator } from './ErrorTranslator';\n\nexport class StreamProtocolError extends StreamTransportError {}\n\nexport class StreamDisconnectedError extends StreamTransportError {}\n\nclass ControlStatus {\n code?: number;\n reason?: string;\n}\n\nclass ControlHeader {\n name?: string;\n value?: string;\n}\n\nclass ControlResponse {\n status?: ControlStatus;\n headers?: ControlHeader[];\n body?: DtoValue;\n}\n\nclass ControlRecord {\n control?: string;\n response?: ControlResponse;\n}\n\n/**\n * Reserved ASCII-RS controls share exactly the unary ErrorTranslator seam in both directions —\n * including its `CallScope` ladder: the translator is resolved for the streaming ROUTE's contract\n * class and method (method -> API -> ALL -> webpieces default), exactly as a unary call's is.\n */\nexport class StreamErrorControl {\n constructor(private readonly route: RouteMetadata) {}\n\n encode(error: Error): string {\n const response = this.translator().toWire(error);\n return `${String.fromCharCode(0x1e)}${JSON.stringify({ control: 'error', response })}\\n`;\n }\n\n /** @param role - who is receiving the record, declared at the reading side's setup. */\n decode(line: string, role: ClientRole): Error {\n const response = this.response(line);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- fromWire returns its typed result by throwing\n try {\n this.translator().fromWire(response, role);\n WEBPIECES_DEFAULT_ERROR_TRANSLATOR.fromWire(response, role);\n } catch (err: unknown) {\n const error = toError(err);\n return error;\n }\n return new StreamProtocolError(\n 'Error translator returned for a JSONL error control record.',\n );\n }\n\n private translator(): ErrorTranslator {\n return ClientRegistry.getErrorTranslator(this.route.apiClass, this.route.methodName);\n }\n\n private response(line: string): HttpResponseDto {\n let control: ControlRecord;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted control boundary\n try {\n control = JSON.parse(line.slice(1)) as ControlRecord;\n } catch (err: unknown) {\n const error = toError(err);\n throw new StreamProtocolError(\n 'Malformed JSONL error control record.',\n undefined,\n undefined,\n error,\n );\n }\n const status = control?.response?.status;\n const headers = control?.response?.headers;\n if (\n control?.control !== 'error' ||\n typeof status?.code !== 'number' ||\n typeof status.reason !== 'string' ||\n !Array.isArray(headers)\n )\n throw new StreamProtocolError('Malformed JSONL error control record.');\n const mapped = headers.map((header: ControlHeader): HttpHeader => {\n if (typeof header?.name !== 'string' || typeof header.value !== 'string') {\n throw new StreamProtocolError('Malformed JSONL error control header.');\n }\n return new HttpHeader(header.name, header.value);\n });\n return new HttpResponseDto(\n new HttpResponseStatus(status.code, status.reason),\n mapped,\n control.response?.body,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"StreamErrorControl.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/StreamErrorControl.ts"],"names":[],"mappings":"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","sourcesContent":["import { ClientRole } from '../errors/ClientRole';\nimport { ApiRegistry } from './ApiRegistry';\nimport { HttpHeader, HttpResponseDto, HttpResponseStatus } from './HttpResponseDto';\nimport { WEBPIECES_DEFAULT_ERROR_TRANSLATOR } from './WebpiecesDefaultErrorTranslator';\nimport { DtoValue } from '../mcp/DtoSchema';\nimport { toError } from '../lib/errorUtils';\nimport { StreamTransportError } from './StreamingContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { ErrorTranslator } from './ErrorTranslator';\n\nexport class StreamProtocolError extends StreamTransportError {}\n\nexport class StreamDisconnectedError extends StreamTransportError {}\n\nclass ControlStatus {\n code?: number;\n reason?: string;\n}\n\nclass ControlHeader {\n name?: string;\n value?: string;\n}\n\nclass ControlResponse {\n status?: ControlStatus;\n headers?: ControlHeader[];\n body?: DtoValue;\n}\n\nclass ControlRecord {\n control?: string;\n response?: ControlResponse;\n}\n\n/**\n * Reserved ASCII-RS controls share exactly the unary ErrorTranslator seam in both directions —\n * including its `CallScope` ladder: the translator is resolved for the streaming ROUTE's contract\n * class and method (method -> API -> ALL -> webpieces default), exactly as a unary call's is.\n */\nexport class StreamErrorControl {\n constructor(private readonly route: RouteMetadata) {}\n\n encode(error: Error): string {\n const response = this.translator().toWire(error);\n return `${String.fromCharCode(0x1e)}${JSON.stringify({ control: 'error', response })}\\n`;\n }\n\n /** @param role - who is receiving the record, declared at the reading side's setup. */\n decode(line: string, role: ClientRole): Error {\n const response = this.response(line);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- fromWire returns its typed result by throwing\n try {\n this.translator().fromWire(response, role);\n WEBPIECES_DEFAULT_ERROR_TRANSLATOR.fromWire(response, role);\n } catch (err: unknown) {\n const error = toError(err);\n return error;\n }\n return new StreamProtocolError(\n 'Error translator returned for a JSONL error control record.',\n );\n }\n\n private translator(): ErrorTranslator {\n return ApiRegistry.getErrorTranslator(this.route.apiClass, this.route.methodName);\n }\n\n private response(line: string): HttpResponseDto {\n let control: ControlRecord;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted control boundary\n try {\n control = JSON.parse(line.slice(1)) as ControlRecord;\n } catch (err: unknown) {\n const error = toError(err);\n throw new StreamProtocolError(\n 'Malformed JSONL error control record.',\n undefined,\n undefined,\n error,\n );\n }\n const status = control?.response?.status;\n const headers = control?.response?.headers;\n if (\n control?.control !== 'error' ||\n typeof status?.code !== 'number' ||\n typeof status.reason !== 'string' ||\n !Array.isArray(headers)\n )\n throw new StreamProtocolError('Malformed JSONL error control record.');\n const mapped = headers.map((header: ControlHeader): HttpHeader => {\n if (typeof header?.name !== 'string' || typeof header.value !== 'string') {\n throw new StreamProtocolError('Malformed JSONL error control header.');\n }\n return new HttpHeader(header.name, header.value);\n });\n return new HttpResponseDto(\n new HttpResponseStatus(status.code, status.reason),\n mapped,\n control.response?.body,\n );\n }\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  import { CallStrategy } from './CallStrategy';
2
2
  /**
3
3
  * What a TRANSPORT does for a call nobody registered a policy for — the last rung of
4
- * {@link CallRegistry.execute}'s ladder (strategy method → API → ALL, else timeout method → API →
4
+ * {@link ClientRegistry.execute}'s ladder (strategy method → API → ALL, else timeout method → API →
5
5
  * ALL, else this).
6
6
  *
7
- * It is the ONLY per-transport difference: every transport calls the same `CallRegistry.execute`, so
7
+ * It is the ONLY per-transport difference: every transport calls the same `ClientRegistry.execute`, so
8
8
  * a strategy registered at ALL / API / method governs RPC and Cloud Tasks enqueue identically.
9
9
  *
10
10
  * - RPC (node and browser, through `ProxyClient`): `new TransportDefault(30_000)` — one attempt, no
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TransportDefault = void 0;
4
4
  /**
5
5
  * What a TRANSPORT does for a call nobody registered a policy for — the last rung of
6
- * {@link CallRegistry.execute}'s ladder (strategy method → API → ALL, else timeout method → API →
6
+ * {@link ClientRegistry.execute}'s ladder (strategy method → API → ALL, else timeout method → API →
7
7
  * ALL, else this).
8
8
  *
9
- * It is the ONLY per-transport difference: every transport calls the same `CallRegistry.execute`, so
9
+ * It is the ONLY per-transport difference: every transport calls the same `ClientRegistry.execute`, so
10
10
  * a strategy registered at ALL / API / method governs RPC and Cloud Tasks enqueue identically.
11
11
  *
12
12
  * - RPC (node and browser, through `ProxyClient`): `new TransportDefault(30_000)` — one attempt, no
@@ -1 +1 @@
1
- {"version":3,"file":"TransportDefault.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/TransportDefault.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACH,MAAa,gBAAgB;IAGZ;IAGA;IALb;IACI,mFAAmF;IAC1E,SAAiB;IAC1B,2FAA2F;IAC3F,4FAA4F;IACnF,QAAgC;QAHhC,cAAS,GAAT,SAAS,CAAQ;QAGjB,aAAQ,GAAR,QAAQ,CAAwB;IAC1C,CAAC;CACP;AARD,4CAQC","sourcesContent":["import { CallStrategy } from './CallStrategy';\n\n/**\n * What a TRANSPORT does for a call nobody registered a policy for — the last rung of\n * {@link CallRegistry.execute}'s ladder (strategy method → API → ALL, else timeout method → API →\n * ALL, else this).\n *\n * It is the ONLY per-transport difference: every transport calls the same `CallRegistry.execute`, so\n * a strategy registered at ALL / API / method governs RPC and Cloud Tasks enqueue identically.\n *\n * - RPC (node and browser, through `ProxyClient`): `new TransportDefault(30_000)` — one attempt, no\n * default retry, because only the application knows whether repeating a POST is safe.\n * - Cloud Tasks enqueue: `new TransportDefault(30_000, new RetryStrategy(5_000, 3, 500, 2, 4_000).run)`\n * — enqueue is idempotent by construction (one task name for every attempt), so it retries.\n */\nexport class TransportDefault {\n constructor(\n /** The single attempt's deadline when no strategy (registered or default) runs. */\n readonly timeoutMs: number,\n /** The transport's own strategy; when set it owns timing exactly like a registered one. */\n // webpieces-disable no-any-unknown -- transport strategies span heterogeneous response DTOs\n readonly strategy?: CallStrategy<unknown>,\n ) {}\n}\n"]}
1
+ {"version":3,"file":"TransportDefault.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/TransportDefault.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACH,MAAa,gBAAgB;IAGZ;IAGA;IALb;IACI,mFAAmF;IAC1E,SAAiB;IAC1B,2FAA2F;IAC3F,4FAA4F;IACnF,QAAgC;QAHhC,cAAS,GAAT,SAAS,CAAQ;QAGjB,aAAQ,GAAR,QAAQ,CAAwB;IAC1C,CAAC;CACP;AARD,4CAQC","sourcesContent":["import { CallStrategy } from './CallStrategy';\n\n/**\n * What a TRANSPORT does for a call nobody registered a policy for — the last rung of\n * {@link ClientRegistry.execute}'s ladder (strategy method → API → ALL, else timeout method → API →\n * ALL, else this).\n *\n * It is the ONLY per-transport difference: every transport calls the same `ClientRegistry.execute`, so\n * a strategy registered at ALL / API / method governs RPC and Cloud Tasks enqueue identically.\n *\n * - RPC (node and browser, through `ProxyClient`): `new TransportDefault(30_000)` — one attempt, no\n * default retry, because only the application knows whether repeating a POST is safe.\n * - Cloud Tasks enqueue: `new TransportDefault(30_000, new RetryStrategy(5_000, 3, 500, 2, 4_000).run)`\n * — enqueue is idempotent by construction (one task name for every attempt), so it retries.\n */\nexport class TransportDefault {\n constructor(\n /** The single attempt's deadline when no strategy (registered or default) runs. */\n readonly timeoutMs: number,\n /** The transport's own strategy; when set it owns timing exactly like a registered one. */\n // webpieces-disable no-any-unknown -- transport strategies span heterogeneous response DTOs\n readonly strategy?: CallStrategy<unknown>,\n ) {}\n}\n"]}
@@ -5,7 +5,7 @@ import { HttpResponseDto } from './HttpResponseDto';
5
5
  * THE webpieces default {@link ErrorTranslator} — ONE class, BOTH halves, used by the node server, by
6
6
  * every node client and by the browser bundle alike.
7
7
  *
8
- * It is what {@link ClientRegistry.getErrorTranslator} answers for a call no `CallScope` rung
8
+ * It is what {@link ApiRegistry.getErrorTranslator} answers for a call no `CallScope` rung
9
9
  * (method, API, ALL) has a translator for, and it is
10
10
  * what an app's own translator calls to DECLINE an error:
11
11
  *
@@ -14,7 +14,7 @@ import { HttpResponseDto } from './HttpResponseDto';
14
14
  * ```
15
15
  *
16
16
  * so "no translator registered" and "a translator that does not claim this error" produce
17
- * byte-identical output. It lives in core-util, beside {@link ClientRegistry}, because core-util is
17
+ * byte-identical output. It lives in core-util, beside {@link ApiRegistry}, because core-util is
18
18
  * the package every API contract already depends on and the only one both a server and a browser
19
19
  * bundle can reach. Both halves needed nothing else: the outbound half wants
20
20
  * {@link ApiErrorBoundary} + {@link ApiErrorHttpStatus}, the inbound half wants
@@ -14,7 +14,7 @@ const HttpResponseDto_1 = require("./HttpResponseDto");
14
14
  * THE webpieces default {@link ErrorTranslator} — ONE class, BOTH halves, used by the node server, by
15
15
  * every node client and by the browser bundle alike.
16
16
  *
17
- * It is what {@link ClientRegistry.getErrorTranslator} answers for a call no `CallScope` rung
17
+ * It is what {@link ApiRegistry.getErrorTranslator} answers for a call no `CallScope` rung
18
18
  * (method, API, ALL) has a translator for, and it is
19
19
  * what an app's own translator calls to DECLINE an error:
20
20
  *
@@ -23,7 +23,7 @@ const HttpResponseDto_1 = require("./HttpResponseDto");
23
23
  * ```
24
24
  *
25
25
  * so "no translator registered" and "a translator that does not claim this error" produce
26
- * byte-identical output. It lives in core-util, beside {@link ClientRegistry}, because core-util is
26
+ * byte-identical output. It lives in core-util, beside {@link ApiRegistry}, because core-util is
27
27
  * the package every API contract already depends on and the only one both a server and a browser
28
28
  * bundle can reach. Both halves needed nothing else: the outbound half wants
29
29
  * {@link ApiErrorBoundary} + {@link ApiErrorHttpStatus}, the inbound half wants