@webpieces/core-util 0.4.956 → 0.4.958
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/http/ApiCallInfo.d.ts +71 -11
- package/src/http/ApiCallInfo.js +81 -7
- package/src/http/ApiCallInfo.js.map +1 -1
- package/src/http/ApiMethodInfo.d.ts +14 -0
- package/src/http/ApiMethodInfo.js +3 -0
- package/src/http/ApiMethodInfo.js.map +1 -1
- package/src/http/CallRegistry.d.ts +5 -10
- package/src/http/CallRegistry.js +21 -43
- package/src/http/CallRegistry.js.map +1 -1
- package/src/http/CallRegistryCompileAssertions.js +1 -1
- package/src/http/CallRegistryCompileAssertions.js.map +1 -1
- package/src/http/CallScope.d.ts +33 -0
- package/src/http/CallScope.js +70 -0
- package/src/http/CallScope.js.map +1 -0
- package/src/http/CallStrategy.d.ts +13 -1
- package/src/http/CallStrategy.js +4 -1
- package/src/http/CallStrategy.js.map +1 -1
- package/src/http/ClientRegistry.d.ts +54 -58
- package/src/http/ClientRegistry.js +72 -73
- package/src/http/ClientRegistry.js.map +1 -1
- package/src/http/ContextMgr.js +12 -2
- package/src/http/ContextMgr.js.map +1 -1
- package/src/http/DeletedAuthCompileAssertions.js +3 -1
- package/src/http/DeletedAuthCompileAssertions.js.map +1 -1
- package/src/http/DestinationTrust.d.ts +33 -4
- package/src/http/DestinationTrust.js +54 -11
- package/src/http/DestinationTrust.js.map +1 -1
- package/src/http/EndpointCompileAssertions.js +12 -6
- package/src/http/EndpointCompileAssertions.js.map +1 -1
- package/src/http/ErrorClassifier.d.ts +20 -27
- package/src/http/ErrorClassifier.js +1 -15
- package/src/http/ErrorClassifier.js.map +1 -1
- package/src/http/ErrorLadderCompileAssertions.d.ts +1 -0
- package/src/http/ErrorLadderCompileAssertions.js +42 -0
- package/src/http/ErrorLadderCompileAssertions.js.map +1 -0
- package/src/http/ErrorTranslator.d.ts +8 -4
- package/src/http/ErrorTranslator.js.map +1 -1
- package/src/http/ExternalContextKeys.d.ts +31 -0
- package/src/http/ExternalContextKeys.js +77 -0
- package/src/http/ExternalContextKeys.js.map +1 -0
- package/src/http/HttpContract.d.ts +45 -7
- package/src/http/HttpContract.js +84 -6
- package/src/http/HttpContract.js.map +1 -1
- package/src/http/HttpEndpointOptions.d.ts +22 -3
- package/src/http/HttpEndpointOptions.js +8 -1
- package/src/http/HttpEndpointOptions.js.map +1 -1
- package/src/http/LogApiCall.d.ts +4 -1
- package/src/http/LogApiCall.js +9 -7
- package/src/http/LogApiCall.js.map +1 -1
- package/src/http/QueryParams.d.ts +47 -0
- package/src/http/QueryParams.js +89 -0
- package/src/http/QueryParams.js.map +1 -0
- package/src/http/RetryStrategy.js +1 -1
- package/src/http/RetryStrategy.js.map +1 -1
- package/src/http/RouteMetadata.d.ts +16 -2
- package/src/http/RouteMetadata.js +19 -3
- package/src/http/RouteMetadata.js.map +1 -1
- package/src/http/RouteMetadataFactory.d.ts +25 -0
- package/src/http/RouteMetadataFactory.js +97 -12
- package/src/http/RouteMetadataFactory.js.map +1 -1
- package/src/http/StreamErrorControl.d.ts +9 -1
- package/src/http/StreamErrorControl.js +14 -3
- package/src/http/StreamErrorControl.js.map +1 -1
- package/src/http/WebpiecesDefaultErrorClassifier.d.ts +2 -2
- package/src/http/WebpiecesDefaultErrorClassifier.js +2 -2
- package/src/http/WebpiecesDefaultErrorClassifier.js.map +1 -1
- package/src/http/WebpiecesDefaultErrorTranslator.d.ts +2 -1
- package/src/http/WebpiecesDefaultErrorTranslator.js +2 -1
- package/src/http/WebpiecesDefaultErrorTranslator.js.map +1 -1
- package/src/http/WpAuthApiKeyCompileAssertions.js +2 -2
- package/src/http/WpAuthApiKeyCompileAssertions.js.map +1 -1
- package/src/http/api-kind.d.ts +3 -2
- package/src/http/api-kind.js +5 -4
- package/src/http/api-kind.js.map +1 -1
- package/src/http/auth-mode.d.ts +8 -2
- package/src/http/auth-mode.js +11 -5
- package/src/http/auth-mode.js.map +1 -1
- package/src/http/decorators.d.ts +39 -21
- package/src/http/decorators.js +69 -37
- package/src/http/decorators.js.map +1 -1
- package/src/http/external-caller.d.ts +12 -17
- package/src/http/external-caller.js +12 -17
- package/src/http/external-caller.js.map +1 -1
- package/src/http/http-parameter-decorators.d.ts +17 -3
- package/src/http/http-parameter-decorators.js +27 -6
- package/src/http/http-parameter-decorators.js.map +1 -1
- package/src/index.d.ts +14 -9
- package/src/index.js +32 -13
- package/src/index.js.map +1 -1
- package/src/ipc/IpcConnection.js +1 -1
- package/src/ipc/IpcConnection.js.map +1 -1
- package/src/ipc/IpcLogging.js +1 -1
- package/src/ipc/IpcLogging.js.map +1 -1
|
@@ -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';\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 /** 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 );\n route.apiClass = this.apiClass;\n route.authorization = this.authorization;\n route.localOnly = this.localOnly;\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 the `webhook(...)` hook to verify a vendor signature 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 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 ) {\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 }\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 @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"]}
|
|
@@ -6,8 +6,16 @@ import { RouteMetadata } from './RouteMetadata';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class RouteMetadataFactory {
|
|
8
8
|
static create(apiClass: Function, methodName: string, controllerClassName?: string): RouteMetadata;
|
|
9
|
+
/** Each declaration resolved against its argument's reflected type, in argument order. */
|
|
10
|
+
private static bindingsOf;
|
|
9
11
|
private static endpointPath;
|
|
10
12
|
private static validateEndpointOptions;
|
|
13
|
+
/**
|
|
14
|
+
* A CLOUDTASKS or CRON endpoint must be POST: Cloud Tasks, Cloud Scheduler and the in-memory task
|
|
15
|
+
* invoker all deliver with POST, so any other verb would register a route nothing ever reaches
|
|
16
|
+
* (a 404 at delivery time, not at startup).
|
|
17
|
+
*/
|
|
18
|
+
private static validateTriggerVerb;
|
|
11
19
|
private static validateStreamingParameters;
|
|
12
20
|
/**
|
|
13
21
|
* Refuse a contract in which two endpoints resolve to the same HTTP method + path.
|
|
@@ -36,6 +44,23 @@ export declare class RouteMetadataFactory {
|
|
|
36
44
|
*/
|
|
37
45
|
private static joinPath;
|
|
38
46
|
private static responseType;
|
|
47
|
+
/**
|
|
48
|
+
* The EXTERNAL_* rules ({@link assertExternalEndpointKinds}), then whether the contract is an
|
|
49
|
+
* outside system's API. `@ExternalContextKeys` says which context an OUTSIDE system may see, so it
|
|
50
|
+
* is refused on a contract this repo serves, which already receives the platform context.
|
|
51
|
+
*/
|
|
52
|
+
private static externalOutbound;
|
|
53
|
+
/**
|
|
54
|
+
* `@QueryString()` (a free-form {@link QueryParams}) is the query spelling of an EXTERNAL_OUTBOUND
|
|
55
|
+
* contract ONLY — our own contracts keep the typed `@QueryParam` — and its argument must BE a
|
|
56
|
+
* `QueryParams`. An item type (`@QueryParam('ids', Number)`) only means something on a list.
|
|
57
|
+
*/
|
|
58
|
+
private static validateQueryDeclarations;
|
|
59
|
+
/**
|
|
60
|
+
* `responseBody: NONE` means the success response is 0 bytes, so it cannot combine with a
|
|
61
|
+
* caller-owned full response or a streaming endpoint (both of which DEFINE a response).
|
|
62
|
+
*/
|
|
63
|
+
private static noResponseBody;
|
|
39
64
|
private static parameterTypes;
|
|
40
65
|
private static validateParameters;
|
|
41
66
|
private static valueType;
|
|
@@ -5,9 +5,12 @@ const decorators_1 = require("./decorators");
|
|
|
5
5
|
const http_parameter_decorators_1 = require("./http-parameter-decorators");
|
|
6
6
|
const HttpContract_1 = require("./HttpContract");
|
|
7
7
|
const RouteMetadata_1 = require("./RouteMetadata");
|
|
8
|
+
const QueryParams_1 = require("./QueryParams");
|
|
9
|
+
const HttpEndpointOptions_1 = require("./HttpEndpointOptions");
|
|
8
10
|
const StreamingContract_1 = require("./StreamingContract");
|
|
9
11
|
const authorization_1 = require("./authorization");
|
|
10
12
|
const decorators_2 = require("./decorators");
|
|
13
|
+
const ExternalContextKeys_1 = require("./ExternalContextKeys");
|
|
11
14
|
const StreamingContract_2 = require("./StreamingContract");
|
|
12
15
|
/**
|
|
13
16
|
* Build the one runtime route model consumed by incoming routing, in-process clients, and both
|
|
@@ -18,26 +21,36 @@ class RouteMetadataFactory {
|
|
|
18
21
|
// webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes
|
|
19
22
|
static create(apiClass, methodName, controllerClassName) {
|
|
20
23
|
(0, decorators_2.assertEveryEndpointHasAuthMode)(apiClass);
|
|
24
|
+
const externalOutbound = this.externalOutbound(apiClass);
|
|
21
25
|
const fullPath = this.joinPath((0, decorators_1.getApiPath)(apiClass) ?? '', this.endpointPath(apiClass, methodName));
|
|
22
26
|
const options = (0, decorators_1.getEndpointOptions)(apiClass, methodName);
|
|
23
27
|
const streaming = (0, StreamingContract_1.getStreamingEndpoint)(apiClass, methodName);
|
|
24
28
|
const httpMethod = (0, decorators_1.getEndpointHttpMethod)(apiClass, methodName);
|
|
25
29
|
const operation = (0, decorators_1.getEndpointOperation)(apiClass, methodName);
|
|
26
30
|
this.validateEndpointOptions(apiClass, methodName, httpMethod, options.formPost, options.rawBody);
|
|
31
|
+
this.validateTriggerVerb(apiClass, methodName, httpMethod);
|
|
27
32
|
const parameterTypes = this.parameterTypes(apiClass, methodName);
|
|
28
33
|
const declarations = (0, http_parameter_decorators_1.getHttpParameterDeclarations)(apiClass, methodName);
|
|
34
|
+
this.validateQueryDeclarations(apiClass, methodName, externalOutbound, parameterTypes, declarations);
|
|
29
35
|
const bodyParameterIndex = streaming
|
|
30
36
|
? this.validateStreamingParameters(apiClass, methodName, httpMethod, parameterTypes, declarations, streaming)
|
|
31
37
|
: this.validateParameters(apiClass, methodName, fullPath, httpMethod, parameterTypes, declarations);
|
|
32
|
-
const bindings = declarations
|
|
33
|
-
|
|
34
|
-
.sort((a, b) => a.index - b.index);
|
|
35
|
-
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);
|
|
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));
|
|
36
40
|
route.authorization = (0, authorization_1.getAuthorization)(apiClass, methodName);
|
|
37
41
|
route.apiClass = apiClass;
|
|
38
42
|
route.localOnly = (0, decorators_2.isLocalOnly)(apiClass, methodName);
|
|
43
|
+
route.externalOutbound = externalOutbound;
|
|
44
|
+
route.externalContextKeys = (0, ExternalContextKeys_1.getExternalContextKeys)(apiClass, methodName);
|
|
39
45
|
return route;
|
|
40
46
|
}
|
|
47
|
+
/** Each declaration resolved against its argument's reflected type, in argument order. */
|
|
48
|
+
// webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction
|
|
49
|
+
static bindingsOf(declarations, parameterTypes) {
|
|
50
|
+
return declarations
|
|
51
|
+
.map((declaration) => new HttpContract_1.HttpParameterBinding(declaration.index, declaration.source, declaration.wireName, this.valueType(parameterTypes[declaration.index]), declaration.itemType))
|
|
52
|
+
.sort((a, b) => a.index - b.index);
|
|
53
|
+
}
|
|
41
54
|
// webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction
|
|
42
55
|
static endpointPath(apiClass, methodName) {
|
|
43
56
|
const path = ((0, decorators_1.getEndpoints)(apiClass) ?? {})[methodName];
|
|
@@ -47,11 +60,26 @@ class RouteMetadataFactory {
|
|
|
47
60
|
}
|
|
48
61
|
// webpieces-disable no-function-outside-class -- private pure helper keeps the existing static metadata factory below the method-size limit
|
|
49
62
|
static validateEndpointOptions(apiClass, methodName, httpMethod, formPost, rawBody) {
|
|
50
|
-
if (
|
|
51
|
-
|
|
63
|
+
if ((0, HttpContract_1.verbCarriesBody)(httpMethod))
|
|
64
|
+
return;
|
|
65
|
+
if (formPost === true) {
|
|
66
|
+
throw new Error(`${apiClass.name}.${methodName} is ${httpMethod} and cannot declare formPost:true because ${httpMethod} has no request body.`);
|
|
52
67
|
}
|
|
53
|
-
if (
|
|
54
|
-
throw new Error(`${apiClass.name}.${methodName} is
|
|
68
|
+
if (rawBody === true) {
|
|
69
|
+
throw new Error(`${apiClass.name}.${methodName} is ${httpMethod} and cannot declare rawBody:true because ${httpMethod} has no request body.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A CLOUDTASKS or CRON endpoint must be POST: Cloud Tasks, Cloud Scheduler and the in-memory task
|
|
74
|
+
* invoker all deliver with POST, so any other verb would register a route nothing ever reaches
|
|
75
|
+
* (a 404 at delivery time, not at startup).
|
|
76
|
+
*/
|
|
77
|
+
// webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory
|
|
78
|
+
static validateTriggerVerb(apiClass, methodName, httpMethod) {
|
|
79
|
+
const kind = (0, decorators_1.getEndpointKind)(apiClass, methodName);
|
|
80
|
+
if ((kind === 'cloudtasks' || kind === 'cron') && httpMethod !== HttpContract_1.POST) {
|
|
81
|
+
throw new Error(`${apiClass.name || 'Unknown'}.${methodName} is a ${kind.toUpperCase()} endpoint declared ${httpMethod}; ` +
|
|
82
|
+
`task and scheduler delivery always uses POST, so declare @Endpoint(POST, ...).`);
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
85
|
// webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators
|
|
@@ -63,7 +91,7 @@ class RouteMetadataFactory {
|
|
|
63
91
|
(streaming.direction !== StreamingContract_2.StreamDirection.REQUEST && !streaming.responseSchema)) {
|
|
64
92
|
throw new Error(`${label} requires generated streaming schemas. Run wp-openapi and registerStreamingCatalog(${apiClass.name}, catalog) before binding.`);
|
|
65
93
|
}
|
|
66
|
-
if (httpMethod !==
|
|
94
|
+
if (httpMethod !== HttpContract_1.POST)
|
|
67
95
|
throw new Error(`${label} is streaming and must use POST.`);
|
|
68
96
|
if (declarations.length > 0)
|
|
69
97
|
throw new Error(`${label} is streaming and cannot declare path/query parameters.`);
|
|
@@ -135,6 +163,63 @@ class RouteMetadataFactory {
|
|
|
135
163
|
return responseType;
|
|
136
164
|
throw new Error(`${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`);
|
|
137
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* The EXTERNAL_* rules ({@link assertExternalEndpointKinds}), then whether the contract is an
|
|
168
|
+
* outside system's API. `@ExternalContextKeys` says which context an OUTSIDE system may see, so it
|
|
169
|
+
* is refused on a contract this repo serves, which already receives the platform context.
|
|
170
|
+
*/
|
|
171
|
+
// webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory
|
|
172
|
+
static externalOutbound(apiClass) {
|
|
173
|
+
(0, decorators_2.assertExternalEndpointKinds)(apiClass);
|
|
174
|
+
const externalOutbound = (0, decorators_2.isExternalOutboundApi)(apiClass);
|
|
175
|
+
if (!externalOutbound && (0, ExternalContextKeys_1.declaresExternalContextKeys)(apiClass)) {
|
|
176
|
+
throw new Error(`${apiClass.name || 'Unknown'} declares @ExternalContextKeys but is not an EXTERNAL_OUTBOUND contract. ` +
|
|
177
|
+
'The list says which context an OUTSIDE system may see; a contract served in this repo already ' +
|
|
178
|
+
'receives the platform context.');
|
|
179
|
+
}
|
|
180
|
+
return externalOutbound;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* `@QueryString()` (a free-form {@link QueryParams}) is the query spelling of an EXTERNAL_OUTBOUND
|
|
184
|
+
* contract ONLY — our own contracts keep the typed `@QueryParam` — and its argument must BE a
|
|
185
|
+
* `QueryParams`. An item type (`@QueryParam('ids', Number)`) only means something on a list.
|
|
186
|
+
*/
|
|
187
|
+
// webpieces-disable no-function-outside-class -- private pure validator for the static metadata factory
|
|
188
|
+
static validateQueryDeclarations(apiClass, methodName, externalOutbound, parameterTypes, declarations) {
|
|
189
|
+
const label = `${apiClass.name || 'Unknown'}.${methodName}`;
|
|
190
|
+
for (const declaration of declarations) {
|
|
191
|
+
const type = parameterTypes[declaration.index];
|
|
192
|
+
if (declaration.source === 'query-map' && !externalOutbound) {
|
|
193
|
+
throw new Error(`${label} declares @QueryString(), which only an EXTERNAL_OUTBOUND contract may use — a vendor's ` +
|
|
194
|
+
`query string is free-form. A contract this repo serves names each key: @QueryParam('name').`);
|
|
195
|
+
}
|
|
196
|
+
if (declaration.source === 'query-map' && type !== undefined && type !== QueryParams_1.QueryParams) {
|
|
197
|
+
throw new Error(`${label} argument ${declaration.index} is @QueryString() and must be typed QueryParams.`);
|
|
198
|
+
}
|
|
199
|
+
if (declaration.itemType !== undefined && type !== undefined && type !== Array) {
|
|
200
|
+
throw new Error(`${label} @QueryParam('${declaration.wireName}', ${declaration.itemType.name}) names an item type, ` +
|
|
201
|
+
`but argument ${declaration.index} is not a list.`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* `responseBody: NONE` means the success response is 0 bytes, so it cannot combine with a
|
|
207
|
+
* caller-owned full response or a streaming endpoint (both of which DEFINE a response).
|
|
208
|
+
*/
|
|
209
|
+
// 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;
|
|
213
|
+
const label = `${apiClass.name || 'Unknown'}.${methodName}`;
|
|
214
|
+
if (options.responseBody !== HttpEndpointOptions_1.NONE) {
|
|
215
|
+
throw new Error(`${label} declares unsupported responseBody '${String(options.responseBody)}'; the only value is NONE.`);
|
|
216
|
+
}
|
|
217
|
+
if (options.responseType === 'full' || streaming !== undefined) {
|
|
218
|
+
throw new Error(`${label} declares responseBody: NONE together with ${streaming ? 'a stream' : "responseType: 'full'"}; ` +
|
|
219
|
+
'a 0-byte response cannot also carry a caller-owned or streamed response.');
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
138
223
|
// webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency
|
|
139
224
|
static parameterTypes(apiClass, methodName) {
|
|
140
225
|
return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];
|
|
@@ -172,11 +257,11 @@ class RouteMetadataFactory {
|
|
|
172
257
|
if (!mappedIndexes.has(index))
|
|
173
258
|
unbound.push(index);
|
|
174
259
|
}
|
|
175
|
-
if (httpMethod
|
|
176
|
-
throw new Error(`${label} is
|
|
260
|
+
if (!(0, HttpContract_1.verbCarriesBody)(httpMethod) && unbound.length > 0) {
|
|
261
|
+
throw new Error(`${label} is ${httpMethod}, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`);
|
|
177
262
|
}
|
|
178
263
|
if (unbound.length > 1) {
|
|
179
|
-
throw new Error(`${label} has ${unbound.length} unmapped parameters.
|
|
264
|
+
throw new Error(`${label} has ${unbound.length} unmapped parameters. ${httpMethod} permits exactly one unannotated request body; map every other parameter explicitly.`);
|
|
180
265
|
}
|
|
181
266
|
return unbound[0];
|
|
182
267
|
}
|
|
@@ -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 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} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { getStreamingEndpoint } from './StreamingContract';\nimport { getAuthorization } from './authorization';\nimport { assertEveryEndpointHasAuthMode, isLocalOnly } from './decorators';\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 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 const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\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 = 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 ),\n )\n .sort((a: HttpParameterBinding, b: HttpParameterBinding) => a.index - b.index);\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 );\n route.authorization = getAuthorization(apiClass, methodName);\n route.apiClass = apiClass;\n route.localOnly = isLocalOnly(apiClass, methodName);\n return route;\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 (httpMethod === 'GET' && formPost === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare formPost:true because GET has no request body.`,\n );\n }\n if (httpMethod === 'GET' && rawBody === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare rawBody:true because GET has no request body.`,\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 // 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 (httpMethod === 'GET' && unbound.length > 0) {\n throw new Error(\n `${label} is GET, 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. POST 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 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,13 +1,21 @@
|
|
|
1
1
|
import { ClientRole } from '../errors/ClientRole';
|
|
2
2
|
import { StreamTransportError } from './StreamingContract';
|
|
3
|
+
import { RouteMetadata } from './RouteMetadata';
|
|
3
4
|
export declare class StreamProtocolError extends StreamTransportError {
|
|
4
5
|
}
|
|
5
6
|
export declare class StreamDisconnectedError extends StreamTransportError {
|
|
6
7
|
}
|
|
7
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Reserved ASCII-RS controls share exactly the unary ErrorTranslator seam in both directions —
|
|
10
|
+
* including its `CallScope` ladder: the translator is resolved for the streaming ROUTE's contract
|
|
11
|
+
* class and method (method -> API -> ALL -> webpieces default), exactly as a unary call's is.
|
|
12
|
+
*/
|
|
8
13
|
export declare class StreamErrorControl {
|
|
14
|
+
private readonly route;
|
|
15
|
+
constructor(route: RouteMetadata);
|
|
9
16
|
encode(error: Error): string;
|
|
10
17
|
/** @param role - who is receiving the record, declared at the reading side's setup. */
|
|
11
18
|
decode(line: string, role: ClientRole): Error;
|
|
19
|
+
private translator;
|
|
12
20
|
private response;
|
|
13
21
|
}
|
|
@@ -29,10 +29,18 @@ class ControlRecord {
|
|
|
29
29
|
control;
|
|
30
30
|
response;
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Reserved ASCII-RS controls share exactly the unary ErrorTranslator seam in both directions —
|
|
34
|
+
* including its `CallScope` ladder: the translator is resolved for the streaming ROUTE's contract
|
|
35
|
+
* class and method (method -> API -> ALL -> webpieces default), exactly as a unary call's is.
|
|
36
|
+
*/
|
|
33
37
|
class StreamErrorControl {
|
|
38
|
+
route;
|
|
39
|
+
constructor(route) {
|
|
40
|
+
this.route = route;
|
|
41
|
+
}
|
|
34
42
|
encode(error) {
|
|
35
|
-
const response =
|
|
43
|
+
const response = this.translator().toWire(error);
|
|
36
44
|
return `${String.fromCharCode(0x1e)}${JSON.stringify({ control: 'error', response })}\n`;
|
|
37
45
|
}
|
|
38
46
|
/** @param role - who is receiving the record, declared at the reading side's setup. */
|
|
@@ -40,7 +48,7 @@ class StreamErrorControl {
|
|
|
40
48
|
const response = this.response(line);
|
|
41
49
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- fromWire returns its typed result by throwing
|
|
42
50
|
try {
|
|
43
|
-
|
|
51
|
+
this.translator().fromWire(response, role);
|
|
44
52
|
WebpiecesDefaultErrorTranslator_1.WEBPIECES_DEFAULT_ERROR_TRANSLATOR.fromWire(response, role);
|
|
45
53
|
}
|
|
46
54
|
catch (err) {
|
|
@@ -49,6 +57,9 @@ class StreamErrorControl {
|
|
|
49
57
|
}
|
|
50
58
|
return new StreamProtocolError('Error translator returned for a JSONL error control record.');
|
|
51
59
|
}
|
|
60
|
+
translator() {
|
|
61
|
+
return ClientRegistry_1.ClientRegistry.getErrorTranslator(this.route.apiClass, this.route.methodName);
|
|
62
|
+
}
|
|
52
63
|
response(line) {
|
|
53
64
|
let control;
|
|
54
65
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted control boundary
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamErrorControl.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/StreamErrorControl.ts"],"names":[],"mappings":";;;AACA,qDAAkD;AAClD,uDAAoF;AACpF,uFAAuF;AAEvF,kDAA4C;AAC5C,2DAA2D;
|
|
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,8 +1,8 @@
|
|
|
1
1
|
import { ApiMethodInfo } from './ApiMethodInfo';
|
|
2
2
|
import { CallOutcome, ErrorClassifier } from './ErrorClassifier';
|
|
3
3
|
/**
|
|
4
|
-
* The webpieces built-in {@link ErrorClassifier} — the terminal
|
|
5
|
-
*
|
|
4
|
+
* The webpieces built-in {@link ErrorClassifier} — the terminal rung, used when no classifier on the
|
|
5
|
+
* `CallScope` ladder (method, API, ALL) claims an error. Two tables, one per question:
|
|
6
6
|
*
|
|
7
7
|
* # 1. EXPECTED or not — "are things WORKING?"
|
|
8
8
|
*
|
|
@@ -5,8 +5,8 @@ const ApiError_1 = require("../errors/ApiError");
|
|
|
5
5
|
const OAuthProtocolError_1 = require("../oauth/OAuthProtocolError");
|
|
6
6
|
const ErrorClassifier_1 = require("./ErrorClassifier");
|
|
7
7
|
/**
|
|
8
|
-
* The webpieces built-in {@link ErrorClassifier} — the terminal
|
|
9
|
-
*
|
|
8
|
+
* The webpieces built-in {@link ErrorClassifier} — the terminal rung, used when no classifier on the
|
|
9
|
+
* `CallScope` ladder (method, API, ALL) claims an error. Two tables, one per question:
|
|
10
10
|
*
|
|
11
11
|
* # 1. EXPECTED or not — "are things WORKING?"
|
|
12
12
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesDefaultErrorClassifier.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesDefaultErrorClassifier.ts"],"names":[],"mappings":";;;AAAA,iDAmB4B;AAC5B,oEAAiE;AAEjE,uDAAiE;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAa,+BAA+B;IACxC,+CAA+C;IAC/C,QAAQ,CAAC,KAAY,EAAE,UAAyB;QAC5C,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;YACrC,OAAO,6BAAW,CAAC,QAAQ,CAAC;QAChC,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,6BAAW,CAAC,iBAAiB,CAAC,CAAC,CAAC,6BAAW,CAAC,OAAO,CAAC;IACzF,CAAC;IAEO,UAAU,CAAC,KAAY,EAAE,UAAyB;QACtD,gEAAgE;QAChE,IAAI,KAAK,YAAY,0BAAe,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,6DAA6D;QAC7D,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,CACH,KAAK,YAAY,6BAAkB;YACnC,KAAK,YAAY,+BAAoB;YACrC,KAAK,YAAY,4BAAiB;YAClC,KAAK,YAAY,2BAAgB;YACjC,KAAK,YAAY,2BAAgB;YACjC,KAAK,YAAY,gCAAqB;YACtC,KAAK,YAAY,qCAA0B;YAC3C,KAAK,YAAY,uCAA4B;YAC7C,CAAC,KAAK,YAAY,wBAAa,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;YACzD,CAAC,KAAK,YAAY,uCAAkB,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC,CACjE,CAAC;IACN,CAAC;IAEO,WAAW,CAAC,KAAY;QAC5B,2FAA2F;QAC3F,IAAI,KAAK,YAAY,mCAAwB,EAAE,CAAC;YAC5C,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,KAAK,YAAY,wBAAa,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC,UAAU,KAAK,GAAG,IAAI,KAAK,CAAC,UAAU,KAAK,GAAG,CAAC;QAChE,CAAC;QACD,OAAO,CACH,KAAK,YAAY,iCAAsB;YACvC,KAAK,YAAY,oCAAyB;YAC1C,KAAK,YAAY,6BAAkB;YACnC,KAAK,YAAY,8BAAmB;YACpC,KAAK,YAAY,8BAAmB;YACpC,KAAK,YAAY,oCAAyB;YAC1C,KAAK,YAAY,6BAAkB,CACtC,CAAC;IACN,CAAC;CACJ;AAlDD,0EAkDC;AAED,oFAAoF;AACvE,QAAA,kCAAkC,GAAG,IAAI,+BAA+B,EAAE,CAAC","sourcesContent":["import {\n ApiBadRequestError,\n ApiUnauthorizedError,\n ApiForbiddenError,\n ApiNotFoundError,\n ApiEndUserError,\n ApiConflictError,\n ApiUnprocessableError,\n ApiPreconditionFailedError,\n ApiUnsupportedMediaTypeError,\n ApiCodedError,\n ApiRetriesExhaustedError,\n ApiRequestTimeoutError,\n ApiDependencyTimeoutError,\n ApiBadGatewayError,\n ApiUnavailableError,\n ApiRateLimitedError,\n ApiDependencyBackoffError,\n ApiConnectionError,\n} from '../errors/ApiError';\nimport { OAuthProtocolError } from '../oauth/OAuthProtocolError';\nimport { ApiMethodInfo } from './ApiMethodInfo';\nimport { CallOutcome, ErrorClassifier } from './ErrorClassifier';\n\n/**\n * The webpieces built-in {@link ErrorClassifier} — the terminal
|
|
1
|
+
{"version":3,"file":"WebpiecesDefaultErrorClassifier.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesDefaultErrorClassifier.ts"],"names":[],"mappings":";;;AAAA,iDAmB4B;AAC5B,oEAAiE;AAEjE,uDAAiE;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAa,+BAA+B;IACxC,+CAA+C;IAC/C,QAAQ,CAAC,KAAY,EAAE,UAAyB;QAC5C,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;YACrC,OAAO,6BAAW,CAAC,QAAQ,CAAC;QAChC,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,6BAAW,CAAC,iBAAiB,CAAC,CAAC,CAAC,6BAAW,CAAC,OAAO,CAAC;IACzF,CAAC;IAEO,UAAU,CAAC,KAAY,EAAE,UAAyB;QACtD,gEAAgE;QAChE,IAAI,KAAK,YAAY,0BAAe,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,6DAA6D;QAC7D,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,CACH,KAAK,YAAY,6BAAkB;YACnC,KAAK,YAAY,+BAAoB;YACrC,KAAK,YAAY,4BAAiB;YAClC,KAAK,YAAY,2BAAgB;YACjC,KAAK,YAAY,2BAAgB;YACjC,KAAK,YAAY,gCAAqB;YACtC,KAAK,YAAY,qCAA0B;YAC3C,KAAK,YAAY,uCAA4B;YAC7C,CAAC,KAAK,YAAY,wBAAa,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;YACzD,CAAC,KAAK,YAAY,uCAAkB,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC,CACjE,CAAC;IACN,CAAC;IAEO,WAAW,CAAC,KAAY;QAC5B,2FAA2F;QAC3F,IAAI,KAAK,YAAY,mCAAwB,EAAE,CAAC;YAC5C,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,KAAK,YAAY,wBAAa,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC,UAAU,KAAK,GAAG,IAAI,KAAK,CAAC,UAAU,KAAK,GAAG,CAAC;QAChE,CAAC;QACD,OAAO,CACH,KAAK,YAAY,iCAAsB;YACvC,KAAK,YAAY,oCAAyB;YAC1C,KAAK,YAAY,6BAAkB;YACnC,KAAK,YAAY,8BAAmB;YACpC,KAAK,YAAY,8BAAmB;YACpC,KAAK,YAAY,oCAAyB;YAC1C,KAAK,YAAY,6BAAkB,CACtC,CAAC;IACN,CAAC;CACJ;AAlDD,0EAkDC;AAED,oFAAoF;AACvE,QAAA,kCAAkC,GAAG,IAAI,+BAA+B,EAAE,CAAC","sourcesContent":["import {\n ApiBadRequestError,\n ApiUnauthorizedError,\n ApiForbiddenError,\n ApiNotFoundError,\n ApiEndUserError,\n ApiConflictError,\n ApiUnprocessableError,\n ApiPreconditionFailedError,\n ApiUnsupportedMediaTypeError,\n ApiCodedError,\n ApiRetriesExhaustedError,\n ApiRequestTimeoutError,\n ApiDependencyTimeoutError,\n ApiBadGatewayError,\n ApiUnavailableError,\n ApiRateLimitedError,\n ApiDependencyBackoffError,\n ApiConnectionError,\n} from '../errors/ApiError';\nimport { OAuthProtocolError } from '../oauth/OAuthProtocolError';\nimport { ApiMethodInfo } from './ApiMethodInfo';\nimport { CallOutcome, ErrorClassifier } from './ErrorClassifier';\n\n/**\n * The webpieces built-in {@link ErrorClassifier} — the terminal rung, used when no classifier on the\n * `CallScope` ladder (method, API, ALL) claims an error. Two tables, one per question:\n *\n * # 1. EXPECTED or not — \"are things WORKING?\"\n *\n * NOT \"was it an HTTP 4xx vs 5xx\" — the two differ (see 408). Classification is by portable Error\n * TYPE, never by transport: this runs over in-process calls, queue handlers and HTTP through one code\n * path and only ever sees a thrown Error.\n *\n * - ApiEndUserError (266): ALWAYS expected, server OR client — an expected \"user made a mistake\".\n * - SERVER — a healthy server correctly rejecting a CLIENT'S mistake is metrics NOISE, not a failure:\n * ApiBadRequestError (400), ApiUnauthorizedError (401), ApiForbiddenError (403), ApiNotFoundError\n * (404), ApiConflictError (409), ApiPreconditionFailedError (412), ApiUnsupportedMediaTypeError\n * (415), ApiUnprocessableError (422), an ApiCodedError whose `isCallerError()` is true (below 500,\n * except 408/429), and an OAuthProtocolError below 500 (a refused grant is the OAuth protocol\n * working, #1176). ApiRequestTimeoutError (408) is deliberately absent: the client may NEVER have\n * seen the response.\n * - CLIENT: receiving ANY error except 266 means the outbound call failed — never expected here.\n * (An external client whose 404 means \"miss\" registers its own classifier for that.)\n *\n * # 2. TRANSIENT or FAILURE — \"may repeating it work?\"\n *\n * The kind table that used to live inside the MCP renderer:\n *\n * - TRANSIENT: request-timeout (408), dependency-timeout (504, incl. a client's own\n * `ApiCallTimeoutError`), bad-gateway (502 proxy), unavailable (503), rate-limited (429),\n * dependency-backoff, connection, and an ApiCodedError of 408/429.\n * - FAILURE: everything else — a received 4xx (our bug), implementation, not-implemented (501), a\n * generic dependency defect, and {@link ApiRetriesExhaustedError}, which is checked FIRST: it is an\n * `ApiDependencyError`, and classifying it transient would let every layer above retry again.\n */\nexport class WebpiecesDefaultErrorClassifier implements ErrorClassifier {\n /** Terminal tier — NEVER returns undefined. */\n classify(error: Error, methodInfo: ApiMethodInfo): CallOutcome {\n if (this.isExpected(error, methodInfo)) {\n return CallOutcome.EXPECTED;\n }\n return this.isTransient(error) ? CallOutcome.TRANSIENT_FAILURE : CallOutcome.FAILURE;\n }\n\n private isExpected(error: Error, methodInfo: ApiMethodInfo): boolean {\n // 266 is the one error that is never a failure, on either side.\n if (error instanceof ApiEndUserError) {\n return true;\n }\n // A client that RECEIVED any other error made a failed call.\n if (methodInfo.side !== 'server') {\n return false;\n }\n return (\n error instanceof ApiBadRequestError ||\n error instanceof ApiUnauthorizedError ||\n error instanceof ApiForbiddenError ||\n error instanceof ApiNotFoundError ||\n error instanceof ApiConflictError ||\n error instanceof ApiUnprocessableError ||\n error instanceof ApiPreconditionFailedError ||\n error instanceof ApiUnsupportedMediaTypeError ||\n (error instanceof ApiCodedError && error.isCallerError()) ||\n (error instanceof OAuthProtocolError && error.isCallerError())\n );\n }\n\n private isTransient(error: Error): boolean {\n // FIRST: a lower layer already retried. Never transient, or every layer above retries too.\n if (error instanceof ApiRetriesExhaustedError) {\n return false;\n }\n if (error instanceof ApiCodedError) {\n return error.statusCode === 408 || error.statusCode === 429;\n }\n return (\n error instanceof ApiRequestTimeoutError ||\n error instanceof ApiDependencyTimeoutError ||\n error instanceof ApiBadGatewayError ||\n error instanceof ApiUnavailableError ||\n error instanceof ApiRateLimitedError ||\n error instanceof ApiDependencyBackoffError ||\n error instanceof ApiConnectionError\n );\n }\n}\n\n/** Process-wide built-in instance — stateless, so one shared instance is enough. */\nexport const WEBPIECES_DEFAULT_ERROR_CLASSIFIER = new WebpiecesDefaultErrorClassifier();\n"]}
|
|
@@ -5,7 +5,8 @@ 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}
|
|
8
|
+
* It is what {@link ClientRegistry.getErrorTranslator} answers for a call no `CallScope` rung
|
|
9
|
+
* (method, API, ALL) has a translator for, and it is
|
|
9
10
|
* what an app's own translator calls to DECLINE an error:
|
|
10
11
|
*
|
|
11
12
|
* ```ts
|
|
@@ -14,7 +14,8 @@ 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}
|
|
17
|
+
* It is what {@link ClientRegistry.getErrorTranslator} answers for a call no `CallScope` rung
|
|
18
|
+
* (method, API, ALL) has a translator for, and it is
|
|
18
19
|
* what an app's own translator calls to DECLINE an error:
|
|
19
20
|
*
|
|
20
21
|
* ```ts
|