@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":"WebpiecesDefaultErrorTranslator.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesDefaultErrorTranslator.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiErrorBoundary } from '../errors/ApiErrorBoundary';\nimport { ApiErrorCodec } from '../errors/ApiErrorCodec';\nimport { ApiCodedError, ApiError } from '../errors/ApiError';\nimport { ClientRole } from '../errors/ClientRole';\nimport { ReceivedApiErrorRule } from '../errors/ReceivedApiErrorRule';\nimport { OAuthErrorResponse } from '../oauth/OAuthErrorResponse';\nimport { OAuthProtocolError } from '../oauth/OAuthProtocolError';\nimport { ApiCallTimeoutError } from './ApiCallTimeoutError';\nimport { ApiErrorHttpStatus, PublishedKind } from './ApiErrorHttpStatus';\nimport { ErrorTranslator } from './ErrorTranslator';\nimport { HttpHeader, HttpResponseDto, HttpResponseStatus } from './HttpResponseDto';\n\n/**\n * THE webpieces default {@link ErrorTranslator} — ONE class, BOTH halves, used by the node server, by\n * every node client and by the browser bundle alike.\n *\n * It is what {@link ClientRegistry.getErrorTranslator} holds until an app installs its own, and it is\n * what an app's own translator calls to DECLINE an error:\n *\n * ```ts\n * private readonly fallback = new WebpiecesDefaultErrorTranslator();\n * ```\n *\n * so \"no translator registered\" and \"a translator that does not claim this error\" produce\n * byte-identical output. It lives in core-util, beside {@link ClientRegistry}, because core-util is\n * the package every API contract already depends on and the only one both a server and a browser\n * bundle can reach. Both halves needed nothing else: the outbound half wants\n * {@link ApiErrorBoundary} + {@link ApiErrorHttpStatus}, the inbound half wants\n * {@link ApiErrorCodec} + {@link ReceivedApiErrorRule}, and all four were already here.\n *\n * It holds no ROLE: who is receiving ({@link ClientRole}) is an argument of `fromWire`, supplied by\n * the client that read the response from its own setup, so one stateless instance serves a server's\n * clients and a browser's alike.\n *\n * Stateless, so {@link WEBPIECES_DEFAULT_ERROR_TRANSLATOR} is a shared instance — but constructing\n * your own costs nothing and reads better at a delegation site.\n */\nexport class WebpiecesDefaultErrorTranslator implements ErrorTranslator {\n /** Reason phrase for a client deadline; see {@link ApiCallTimeoutError}. Never 'Gateway Timeout'. */\n static readonly CALL_TIMEOUT_REASON = 'Network Timeout';\n\n private readonly boundary = new ApiErrorBoundary();\n\n private readonly genericMessages: Map<number, string> = new Map<number, string>([\n [266, 'End User Error'],\n [400, 'Bad Request'],\n [401, 'Unauthorized'],\n [403, 'Forbidden'],\n [404, 'Not Found'],\n [408, 'Request Timeout'],\n [409, 'Conflict'],\n [412, 'Precondition Failed'],\n [415, 'Unsupported Media Type'],\n [422, 'Unprocessable Content'],\n [426, 'Upgrade Required'],\n [429, 'Too Many Requests'],\n [500, 'Internal Server Error'],\n [501, 'Not Implemented'],\n [502, 'Bad Gateway'],\n [503, 'Service Unavailable'],\n [504, 'Gateway Timeout'],\n ]);\n\n /**\n * SERVER: an exception in, the exact HTTP response out.\n *\n * The payload IS the answer. {@link ApiErrorBoundary.encode} has already applied both shared\n * rules — a caller-local `ApiConnectionError` (and every non-`ApiError`) publishes as\n * `implementation`, and only an `ApiEndUserError`'s own message is disclosed — and the payload\n * carries everything HTTP needs after that: `kind`, `statusCode`, `edgeHttpStatus` and\n * `retryAfterSeconds`. So there is no `instanceof` here and nothing re-derived from the raw\n * error; unknown throws and caller-local failures come out as a 500 implementation failure\n * because that is what the payload says.\n *\n * The one exception is an {@link OAuthProtocolError}: its caller is an OAuth client, so it is\n * answered in the RFC shape by `OAuthErrorResponse`, not as a payload.\n *\n * An `ApiEndUserError` publishes 266 here, ALWAYS. Whether a particular REQUEST republishes that\n * as a real 4xx is a property of the CALLER, not of the error, so it is decided once per request\n * from `WebpiecesCoreHeaders.SURFACE` by the server boundary (`ExpressWrapper.handleError`) —\n * see {@link SurfaceEndUserStatus}. That is why this class takes no constructor argument any\n * more: the old `new ApiErrorHttpMapper('gui' | 'edge')` made every delegating app translator\n * responsible for repeating its router's mode, and repeating it wrongly was silent.\n */\n toWire(error: Error): HttpResponseDto {\n // An OAuth refusal is answered to an OAuth CLIENT, which parses the RFC 6749/6750 body and\n // never the ApiError envelope (#1176). Same renderer as every other OAuth boundary.\n if (error instanceof OAuthProtocolError) {\n return OAuthErrorResponse.toResponse(error, undefined);\n }\n const payload = this.boundary.encode(error);\n // The boundary never publishes kind 'connection' — that is the whole first rule above — so\n // this is the published subset ApiErrorHttpStatus.codeFor accepts.\n const kind = payload.kind as PublishedKind;\n const status = ApiErrorHttpStatus.codeFor(kind, payload.statusCode);\n const headers =\n payload.retryAfterSeconds !== undefined\n ? [new HttpHeader('retry-after', String(payload.retryAfterSeconds))]\n : [];\n return new HttpResponseDto(\n new HttpResponseStatus(status, this.reasonPhrase(error, status)),\n headers,\n payload,\n );\n }\n\n /**\n * The reason phrase for `error`. An {@link ApiCallTimeoutError} is THIS process's own client\n * deadline firing on an outbound call: no gateway was involved, so it is never titled\n * 'Gateway Timeout' (issue #1170). It keeps its 504 status and `dependency-timeout` payload —\n * retry semantics are unchanged — and only the human framing differs.\n */\n private reasonPhrase(error: Error, status: number): string {\n if (error instanceof ApiCallTimeoutError) {\n return WebpiecesDefaultErrorTranslator.CALL_TIMEOUT_REASON;\n }\n return this.genericMessage(status);\n }\n\n /**\n * CLIENT: the response in, a THROW out — or a silent return for an ordinary success.\n *\n * Called for EVERY response, 2xx included (see {@link ErrorTranslator.fromWire}). A 2xx that is\n * not 266 is exactly the case where webpieces has nothing to say, so it returns and the caller\n * gets its DTO. Everything else throws, which is what lets `ProxyClient` call this and then simply\n * stop — a failure response can never fall through to a typed caller.\n *\n * The rule applied is {@link ReceivedApiErrorRule}, shared verbatim with IPC: ordinary 4xx is MY\n * bug, ordinary 5xx is THEIRS, retry-relevant statuses retain their specific timeout/gateway/\n * backoff/unavailable meaning, 266 is the end user's own answer, a 426 `client-too-old` stays\n * {@link ApiClientTooOldError}, and a 401 depends on `role` (see {@link ClientRole}).\n *\n * @param role - who is receiving, declared at the client's setup. No default.\n */\n fromWire(response: HttpResponseDto, role: ClientRole): void {\n const code = response.status.code;\n if (code >= 200 && code < 300 && code !== 266) {\n return;\n }\n const decoded = this.decodeBody(response);\n const message =\n decoded?.message ?? this.fallbackMessage(response.body, response.status.reason);\n throw ReceivedApiErrorRule.adapt(\n role,\n code,\n message,\n decoded,\n this.retryAfterSeconds(response.headers),\n );\n }\n\n /** The reason phrase webpieces writes beside a status it chose itself. */\n genericMessage(code: number): string {\n return this.genericMessages.get(code) ?? 'Request Failed';\n }\n\n /**\n * A webpieces peer sends an {@link ApiErrorPayload}; a foreign responder sends anything at all.\n * The status is only believed when it AGREES with the kind the body claims — a body saying\n * `not-found` beside a 500 is a mismatched response, not a 404, and falls back to the status.\n */\n private decodeBody(response: HttpResponseDto): ApiError | undefined {\n if (!ApiErrorCodec.isPayload(response.body)) {\n return undefined;\n }\n const decoded = ApiErrorCodec.decode(response.body);\n if (!ApiErrorHttpStatus.hasCode(decoded)) {\n return undefined;\n }\n // `statusCode` is the 'coded' kind's own status, the one thing the shared table cannot know.\n const statusCode = decoded instanceof ApiCodedError ? decoded.statusCode : undefined;\n const declared = ApiErrorHttpStatus.codeFor(decoded.kind, statusCode);\n return declared === response.status.code ? decoded : undefined;\n }\n\n // webpieces-disable no-any-unknown -- HTTP response bodies are app-owned until this boundary safely inspects them\n private fallbackMessage(body: unknown, reason: string): string {\n if (typeof body === 'object' && body !== null) {\n const message = Object.getOwnPropertyDescriptor(body, 'message')?.value;\n if (typeof message === 'string' && message.length > 0) return message.slice(0, 4096);\n }\n return reason || 'Request Failed';\n }\n\n /** Parse either legal Retry-After form and bound untrusted guidance to one day. */\n private retryAfterSeconds(headers: readonly HttpHeader[]): number | undefined {\n const raw = headers.find(\n (header: HttpHeader) => header.name.toLowerCase() === 'retry-after',\n )?.value;\n if (raw === undefined) return undefined;\n const seconds = Number(raw.trim());\n if (Number.isFinite(seconds) && seconds >= 0) return Math.min(Math.ceil(seconds), 86_400);\n const at = Date.parse(raw);\n if (!Number.isFinite(at)) return undefined;\n return Math.min(Math.max(Math.ceil((at - Date.now()) / 1_000), 0), 86_400);\n }\n}\n\n/** Process-wide built-in instance — stateless, so one shared instance is enough. */\nexport const WEBPIECES_DEFAULT_ERROR_TRANSLATOR = new WebpiecesDefaultErrorTranslator();\n"]}
|
|
1
|
+
{"version":3,"file":"WebpiecesDefaultErrorTranslator.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesDefaultErrorTranslator.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiErrorBoundary } from '../errors/ApiErrorBoundary';\nimport { ApiErrorCodec } from '../errors/ApiErrorCodec';\nimport { ApiCodedError, ApiError } from '../errors/ApiError';\nimport { ClientRole } from '../errors/ClientRole';\nimport { ReceivedApiErrorRule } from '../errors/ReceivedApiErrorRule';\nimport { OAuthErrorResponse } from '../oauth/OAuthErrorResponse';\nimport { OAuthProtocolError } from '../oauth/OAuthProtocolError';\nimport { ApiCallTimeoutError } from './ApiCallTimeoutError';\nimport { ApiErrorHttpStatus, PublishedKind } from './ApiErrorHttpStatus';\nimport { ErrorTranslator } from './ErrorTranslator';\nimport { HttpHeader, HttpResponseDto, HttpResponseStatus } from './HttpResponseDto';\n\n/**\n * THE webpieces default {@link ErrorTranslator} — ONE class, BOTH halves, used by the node server, by\n * every node client and by the browser bundle alike.\n *\n * It is what {@link ClientRegistry.getErrorTranslator} answers for a call no `CallScope` rung\n * (method, API, ALL) has a translator for, and it is\n * what an app's own translator calls to DECLINE an error:\n *\n * ```ts\n * private readonly fallback = new WebpiecesDefaultErrorTranslator();\n * ```\n *\n * so \"no translator registered\" and \"a translator that does not claim this error\" produce\n * byte-identical output. It lives in core-util, beside {@link ClientRegistry}, because core-util is\n * the package every API contract already depends on and the only one both a server and a browser\n * bundle can reach. Both halves needed nothing else: the outbound half wants\n * {@link ApiErrorBoundary} + {@link ApiErrorHttpStatus}, the inbound half wants\n * {@link ApiErrorCodec} + {@link ReceivedApiErrorRule}, and all four were already here.\n *\n * It holds no ROLE: who is receiving ({@link ClientRole}) is an argument of `fromWire`, supplied by\n * the client that read the response from its own setup, so one stateless instance serves a server's\n * clients and a browser's alike.\n *\n * Stateless, so {@link WEBPIECES_DEFAULT_ERROR_TRANSLATOR} is a shared instance — but constructing\n * your own costs nothing and reads better at a delegation site.\n */\nexport class WebpiecesDefaultErrorTranslator implements ErrorTranslator {\n /** Reason phrase for a client deadline; see {@link ApiCallTimeoutError}. Never 'Gateway Timeout'. */\n static readonly CALL_TIMEOUT_REASON = 'Network Timeout';\n\n private readonly boundary = new ApiErrorBoundary();\n\n private readonly genericMessages: Map<number, string> = new Map<number, string>([\n [266, 'End User Error'],\n [400, 'Bad Request'],\n [401, 'Unauthorized'],\n [403, 'Forbidden'],\n [404, 'Not Found'],\n [408, 'Request Timeout'],\n [409, 'Conflict'],\n [412, 'Precondition Failed'],\n [415, 'Unsupported Media Type'],\n [422, 'Unprocessable Content'],\n [426, 'Upgrade Required'],\n [429, 'Too Many Requests'],\n [500, 'Internal Server Error'],\n [501, 'Not Implemented'],\n [502, 'Bad Gateway'],\n [503, 'Service Unavailable'],\n [504, 'Gateway Timeout'],\n ]);\n\n /**\n * SERVER: an exception in, the exact HTTP response out.\n *\n * The payload IS the answer. {@link ApiErrorBoundary.encode} has already applied both shared\n * rules — a caller-local `ApiConnectionError` (and every non-`ApiError`) publishes as\n * `implementation`, and only an `ApiEndUserError`'s own message is disclosed — and the payload\n * carries everything HTTP needs after that: `kind`, `statusCode`, `edgeHttpStatus` and\n * `retryAfterSeconds`. So there is no `instanceof` here and nothing re-derived from the raw\n * error; unknown throws and caller-local failures come out as a 500 implementation failure\n * because that is what the payload says.\n *\n * The one exception is an {@link OAuthProtocolError}: its caller is an OAuth client, so it is\n * answered in the RFC shape by `OAuthErrorResponse`, not as a payload.\n *\n * An `ApiEndUserError` publishes 266 here, ALWAYS. Whether a particular REQUEST republishes that\n * as a real 4xx is a property of the CALLER, not of the error, so it is decided once per request\n * from `WebpiecesCoreHeaders.SURFACE` by the server boundary (`ExpressWrapper.handleError`) —\n * see {@link SurfaceEndUserStatus}. That is why this class takes no constructor argument any\n * more: the old `new ApiErrorHttpMapper('gui' | 'edge')` made every delegating app translator\n * responsible for repeating its router's mode, and repeating it wrongly was silent.\n */\n toWire(error: Error): HttpResponseDto {\n // An OAuth refusal is answered to an OAuth CLIENT, which parses the RFC 6749/6750 body and\n // never the ApiError envelope (#1176). Same renderer as every other OAuth boundary.\n if (error instanceof OAuthProtocolError) {\n return OAuthErrorResponse.toResponse(error, undefined);\n }\n const payload = this.boundary.encode(error);\n // The boundary never publishes kind 'connection' — that is the whole first rule above — so\n // this is the published subset ApiErrorHttpStatus.codeFor accepts.\n const kind = payload.kind as PublishedKind;\n const status = ApiErrorHttpStatus.codeFor(kind, payload.statusCode);\n const headers =\n payload.retryAfterSeconds !== undefined\n ? [new HttpHeader('retry-after', String(payload.retryAfterSeconds))]\n : [];\n return new HttpResponseDto(\n new HttpResponseStatus(status, this.reasonPhrase(error, status)),\n headers,\n payload,\n );\n }\n\n /**\n * The reason phrase for `error`. An {@link ApiCallTimeoutError} is THIS process's own client\n * deadline firing on an outbound call: no gateway was involved, so it is never titled\n * 'Gateway Timeout' (issue #1170). It keeps its 504 status and `dependency-timeout` payload —\n * retry semantics are unchanged — and only the human framing differs.\n */\n private reasonPhrase(error: Error, status: number): string {\n if (error instanceof ApiCallTimeoutError) {\n return WebpiecesDefaultErrorTranslator.CALL_TIMEOUT_REASON;\n }\n return this.genericMessage(status);\n }\n\n /**\n * CLIENT: the response in, a THROW out — or a silent return for an ordinary success.\n *\n * Called for EVERY response, 2xx included (see {@link ErrorTranslator.fromWire}). A 2xx that is\n * not 266 is exactly the case where webpieces has nothing to say, so it returns and the caller\n * gets its DTO. Everything else throws, which is what lets `ProxyClient` call this and then simply\n * stop — a failure response can never fall through to a typed caller.\n *\n * The rule applied is {@link ReceivedApiErrorRule}, shared verbatim with IPC: ordinary 4xx is MY\n * bug, ordinary 5xx is THEIRS, retry-relevant statuses retain their specific timeout/gateway/\n * backoff/unavailable meaning, 266 is the end user's own answer, a 426 `client-too-old` stays\n * {@link ApiClientTooOldError}, and a 401 depends on `role` (see {@link ClientRole}).\n *\n * @param role - who is receiving, declared at the client's setup. No default.\n */\n fromWire(response: HttpResponseDto, role: ClientRole): void {\n const code = response.status.code;\n if (code >= 200 && code < 300 && code !== 266) {\n return;\n }\n const decoded = this.decodeBody(response);\n const message =\n decoded?.message ?? this.fallbackMessage(response.body, response.status.reason);\n throw ReceivedApiErrorRule.adapt(\n role,\n code,\n message,\n decoded,\n this.retryAfterSeconds(response.headers),\n );\n }\n\n /** The reason phrase webpieces writes beside a status it chose itself. */\n genericMessage(code: number): string {\n return this.genericMessages.get(code) ?? 'Request Failed';\n }\n\n /**\n * A webpieces peer sends an {@link ApiErrorPayload}; a foreign responder sends anything at all.\n * The status is only believed when it AGREES with the kind the body claims — a body saying\n * `not-found` beside a 500 is a mismatched response, not a 404, and falls back to the status.\n */\n private decodeBody(response: HttpResponseDto): ApiError | undefined {\n if (!ApiErrorCodec.isPayload(response.body)) {\n return undefined;\n }\n const decoded = ApiErrorCodec.decode(response.body);\n if (!ApiErrorHttpStatus.hasCode(decoded)) {\n return undefined;\n }\n // `statusCode` is the 'coded' kind's own status, the one thing the shared table cannot know.\n const statusCode = decoded instanceof ApiCodedError ? decoded.statusCode : undefined;\n const declared = ApiErrorHttpStatus.codeFor(decoded.kind, statusCode);\n return declared === response.status.code ? decoded : undefined;\n }\n\n // webpieces-disable no-any-unknown -- HTTP response bodies are app-owned until this boundary safely inspects them\n private fallbackMessage(body: unknown, reason: string): string {\n if (typeof body === 'object' && body !== null) {\n const message = Object.getOwnPropertyDescriptor(body, 'message')?.value;\n if (typeof message === 'string' && message.length > 0) return message.slice(0, 4096);\n }\n return reason || 'Request Failed';\n }\n\n /** Parse either legal Retry-After form and bound untrusted guidance to one day. */\n private retryAfterSeconds(headers: readonly HttpHeader[]): number | undefined {\n const raw = headers.find(\n (header: HttpHeader) => header.name.toLowerCase() === 'retry-after',\n )?.value;\n if (raw === undefined) return undefined;\n const seconds = Number(raw.trim());\n if (Number.isFinite(seconds) && seconds >= 0) return Math.min(Math.ceil(seconds), 86_400);\n const at = Date.parse(raw);\n if (!Number.isFinite(at)) return undefined;\n return Math.min(Math.max(Math.ceil((at - Date.now()) / 1_000), 0), 86_400);\n }\n}\n\n/** Process-wide built-in instance — stateless, so one shared instance is enough. */\nexport const WEBPIECES_DEFAULT_ERROR_TRANSLATOR = new WebpiecesDefaultErrorTranslator();\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WpAuthApiKeyCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WpAuthApiKeyCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { apiKey, ApiKeyCredential, AuthMode } from './auth-mode';\nimport { WpAuth } from './decorators';\n\n/**\n * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the\n * {@link apiKey} descriptor and {@link WpAuth} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, \"unused\n * '@ts-expect-error' directive\") if the line it guards ever starts compiling.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:\n * tsconfig.lib.json EXCLUDES specs and vitest strips types with esbuild, so a `@ts-expect-error` in a\n * spec is inert and the suite passes whether or not the guarded line really errors.\n *\n * WHAT IT PINS.\n *\n * 1. The union member is `{ kind: 'apikey'; regime: string; credentials: [...] }` and nothing looser.\n * `regime` is the lookup key selecting WHICH key regime a route belongs to, so a mode with no\n * regime — or spelled `api-key`, which is what a reader guesses from the decorator — must not\n * type-check its way into a switch that would then silently miss it.\n * 2. `credentials` is NON-EMPTY. A regime that names no credential generates a published document\n * with no security block, which is precisely the silent failure the argument exists to remove, so\n * `[]` is a compile error rather than a runtime throw (shim shape #4).\n * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential\n * must carry the ContextKey whose `httpHeader` it rides under, and a bearer credential must NOT —\n * its location IS `Authorization`, so a key beside it would be a lie a generator has to guess about.\n * 4. The ONE-ARGUMENT `@apiKey('regime')` form is GONE. It could declare a key regime while\n * saying nothing about where the credential rides, which is the whole defect; per the\n * no-backwards-compatibility rule there is no overload and no optional second parameter left\n * behind, and this directive is what proves it.\n * 5. The header-name STRING spelling `{ in: 'header', name: 'x-api-key' }` is GONE (#1249). A header\n * is defined ONCE, as a ContextKey, and named by that key everywhere; a string here was a second\n * copy a rename leaves silently stale.\n *\n * The EXHAUSTIVENESS half needs no directive: `apiKeyIsCoveredExhaustively` below returns on every\n * branch with no `default`, so dropping the `apikey` case makes tsc fail with TS7030 (not all code\n * paths return a value). That is the same property `AuthFilter.verifiesCaller` and\n * `DestinationTrust.forAuthMode` rely on, asserted here where it cannot be edited away by accident.\n */\nexport class AuthApiKeyCompileAssertions {\n private static readonly API_KEY = ContextKey.untrusted<string>('apiKey', 'x-api-key', true);\n private static readonly ORG_ID = ContextKey.untrusted<string>('organizationId', 'x-organization-id');\n\n /** The one legitimate spelling must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): AuthMode {\n const mode: AuthMode = {\n kind: 'apikey',\n regime: 'onetablet-partner',\n credentials: [\n {\n in: 'header',\n key: AuthApiKeyCompileAssertions.API_KEY,\n description: 'The key issued to your integration.',\n },\n { in: 'header', key: AuthApiKeyCompileAssertions.ORG_ID },\n ],\n };\n return mode;\n }\n\n /** The bearer branch, likewise asserted by the ABSENCE of an error. */\n legitimateBearer(): ApiKeyCredential {\n const credential: ApiKeyCredential = {\n in: 'bearer',\n description: 'Send the key as a bearer token.',\n };\n return credential;\n }\n\n /** apiKey takes regime and credentials; WpAuth wraps the descriptor in a nonempty list. */\n legitimateDecorator(): MethodDecorator {\n return WpAuth([apiKey('onetablet-partner', [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }])]);\n }\n\n /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */\n rejected(): void {\n // @ts-expect-error `regime` is REQUIRED — it selects which key regime, so it cannot be omitted\n const noRegime: AuthMode = {\n kind: 'apikey',\n credentials: [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }],\n };\n void noRegime;\n // @ts-expect-error `credentials` is REQUIRED — a regime that declares no location is the defect\n const noCredentials: AuthMode = { kind: 'apikey', regime: 'onetablet-partner' };\n void noCredentials;\n // @ts-expect-error EMPTY is not a widening — it would emit a document with no security block\n const empty: AuthMode = { kind: 'apikey', regime: 'onetablet-partner', credentials: [] };\n void empty;\n const misspelled: AuthMode = {\n // @ts-expect-error the discriminant is 'apikey'; 'api-key' is not a member of the union\n kind: 'api-key',\n regime: 'onetablet-partner',\n credentials: [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }],\n };\n void misspelled;\n // @ts-expect-error bearer's location IS `Authorization`; a ContextKey beside it is a lie\n const bearerWithKey: ApiKeyCredential = { in: 'bearer', key: AuthApiKeyCompileAssertions.API_KEY };\n void bearerWithKey;\n // @ts-expect-error a header credential with no ContextKey cannot become a securityScheme\n const headerWithNoKey: ApiKeyCredential = { in: 'header' };\n void headerWithNoKey;\n // @ts-expect-error the header-name STRING spelling is deleted — name the ContextKey: `key: MyKeys.API_KEY`\n const headerNameString: ApiKeyCredential = { in: 'header', name: 'x-api-key' };\n void headerNameString;\n // @ts-expect-error `key` is a ContextKey, never the header name itself\n const keyAsString: ApiKeyCredential = { in: 'header', key: 'x-api-key' };\n void keyAsString;\n // @ts-expect-error 'query' is not reachable — the framework declares only these two locations\n const unknownLocation: ApiKeyCredential = { in: 'query', key: AuthApiKeyCompileAssertions.API_KEY };\n void unknownLocation;\n }\n\n /** The apiKey descriptor requires an explicit credential list; no one-argument form exists. */\n oneArgumentFormIsGone(): void {\n // @ts-expect-error apiKey requires regime and credentials\n const legacy = apiKey('onetablet-partner');\n void legacy;\n }\n\n /**\n * Exhaustiveness, stated as code: NO `default`, a return on every branch. Deleting the `apikey`\n * case turns this into TS7030 — the compile error that forces a DECISION about a new mode's trust\n * posture rather than letting it land on one by accident.\n */\n apiKeyIsCoveredExhaustively(mode: AuthMode): string {\n switch (mode.kind) {\n case 'public':\n return 'public';\n case 'jwt':\n return 'jwt';\n case 'oidc':\n return 'oidc';\n case 'shared-secret':\n return 'shared-secret';\n case 'webhook':\n return 'webhook';\n case 'apikey':\n return 'apikey';\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WpAuthApiKeyCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WpAuthApiKeyCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,8CAA2C;AAC3C,2CAAiE;AACjE,6CAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAa,2BAA2B;IAC5B,MAAM,CAAU,OAAO,GAAG,uBAAU,CAAC,SAAS,CAAS,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;IACpF,MAAM,CAAU,MAAM,GAAG,uBAAU,CAAC,SAAS,CAAS,gBAAgB,EAAE,mBAAmB,CAAC,CAAC;IAErG,4FAA4F;IAC5F,UAAU;QACN,MAAM,IAAI,GAAa;YACnB,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,mBAAmB;YAC3B,WAAW,EAAE;gBACT;oBACI,EAAE,EAAE,QAAQ;oBACZ,GAAG,EAAE,2BAA2B,CAAC,OAAO;oBACxC,WAAW,EAAE,qCAAqC;iBACrD;gBACD,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,2BAA2B,CAAC,MAAM,EAAE;aAC5D;SACJ,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,uEAAuE;IACvE,gBAAgB;QACZ,MAAM,UAAU,GAAqB;YACjC,EAAE,EAAE,QAAQ;YACZ,WAAW,EAAE,iCAAiC;SACjD,CAAC;QACF,OAAO,UAAU,CAAC;IACtB,CAAC;IAED,2FAA2F;IAC3F,mBAAmB;QACf,OAAO,IAAA,mBAAM,EAAC,CAAC,IAAA,kBAAM,EAAC,mBAAmB,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,2BAA2B,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/G,CAAC;IAED,4FAA4F;IAC5F,QAAQ;QACJ,+FAA+F;QAC/F,MAAM,QAAQ,GAAa;YACvB,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,2BAA2B,CAAC,OAAO,EAAE,CAAC;SAC5E,CAAC;QACF,KAAK,QAAQ,CAAC;QACd,gGAAgG;QAChG,MAAM,aAAa,GAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;QAChF,KAAK,aAAa,CAAC;QACnB,6FAA6F;QAC7F,MAAM,KAAK,GAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QACzF,KAAK,KAAK,CAAC;QACX,MAAM,UAAU,GAAa;YACzB,wFAAwF;YACxF,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,mBAAmB;YAC3B,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,2BAA2B,CAAC,OAAO,EAAE,CAAC;SAC5E,CAAC;QACF,KAAK,UAAU,CAAC;QAChB,yFAAyF;QACzF,MAAM,aAAa,GAAqB,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,2BAA2B,CAAC,OAAO,EAAE,CAAC;QACnG,KAAK,aAAa,CAAC;QACnB,yFAAyF;QACzF,MAAM,eAAe,GAAqB,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC;QAC3D,KAAK,eAAe,CAAC;QACrB,2GAA2G;QAC3G,MAAM,gBAAgB,GAAqB,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QAC/E,KAAK,gBAAgB,CAAC;QACtB,uEAAuE;QACvE,MAAM,WAAW,GAAqB,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;QACzE,KAAK,WAAW,CAAC;QACjB,8FAA8F;QAC9F,MAAM,eAAe,GAAqB,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,2BAA2B,CAAC,OAAO,EAAE,CAAC;QACpG,KAAK,eAAe,CAAC;IACzB,CAAC;IAED,+FAA+F;IAC/F,qBAAqB;QACjB,0DAA0D;QAC1D,MAAM,MAAM,GAAG,IAAA,kBAAM,EAAC,mBAAmB,CAAC,CAAC;QAC3C,KAAK,MAAM,CAAC;IAChB,CAAC;IAED;;;;OAIG;IACH,2BAA2B,CAAC,IAAc;QACtC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,QAAQ;gBACT,OAAO,QAAQ,CAAC;YACpB,KAAK,KAAK;gBACN,OAAO,KAAK,CAAC;YACjB,KAAK,MAAM;gBACP,OAAO,MAAM,CAAC;YAClB,KAAK,eAAe;gBAChB,OAAO,eAAe,CAAC;YAC3B,KAAK,YAAY;gBACb,OAAO,YAAY,CAAC;YACxB,KAAK,QAAQ;gBACT,OAAO,QAAQ,CAAC;QACxB,CAAC;IACL,CAAC;;AApGL,kEAqGC","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { apiKey, ApiKeyCredential, AuthMode } from './auth-mode';\nimport { WpAuth } from './decorators';\n\n/**\n * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the\n * {@link apiKey} descriptor and {@link WpAuth} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, \"unused\n * '@ts-expect-error' directive\") if the line it guards ever starts compiling.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:\n * tsconfig.lib.json EXCLUDES specs and vitest strips types with esbuild, so a `@ts-expect-error` in a\n * spec is inert and the suite passes whether or not the guarded line really errors.\n *\n * WHAT IT PINS.\n *\n * 1. The union member is `{ kind: 'apikey'; regime: string; credentials: [...] }` and nothing looser.\n * `regime` is the lookup key selecting WHICH key regime a route belongs to, so a mode with no\n * regime — or spelled `api-key`, which is what a reader guesses from the decorator — must not\n * type-check its way into a switch that would then silently miss it.\n * 2. `credentials` is NON-EMPTY. A regime that names no credential generates a published document\n * with no security block, which is precisely the silent failure the argument exists to remove, so\n * `[]` is a compile error rather than a runtime throw (shim shape #4).\n * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential\n * must carry the ContextKey whose `httpHeader` it rides under, and a bearer credential must NOT —\n * its location IS `Authorization`, so a key beside it would be a lie a generator has to guess about.\n * 4. The ONE-ARGUMENT `@apiKey('regime')` form is GONE. It could declare a key regime while\n * saying nothing about where the credential rides, which is the whole defect; per the\n * no-backwards-compatibility rule there is no overload and no optional second parameter left\n * behind, and this directive is what proves it.\n * 5. The header-name STRING spelling `{ in: 'header', name: 'x-api-key' }` is GONE (#1249). A header\n * is defined ONCE, as a ContextKey, and named by that key everywhere; a string here was a second\n * copy a rename leaves silently stale.\n *\n * The EXHAUSTIVENESS half needs no directive: `apiKeyIsCoveredExhaustively` below returns on every\n * branch with no `default`, so dropping the `apikey` case makes tsc fail with TS7030 (not all code\n * paths return a value). That is the same property `AuthFilter.verifiesCaller` and\n * `DestinationTrust.forAuthMode` rely on, asserted here where it cannot be edited away by accident.\n */\nexport class AuthApiKeyCompileAssertions {\n private static readonly API_KEY = ContextKey.untrusted<string>('apiKey', 'x-api-key', true);\n private static readonly ORG_ID = ContextKey.untrusted<string>('organizationId', 'x-organization-id');\n\n /** The one legitimate spelling must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): AuthMode {\n const mode: AuthMode = {\n kind: 'apikey',\n regime: 'onetablet-partner',\n credentials: [\n {\n in: 'header',\n key: AuthApiKeyCompileAssertions.API_KEY,\n description: 'The key issued to your integration.',\n },\n { in: 'header', key: AuthApiKeyCompileAssertions.ORG_ID },\n ],\n };\n return mode;\n }\n\n /** The bearer branch, likewise asserted by the ABSENCE of an error. */\n legitimateBearer(): ApiKeyCredential {\n const credential: ApiKeyCredential = {\n in: 'bearer',\n description: 'Send the key as a bearer token.',\n };\n return credential;\n }\n\n /** apiKey takes regime and credentials; WpAuth wraps the descriptor in a nonempty list. */\n legitimateDecorator(): MethodDecorator {\n return WpAuth([apiKey('onetablet-partner', [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }])]);\n }\n\n /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */\n rejected(): void {\n // @ts-expect-error `regime` is REQUIRED — it selects which key regime, so it cannot be omitted\n const noRegime: AuthMode = {\n kind: 'apikey',\n credentials: [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }],\n };\n void noRegime;\n // @ts-expect-error `credentials` is REQUIRED — a regime that declares no location is the defect\n const noCredentials: AuthMode = { kind: 'apikey', regime: 'onetablet-partner' };\n void noCredentials;\n // @ts-expect-error EMPTY is not a widening — it would emit a document with no security block\n const empty: AuthMode = { kind: 'apikey', regime: 'onetablet-partner', credentials: [] };\n void empty;\n const misspelled: AuthMode = {\n // @ts-expect-error the discriminant is 'apikey'; 'api-key' is not a member of the union\n kind: 'api-key',\n regime: 'onetablet-partner',\n credentials: [{ in: 'header', key: AuthApiKeyCompileAssertions.API_KEY }],\n };\n void misspelled;\n // @ts-expect-error bearer's location IS `Authorization`; a ContextKey beside it is a lie\n const bearerWithKey: ApiKeyCredential = { in: 'bearer', key: AuthApiKeyCompileAssertions.API_KEY };\n void bearerWithKey;\n // @ts-expect-error a header credential with no ContextKey cannot become a securityScheme\n const headerWithNoKey: ApiKeyCredential = { in: 'header' };\n void headerWithNoKey;\n // @ts-expect-error the header-name STRING spelling is deleted — name the ContextKey: `key: MyKeys.API_KEY`\n const headerNameString: ApiKeyCredential = { in: 'header', name: 'x-api-key' };\n void headerNameString;\n // @ts-expect-error `key` is a ContextKey, never the header name itself\n const keyAsString: ApiKeyCredential = { in: 'header', key: 'x-api-key' };\n void keyAsString;\n // @ts-expect-error 'query' is not reachable — the framework declares only these two locations\n const unknownLocation: ApiKeyCredential = { in: 'query', key: AuthApiKeyCompileAssertions.API_KEY };\n void unknownLocation;\n }\n\n /** The apiKey descriptor requires an explicit credential list; no one-argument form exists. */\n oneArgumentFormIsGone(): void {\n // @ts-expect-error apiKey requires regime and credentials\n const legacy = apiKey('onetablet-partner');\n void legacy;\n }\n\n /**\n * Exhaustiveness, stated as code: NO `default`, a return on every branch. Deleting the `apikey`\n * case turns this into TS7030 — the compile error that forces a DECISION about a new mode's trust\n * posture rather than letting it land on one by accident.\n */\n apiKeyIsCoveredExhaustively(mode: AuthMode): string {\n switch (mode.kind) {\n case 'public':\n return 'public';\n case 'jwt':\n return 'jwt';\n case 'oidc':\n return 'oidc';\n case 'shared-secret':\n return 'shared-secret';\n case 'vendorAuth':\n return 'vendorAuth';\n case 'apikey':\n return 'apikey';\n }\n }\n}\n"]}
|
package/src/http/api-kind.d.ts
CHANGED
|
@@ -44,8 +44,9 @@ export declare function assertApiKind(apiClass: Function, expected: ApiKind): vo
|
|
|
44
44
|
/**
|
|
45
45
|
* Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered
|
|
46
46
|
* asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so
|
|
47
|
-
* `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.
|
|
48
|
-
* is legal on both — a webhook posts synchronously, a push subscription does not.
|
|
47
|
+
* `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.
|
|
48
|
+
* `external-inbound` is legal on both — a webhook posts synchronously, a push subscription does not.
|
|
49
|
+
* `external-outbound` is an outside system's synchronous API WE call, so it is @Rpc only.
|
|
49
50
|
*
|
|
50
51
|
* Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.
|
|
51
52
|
*/
|
package/src/http/api-kind.js
CHANGED
|
@@ -71,14 +71,15 @@ function assertApiKind(apiClass, expected) {
|
|
|
71
71
|
/**
|
|
72
72
|
* Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered
|
|
73
73
|
* asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so
|
|
74
|
-
* `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.
|
|
75
|
-
* is legal on both — a webhook posts synchronously, a push subscription does not.
|
|
74
|
+
* `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.
|
|
75
|
+
* `external-inbound` is legal on both — a webhook posts synchronously, a push subscription does not.
|
|
76
|
+
* `external-outbound` is an outside system's synchronous API WE call, so it is @Rpc only.
|
|
76
77
|
*
|
|
77
78
|
* Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.
|
|
78
79
|
*/
|
|
79
80
|
exports.ENDPOINT_KINDS_BY_API_KIND = {
|
|
80
|
-
rpc: [decorators_1.RPC, decorators_1.
|
|
81
|
-
pubsub: [decorators_1.CLOUDTASKS, decorators_1.CRON, decorators_1.
|
|
81
|
+
rpc: [decorators_1.RPC, decorators_1.EXTERNAL_INBOUND, decorators_1.EXTERNAL_OUTBOUND],
|
|
82
|
+
pubsub: [decorators_1.CLOUDTASKS, decorators_1.CRON, decorators_1.EXTERNAL_INBOUND],
|
|
82
83
|
};
|
|
83
84
|
/**
|
|
84
85
|
* Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at
|
package/src/http/api-kind.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-kind.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/api-kind.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport {\n EndpointKind,\n RPC,\n CLOUDTASKS,\n CRON,\n EXTERNAL,\n METADATA_KEYS,\n getEndpoints,\n getEndpointKinds,\n isApiPath,\n} from './decorators';\n\n/**\n * API KIND — whether a contract is synchronous RPC or fire-and-forget over a queue — plus the\n * queue-naming rules that only a @PubSub contract has.\n *\n * Split out of `decorators.ts` purely for size (max-file-lines); the dependency runs ONE way,\n * api-kind -> decorators, so there is no cycle. Auth modes and endpoint shape stay in\n * `decorators.ts`; everything here is re-exported from the package barrel, so no consumer import\n * changes and there is no second spelling of anything.\n */\n\n// ============================================================\n// API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n// ============================================================\n\n/**\n * API kind. 'rpc' = synchronous request/response (http-client ↔ ApiRoutingFactory).\n * 'pubsub' = fire-and-forget cloud task; the enqueue client (cloudtasks-client)\n * schedules a Cloud Task that is later delivered to the SAME controller endpoint.\n */\nexport type ApiKind = 'rpc' | 'pubsub';\n\n/**\n * @Rpc() - marks an API class as synchronous request/response (the default kind).\n * Present mostly for symmetry/readability; an undecorated API is treated as 'rpc'.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function Rpc(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'rpc' as ApiKind, target);\n };\n}\n\n/**\n * @PubSub() - marks an API class as fire-and-forget over Cloud Tasks. Every method\n * MUST return Promise<void> (a compile-time contract on the abstract API). The\n * enqueue client and the controller share this one class, exactly like RPC.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function PubSub(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'pubsub' as ApiKind, target);\n };\n}\n\n/**\n * @Queue(name) - override the Cloud Tasks queue name for a @PubSub method. Default\n * (no decorator) is `${ApiClassName}-${methodName}`, matched 1:1 by Terraform.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function Queue(name: string): MethodDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey: string | symbol, _descriptor: PropertyDescriptor) => {\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, metadataTarget) || {};\n overrides[propertyKey as string] = name;\n Reflect.defineMetadata(METADATA_KEYS.QUEUE_OVERRIDE, overrides, metadataTarget);\n };\n}\n\n/**\n * Get the API kind. Defaults to 'rpc' when neither @Rpc nor @PubSub is present.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function getApiKind(apiClass: Function): ApiKind {\n return (Reflect.getMetadata(METADATA_KEYS.API_KIND, apiClass) as ApiKind) ?? 'rpc';\n}\n\n/**\n * Assert the API class is of the expected kind (used by the clients: the RPC\n * client rejects a @PubSub api and vice-versa).\n * @throws Error if the kind doesn't match.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function assertApiKind(apiClass: Function, expected: ApiKind): void {\n const actual = getApiKind(apiClass);\n if (actual !== expected) {\n const apiName = apiClass.name || 'Unknown';\n throw new Error(\n `API ${apiName} is @${actual === 'pubsub' ? 'PubSub' : 'Rpc'} but a ` +\n `${expected === 'pubsub' ? '@PubSub (cloud task)' : '@Rpc'} API was required here.`,\n );\n }\n}\n\n/**\n * Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered\n * asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so\n * `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to. `external`\n * is legal on both — a webhook posts synchronously, a push subscription does not.\n *\n * Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.\n */\nexport const ENDPOINT_KINDS_BY_API_KIND: Record<ApiKind, readonly EndpointKind[]> = {\n rpc: [RPC, EXTERNAL],\n pubsub: [CLOUDTASKS, CRON, EXTERNAL],\n};\n\n/**\n * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at\n * least one endpoint, and every endpoint must declare a kind this api kind can actually deliver.\n * (Return-type is Promise<void>, a compile-time contract — TS erases types at runtime so it cannot\n * be re-checked here.)\n * @throws Error if conventions are violated.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function assertPubSubConventions(apiClass: Function): void {\n assertApiKind(apiClass, 'pubsub');\n const apiName = apiClass.name || 'Unknown';\n if (!isApiPath(apiClass)) {\n throw new Error(`@PubSub API ${apiName} must also be decorated with @ApiPath()`);\n }\n const endpoints = getEndpoints(apiClass) || {};\n if (Object.keys(endpoints).length === 0) {\n throw new Error(`@PubSub API ${apiName} declares no @Endpoint methods`);\n }\n const allowed = ENDPOINT_KINDS_BY_API_KIND.pubsub;\n const kinds = getEndpointKinds(apiClass);\n for (const methodName of Object.keys(endpoints)) {\n const kind = kinds[methodName];\n if (kind !== undefined && allowed.includes(kind)) continue;\n throw new Error(\n `@PubSub API ${apiName}.${methodName} declares @Endpoint(..., '${kind ?? 'missing'}') — a ` +\n `@PubSub contract is delivered through a queue, so it must be one of: ${allowed.join(' | ')}.`,\n );\n }\n}\n\n/**\n * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if\n * present, else `${ApiClassName}-${methodName}`.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function getQueueName(apiClass: Function, methodName: string): string {\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, apiClass) || {};\n return overrides[methodName] ?? `${apiClass.name || 'Unknown'}-${methodName}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api-kind.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/api-kind.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport {\n EndpointKind,\n RPC,\n CLOUDTASKS,\n CRON,\n EXTERNAL_INBOUND,\n EXTERNAL_OUTBOUND,\n METADATA_KEYS,\n getEndpoints,\n getEndpointKinds,\n isApiPath,\n} from './decorators';\n\n/**\n * API KIND — whether a contract is synchronous RPC or fire-and-forget over a queue — plus the\n * queue-naming rules that only a @PubSub contract has.\n *\n * Split out of `decorators.ts` purely for size (max-file-lines); the dependency runs ONE way,\n * api-kind -> decorators, so there is no cycle. Auth modes and endpoint shape stay in\n * `decorators.ts`; everything here is re-exported from the package barrel, so no consumer import\n * changes and there is no second spelling of anything.\n */\n\n// ============================================================\n// API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n// ============================================================\n\n/**\n * API kind. 'rpc' = synchronous request/response (http-client ↔ ApiRoutingFactory).\n * 'pubsub' = fire-and-forget cloud task; the enqueue client (cloudtasks-client)\n * schedules a Cloud Task that is later delivered to the SAME controller endpoint.\n */\nexport type ApiKind = 'rpc' | 'pubsub';\n\n/**\n * @Rpc() - marks an API class as synchronous request/response (the default kind).\n * Present mostly for symmetry/readability; an undecorated API is treated as 'rpc'.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function Rpc(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'rpc' as ApiKind, target);\n };\n}\n\n/**\n * @PubSub() - marks an API class as fire-and-forget over Cloud Tasks. Every method\n * MUST return Promise<void> (a compile-time contract on the abstract API). The\n * enqueue client and the controller share this one class, exactly like RPC.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function PubSub(): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_KIND, 'pubsub' as ApiKind, target);\n };\n}\n\n/**\n * @Queue(name) - override the Cloud Tasks queue name for a @PubSub method. Default\n * (no decorator) is `${ApiClassName}-${methodName}`, matched 1:1 by Terraform.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function Queue(name: string): MethodDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey: string | symbol, _descriptor: PropertyDescriptor) => {\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, metadataTarget) || {};\n overrides[propertyKey as string] = name;\n Reflect.defineMetadata(METADATA_KEYS.QUEUE_OVERRIDE, overrides, metadataTarget);\n };\n}\n\n/**\n * Get the API kind. Defaults to 'rpc' when neither @Rpc nor @PubSub is present.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function getApiKind(apiClass: Function): ApiKind {\n return (Reflect.getMetadata(METADATA_KEYS.API_KIND, apiClass) as ApiKind) ?? 'rpc';\n}\n\n/**\n * Assert the API class is of the expected kind (used by the clients: the RPC\n * client rejects a @PubSub api and vice-versa).\n * @throws Error if the kind doesn't match.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function assertApiKind(apiClass: Function, expected: ApiKind): void {\n const actual = getApiKind(apiClass);\n if (actual !== expected) {\n const apiName = apiClass.name || 'Unknown';\n throw new Error(\n `API ${apiName} is @${actual === 'pubsub' ? 'PubSub' : 'Rpc'} but a ` +\n `${expected === 'pubsub' ? '@PubSub (cloud task)' : '@Rpc'} API was required here.`,\n );\n }\n}\n\n/**\n * Which {@link EndpointKind}s each {@link ApiKind} may declare. A @PubSub contract is delivered\n * asynchronously by definition, so `rpc` is meaningless on it; an @Rpc contract has no queue, so\n * `cloudtasks`/`cron` on it would name a queue/schedule nothing could ever deliver to.\n * `external-inbound` is legal on both — a webhook posts synchronously, a push subscription does not.\n * `external-outbound` is an outside system's synchronous API WE call, so it is @Rpc only.\n *\n * Shared so the wiring-time assert below and the build-time architecture scan enforce ONE rule.\n */\nexport const ENDPOINT_KINDS_BY_API_KIND: Record<ApiKind, readonly EndpointKind[]> = {\n rpc: [RPC, EXTERNAL_INBOUND, EXTERNAL_OUTBOUND],\n pubsub: [CLOUDTASKS, CRON, EXTERNAL_INBOUND],\n};\n\n/**\n * Validate @PubSub conventions at wiring time: the class must be @ApiPath + @PubSub, declare at\n * least one endpoint, and every endpoint must declare a kind this api kind can actually deliver.\n * (Return-type is Promise<void>, a compile-time contract — TS erases types at runtime so it cannot\n * be re-checked here.)\n * @throws Error if conventions are violated.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function assertPubSubConventions(apiClass: Function): void {\n assertApiKind(apiClass, 'pubsub');\n const apiName = apiClass.name || 'Unknown';\n if (!isApiPath(apiClass)) {\n throw new Error(`@PubSub API ${apiName} must also be decorated with @ApiPath()`);\n }\n const endpoints = getEndpoints(apiClass) || {};\n if (Object.keys(endpoints).length === 0) {\n throw new Error(`@PubSub API ${apiName} declares no @Endpoint methods`);\n }\n const allowed = ENDPOINT_KINDS_BY_API_KIND.pubsub;\n const kinds = getEndpointKinds(apiClass);\n for (const methodName of Object.keys(endpoints)) {\n const kind = kinds[methodName];\n if (kind !== undefined && allowed.includes(kind)) continue;\n throw new Error(\n `@PubSub API ${apiName}.${methodName} declares @Endpoint(..., '${kind ?? 'missing'}') — a ` +\n `@PubSub contract is delivered through a queue, so it must be one of: ${allowed.join(' | ')}.`,\n );\n }\n}\n\n/**\n * Resolve the Cloud Tasks queue name for a @PubSub method: the @Queue override if\n * present, else `${ApiClassName}-${methodName}`.\n */\n// webpieces-disable no-function-outside-class -- decorator factory / reflect-metadata reader; moved verbatim from decorators.ts for file size, same module-scope shape as its siblings there\nexport function getQueueName(apiClass: Function, methodName: string): string {\n const overrides: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.QUEUE_OVERRIDE, apiClass) || {};\n return overrides[methodName] ?? `${apiClass.name || 'Unknown'}-${methodName}`;\n}\n"]}
|
package/src/http/auth-mode.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export type AuthMethod = {
|
|
|
55
55
|
kind: 'shared-secret';
|
|
56
56
|
secretKey: string;
|
|
57
57
|
} | {
|
|
58
|
-
kind: '
|
|
58
|
+
kind: 'vendorAuth';
|
|
59
59
|
name: string;
|
|
60
60
|
} | {
|
|
61
61
|
kind: 'apikey';
|
|
@@ -103,5 +103,11 @@ export declare function jwt(): AuthMethod;
|
|
|
103
103
|
export declare function oidcSameProject(): OidcAuthMethod;
|
|
104
104
|
export declare function oidcSvcs(callers: OidcCallers): OidcAuthMethod;
|
|
105
105
|
export declare function sharedSecret(secretKey: string): AuthMethod;
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* A credential in a VENDOR's scheme, on both ends of a hop: the app-bound `VendorAuthHook` (core-context)
|
|
108
|
+
* mints it when WE call the vendor (and refreshes it once on a 401), and verifies it when the vendor
|
|
109
|
+
* calls US. `name` selects the vendor. A served `vendorAuth(...)` endpoint requires
|
|
110
|
+
* `@Endpoint(..., { rawBody: true })` so the hook has the exact bytes to verify.
|
|
111
|
+
*/
|
|
112
|
+
export declare function vendorAuth(name: string): AuthMethod;
|
|
107
113
|
export declare function apiKey(regime: string, credentials: ApiKeyCredentials): AuthMethod;
|
package/src/http/auth-mode.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.jwt = jwt;
|
|
|
5
5
|
exports.oidcSameProject = oidcSameProject;
|
|
6
6
|
exports.oidcSvcs = oidcSvcs;
|
|
7
7
|
exports.sharedSecret = sharedSecret;
|
|
8
|
-
exports.
|
|
8
|
+
exports.vendorAuth = vendorAuth;
|
|
9
9
|
exports.apiKey = apiKey;
|
|
10
10
|
/** Alternatives remain unresolved until a concrete mechanism is chosen for this hop. */
|
|
11
11
|
class AuthMeta {
|
|
@@ -74,9 +74,9 @@ class AuthDeclaration {
|
|
|
74
74
|
keys = ['kind', 'secretKey'];
|
|
75
75
|
this.requireText(method.secretKey, 'sharedSecret');
|
|
76
76
|
break;
|
|
77
|
-
case '
|
|
77
|
+
case 'vendorAuth':
|
|
78
78
|
keys = ['kind', 'name'];
|
|
79
|
-
this.requireText(method.name, '
|
|
79
|
+
this.requireText(method.name, 'vendorAuth');
|
|
80
80
|
break;
|
|
81
81
|
case 'apikey':
|
|
82
82
|
keys = ['kind', 'regime', 'credentials'];
|
|
@@ -90,7 +90,7 @@ class AuthDeclaration {
|
|
|
90
90
|
throw new Error('Invalid API-key credential location.');
|
|
91
91
|
}
|
|
92
92
|
break;
|
|
93
|
-
default: throw new Error('Unknown @WpAuth method; use jwt/oidcSameProject/oidcSvcs/sharedSecret/
|
|
93
|
+
default: throw new Error('Unknown @WpAuth method; use jwt/oidcSameProject/oidcSvcs/sharedSecret/vendorAuth/apiKey descriptors.');
|
|
94
94
|
}
|
|
95
95
|
for (const key of Object.keys(method)) {
|
|
96
96
|
if (!keys.includes(key))
|
|
@@ -145,8 +145,14 @@ function oidcSameProject() { return { kind: 'oidc', callerScope: 'same-project'
|
|
|
145
145
|
function oidcSvcs(callers) { return { kind: 'oidc', callerScope: 'services', callers }; }
|
|
146
146
|
// webpieces-disable no-function-outside-class -- canonical credential descriptor factory
|
|
147
147
|
function sharedSecret(secretKey) { return { kind: 'shared-secret', secretKey }; }
|
|
148
|
+
/**
|
|
149
|
+
* A credential in a VENDOR's scheme, on both ends of a hop: the app-bound `VendorAuthHook` (core-context)
|
|
150
|
+
* mints it when WE call the vendor (and refreshes it once on a 401), and verifies it when the vendor
|
|
151
|
+
* calls US. `name` selects the vendor. A served `vendorAuth(...)` endpoint requires
|
|
152
|
+
* `@Endpoint(..., { rawBody: true })` so the hook has the exact bytes to verify.
|
|
153
|
+
*/
|
|
148
154
|
// webpieces-disable no-function-outside-class -- canonical credential descriptor factory
|
|
149
|
-
function
|
|
155
|
+
function vendorAuth(name) { return { kind: 'vendorAuth', name }; }
|
|
150
156
|
// webpieces-disable no-function-outside-class -- canonical credential descriptor factory
|
|
151
157
|
function apiKey(regime, credentials) { return { kind: 'apikey', regime, credentials }; }
|
|
152
158
|
//# sourceMappingURL=auth-mode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-mode.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/auth-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyContextKey } from '../ContextKey';\n\n/**\n * WHERE one api-key credential rides.\n *\n * A header credential names the {@link AnyContextKey ContextKey} that carries it — NEVER a header-name\n * string. The key's `httpHeader` is the wire name, so the ONE definition of each header (the\n * ContextKey) is reused on every surface: `RequestContext` put/get, the client/server header\n * transfer, this declaration, and the published OpenAPI document. A string here was a second copy a\n * rename left stale (#1249). The key MUST declare an `httpHeader` — {@link AuthDeclaration} refuses\n * one that does not, and the OpenAPI generator reads the header name off the key's\n * `ContextKey.trusted(...)` / `ContextKey.untrusted(...)` definition.\n */\nexport type ApiKeyCredential =\n | { in: 'header'; key: AnyContextKey; description?: string }\n | { in: 'bearer'; key?: never; description?: string };\n\nexport type ApiKeyCredentials = readonly [ApiKeyCredential, ...ApiKeyCredential[]];\n\n/**\n * The service accounts an `oidcSvcs([...])` endpoint names, at least one: an empty list is not a\n * policy anybody could mean, so it does not compile. Each entry resolves as `'self'` (this service's\n * runtime SA), a bare id (`<id>@<project>.iam.gserviceaccount.com`) or a full email (verbatim).\n */\nexport type OidcCallers = readonly [string, ...string[]];\n\n/**\n * WHICH Google-signed callers an OIDC endpoint accepts, named by `callerScope`:\n * - `'same-project'` (`oidcSameProject()`): any service account of THIS GCP project. Off GCP there is\n * no project, so the check is open.\n * - `'services'` (`oidcSvcs([...])`): exactly the listed service accounts, which may belong to other\n * projects.\n * One endpoint may declare one of each; see {@link OidcAuthMethods}.\n */\nexport type OidcAuthMethod =\n | { kind: 'oidc'; callerScope: 'same-project' }\n | { kind: 'oidc'; callerScope: 'services'; callers: OidcCallers };\n\n/**\n * The OIDC methods ONE endpoint declares, in declaration order: one, or one per caller scope. They are\n * ONE bearer credential, so a verifier receives them together, checks the token's signature once, and\n * admits the call when ANY of them admits the caller.\n */\nexport type OidcAuthMethods = readonly [OidcAuthMethod, ...OidcAuthMethod[]];\n\n/** One concrete mechanism; user policy belongs exclusively to WpAuthorization. */\nexport type AuthMethod =\n | { kind: 'jwt' }\n | OidcAuthMethod\n | { kind: 'shared-secret'; secretKey: string }\n | { kind: 'webhook'; name: string }\n | { kind: 'apikey'; regime: string; credentials: ApiKeyCredentials };\n\nexport type AuthMethods = readonly [AuthMethod, ...AuthMethod[]];\nexport type AuthMode = AuthMethod | { kind: 'public' };\n\n/** Alternatives remain unresolved until a concrete mechanism is chosen for this hop. */\nexport class AuthMeta {\n readonly methods: readonly [AuthMode, ...AuthMode[]];\n\n constructor(\n methods: readonly [AuthMode, ...AuthMode[]],\n public readonly publicReason: string | undefined,\n ) {\n this.methods = methods;\n AuthDeclaration.validate(this);\n this.methods = Object.freeze(methods.map((method: AuthMode) => AuthMeta.snapshot(method))) as readonly [AuthMode, ...AuthMode[]];\n Object.freeze(this);\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static snapshot(method: AuthMode): AuthMode {\n switch (method.kind) {\n case 'oidc': return method.callerScope === 'services'\n ? Object.freeze({ ...method, callers: Object.freeze([...method.callers]) as OidcCallers })\n : Object.freeze({ ...method });\n case 'apikey': return Object.freeze({ ...method, credentials: Object.freeze(method.credentials.map((credential: ApiKeyCredential) => Object.freeze({ ...credential }))) as ApiKeyCredentials });\n default: return Object.freeze({ ...method });\n }\n }\n}\n\nexport class AuthDeclaration {\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n static validate(meta: AuthMeta): void {\n if (!Array.isArray(meta.methods) || meta.methods.length === 0) throw new Error('@WpAuth requires a nonempty methods tuple.');\n const kinds = new Set<string>();\n for (const method of meta.methods) {\n if (!method || typeof method !== 'object' || kinds.has(this.uniquenessKey(method))) throw new Error('@WpAuth rejects duplicate or invalid credential methods.');\n kinds.add(this.uniquenessKey(method));\n this.validateMethod(method);\n }\n if (kinds.has('public')) {\n if (meta.methods.length !== 1 || typeof meta.publicReason !== 'string' || !meta.publicReason.trim()) {\n throw new Error('Public auth must be declared separately with @WpAuthPublic(reason).');\n }\n } else if (meta.publicReason !== undefined) throw new Error('Protected @WpAuth rejects public reasons.');\n }\n\n /**\n * One method per credential kind, except OIDC: one per `callerScope`, so `oidcSameProject()` and\n * `oidcSvcs([...])` may pair on one endpoint (this project's accounts OR the listed ones). They are\n * still ONE bearer credential, which AuthFilter verifies once for both.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static uniquenessKey(method: AuthMode): string {\n return method.kind === 'oidc' ? `oidc:${method.callerScope}` : method.kind;\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static validateMethod(method: AuthMode): void {\n let keys: readonly string[];\n switch (method.kind) {\n case 'jwt':\n case 'public': keys = ['kind']; break;\n case 'oidc': keys = this.oidcKeys(method); break;\n case 'shared-secret':\n keys = ['kind', 'secretKey'];\n this.requireText(method.secretKey, 'sharedSecret'); break;\n case 'webhook':\n keys = ['kind', 'name'];\n this.requireText(method.name, 'webhook'); break;\n case 'apikey':\n keys = ['kind', 'regime', 'credentials'];\n this.requireText(method.regime, 'apiKey');\n if (!Array.isArray(method.credentials) || !method.credentials.length) throw new Error('apiKey(...) requires a nonempty credentials tuple.');\n for (const credential of method.credentials) {\n if (credential.in === 'header') this.requireHeaderKey(credential.key);\n else if (credential.in !== 'bearer' || Object.hasOwn(credential, 'key')) throw new Error('Invalid API-key credential location.');\n }\n break;\n default: throw new Error('Unknown @WpAuth method; use jwt/oidcSameProject/oidcSvcs/sharedSecret/webhook/apiKey descriptors.');\n }\n for (const key of Object.keys(method)) {\n if (!keys.includes(key)) throw new Error(`Auth method rejects '${key}'; operation policy belongs in @WpAuthorization.`);\n }\n }\n\n /**\n * The keys an OIDC descriptor may carry, after checking its scope. `OidcCallers` already makes an\n * empty `oidcSvcs([])` a compile error; this is the backstop for a list that reached the decorator\n * around the type (a cast, a spread of a runtime array, plain JS), so it fails when the decorator\n * is evaluated instead of reaching a verifier.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static oidcKeys(method: OidcAuthMethod): readonly string[] {\n if (method.callerScope === 'same-project') return ['kind', 'callerScope'];\n if (method.callerScope !== 'services') throw new Error(\"Unknown OIDC callerScope; use oidcSameProject() or oidcSvcs(['svc-a']).\");\n if (!Array.isArray(method.callers) || method.callers.length === 0 || !method.callers.every((caller: string) => typeof caller === 'string' && caller.trim().length > 0)) {\n throw new Error(\"oidcSvcs([...]) requires at least one nonblank service account; use oidcSameProject() to accept this GCP project's service accounts.\");\n }\n return ['kind', 'callerScope', 'callers'];\n }\n\n /**\n * A header credential's key must be a ContextKey that rides the wire. `httpHeader` is optional on\n * ContextKey itself (most keys never leave the process), and the factories' explicit value-type\n * argument (`ContextKey.untrusted<string>(...)`) stops TypeScript inferring whether one was\n * passed, so the type cannot say \"a key WITH a header\". It is checked once, when the contract is\n * decorated, and the OpenAPI generator refuses the same key statically.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static requireHeaderKey(key: AnyContextKey | undefined): void {\n if (!key || typeof key !== 'object' || typeof key.name !== 'string') {\n throw new Error(\"apiKey header credential requires `key`: the ContextKey carrying it, e.g. { in: 'header', key: MyKeys.API_KEY }.\");\n }\n if (typeof key.httpHeader !== 'string' || !key.httpHeader.trim()) {\n throw new Error(`apiKey header credential key '${key.name}' declares no httpHeader; define it with ContextKey.trusted(name, provenance, '<header>') or ContextKey.untrusted(name, '<header>').`);\n }\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static requireText(value: string, label: string): void {\n if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} requires a nonempty name.`);\n }\n}\n\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function jwt(): AuthMethod { return { kind: 'jwt' }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function oidcSameProject(): OidcAuthMethod { return { kind: 'oidc', callerScope: 'same-project' }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function oidcSvcs(callers: OidcCallers): OidcAuthMethod { return { kind: 'oidc', callerScope: 'services', callers }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function sharedSecret(secretKey: string): AuthMethod { return { kind: 'shared-secret', secretKey }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function webhook(name: string): AuthMethod { return { kind: 'webhook', name }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function apiKey(regime: string, credentials: ApiKeyCredentials): AuthMethod { return { kind: 'apikey', regime, credentials }; }\n"]}
|
|
1
|
+
{"version":3,"file":"auth-mode.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/auth-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyContextKey } from '../ContextKey';\n\n/**\n * WHERE one api-key credential rides.\n *\n * A header credential names the {@link AnyContextKey ContextKey} that carries it — NEVER a header-name\n * string. The key's `httpHeader` is the wire name, so the ONE definition of each header (the\n * ContextKey) is reused on every surface: `RequestContext` put/get, the client/server header\n * transfer, this declaration, and the published OpenAPI document. A string here was a second copy a\n * rename left stale (#1249). The key MUST declare an `httpHeader` — {@link AuthDeclaration} refuses\n * one that does not, and the OpenAPI generator reads the header name off the key's\n * `ContextKey.trusted(...)` / `ContextKey.untrusted(...)` definition.\n */\nexport type ApiKeyCredential =\n | { in: 'header'; key: AnyContextKey; description?: string }\n | { in: 'bearer'; key?: never; description?: string };\n\nexport type ApiKeyCredentials = readonly [ApiKeyCredential, ...ApiKeyCredential[]];\n\n/**\n * The service accounts an `oidcSvcs([...])` endpoint names, at least one: an empty list is not a\n * policy anybody could mean, so it does not compile. Each entry resolves as `'self'` (this service's\n * runtime SA), a bare id (`<id>@<project>.iam.gserviceaccount.com`) or a full email (verbatim).\n */\nexport type OidcCallers = readonly [string, ...string[]];\n\n/**\n * WHICH Google-signed callers an OIDC endpoint accepts, named by `callerScope`:\n * - `'same-project'` (`oidcSameProject()`): any service account of THIS GCP project. Off GCP there is\n * no project, so the check is open.\n * - `'services'` (`oidcSvcs([...])`): exactly the listed service accounts, which may belong to other\n * projects.\n * One endpoint may declare one of each; see {@link OidcAuthMethods}.\n */\nexport type OidcAuthMethod =\n | { kind: 'oidc'; callerScope: 'same-project' }\n | { kind: 'oidc'; callerScope: 'services'; callers: OidcCallers };\n\n/**\n * The OIDC methods ONE endpoint declares, in declaration order: one, or one per caller scope. They are\n * ONE bearer credential, so a verifier receives them together, checks the token's signature once, and\n * admits the call when ANY of them admits the caller.\n */\nexport type OidcAuthMethods = readonly [OidcAuthMethod, ...OidcAuthMethod[]];\n\n/** One concrete mechanism; user policy belongs exclusively to WpAuthorization. */\nexport type AuthMethod =\n | { kind: 'jwt' }\n | OidcAuthMethod\n | { kind: 'shared-secret'; secretKey: string }\n | { kind: 'vendorAuth'; name: string }\n | { kind: 'apikey'; regime: string; credentials: ApiKeyCredentials };\n\nexport type AuthMethods = readonly [AuthMethod, ...AuthMethod[]];\nexport type AuthMode = AuthMethod | { kind: 'public' };\n\n/** Alternatives remain unresolved until a concrete mechanism is chosen for this hop. */\nexport class AuthMeta {\n readonly methods: readonly [AuthMode, ...AuthMode[]];\n\n constructor(\n methods: readonly [AuthMode, ...AuthMode[]],\n public readonly publicReason: string | undefined,\n ) {\n this.methods = methods;\n AuthDeclaration.validate(this);\n this.methods = Object.freeze(methods.map((method: AuthMode) => AuthMeta.snapshot(method))) as readonly [AuthMode, ...AuthMode[]];\n Object.freeze(this);\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static snapshot(method: AuthMode): AuthMode {\n switch (method.kind) {\n case 'oidc': return method.callerScope === 'services'\n ? Object.freeze({ ...method, callers: Object.freeze([...method.callers]) as OidcCallers })\n : Object.freeze({ ...method });\n case 'apikey': return Object.freeze({ ...method, credentials: Object.freeze(method.credentials.map((credential: ApiKeyCredential) => Object.freeze({ ...credential }))) as ApiKeyCredentials });\n default: return Object.freeze({ ...method });\n }\n }\n}\n\nexport class AuthDeclaration {\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n static validate(meta: AuthMeta): void {\n if (!Array.isArray(meta.methods) || meta.methods.length === 0) throw new Error('@WpAuth requires a nonempty methods tuple.');\n const kinds = new Set<string>();\n for (const method of meta.methods) {\n if (!method || typeof method !== 'object' || kinds.has(this.uniquenessKey(method))) throw new Error('@WpAuth rejects duplicate or invalid credential methods.');\n kinds.add(this.uniquenessKey(method));\n this.validateMethod(method);\n }\n if (kinds.has('public')) {\n if (meta.methods.length !== 1 || typeof meta.publicReason !== 'string' || !meta.publicReason.trim()) {\n throw new Error('Public auth must be declared separately with @WpAuthPublic(reason).');\n }\n } else if (meta.publicReason !== undefined) throw new Error('Protected @WpAuth rejects public reasons.');\n }\n\n /**\n * One method per credential kind, except OIDC: one per `callerScope`, so `oidcSameProject()` and\n * `oidcSvcs([...])` may pair on one endpoint (this project's accounts OR the listed ones). They are\n * still ONE bearer credential, which AuthFilter verifies once for both.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static uniquenessKey(method: AuthMode): string {\n return method.kind === 'oidc' ? `oidc:${method.callerScope}` : method.kind;\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static validateMethod(method: AuthMode): void {\n let keys: readonly string[];\n switch (method.kind) {\n case 'jwt':\n case 'public': keys = ['kind']; break;\n case 'oidc': keys = this.oidcKeys(method); break;\n case 'shared-secret':\n keys = ['kind', 'secretKey'];\n this.requireText(method.secretKey, 'sharedSecret'); break;\n case 'vendorAuth':\n keys = ['kind', 'name'];\n this.requireText(method.name, 'vendorAuth'); break;\n case 'apikey':\n keys = ['kind', 'regime', 'credentials'];\n this.requireText(method.regime, 'apiKey');\n if (!Array.isArray(method.credentials) || !method.credentials.length) throw new Error('apiKey(...) requires a nonempty credentials tuple.');\n for (const credential of method.credentials) {\n if (credential.in === 'header') this.requireHeaderKey(credential.key);\n else if (credential.in !== 'bearer' || Object.hasOwn(credential, 'key')) throw new Error('Invalid API-key credential location.');\n }\n break;\n default: throw new Error('Unknown @WpAuth method; use jwt/oidcSameProject/oidcSvcs/sharedSecret/vendorAuth/apiKey descriptors.');\n }\n for (const key of Object.keys(method)) {\n if (!keys.includes(key)) throw new Error(`Auth method rejects '${key}'; operation policy belongs in @WpAuthorization.`);\n }\n }\n\n /**\n * The keys an OIDC descriptor may carry, after checking its scope. `OidcCallers` already makes an\n * empty `oidcSvcs([])` a compile error; this is the backstop for a list that reached the decorator\n * around the type (a cast, a spread of a runtime array, plain JS), so it fails when the decorator\n * is evaluated instead of reaching a verifier.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static oidcKeys(method: OidcAuthMethod): readonly string[] {\n if (method.callerScope === 'same-project') return ['kind', 'callerScope'];\n if (method.callerScope !== 'services') throw new Error(\"Unknown OIDC callerScope; use oidcSameProject() or oidcSvcs(['svc-a']).\");\n if (!Array.isArray(method.callers) || method.callers.length === 0 || !method.callers.every((caller: string) => typeof caller === 'string' && caller.trim().length > 0)) {\n throw new Error(\"oidcSvcs([...]) requires at least one nonblank service account; use oidcSameProject() to accept this GCP project's service accounts.\");\n }\n return ['kind', 'callerScope', 'callers'];\n }\n\n /**\n * A header credential's key must be a ContextKey that rides the wire. `httpHeader` is optional on\n * ContextKey itself (most keys never leave the process), and the factories' explicit value-type\n * argument (`ContextKey.untrusted<string>(...)`) stops TypeScript inferring whether one was\n * passed, so the type cannot say \"a key WITH a header\". It is checked once, when the contract is\n * decorated, and the OpenAPI generator refuses the same key statically.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static requireHeaderKey(key: AnyContextKey | undefined): void {\n if (!key || typeof key !== 'object' || typeof key.name !== 'string') {\n throw new Error(\"apiKey header credential requires `key`: the ContextKey carrying it, e.g. { in: 'header', key: MyKeys.API_KEY }.\");\n }\n if (typeof key.httpHeader !== 'string' || !key.httpHeader.trim()) {\n throw new Error(`apiKey header credential key '${key.name}' declares no httpHeader; define it with ContextKey.trusted(name, provenance, '<header>') or ContextKey.untrusted(name, '<header>').`);\n }\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static requireText(value: string, label: string): void {\n if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} requires a nonempty name.`);\n }\n}\n\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function jwt(): AuthMethod { return { kind: 'jwt' }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function oidcSameProject(): OidcAuthMethod { return { kind: 'oidc', callerScope: 'same-project' }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function oidcSvcs(callers: OidcCallers): OidcAuthMethod { return { kind: 'oidc', callerScope: 'services', callers }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function sharedSecret(secretKey: string): AuthMethod { return { kind: 'shared-secret', secretKey }; }\n/**\n * A credential in a VENDOR's scheme, on both ends of a hop: the app-bound `VendorAuthHook` (core-context)\n * mints it when WE call the vendor (and refreshes it once on a 401), and verifies it when the vendor\n * calls US. `name` selects the vendor. A served `vendorAuth(...)` endpoint requires\n * `@Endpoint(..., { rawBody: true })` so the hook has the exact bytes to verify.\n */\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function vendorAuth(name: string): AuthMethod { return { kind: 'vendorAuth', name }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function apiKey(regime: string, credentials: ApiKeyCredentials): AuthMethod { return { kind: 'apikey', regime, credentials }; }\n"]}
|
package/src/http/decorators.d.ts
CHANGED
|
@@ -5,10 +5,11 @@ import { EndpointOperation, EndpointOptions, ExternalEndpointOptions } from './H
|
|
|
5
5
|
import { HttpMethod } from './HttpContract';
|
|
6
6
|
export type { EndpointOptions, ExternalEndpointOptions } from './HttpEndpointOptions';
|
|
7
7
|
export type { EndpointOperation } from './HttpEndpointOptions';
|
|
8
|
-
export { READ, WRITE_IDEMPOTENT, WRITE } from './HttpEndpointOptions';
|
|
8
|
+
export { READ, WRITE_IDEMPOTENT, WRITE, NONE } from './HttpEndpointOptions';
|
|
9
|
+
export type { ResponseBodyKind } from './HttpEndpointOptions';
|
|
9
10
|
export type { HttpMethod } from './HttpContract';
|
|
10
|
-
export { GET, POST } from './HttpContract';
|
|
11
|
-
export { PathParam, QueryParam, getHttpParameterDeclarations } from './http-parameter-decorators';
|
|
11
|
+
export { GET, POST, PUT, PATCH, DELETE } from './HttpContract';
|
|
12
|
+
export { PathParam, QueryParam, QueryString, getHttpParameterDeclarations } from './http-parameter-decorators';
|
|
12
13
|
/**
|
|
13
14
|
* Metadata keys for storing API routing information.
|
|
14
15
|
* These keys are used by both server-side (routing) and client-side (client generation).
|
|
@@ -29,9 +30,9 @@ export declare const METADATA_KEYS: {
|
|
|
29
30
|
ENDPOINT_HTTP_METHOD: string;
|
|
30
31
|
/** Per-method required read/write semantics, parallel to ENDPOINTS. */
|
|
31
32
|
ENDPOINT_OPERATION: string;
|
|
32
|
-
/** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external), parallel to ENDPOINTS. */
|
|
33
|
+
/** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external-inbound | external-outbound), parallel to ENDPOINTS. */
|
|
33
34
|
ENDPOINT_KIND: string;
|
|
34
|
-
/** Per-method declared external CALLER (only for kind 'external'), parallel to ENDPOINTS. */
|
|
35
|
+
/** Per-method declared external CALLER (only for kind 'external-inbound'), parallel to ENDPOINTS. */
|
|
35
36
|
ENDPOINT_CALLER: string;
|
|
36
37
|
/** Per-method @MaskLog spec (which DTO fields the LogApiCall path masks). */
|
|
37
38
|
MASK_LOG: string;
|
|
@@ -51,24 +52,35 @@ declare enum EndpointKindValue {
|
|
|
51
52
|
RPC = "rpc",
|
|
52
53
|
CLOUDTASKS = "cloudtasks",
|
|
53
54
|
CRON = "cron",
|
|
54
|
-
|
|
55
|
+
EXTERNAL_INBOUND = "external-inbound",
|
|
56
|
+
EXTERNAL_OUTBOUND = "external-outbound"
|
|
55
57
|
}
|
|
56
58
|
/** Short, statically importable decorator arguments. */
|
|
57
59
|
export declare const RPC = EndpointKindValue.RPC;
|
|
58
60
|
export declare const CLOUDTASKS = EndpointKindValue.CLOUDTASKS;
|
|
59
61
|
export declare const CRON = EndpointKindValue.CRON;
|
|
60
|
-
|
|
62
|
+
/**
|
|
63
|
+
* An OUTSIDE system calls US (a vendor webhook, a push subscription). Requires `calledBy`, which names
|
|
64
|
+
* that system on the runtime architecture graph.
|
|
65
|
+
*/
|
|
66
|
+
export declare const EXTERNAL_INBOUND = EndpointKindValue.EXTERNAL_INBOUND;
|
|
67
|
+
/**
|
|
68
|
+
* WE call an OUTSIDE system (a vendor API, a partner's webhook receiver). No server in this repo
|
|
69
|
+
* implements it, every method of the contract must be this kind, and the client sends ONLY the
|
|
70
|
+
* context keys listed with `@ExternalContextKeys(...)` and merges no response header into our context.
|
|
71
|
+
*/
|
|
72
|
+
export declare const EXTERNAL_OUTBOUND = EndpointKindValue.EXTERNAL_OUTBOUND;
|
|
61
73
|
/** Runtime trigger/delivery owner used by validation, architecture, and infrastructure tooling. */
|
|
62
|
-
export type EndpointKind = typeof RPC | typeof CLOUDTASKS | typeof CRON | typeof
|
|
74
|
+
export type EndpointKind = typeof RPC | typeof CLOUDTASKS | typeof CRON | typeof EXTERNAL_INBOUND | typeof EXTERNAL_OUTBOUND;
|
|
63
75
|
/** Class decorator that sets the shared base path for all endpoints. */
|
|
64
76
|
export declare function ApiPath(basePath: string): ClassDecorator;
|
|
65
77
|
/**
|
|
66
78
|
* Registers `@Endpoint(POST, path, WRITE, RPC)`. Method, operation, and trigger are required enum
|
|
67
|
-
* values. Operation is independent of the HTTP verb.
|
|
79
|
+
* values. Operation is independent of the HTTP verb. EXTERNAL_INBOUND additionally requires `calledBy`.
|
|
68
80
|
* Each classification rides a parallel metadata map while ENDPOINTS remains method-name -> path.
|
|
69
81
|
*/
|
|
70
|
-
export declare function Endpoint(httpMethod: HttpMethod, path: string, operation: EndpointOperation, kind: typeof
|
|
71
|
-
export declare function Endpoint(httpMethod: HttpMethod, path: string, operation: EndpointOperation, kind: Exclude<EndpointKind, typeof
|
|
82
|
+
export declare function Endpoint(httpMethod: HttpMethod, path: string, operation: EndpointOperation, kind: typeof EXTERNAL_INBOUND, options: ExternalEndpointOptions): MethodDecorator;
|
|
83
|
+
export declare function Endpoint(httpMethod: HttpMethod, path: string, operation: EndpointOperation, kind: Exclude<EndpointKind, typeof EXTERNAL_INBOUND>, options?: EndpointOptions): MethodDecorator;
|
|
72
84
|
/** Declares request/response DTO field names that API logging must mask at any depth. */
|
|
73
85
|
export declare function MaskLog(fields: Record<string, MaskMode>): MethodDecorator;
|
|
74
86
|
/**
|
|
@@ -118,12 +130,18 @@ export declare function getEndpointOptions(apiClass: Function, methodName: strin
|
|
|
118
130
|
/** Read one endpoint's required side-effect semantics. */
|
|
119
131
|
export declare function getEndpointOperation(apiClass: Function, methodName: string): EndpointOperation;
|
|
120
132
|
/**
|
|
121
|
-
* Fail-fast at wiring time
|
|
122
|
-
*
|
|
123
|
-
* a
|
|
124
|
-
*
|
|
133
|
+
* Fail-fast at wiring time on the two EXTERNAL_* rules, called by `RouteMetadataFactory` for every
|
|
134
|
+
* transport (server routes and every client). The {@link Endpoint} overloads already make a missing
|
|
135
|
+
* `calledBy` a COMPILE error; this is the backstop for the ways TS is bypassed — a JS caller, an
|
|
136
|
+
* `as any` options object, a hand-rolled Reflect.defineMetadata. The second rule cannot be a type:
|
|
137
|
+
* "every method of this class" spans several decorators.
|
|
138
|
+
*
|
|
139
|
+
* @throws Error naming the first EXTERNAL_INBOUND endpoint with no `calledBy`, or the first method of
|
|
140
|
+
* a contract that mixes EXTERNAL_OUTBOUND with any other kind.
|
|
125
141
|
*/
|
|
126
|
-
export declare function
|
|
142
|
+
export declare function assertExternalEndpointKinds(apiClass: Function): void;
|
|
143
|
+
/** True when the contract is an outside system's API: every method is EXTERNAL_OUTBOUND. */
|
|
144
|
+
export declare function isExternalOutboundApi(apiClass: Function): boolean;
|
|
127
145
|
/**
|
|
128
146
|
* True when the method's @Endpoint declared `{ formPost: true }` — its body is
|
|
129
147
|
* application/x-www-form-urlencoded (flat), not JSON.
|
|
@@ -131,22 +149,22 @@ export declare function assertEveryExternalEndpointDeclaresCaller(apiClass: Func
|
|
|
131
149
|
export declare function isFormPost(apiClass: Function, methodName: string): boolean;
|
|
132
150
|
/**
|
|
133
151
|
* True when the method's @Endpoint declared `{ rawBody: true }` — the transport must retain the
|
|
134
|
-
* verbatim bytes + absolute url for
|
|
152
|
+
* verbatim bytes + absolute url for a `VendorAuthHook.verify` to check.
|
|
135
153
|
*/
|
|
136
154
|
export declare function isRawBody(apiClass: Function, methodName: string): boolean;
|
|
137
155
|
/**
|
|
138
|
-
* Fail-fast at wiring time when
|
|
156
|
+
* Fail-fast at wiring time when a SERVED `vendorAuth(...)` endpoint did not ask the transport to keep the
|
|
139
157
|
* bytes it is supposed to verify. A hook with nothing to verify is a MISCONFIGURATION, and it must
|
|
140
158
|
* surface at startup, naming the fix — not as a 401 in production on exactly the traffic the endpoint
|
|
141
159
|
* exists for.
|
|
142
160
|
*
|
|
143
161
|
* This pairing is a runtime assert rather than a type because the two halves live on DIFFERENT
|
|
144
|
-
* decorators (`
|
|
162
|
+
* decorators (`vendorAuth(...)` and `@Endpoint`), and no union over one decorator's argument can say
|
|
145
163
|
* anything about the other's.
|
|
146
164
|
*
|
|
147
|
-
* @throws Error naming the first `
|
|
165
|
+
* @throws Error naming the first `vendorAuth(...)` endpoint missing `{ rawBody: true }`.
|
|
148
166
|
*/
|
|
149
|
-
export declare function
|
|
167
|
+
export declare function assertEveryVendorAuthEndpointRetainsRawBody(apiClass: Function): void;
|
|
150
168
|
/**
|
|
151
169
|
* Check if a class has @ApiPath decorator.
|
|
152
170
|
*/
|