@webpieces/core-util 0.4.894 → 0.4.896

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/package.json +1 -1
  2. package/src/errors/ApiError.d.ts +27 -1
  3. package/src/errors/ApiError.js +37 -1
  4. package/src/errors/ApiError.js.map +1 -1
  5. package/src/errors/ApiErrorCodec.js +5 -0
  6. package/src/errors/ApiErrorCodec.js.map +1 -1
  7. package/src/errors/ReceivedApiErrorRule.d.ts +6 -2
  8. package/src/errors/ReceivedApiErrorRule.js +6 -2
  9. package/src/errors/ReceivedApiErrorRule.js.map +1 -1
  10. package/src/errors/index.d.ts +1 -1
  11. package/src/errors/index.js +2 -1
  12. package/src/errors/index.js.map +1 -1
  13. package/src/http/ApiCallInfo.d.ts +3 -3
  14. package/src/http/ApiCallInfo.js +1 -1
  15. package/src/http/ApiCallInfo.js.map +1 -1
  16. package/src/http/ApiErrorHttpStatus.js +3 -0
  17. package/src/http/ApiErrorHttpStatus.js.map +1 -1
  18. package/src/http/CallRegistry.d.ts +31 -8
  19. package/src/http/CallRegistry.js +93 -7
  20. package/src/http/CallRegistry.js.map +1 -1
  21. package/src/http/CallRegistryCompileAssertions.js +9 -0
  22. package/src/http/CallRegistryCompileAssertions.js.map +1 -1
  23. package/src/http/ClientRegistry.d.ts +25 -23
  24. package/src/http/ClientRegistry.js +33 -31
  25. package/src/http/ClientRegistry.js.map +1 -1
  26. package/src/http/ErrorClassifier.d.ts +64 -0
  27. package/src/http/ErrorClassifier.js +39 -0
  28. package/src/http/ErrorClassifier.js.map +1 -0
  29. package/src/http/LogApiCall.d.ts +2 -18
  30. package/src/http/LogApiCall.js +7 -24
  31. package/src/http/LogApiCall.js.map +1 -1
  32. package/src/http/RetryStrategy.d.ts +32 -0
  33. package/src/http/RetryStrategy.js +96 -0
  34. package/src/http/RetryStrategy.js.map +1 -0
  35. package/src/http/TransportDefault.d.ts +25 -0
  36. package/src/http/TransportDefault.js +31 -0
  37. package/src/http/TransportDefault.js.map +1 -0
  38. package/src/http/WebpiecesDefaultErrorClassifier.d.ts +42 -0
  39. package/src/http/WebpiecesDefaultErrorClassifier.js +87 -0
  40. package/src/http/WebpiecesDefaultErrorClassifier.js.map +1 -0
  41. package/src/index.d.ts +7 -4
  42. package/src/index.js +13 -7
  43. package/src/index.js.map +1 -1
  44. package/src/http/FailureClassifier.d.ts +0 -48
  45. package/src/http/FailureClassifier.js +0 -19
  46. package/src/http/FailureClassifier.js.map +0 -1
  47. package/src/http/WebpiecesDefaultFailureClassifier.d.ts +0 -40
  48. package/src/http/WebpiecesDefaultFailureClassifier.js +0 -67
  49. package/src/http/WebpiecesDefaultFailureClassifier.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/core-util",
3
- "version": "0.4.894",
3
+ "version": "0.4.896",
4
4
  "description": "Utility functions for WebPieces - works in browser and Node.js",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -1,5 +1,5 @@
1
1
  import { ApiCallSite } from './ApiCallSite';
2
- export type ApiErrorKind = 'end-user' | 'bad-request' | 'unauthorized' | 'forbidden' | 'not-found' | 'endpoint-not-found' | 'request-timeout' | 'rate-limited' | 'conflict' | 'unprocessable' | 'precondition-failed' | 'unsupported-media-type' | 'not-implemented' | 'coded' | 'implementation' | 'dependency' | 'bad-gateway' | 'unavailable' | 'dependency-timeout' | 'dependency-backoff' | 'connection';
2
+ export type ApiErrorKind = 'end-user' | 'bad-request' | 'unauthorized' | 'forbidden' | 'not-found' | 'endpoint-not-found' | 'request-timeout' | 'rate-limited' | 'conflict' | 'unprocessable' | 'precondition-failed' | 'unsupported-media-type' | 'not-implemented' | 'coded' | 'implementation' | 'dependency' | 'bad-gateway' | 'unavailable' | 'dependency-timeout' | 'dependency-backoff' | 'retries-exhausted' | 'connection';
3
3
  /** Category for portable API failures. Concrete subclasses alone define protocol mappings. */
4
4
  export declare abstract class ApiError extends Error {
5
5
  abstract readonly kind: ApiErrorKind;
@@ -30,6 +30,32 @@ export declare class ApiUnavailableError extends ApiDependencyError {
30
30
  export declare class ApiDependencyTimeoutError extends ApiDependencyError {
31
31
  readonly kind: "dependency-timeout";
32
32
  }
33
+ /**
34
+ * A `RetryStrategy` retried a TRANSIENT failure and ran out of attempts. Thrown ONLY by the strategy,
35
+ * and only when it actually retried — a single attempt rethrows its own error unchanged. `cause` is
36
+ * the LAST attempt's error.
37
+ *
38
+ * Classified `CallOutcome.FAILURE`, never transient: that is the anti-amplification property. Retries
39
+ * happen at ONE layer; without it, 4 attempts x 4 attempts x 4 attempts across three hops is 64 calls
40
+ * into a struggling dependency. For the same reason it publishes HTTP 502 (Bad Gateway: "the thing
41
+ * behind me failed"), not 503 — generic clients and load balancers retry a 503.
42
+ *
43
+ * A webpieces peer decodes `kind: 'retries-exhausted'` back into this class (see
44
+ * {@link ApiRetriesExhaustedError.received}), so the upstream classifies it FAILURE and does not
45
+ * retry; a foreign caller just sees a 502.
46
+ */
47
+ export declare class ApiRetriesExhaustedError extends ApiDependencyError {
48
+ readonly attempts: number;
49
+ readonly lastError: Error;
50
+ readonly kind: "retries-exhausted";
51
+ constructor(attempts: number, lastError: Error);
52
+ /**
53
+ * The error as DECODED from a peer's wire payload. The peer published neither its attempt count
54
+ * nor its last error (an operator message never crosses the wire), so `attempts` is 0 — "unknown,
55
+ * a peer gave up" — and `lastError` carries the published message.
56
+ */
57
+ static received(message: string): ApiRetriesExhaustedError;
58
+ }
33
59
  /** A bug or invalid implementation state. Local errors become server errors only after a remote decode. */
34
60
  export declare class ApiImplementationError extends ApiError {
35
61
  readonly serverError: boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ApiClientTooOldError = exports.ApiCodedError = exports.ApiNotImplementedError = exports.ApiUnsupportedMediaTypeError = exports.ApiPreconditionFailedError = exports.ApiUnprocessableError = exports.ApiConflictError = exports.ApiDependencyBackoffError = exports.ApiUnauthorizedError = exports.ApiBadRequestError = exports.ApiEndUserError = exports.ApiEndpointNotFoundError = exports.ApiConnectionError = exports.ApiRequestTimeoutError = exports.ApiForbiddenError = exports.ApiRateLimitedError = exports.ApiImplementationError = exports.ApiDependencyTimeoutError = exports.ApiUnavailableError = exports.ApiBadGatewayError = exports.ApiDependencyError = exports.ApiNotFoundError = exports.ApiError = void 0;
3
+ exports.ApiClientTooOldError = exports.ApiCodedError = exports.ApiNotImplementedError = exports.ApiUnsupportedMediaTypeError = exports.ApiPreconditionFailedError = exports.ApiUnprocessableError = exports.ApiConflictError = exports.ApiDependencyBackoffError = exports.ApiUnauthorizedError = exports.ApiBadRequestError = exports.ApiEndUserError = exports.ApiEndpointNotFoundError = exports.ApiConnectionError = exports.ApiRequestTimeoutError = exports.ApiForbiddenError = exports.ApiRateLimitedError = exports.ApiImplementationError = exports.ApiRetriesExhaustedError = exports.ApiDependencyTimeoutError = exports.ApiUnavailableError = exports.ApiBadGatewayError = exports.ApiDependencyError = exports.ApiNotFoundError = exports.ApiError = void 0;
4
4
  /** Category for portable API failures. Concrete subclasses alone define protocol mappings. */
5
5
  class ApiError extends Error {
6
6
  subType;
@@ -31,6 +31,42 @@ class ApiDependencyTimeoutError extends ApiDependencyError {
31
31
  kind = 'dependency-timeout';
32
32
  }
33
33
  exports.ApiDependencyTimeoutError = ApiDependencyTimeoutError;
34
+ /**
35
+ * A `RetryStrategy` retried a TRANSIENT failure and ran out of attempts. Thrown ONLY by the strategy,
36
+ * and only when it actually retried — a single attempt rethrows its own error unchanged. `cause` is
37
+ * the LAST attempt's error.
38
+ *
39
+ * Classified `CallOutcome.FAILURE`, never transient: that is the anti-amplification property. Retries
40
+ * happen at ONE layer; without it, 4 attempts x 4 attempts x 4 attempts across three hops is 64 calls
41
+ * into a struggling dependency. For the same reason it publishes HTTP 502 (Bad Gateway: "the thing
42
+ * behind me failed"), not 503 — generic clients and load balancers retry a 503.
43
+ *
44
+ * A webpieces peer decodes `kind: 'retries-exhausted'` back into this class (see
45
+ * {@link ApiRetriesExhaustedError.received}), so the upstream classifies it FAILURE and does not
46
+ * retry; a foreign caller just sees a 502.
47
+ */
48
+ class ApiRetriesExhaustedError extends ApiDependencyError {
49
+ attempts;
50
+ lastError;
51
+ kind = 'retries-exhausted';
52
+ constructor(attempts, lastError) {
53
+ super(`gave up after ${attempts} attempts: ${lastError.message}`, lastError);
54
+ this.attempts = attempts;
55
+ this.lastError = lastError;
56
+ }
57
+ /**
58
+ * The error as DECODED from a peer's wire payload. The peer published neither its attempt count
59
+ * nor its last error (an operator message never crosses the wire), so `attempts` is 0 — "unknown,
60
+ * a peer gave up" — and `lastError` carries the published message.
61
+ */
62
+ // webpieces-disable no-function-outside-class -- wire-decode factory owned by this error
63
+ static received(message) {
64
+ const error = new ApiRetriesExhaustedError(0, new ApiDependencyError(message));
65
+ error.message = message;
66
+ return error;
67
+ }
68
+ }
69
+ exports.ApiRetriesExhaustedError = ApiRetriesExhaustedError;
34
70
  /** A bug or invalid implementation state. Local errors become server errors only after a remote decode. */
35
71
  class ApiImplementationError extends ApiError {
36
72
  serverError;
@@ -1 +1 @@
1
- {"version":3,"file":"ApiError.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/errors/ApiError.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCallSite } from './ApiCallSite';\n\nexport type ApiErrorKind =\n | 'end-user'\n | 'bad-request'\n | 'unauthorized'\n | 'forbidden'\n | 'not-found'\n | 'endpoint-not-found'\n | 'request-timeout'\n | 'rate-limited'\n | 'conflict'\n | 'unprocessable'\n | 'precondition-failed'\n | 'unsupported-media-type'\n | 'not-implemented'\n | 'coded'\n | 'implementation'\n | 'dependency'\n | 'bad-gateway'\n | 'unavailable'\n | 'dependency-timeout'\n | 'dependency-backoff'\n | 'connection';\n\n/** Category for portable API failures. Concrete subclasses alone define protocol mappings. */\nexport abstract class ApiError extends Error {\n abstract readonly kind: ApiErrorKind;\n public subType?: string;\n\n /**\n * Where the app called the generated client method that rejected with this error — present only\n * on an error a generated api client (HTTP, IPC or Cloud Tasks) rejected with. Set by\n * {@link ApiCallSite.attachTo} as a NON-ENUMERABLE own property, so it never reaches JSON or a\n * structured log; `declare` emits no field, so an error that never failed a client call carries\n * no `callSite` key at all. See {@link ApiCallSite} for why it is not `cause`.\n */\n declare readonly callSite?: ApiCallSite;\n\n constructor(message: string, cause?: Error) {\n super(message, { cause });\n this.name = new.target.name;\n }\n}\n\nexport class ApiNotFoundError extends ApiError {\n override readonly kind: ApiErrorKind = 'not-found';\n}\nexport class ApiDependencyError extends ApiError {\n override readonly kind: ApiErrorKind = 'dependency';\n}\n/** A retry-eligible HTTP 502/proxy failure, distinct from a generic dependency defect. */\nexport class ApiBadGatewayError extends ApiDependencyError {\n override readonly kind = 'bad-gateway' as const;\n}\nexport class ApiUnavailableError extends ApiDependencyError {\n override readonly kind = 'unavailable' as const;\n}\nexport class ApiDependencyTimeoutError extends ApiDependencyError {\n override readonly kind = 'dependency-timeout' as const;\n}\n\n/** A bug or invalid implementation state. Local errors become server errors only after a remote decode. */\nexport class ApiImplementationError extends ApiError {\n override readonly kind = 'implementation' as const;\n\n constructor(\n message: string,\n cause?: Error,\n public readonly serverError = false,\n ) {\n super(message, cause);\n }\n}\n\nexport class ApiRateLimitedError extends ApiError {\n override readonly kind = 'rate-limited' as const;\n}\nexport class ApiForbiddenError extends ApiError {\n override readonly kind = 'forbidden' as const;\n}\nexport class ApiRequestTimeoutError extends ApiError {\n override readonly kind = 'request-timeout' as const;\n}\n\n/**\n * A caller-local connection failure. HTTP servers normalize this to ApiImplementationError.\n *\n * `message` is the developer text (it names the url and the transport's own reason); `userMessage`\n * is the stable text an app renders verbatim to an end user, matching `ApiCallTimeoutError.userMessage`\n * so the two transport failures read consistently.\n */\nexport class ApiConnectionError extends ApiError {\n static readonly USER_MESSAGE =\n \"We couldn't reach the server. Please check your network connection and try again.\";\n\n override readonly kind = 'connection' as const;\n /** End-user text; `message` stays the developer text. */\n readonly userMessage: string = ApiConnectionError.USER_MESSAGE;\n}\n\n/** The API operation is missing, distinct from an absent domain entity. */\nexport class ApiEndpointNotFoundError extends ApiNotFoundError {\n override readonly kind = 'endpoint-not-found' as const;\n}\n\n/**\n * The HTTP status an API EDGE answers for an {@link ApiEndUserError}. End-user outcomes only, so\n * nobody tunnels a 5xx/401/403/429 through the end-user channel (auth, rate limiting and dependency\n * failure keep their own types). Extend deliberately.\n */\nexport type EdgeHttpStatus = 400 | 404 | 409 | 422;\n\n/**\n * Expected mistake by the ACTOR driving the call: the input was well-formed but wrong in a way the actor\n * must fix (for example \"the two passwords you entered do not match\"). The actor may be a human user in\n * a GUI OR an LLM calling an MCP tool; the same class serves both. Its message is deliberately\n * caller-safe and is the only ApiError message published verbatim: `ApiErrorCodec` keeps it (and\n * `errorCode`) across every remote hop, so a downstream service's text reaches the GUI or the model\n * byte-for-byte.\n *\n * `edgeHttpStatus` travels the same way. It is what a partner-facing API edge (an http-server router\n * in `'edge'` end-user-status mode) answers instead of 266, so one throw site serves both a GUI and a\n * published REST contract: `throw new ApiEndUserError('That report does not exist', 'report_not_found', 404)`.\n * GUI edges ignore it and keep answering 266.\n */\nexport class ApiEndUserError extends ApiError {\n override readonly kind = 'end-user' as const;\n\n constructor(\n message: string,\n public errorCode?: string,\n public edgeHttpStatus?: EdgeHttpStatus,\n cause?: Error,\n ) {\n super(message, cause);\n this.subType = 'USER_ERROR';\n }\n\n /** Narrows a dynamic value (for example one read off the wire) to a legal {@link EdgeHttpStatus}. */\n // webpieces-disable no-function-outside-class -- stateless status guard; webpieces-disable no-any-unknown -- wire values are narrowed here\n static isEdgeHttpStatus(value: unknown): value is EdgeHttpStatus {\n return value === 400 || value === 404 || value === 409 || value === 422;\n }\n}\n\n/**\n * Invalid API caller input (malformed, or failed validation). `message` is operator-only diagnostic\n * text and may name internals; it is never published. `callerMessage` (plus `field`) is what a GUI OR\n * an MCP model is shown so it can correct its input, so throw sites that want the caller to\n * self-correct MUST set it.\n */\nexport class ApiBadRequestError extends ApiError {\n override readonly kind = 'bad-request' as const;\n\n constructor(\n message: string,\n public field?: string,\n public callerMessage?: string,\n cause?: Error,\n ) {\n super(message, cause);\n }\n}\n\nexport class ApiUnauthorizedError extends ApiError {\n override readonly kind = 'unauthorized' as const;\n\n constructor(message: string, subType?: string, cause?: Error) {\n super(message, cause);\n this.subType = subType;\n }\n}\n\nexport class ApiDependencyBackoffError extends ApiDependencyError {\n override readonly kind = 'dependency-backoff' as const;\n\n constructor(\n message: string,\n public retryAfterSeconds = 30,\n cause?: Error,\n ) {\n super(message, cause);\n }\n}\n\n/** The request conflicts with the current state of the target resource (HTTP 409). */\nexport class ApiConflictError extends ApiError {\n override readonly kind = 'conflict' as const;\n}\n\n/** The request was well-formed but semantically unprocessable (HTTP 422). */\nexport class ApiUnprocessableError extends ApiError {\n override readonly kind = 'unprocessable' as const;\n}\n\n/** A caller-supplied precondition (for example If-Match) did not hold (HTTP 412). */\nexport class ApiPreconditionFailedError extends ApiError {\n override readonly kind = 'precondition-failed' as const;\n}\n\n/** The request body is in a media type this operation does not accept (HTTP 415). */\nexport class ApiUnsupportedMediaTypeError extends ApiError {\n override readonly kind = 'unsupported-media-type' as const;\n}\n\n/** The operation exists in the contract but this server does not implement it (HTTP 501). */\nexport class ApiNotImplementedError extends ApiError {\n override readonly kind = 'not-implemented' as const;\n}\n\n/** Builds the tuple [0, 1, ..., N-1]; tail-recursive so TypeScript can reach 600. */\ntype StatusRange<N extends number, Acc extends number[] = []> = Acc['length'] extends N\n ? Acc\n : StatusRange<N, [...Acc, Acc['length']]>;\n\n/**\n * Every integer from 100 through 599 as a literal union. `new ApiCodedError(msg, 600)`,\n * `new ApiCodedError(msg, 99)` and `new ApiCodedError(msg, 404.5)` do not compile; a dynamic\n * `number` must be narrowed with {@link ApiCodedError.isStatusCode} first.\n */\nexport type ApiStatusCode = Exclude<StatusRange<600>[number], StatusRange<100>[number]>;\n\n/**\n * Catch-all carrying an explicit protocol status for outcomes no named ApiError covers. Any code in\n * 100-599 is accepted, including one a named class already owns. `statusCode` and `errorCode` survive\n * every remote hop through `ApiErrorCodec`; `message` stays operator-only like every other non\n * end-user kind. A status below 500 is a caller outcome (except 408 and 429, which mirror\n * `ApiRequestTimeoutError` and `ApiRateLimitedError`); 500 and above is a server/dependency fault.\n */\nexport class ApiCodedError extends ApiError {\n override readonly kind = 'coded' as const;\n\n constructor(\n message: string,\n public readonly statusCode: ApiStatusCode,\n public errorCode?: string,\n cause?: Error,\n ) {\n super(message, cause);\n }\n\n /** Narrows a dynamic number (for example one read off the wire) to a legal status code. */\n // webpieces-disable no-function-outside-class -- stateless status guard; webpieces-disable no-any-unknown -- wire and app values are narrowed here\n static isStatusCode(value: unknown): value is ApiStatusCode {\n return typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599;\n }\n\n /** True when the status describes the caller's mistake rather than a fault on the serving side. */\n isCallerError(): boolean {\n return this.statusCode < 500 && this.statusCode !== 408 && this.statusCode !== 429;\n }\n}\n\n/**\n * The CALLER's build is too old for this server: HTTP **426 Upgrade Required**, errorCode\n * {@link ApiClientTooOldError.CODE}. It means \"upgrade\", never \"bug\".\n *\n * WHO THROWS IT: a server gate that compares the build the caller DECLARED (a header, a version in\n * the request) against the minimum this server still supports. Every webpieces client and server\n * deployed separately hits this skew (a browser tab left open across a release, a mobile WebView\n * pinned to an old bundle), and only the SERVER can tell that its caller is too old.\n *\n * WHY 426 AND NOT 266: a 266 means \"the user must fix something\" and its message is shown verbatim,\n * so a bundle built before the gate existed would print the server's developer text as a user\n * instruction. A non-266 status reaches every bundle already in the field as a non-user failure.\n *\n * WHAT RECEIVERS DO: `ReceivedApiErrorRule` decodes it back into THIS type on every hop\n * (server-to-server and browser, HTTP and IPC) instead of the `ApiImplementationError` an ordinary\n * received 4xx becomes. A client treats it as \"reload / install the new build\", never as a red bug\n * dialog. It pairs with `allowUpgradeInFlight` (`HttpEndpointOptions`), which declares which calls\n * an upgrade may cut off: that flag is the client deciding WHEN it may upgrade, this error is the\n * server saying that it MUST.\n *\n * NO user-facing copy: `message` is developer text like every non-end-user kind (it is not published\n * across the wire), and the receiving client owns the wording it shows. The status and the code are\n * FIXED here, so a throw site cannot send a 426 that the receivers would not recognise.\n *\n * It is an {@link ApiCodedError} on purpose: the wire form is exactly `kind: 'coded'`,\n * `statusCode: 426`, `errorCode: 'client-too-old'`, which is what an app's own hand-rolled\n * client-too-old error already publishes, so a bundle in the field and a server on the new release\n * agree without either side changing its payload.\n */\nexport class ApiClientTooOldError extends ApiCodedError {\n /** The fixed `errorCode`; together with status 426 it is what a receiver matches on. */\n static readonly CODE = 'client-too-old';\n /** HTTP 426 Upgrade Required. */\n static readonly STATUS_CODE = 426;\n\n constructor(message: string, cause?: Error) {\n super(message, ApiClientTooOldError.STATUS_CODE, ApiClientTooOldError.CODE, cause);\n }\n\n /** True when a coded failure's status and code are this error's wire identity. */\n // webpieces-disable no-function-outside-class -- stateless wire-identity predicate\n static matches(statusCode: number, errorCode: string | undefined): boolean {\n return (\n statusCode === ApiClientTooOldError.STATUS_CODE &&\n errorCode === ApiClientTooOldError.CODE\n );\n }\n}\n"]}
1
+ {"version":3,"file":"ApiError.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/errors/ApiError.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCallSite } from './ApiCallSite';\n\nexport type ApiErrorKind =\n | 'end-user'\n | 'bad-request'\n | 'unauthorized'\n | 'forbidden'\n | 'not-found'\n | 'endpoint-not-found'\n | 'request-timeout'\n | 'rate-limited'\n | 'conflict'\n | 'unprocessable'\n | 'precondition-failed'\n | 'unsupported-media-type'\n | 'not-implemented'\n | 'coded'\n | 'implementation'\n | 'dependency'\n | 'bad-gateway'\n | 'unavailable'\n | 'dependency-timeout'\n | 'dependency-backoff'\n | 'retries-exhausted'\n | 'connection';\n\n/** Category for portable API failures. Concrete subclasses alone define protocol mappings. */\nexport abstract class ApiError extends Error {\n abstract readonly kind: ApiErrorKind;\n public subType?: string;\n\n /**\n * Where the app called the generated client method that rejected with this error — present only\n * on an error a generated api client (HTTP, IPC or Cloud Tasks) rejected with. Set by\n * {@link ApiCallSite.attachTo} as a NON-ENUMERABLE own property, so it never reaches JSON or a\n * structured log; `declare` emits no field, so an error that never failed a client call carries\n * no `callSite` key at all. See {@link ApiCallSite} for why it is not `cause`.\n */\n declare readonly callSite?: ApiCallSite;\n\n constructor(message: string, cause?: Error) {\n super(message, { cause });\n this.name = new.target.name;\n }\n}\n\nexport class ApiNotFoundError extends ApiError {\n override readonly kind: ApiErrorKind = 'not-found';\n}\nexport class ApiDependencyError extends ApiError {\n override readonly kind: ApiErrorKind = 'dependency';\n}\n/** A retry-eligible HTTP 502/proxy failure, distinct from a generic dependency defect. */\nexport class ApiBadGatewayError extends ApiDependencyError {\n override readonly kind = 'bad-gateway' as const;\n}\nexport class ApiUnavailableError extends ApiDependencyError {\n override readonly kind = 'unavailable' as const;\n}\nexport class ApiDependencyTimeoutError extends ApiDependencyError {\n override readonly kind = 'dependency-timeout' as const;\n}\n\n/**\n * A `RetryStrategy` retried a TRANSIENT failure and ran out of attempts. Thrown ONLY by the strategy,\n * and only when it actually retried — a single attempt rethrows its own error unchanged. `cause` is\n * the LAST attempt's error.\n *\n * Classified `CallOutcome.FAILURE`, never transient: that is the anti-amplification property. Retries\n * happen at ONE layer; without it, 4 attempts x 4 attempts x 4 attempts across three hops is 64 calls\n * into a struggling dependency. For the same reason it publishes HTTP 502 (Bad Gateway: \"the thing\n * behind me failed\"), not 503 — generic clients and load balancers retry a 503.\n *\n * A webpieces peer decodes `kind: 'retries-exhausted'` back into this class (see\n * {@link ApiRetriesExhaustedError.received}), so the upstream classifies it FAILURE and does not\n * retry; a foreign caller just sees a 502.\n */\nexport class ApiRetriesExhaustedError extends ApiDependencyError {\n override readonly kind = 'retries-exhausted' as const;\n\n constructor(\n public readonly attempts: number,\n public readonly lastError: Error,\n ) {\n super(`gave up after ${attempts} attempts: ${lastError.message}`, lastError);\n }\n\n /**\n * The error as DECODED from a peer's wire payload. The peer published neither its attempt count\n * nor its last error (an operator message never crosses the wire), so `attempts` is 0 — \"unknown,\n * a peer gave up\" — and `lastError` carries the published message.\n */\n // webpieces-disable no-function-outside-class -- wire-decode factory owned by this error\n static received(message: string): ApiRetriesExhaustedError {\n const error = new ApiRetriesExhaustedError(0, new ApiDependencyError(message));\n error.message = message;\n return error;\n }\n}\n\n/** A bug or invalid implementation state. Local errors become server errors only after a remote decode. */\nexport class ApiImplementationError extends ApiError {\n override readonly kind = 'implementation' as const;\n\n constructor(\n message: string,\n cause?: Error,\n public readonly serverError = false,\n ) {\n super(message, cause);\n }\n}\n\nexport class ApiRateLimitedError extends ApiError {\n override readonly kind = 'rate-limited' as const;\n}\nexport class ApiForbiddenError extends ApiError {\n override readonly kind = 'forbidden' as const;\n}\nexport class ApiRequestTimeoutError extends ApiError {\n override readonly kind = 'request-timeout' as const;\n}\n\n/**\n * A caller-local connection failure. HTTP servers normalize this to ApiImplementationError.\n *\n * `message` is the developer text (it names the url and the transport's own reason); `userMessage`\n * is the stable text an app renders verbatim to an end user, matching `ApiCallTimeoutError.userMessage`\n * so the two transport failures read consistently.\n */\nexport class ApiConnectionError extends ApiError {\n static readonly USER_MESSAGE =\n \"We couldn't reach the server. Please check your network connection and try again.\";\n\n override readonly kind = 'connection' as const;\n /** End-user text; `message` stays the developer text. */\n readonly userMessage: string = ApiConnectionError.USER_MESSAGE;\n}\n\n/** The API operation is missing, distinct from an absent domain entity. */\nexport class ApiEndpointNotFoundError extends ApiNotFoundError {\n override readonly kind = 'endpoint-not-found' as const;\n}\n\n/**\n * The HTTP status an API EDGE answers for an {@link ApiEndUserError}. End-user outcomes only, so\n * nobody tunnels a 5xx/401/403/429 through the end-user channel (auth, rate limiting and dependency\n * failure keep their own types). Extend deliberately.\n */\nexport type EdgeHttpStatus = 400 | 404 | 409 | 422;\n\n/**\n * Expected mistake by the ACTOR driving the call: the input was well-formed but wrong in a way the actor\n * must fix (for example \"the two passwords you entered do not match\"). The actor may be a human user in\n * a GUI OR an LLM calling an MCP tool; the same class serves both. Its message is deliberately\n * caller-safe and is the only ApiError message published verbatim: `ApiErrorCodec` keeps it (and\n * `errorCode`) across every remote hop, so a downstream service's text reaches the GUI or the model\n * byte-for-byte.\n *\n * `edgeHttpStatus` travels the same way. It is what a partner-facing API edge (an http-server router\n * in `'edge'` end-user-status mode) answers instead of 266, so one throw site serves both a GUI and a\n * published REST contract: `throw new ApiEndUserError('That report does not exist', 'report_not_found', 404)`.\n * GUI edges ignore it and keep answering 266.\n */\nexport class ApiEndUserError extends ApiError {\n override readonly kind = 'end-user' as const;\n\n constructor(\n message: string,\n public errorCode?: string,\n public edgeHttpStatus?: EdgeHttpStatus,\n cause?: Error,\n ) {\n super(message, cause);\n this.subType = 'USER_ERROR';\n }\n\n /** Narrows a dynamic value (for example one read off the wire) to a legal {@link EdgeHttpStatus}. */\n // webpieces-disable no-function-outside-class -- stateless status guard; webpieces-disable no-any-unknown -- wire values are narrowed here\n static isEdgeHttpStatus(value: unknown): value is EdgeHttpStatus {\n return value === 400 || value === 404 || value === 409 || value === 422;\n }\n}\n\n/**\n * Invalid API caller input (malformed, or failed validation). `message` is operator-only diagnostic\n * text and may name internals; it is never published. `callerMessage` (plus `field`) is what a GUI OR\n * an MCP model is shown so it can correct its input, so throw sites that want the caller to\n * self-correct MUST set it.\n */\nexport class ApiBadRequestError extends ApiError {\n override readonly kind = 'bad-request' as const;\n\n constructor(\n message: string,\n public field?: string,\n public callerMessage?: string,\n cause?: Error,\n ) {\n super(message, cause);\n }\n}\n\nexport class ApiUnauthorizedError extends ApiError {\n override readonly kind = 'unauthorized' as const;\n\n constructor(message: string, subType?: string, cause?: Error) {\n super(message, cause);\n this.subType = subType;\n }\n}\n\nexport class ApiDependencyBackoffError extends ApiDependencyError {\n override readonly kind = 'dependency-backoff' as const;\n\n constructor(\n message: string,\n public retryAfterSeconds = 30,\n cause?: Error,\n ) {\n super(message, cause);\n }\n}\n\n/** The request conflicts with the current state of the target resource (HTTP 409). */\nexport class ApiConflictError extends ApiError {\n override readonly kind = 'conflict' as const;\n}\n\n/** The request was well-formed but semantically unprocessable (HTTP 422). */\nexport class ApiUnprocessableError extends ApiError {\n override readonly kind = 'unprocessable' as const;\n}\n\n/** A caller-supplied precondition (for example If-Match) did not hold (HTTP 412). */\nexport class ApiPreconditionFailedError extends ApiError {\n override readonly kind = 'precondition-failed' as const;\n}\n\n/** The request body is in a media type this operation does not accept (HTTP 415). */\nexport class ApiUnsupportedMediaTypeError extends ApiError {\n override readonly kind = 'unsupported-media-type' as const;\n}\n\n/** The operation exists in the contract but this server does not implement it (HTTP 501). */\nexport class ApiNotImplementedError extends ApiError {\n override readonly kind = 'not-implemented' as const;\n}\n\n/** Builds the tuple [0, 1, ..., N-1]; tail-recursive so TypeScript can reach 600. */\ntype StatusRange<N extends number, Acc extends number[] = []> = Acc['length'] extends N\n ? Acc\n : StatusRange<N, [...Acc, Acc['length']]>;\n\n/**\n * Every integer from 100 through 599 as a literal union. `new ApiCodedError(msg, 600)`,\n * `new ApiCodedError(msg, 99)` and `new ApiCodedError(msg, 404.5)` do not compile; a dynamic\n * `number` must be narrowed with {@link ApiCodedError.isStatusCode} first.\n */\nexport type ApiStatusCode = Exclude<StatusRange<600>[number], StatusRange<100>[number]>;\n\n/**\n * Catch-all carrying an explicit protocol status for outcomes no named ApiError covers. Any code in\n * 100-599 is accepted, including one a named class already owns. `statusCode` and `errorCode` survive\n * every remote hop through `ApiErrorCodec`; `message` stays operator-only like every other non\n * end-user kind. A status below 500 is a caller outcome (except 408 and 429, which mirror\n * `ApiRequestTimeoutError` and `ApiRateLimitedError`); 500 and above is a server/dependency fault.\n */\nexport class ApiCodedError extends ApiError {\n override readonly kind = 'coded' as const;\n\n constructor(\n message: string,\n public readonly statusCode: ApiStatusCode,\n public errorCode?: string,\n cause?: Error,\n ) {\n super(message, cause);\n }\n\n /** Narrows a dynamic number (for example one read off the wire) to a legal status code. */\n // webpieces-disable no-function-outside-class -- stateless status guard; webpieces-disable no-any-unknown -- wire and app values are narrowed here\n static isStatusCode(value: unknown): value is ApiStatusCode {\n return typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599;\n }\n\n /** True when the status describes the caller's mistake rather than a fault on the serving side. */\n isCallerError(): boolean {\n return this.statusCode < 500 && this.statusCode !== 408 && this.statusCode !== 429;\n }\n}\n\n/**\n * The CALLER's build is too old for this server: HTTP **426 Upgrade Required**, errorCode\n * {@link ApiClientTooOldError.CODE}. It means \"upgrade\", never \"bug\".\n *\n * WHO THROWS IT: a server gate that compares the build the caller DECLARED (a header, a version in\n * the request) against the minimum this server still supports. Every webpieces client and server\n * deployed separately hits this skew (a browser tab left open across a release, a mobile WebView\n * pinned to an old bundle), and only the SERVER can tell that its caller is too old.\n *\n * WHY 426 AND NOT 266: a 266 means \"the user must fix something\" and its message is shown verbatim,\n * so a bundle built before the gate existed would print the server's developer text as a user\n * instruction. A non-266 status reaches every bundle already in the field as a non-user failure.\n *\n * WHAT RECEIVERS DO: `ReceivedApiErrorRule` decodes it back into THIS type on every hop\n * (server-to-server and browser, HTTP and IPC) instead of the `ApiImplementationError` an ordinary\n * received 4xx becomes. A client treats it as \"reload / install the new build\", never as a red bug\n * dialog. It pairs with `allowUpgradeInFlight` (`HttpEndpointOptions`), which declares which calls\n * an upgrade may cut off: that flag is the client deciding WHEN it may upgrade, this error is the\n * server saying that it MUST.\n *\n * NO user-facing copy: `message` is developer text like every non-end-user kind (it is not published\n * across the wire), and the receiving client owns the wording it shows. The status and the code are\n * FIXED here, so a throw site cannot send a 426 that the receivers would not recognise.\n *\n * It is an {@link ApiCodedError} on purpose: the wire form is exactly `kind: 'coded'`,\n * `statusCode: 426`, `errorCode: 'client-too-old'`, which is what an app's own hand-rolled\n * client-too-old error already publishes, so a bundle in the field and a server on the new release\n * agree without either side changing its payload.\n */\nexport class ApiClientTooOldError extends ApiCodedError {\n /** The fixed `errorCode`; together with status 426 it is what a receiver matches on. */\n static readonly CODE = 'client-too-old';\n /** HTTP 426 Upgrade Required. */\n static readonly STATUS_CODE = 426;\n\n constructor(message: string, cause?: Error) {\n super(message, ApiClientTooOldError.STATUS_CODE, ApiClientTooOldError.CODE, cause);\n }\n\n /** True when a coded failure's status and code are this error's wire identity. */\n // webpieces-disable no-function-outside-class -- stateless wire-identity predicate\n static matches(statusCode: number, errorCode: string | undefined): boolean {\n return (\n statusCode === ApiClientTooOldError.STATUS_CODE &&\n errorCode === ApiClientTooOldError.CODE\n );\n }\n}\n"]}
@@ -147,6 +147,8 @@ class ApiErrorCodec {
147
147
  return new ApiError_1.ApiDependencyTimeoutError(message);
148
148
  case 'dependency-backoff':
149
149
  return new ApiError_1.ApiDependencyBackoffError(message, this.wait(field('retryAfterSeconds')));
150
+ case 'retries-exhausted':
151
+ return ApiError_1.ApiRetriesExhaustedError.received(message);
150
152
  case 'connection':
151
153
  return new ApiError_1.ApiConnectionError(message);
152
154
  case 'implementation':
@@ -232,6 +234,8 @@ class ApiErrorCodec {
232
234
  return 'Dependency Timeout';
233
235
  case 'dependency-backoff':
234
236
  return 'Dependency Unavailable';
237
+ case 'retries-exhausted':
238
+ return 'Retries Exhausted';
235
239
  case 'connection':
236
240
  return 'Connection Error';
237
241
  default:
@@ -259,6 +263,7 @@ class ApiErrorCodec {
259
263
  'unavailable',
260
264
  'dependency-timeout',
261
265
  'dependency-backoff',
266
+ 'retries-exhausted',
262
267
  'connection',
263
268
  ]);
264
269
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ApiErrorCodec.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/errors/ApiErrorCodec.ts"],"names":[],"mappings":";;;AAAA,yCA0BoB;AACpB,kDAA4C;AAC5C,iEAA8D;AAE9D,gGAAgG;AAChG,MAAa,eAAe;IAYb;IACA;IAZJ,KAAK,CAAmB;IACxB,OAAO,CAAU;IACjB,KAAK,CAAU;IACf,aAAa,CAAU;IACvB,SAAS,CAAU;IACnB,iBAAiB,CAAU;IAC3B,UAAU,CAAU;IAC3B,mGAAmG;IAC5F,cAAc,CAAU;IAE/B,YACW,OAAe,gBAAgB,EAC/B,UAAkB,gBAAgB;QADlC,SAAI,GAAJ,IAAI,CAA2B;QAC/B,YAAO,GAAP,OAAO,CAA2B;IAC1C,CAAC;CACP;AAfD,0CAeC;AAED,yEAAyE;AACzE,MAAa,aAAa;IACtB;;;;;;;;;OASG;IACH,kFAAkF;IAClF,MAAM,CAAC,MAAM,CAAC,KAAY;QACtB,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,mFAAmF;IAC3E,MAAM,CAAC,WAAW,CAAC,KAAY,EAAE,KAAa;QAClD,MAAM,IAAI,GAAG,KAAK,YAAY,mBAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC;QACvE,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QAC3E,IAAI,KAAK,YAAY,mBAAQ;YAAE,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1E,IAAI,KAAK,YAAY,0BAAe,EAAE,CAAC;YACnC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC/C,OAAO,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;QAClD,CAAC;QACD,IAAI,KAAK,YAAY,wBAAa,EAAE,CAAC;YACjC,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;YACtC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,KAAK,YAAY,6BAAkB,EAAE,CAAC;YACtC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACvC,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,KAAK,YAAY,oCAAyB,EAAE,CAAC;YAC7C,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACZ,yFAAyF;YACzF,wFAAwF;YACxF,MAAM,KAAK,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC;YACrE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACxC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,IAAA,oBAAO,EAAC,KAAK,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAChE,CAAC;QACL,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,+FAA+F;IAC/F,sJAAsJ;IACtJ,MAAM,CAAC,MAAM,CAAC,KAAc;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,4EAA4E;IAC5E,wJAAwJ;IACxJ,MAAM,CAAC,SAAS,CAAC,KAAc;QAC3B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC;QACnE,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAoB,CAAC,CAAC;IAC5E,CAAC;IAED,yJAAyJ;IACjJ,MAAM,CAAC,WAAW,CAAC,KAAc,EAAE,KAAa;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC;YACrE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBAC9C,wGAAwG;gBACxG,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE;oBAClC,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;oBACzC,YAAY,EAAE,IAAI;oBAClB,QAAQ,EAAE,IAAI;iBACjB,CAAC,CAAC;YACP,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,4IAA4I;IACpI,MAAM,CAAC,SAAS,CAAC,KAAc;QACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,yBAAyB,EAAE,CAAC;QACzF,sFAAsF;QACtF,MAAM,KAAK,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC;QAC3F,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAClE,QAAQ,IAAI,EAAE,CAAC;YACX,KAAK,UAAU;gBACX,uFAAuF;gBACvF,OAAO,2CAAoB,CAAC,MAAM,CAC9B,OAAO,EACP,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAC7B,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAC3C,CAAC;YACN,KAAK,aAAa;gBACd,OAAO,IAAI,6BAAkB,CACzB,OAAO,EACP,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EACzB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CACpC,CAAC;YACN,KAAK,cAAc;gBACf,OAAO,IAAI,+BAAoB,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC1E,KAAK,WAAW;gBACZ,OAAO,IAAI,4BAAiB,CAAC,OAAO,CAAC,CAAC;YAC1C,KAAK,WAAW;gBACZ,OAAO,IAAI,2BAAgB,CAAC,OAAO,CAAC,CAAC;YACzC,KAAK,oBAAoB;gBACrB,OAAO,IAAI,mCAAwB,CAAC,OAAO,CAAC,CAAC;YACjD,KAAK,iBAAiB;gBAClB,OAAO,IAAI,iCAAsB,CAAC,OAAO,CAAC,CAAC;YAC/C,KAAK,cAAc;gBACf,OAAO,IAAI,8BAAmB,CAAC,OAAO,CAAC,CAAC;YAC5C,KAAK,UAAU;gBACX,OAAO,IAAI,2BAAgB,CAAC,OAAO,CAAC,CAAC;YACzC,KAAK,eAAe;gBAChB,OAAO,IAAI,gCAAqB,CAAC,OAAO,CAAC,CAAC;YAC9C,KAAK,qBAAqB;gBACtB,OAAO,IAAI,qCAA0B,CAAC,OAAO,CAAC,CAAC;YACnD,KAAK,wBAAwB;gBACzB,OAAO,IAAI,uCAA4B,CAAC,OAAO,CAAC,CAAC;YACrD,KAAK,iBAAiB;gBAClB,OAAO,IAAI,iCAAsB,CAAC,OAAO,CAAC,CAAC;YAC/C,KAAK,OAAO;gBACR,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;YACxE,KAAK,YAAY;gBACb,OAAO,IAAI,6BAAkB,CAAC,OAAO,CAAC,CAAC;YAC3C,KAAK,aAAa;gBACd,OAAO,IAAI,6BAAkB,CAAC,OAAO,CAAC,CAAC;YAC3C,KAAK,aAAa;gBACd,OAAO,IAAI,8BAAmB,CAAC,OAAO,CAAC,CAAC;YAC5C,KAAK,oBAAoB;gBACrB,OAAO,IAAI,oCAAyB,CAAC,OAAO,CAAC,CAAC;YAClD,KAAK,oBAAoB;gBACrB,OAAO,IAAI,oCAAyB,CAChC,OAAO,EACP,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,CACxC,CAAC;YACN,KAAK,YAAY;gBACb,OAAO,IAAI,6BAAkB,CAAC,OAAO,CAAC,CAAC;YAC3C,KAAK,gBAAgB;gBACjB,OAAO,IAAI,iCAAsB,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YAChE;gBACI,OAAO,IAAI,CAAC,yBAAyB,EAAE,CAAC;QAChD,CAAC;IACL,CAAC;IAED,uIAAuI;IAC/H,MAAM,CAAC,KAAK,CAAC,OAAe,EAAE,UAAmB,EAAE,SAAkB;QACzE,IAAI,CAAC,wBAAa,CAAC,YAAY,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC,yBAAyB,EAAE,CAAC;QACrF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClC,oFAAoF;QACpF,sDAAsD;QACtD,IAAI,+BAAoB,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC;YAC9C,OAAO,IAAI,+BAAoB,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,IAAI,wBAAa,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IACxD,CAAC;IAED,oGAAoG;IAC5F,MAAM,CAAC,yBAAyB;QACpC,OAAO,IAAI,iCAAsB,CAAC,gBAAgB,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IACzE,CAAC;IAED,+EAA+E;IACvE,MAAM,CAAC,aAAa,CAAC,KAAY,EAAE,IAAkB;QACzD,IAAI,KAAK,YAAY,0BAAe;YAAE,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1E,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,uIAAuI;IAC/H,MAAM,CAAC,IAAI,CAAC,KAAc;QAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACxE,CAAC;IAED,6GAA6G;IAC7G,uIAAuI;IAC/H,MAAM,CAAC,UAAU,CAAC,KAAc;QACpC,OAAO,0BAAe,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACvE,CAAC;IAED,uIAAuI;IAC/H,MAAM,CAAC,IAAI,CAAC,KAAc;QAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;YACpE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC;YACxB,CAAC,CAAC,SAAS,CAAC;IACpB,CAAC;IAED,wJAAwJ;IAChJ,MAAM,CAAC,OAAO,CAAC,IAAa;QAChC,QAAQ,IAAI,EAAE,CAAC;YACX,KAAK,UAAU;gBACX,OAAO,gBAAgB,CAAC;YAC5B,KAAK,aAAa;gBACd,OAAO,aAAa,CAAC;YACzB,KAAK,cAAc;gBACf,OAAO,cAAc,CAAC;YAC1B,KAAK,WAAW;gBACZ,OAAO,WAAW,CAAC;YACvB,KAAK,WAAW;gBACZ,OAAO,WAAW,CAAC;YACvB,KAAK,oBAAoB;gBACrB,OAAO,oBAAoB,CAAC;YAChC,KAAK,iBAAiB;gBAClB,OAAO,iBAAiB,CAAC;YAC7B,KAAK,cAAc;gBACf,OAAO,cAAc,CAAC;YAC1B,KAAK,UAAU;gBACX,OAAO,UAAU,CAAC;YACtB,KAAK,eAAe;gBAChB,OAAO,uBAAuB,CAAC;YACnC,KAAK,qBAAqB;gBACtB,OAAO,qBAAqB,CAAC;YACjC,KAAK,wBAAwB;gBACzB,OAAO,wBAAwB,CAAC;YACpC,KAAK,iBAAiB;gBAClB,OAAO,iBAAiB,CAAC;YAC7B,KAAK,OAAO;gBACR,OAAO,gBAAgB,CAAC;YAC5B,KAAK,YAAY;gBACb,OAAO,kBAAkB,CAAC;YAC9B,KAAK,aAAa;gBACd,OAAO,aAAa,CAAC;YACzB,KAAK,aAAa;gBACd,OAAO,qBAAqB,CAAC;YACjC,KAAK,oBAAoB;gBACrB,OAAO,oBAAoB,CAAC;YAChC,KAAK,oBAAoB;gBACrB,OAAO,wBAAwB,CAAC;YACpC,KAAK,YAAY;gBACb,OAAO,kBAAkB,CAAC;YAC9B;gBACI,OAAO,gBAAgB,CAAC;QAChC,CAAC;IACL,CAAC;IAEO,MAAM,CAAU,KAAK,GAA8B,IAAI,GAAG,CAAe;QAC7E,UAAU;QACV,aAAa;QACb,cAAc;QACd,WAAW;QACX,WAAW;QACX,oBAAoB;QACpB,iBAAiB;QACjB,cAAc;QACd,UAAU;QACV,eAAe;QACf,qBAAqB;QACrB,wBAAwB;QACxB,iBAAiB;QACjB,OAAO;QACP,gBAAgB;QAChB,YAAY;QACZ,aAAa;QACb,aAAa;QACb,oBAAoB;QACpB,oBAAoB;QACpB,YAAY;KACf,CAAC,CAAC;;AA/PP,sCAgQC","sourcesContent":["import {\n ApiBadRequestError,\n ApiBadGatewayError,\n ApiClientTooOldError,\n ApiCodedError,\n ApiConflictError,\n ApiConnectionError,\n ApiDependencyBackoffError,\n ApiDependencyError,\n ApiDependencyTimeoutError,\n ApiEndpointNotFoundError,\n ApiEndUserError,\n ApiError,\n ApiErrorKind,\n ApiForbiddenError,\n ApiImplementationError,\n ApiNotFoundError,\n ApiNotImplementedError,\n ApiPreconditionFailedError,\n ApiRateLimitedError,\n ApiRequestTimeoutError,\n ApiUnauthorizedError,\n ApiUnavailableError,\n ApiUnprocessableError,\n ApiUnsupportedMediaTypeError,\n EdgeHttpStatus,\n} from './ApiError';\nimport { toError } from '../lib/errorUtils';\nimport { EndUserErrorRegistry } from './EndUserErrorRegistry';\n\n/** Allowlisted transport-neutral envelope. It never contains stacks or arbitrary properties. */\nexport class ApiErrorPayload {\n public cause?: ApiErrorPayload;\n public subType?: string;\n public field?: string;\n public callerMessage?: string;\n public errorCode?: string;\n public retryAfterSeconds?: number;\n public statusCode?: number;\n /** {@link ApiEndUserError.edgeHttpStatus}; absent when the thrower (or an older peer) set none. */\n public edgeHttpStatus?: number;\n\n constructor(\n public kind: string = 'implementation',\n public message: string = 'Internal Error',\n ) {}\n}\n\n/** Bounded safe codec shared by HTTP, IPC, and other remote adapters. */\nexport class ApiErrorCodec {\n /**\n * Encode a thrown error faithfully. The input is an `Error`, never `unknown`: every caller in\n * the framework has already narrowed with `toError` at its catch, and the `unknown` this used to\n * advertise leaked out of the PRIVATE `.cause` recursion below (`Error.cause` is typed `unknown`\n * in TypeScript), which is an internal concern.\n *\n * This is the FAITHFUL encode: it publishes `kind` exactly as the error declares it. The\n * boundary rule that a caller-local `ApiConnectionError` publishes as `implementation` belongs\n * to {@link ApiErrorBoundary}, which every protocol edge uses instead of calling this directly.\n */\n // webpieces-disable no-function-outside-class -- stateless public transport codec\n static encode(error: Error): ApiErrorPayload {\n return this.encodeDepth(error, 0);\n }\n\n // webpieces-disable no-function-outside-class -- recursive bounded transport codec\n private static encodeDepth(error: Error, depth: number): ApiErrorPayload {\n const kind = error instanceof ApiError ? error.kind : 'implementation';\n const payload = new ApiErrorPayload(kind, this.publicMessage(error, kind));\n if (error instanceof ApiError) payload.subType = this.text(error.subType);\n if (error instanceof ApiEndUserError) {\n payload.errorCode = this.text(error.errorCode);\n payload.edgeHttpStatus = error.edgeHttpStatus;\n }\n if (error instanceof ApiCodedError) {\n payload.statusCode = error.statusCode;\n payload.errorCode = this.text(error.errorCode);\n }\n if (error instanceof ApiBadRequestError) {\n payload.field = this.text(error.field);\n payload.callerMessage = this.text(error.callerMessage);\n }\n if (error instanceof ApiDependencyBackoffError) {\n payload.retryAfterSeconds = this.wait(error.retryAfterSeconds);\n }\n if (depth < 3) {\n // `Error.cause` is `unknown` by construction, so the recursion narrows it with `toError`\n // exactly as a catch block would — and then deals only in `Error` like everything else.\n const cause = Object.getOwnPropertyDescriptor(error, 'cause')?.value;\n if (cause !== undefined && cause !== null) {\n payload.cause = this.encodeDepth(toError(cause), depth + 1);\n }\n }\n return payload;\n }\n\n /** Decode a value received from another process. Implementation failures are marked remote. */\n // webpieces-disable no-function-outside-class -- stateless public transport codec; webpieces-disable no-any-unknown -- wire values require validation\n static decode(value: unknown): ApiError {\n return this.decodeDepth(value, 0);\n }\n\n /** Whether a body carries a recognized Webpieces semantic discriminator. */\n // webpieces-disable no-function-outside-class -- stateless wire discriminator check; webpieces-disable no-any-unknown -- wire values require validation\n static isPayload(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false;\n const kind = Object.getOwnPropertyDescriptor(value, 'kind')?.value;\n return typeof kind === 'string' && this.kinds.has(kind as ApiErrorKind);\n }\n\n // webpieces-disable no-function-outside-class -- recursive bounded transport codec; webpieces-disable no-any-unknown -- remote causes require validation\n private static decodeDepth(value: unknown, depth: number): ApiError {\n const error = this.decodeOne(value);\n if (typeof value === 'object' && value !== null && depth < 3) {\n const cause = Object.getOwnPropertyDescriptor(value, 'cause')?.value;\n if (typeof cause === 'object' && cause !== null) {\n // webpieces-disable no-anonymous-object-literals -- standard descriptor preserves Error.cause semantics\n Object.defineProperty(error, 'cause', {\n value: this.decodeDepth(cause, depth + 1),\n configurable: true,\n writable: true,\n });\n }\n }\n return error;\n }\n\n // webpieces-disable no-function-outside-class -- allowlisted wire codec; webpieces-disable no-any-unknown -- wire fields require validation\n private static decodeOne(value: unknown): ApiError {\n if (typeof value !== 'object' || value === null) return this.remoteImplementationError();\n // webpieces-disable no-any-unknown -- own wire fields remain untrusted until narrowed\n const field = (key: string): unknown => Object.getOwnPropertyDescriptor(value, key)?.value;\n const kind = field('kind');\n const message = this.text(field('message')) ?? this.message(kind);\n switch (kind) {\n case 'end-user':\n // The registered SUBCLASS for this errorCode, else the base type with the code intact.\n return EndUserErrorRegistry.create(\n message,\n this.text(field('errorCode')),\n this.edgeStatus(field('edgeHttpStatus')),\n );\n case 'bad-request':\n return new ApiBadRequestError(\n message,\n this.text(field('field')),\n this.text(field('callerMessage')),\n );\n case 'unauthorized':\n return new ApiUnauthorizedError(message, this.text(field('subType')));\n case 'forbidden':\n return new ApiForbiddenError(message);\n case 'not-found':\n return new ApiNotFoundError(message);\n case 'endpoint-not-found':\n return new ApiEndpointNotFoundError(message);\n case 'request-timeout':\n return new ApiRequestTimeoutError(message);\n case 'rate-limited':\n return new ApiRateLimitedError(message);\n case 'conflict':\n return new ApiConflictError(message);\n case 'unprocessable':\n return new ApiUnprocessableError(message);\n case 'precondition-failed':\n return new ApiPreconditionFailedError(message);\n case 'unsupported-media-type':\n return new ApiUnsupportedMediaTypeError(message);\n case 'not-implemented':\n return new ApiNotImplementedError(message);\n case 'coded':\n return this.coded(message, field('statusCode'), field('errorCode'));\n case 'dependency':\n return new ApiDependencyError(message);\n case 'bad-gateway':\n return new ApiBadGatewayError(message);\n case 'unavailable':\n return new ApiUnavailableError(message);\n case 'dependency-timeout':\n return new ApiDependencyTimeoutError(message);\n case 'dependency-backoff':\n return new ApiDependencyBackoffError(\n message,\n this.wait(field('retryAfterSeconds')),\n );\n case 'connection':\n return new ApiConnectionError(message);\n case 'implementation':\n return new ApiImplementationError(message, undefined, true);\n default:\n return this.remoteImplementationError();\n }\n }\n\n // webpieces-disable no-function-outside-class -- coded wire decode; webpieces-disable no-any-unknown -- wire fields require validation\n private static coded(message: string, statusCode: unknown, errorCode: unknown): ApiError {\n if (!ApiCodedError.isStatusCode(statusCode)) return this.remoteImplementationError();\n const code = this.text(errorCode);\n // The one coded failure webpieces names: rebuilt as its own type so a receiver sees\n // \"upgrade\", never a generic coded 4xx (issue #1172).\n if (ApiClientTooOldError.matches(statusCode, code))\n return new ApiClientTooOldError(message);\n return new ApiCodedError(message, statusCode, code);\n }\n\n // webpieces-disable no-function-outside-class -- concrete normalization for malformed remote errors\n private static remoteImplementationError(): ApiImplementationError {\n return new ApiImplementationError('Internal Error', undefined, true);\n }\n\n // webpieces-disable no-function-outside-class -- caller-safe message selection\n private static publicMessage(error: Error, kind: ApiErrorKind): string {\n if (error instanceof ApiEndUserError) return error.message.slice(0, 4096);\n return this.message(kind);\n }\n\n // webpieces-disable no-function-outside-class -- wire string bound; webpieces-disable no-any-unknown -- wire values require validation\n private static text(value: unknown): string | undefined {\n return typeof value === 'string' ? value.slice(0, 4096) : undefined;\n }\n\n /** Tolerant: a peer that sends nothing, or a status outside {@link EdgeHttpStatus}, decodes as undefined. */\n // webpieces-disable no-function-outside-class -- wire status bound; webpieces-disable no-any-unknown -- wire values require validation\n private static edgeStatus(value: unknown): EdgeHttpStatus | undefined {\n return ApiEndUserError.isEdgeHttpStatus(value) ? value : undefined;\n }\n\n // webpieces-disable no-function-outside-class -- wire number bound; webpieces-disable no-any-unknown -- wire values require validation\n private static wait(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0\n ? Math.min(value, 86400)\n : undefined;\n }\n\n // webpieces-disable no-function-outside-class -- allowlisted generic messages; webpieces-disable no-any-unknown -- discriminator is validated by switch\n private static message(kind: unknown): string {\n switch (kind) {\n case 'end-user':\n return 'End User Error';\n case 'bad-request':\n return 'Bad Request';\n case 'unauthorized':\n return 'Unauthorized';\n case 'forbidden':\n return 'Forbidden';\n case 'not-found':\n return 'Not Found';\n case 'endpoint-not-found':\n return 'Endpoint Not Found';\n case 'request-timeout':\n return 'Request Timeout';\n case 'rate-limited':\n return 'Rate Limited';\n case 'conflict':\n return 'Conflict';\n case 'unprocessable':\n return 'Unprocessable Content';\n case 'precondition-failed':\n return 'Precondition Failed';\n case 'unsupported-media-type':\n return 'Unsupported Media Type';\n case 'not-implemented':\n return 'Not Implemented';\n case 'coded':\n return 'Request Failed';\n case 'dependency':\n return 'Dependency Error';\n case 'bad-gateway':\n return 'Bad Gateway';\n case 'unavailable':\n return 'Service Unavailable';\n case 'dependency-timeout':\n return 'Dependency Timeout';\n case 'dependency-backoff':\n return 'Dependency Unavailable';\n case 'connection':\n return 'Connection Error';\n default:\n return 'Internal Error';\n }\n }\n\n private static readonly kinds: ReadonlySet<ApiErrorKind> = new Set<ApiErrorKind>([\n 'end-user',\n 'bad-request',\n 'unauthorized',\n 'forbidden',\n 'not-found',\n 'endpoint-not-found',\n 'request-timeout',\n 'rate-limited',\n 'conflict',\n 'unprocessable',\n 'precondition-failed',\n 'unsupported-media-type',\n 'not-implemented',\n 'coded',\n 'implementation',\n 'dependency',\n 'bad-gateway',\n 'unavailable',\n 'dependency-timeout',\n 'dependency-backoff',\n 'connection',\n ]);\n}\n"]}
1
+ {"version":3,"file":"ApiErrorCodec.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/errors/ApiErrorCodec.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApiBadRequestError,\n ApiBadGatewayError,\n ApiClientTooOldError,\n ApiCodedError,\n ApiConflictError,\n ApiConnectionError,\n ApiDependencyBackoffError,\n ApiDependencyError,\n ApiDependencyTimeoutError,\n ApiEndpointNotFoundError,\n ApiEndUserError,\n ApiError,\n ApiErrorKind,\n ApiForbiddenError,\n ApiImplementationError,\n ApiNotFoundError,\n ApiNotImplementedError,\n ApiPreconditionFailedError,\n ApiRateLimitedError,\n ApiRequestTimeoutError,\n ApiRetriesExhaustedError,\n ApiUnauthorizedError,\n ApiUnavailableError,\n ApiUnprocessableError,\n ApiUnsupportedMediaTypeError,\n EdgeHttpStatus,\n} from './ApiError';\nimport { toError } from '../lib/errorUtils';\nimport { EndUserErrorRegistry } from './EndUserErrorRegistry';\n\n/** Allowlisted transport-neutral envelope. It never contains stacks or arbitrary properties. */\nexport class ApiErrorPayload {\n public cause?: ApiErrorPayload;\n public subType?: string;\n public field?: string;\n public callerMessage?: string;\n public errorCode?: string;\n public retryAfterSeconds?: number;\n public statusCode?: number;\n /** {@link ApiEndUserError.edgeHttpStatus}; absent when the thrower (or an older peer) set none. */\n public edgeHttpStatus?: number;\n\n constructor(\n public kind: string = 'implementation',\n public message: string = 'Internal Error',\n ) {}\n}\n\n/** Bounded safe codec shared by HTTP, IPC, and other remote adapters. */\nexport class ApiErrorCodec {\n /**\n * Encode a thrown error faithfully. The input is an `Error`, never `unknown`: every caller in\n * the framework has already narrowed with `toError` at its catch, and the `unknown` this used to\n * advertise leaked out of the PRIVATE `.cause` recursion below (`Error.cause` is typed `unknown`\n * in TypeScript), which is an internal concern.\n *\n * This is the FAITHFUL encode: it publishes `kind` exactly as the error declares it. The\n * boundary rule that a caller-local `ApiConnectionError` publishes as `implementation` belongs\n * to {@link ApiErrorBoundary}, which every protocol edge uses instead of calling this directly.\n */\n // webpieces-disable no-function-outside-class -- stateless public transport codec\n static encode(error: Error): ApiErrorPayload {\n return this.encodeDepth(error, 0);\n }\n\n // webpieces-disable no-function-outside-class -- recursive bounded transport codec\n private static encodeDepth(error: Error, depth: number): ApiErrorPayload {\n const kind = error instanceof ApiError ? error.kind : 'implementation';\n const payload = new ApiErrorPayload(kind, this.publicMessage(error, kind));\n if (error instanceof ApiError) payload.subType = this.text(error.subType);\n if (error instanceof ApiEndUserError) {\n payload.errorCode = this.text(error.errorCode);\n payload.edgeHttpStatus = error.edgeHttpStatus;\n }\n if (error instanceof ApiCodedError) {\n payload.statusCode = error.statusCode;\n payload.errorCode = this.text(error.errorCode);\n }\n if (error instanceof ApiBadRequestError) {\n payload.field = this.text(error.field);\n payload.callerMessage = this.text(error.callerMessage);\n }\n if (error instanceof ApiDependencyBackoffError) {\n payload.retryAfterSeconds = this.wait(error.retryAfterSeconds);\n }\n if (depth < 3) {\n // `Error.cause` is `unknown` by construction, so the recursion narrows it with `toError`\n // exactly as a catch block would — and then deals only in `Error` like everything else.\n const cause = Object.getOwnPropertyDescriptor(error, 'cause')?.value;\n if (cause !== undefined && cause !== null) {\n payload.cause = this.encodeDepth(toError(cause), depth + 1);\n }\n }\n return payload;\n }\n\n /** Decode a value received from another process. Implementation failures are marked remote. */\n // webpieces-disable no-function-outside-class -- stateless public transport codec; webpieces-disable no-any-unknown -- wire values require validation\n static decode(value: unknown): ApiError {\n return this.decodeDepth(value, 0);\n }\n\n /** Whether a body carries a recognized Webpieces semantic discriminator. */\n // webpieces-disable no-function-outside-class -- stateless wire discriminator check; webpieces-disable no-any-unknown -- wire values require validation\n static isPayload(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false;\n const kind = Object.getOwnPropertyDescriptor(value, 'kind')?.value;\n return typeof kind === 'string' && this.kinds.has(kind as ApiErrorKind);\n }\n\n // webpieces-disable no-function-outside-class -- recursive bounded transport codec; webpieces-disable no-any-unknown -- remote causes require validation\n private static decodeDepth(value: unknown, depth: number): ApiError {\n const error = this.decodeOne(value);\n if (typeof value === 'object' && value !== null && depth < 3) {\n const cause = Object.getOwnPropertyDescriptor(value, 'cause')?.value;\n if (typeof cause === 'object' && cause !== null) {\n // webpieces-disable no-anonymous-object-literals -- standard descriptor preserves Error.cause semantics\n Object.defineProperty(error, 'cause', {\n value: this.decodeDepth(cause, depth + 1),\n configurable: true,\n writable: true,\n });\n }\n }\n return error;\n }\n\n // webpieces-disable no-function-outside-class -- allowlisted wire codec; webpieces-disable no-any-unknown -- wire fields require validation\n private static decodeOne(value: unknown): ApiError {\n if (typeof value !== 'object' || value === null) return this.remoteImplementationError();\n // webpieces-disable no-any-unknown -- own wire fields remain untrusted until narrowed\n const field = (key: string): unknown => Object.getOwnPropertyDescriptor(value, key)?.value;\n const kind = field('kind');\n const message = this.text(field('message')) ?? this.message(kind);\n switch (kind) {\n case 'end-user':\n // The registered SUBCLASS for this errorCode, else the base type with the code intact.\n return EndUserErrorRegistry.create(\n message,\n this.text(field('errorCode')),\n this.edgeStatus(field('edgeHttpStatus')),\n );\n case 'bad-request':\n return new ApiBadRequestError(\n message,\n this.text(field('field')),\n this.text(field('callerMessage')),\n );\n case 'unauthorized':\n return new ApiUnauthorizedError(message, this.text(field('subType')));\n case 'forbidden':\n return new ApiForbiddenError(message);\n case 'not-found':\n return new ApiNotFoundError(message);\n case 'endpoint-not-found':\n return new ApiEndpointNotFoundError(message);\n case 'request-timeout':\n return new ApiRequestTimeoutError(message);\n case 'rate-limited':\n return new ApiRateLimitedError(message);\n case 'conflict':\n return new ApiConflictError(message);\n case 'unprocessable':\n return new ApiUnprocessableError(message);\n case 'precondition-failed':\n return new ApiPreconditionFailedError(message);\n case 'unsupported-media-type':\n return new ApiUnsupportedMediaTypeError(message);\n case 'not-implemented':\n return new ApiNotImplementedError(message);\n case 'coded':\n return this.coded(message, field('statusCode'), field('errorCode'));\n case 'dependency':\n return new ApiDependencyError(message);\n case 'bad-gateway':\n return new ApiBadGatewayError(message);\n case 'unavailable':\n return new ApiUnavailableError(message);\n case 'dependency-timeout':\n return new ApiDependencyTimeoutError(message);\n case 'dependency-backoff':\n return new ApiDependencyBackoffError(\n message,\n this.wait(field('retryAfterSeconds')),\n );\n case 'retries-exhausted':\n return ApiRetriesExhaustedError.received(message);\n case 'connection':\n return new ApiConnectionError(message);\n case 'implementation':\n return new ApiImplementationError(message, undefined, true);\n default:\n return this.remoteImplementationError();\n }\n }\n\n // webpieces-disable no-function-outside-class -- coded wire decode; webpieces-disable no-any-unknown -- wire fields require validation\n private static coded(message: string, statusCode: unknown, errorCode: unknown): ApiError {\n if (!ApiCodedError.isStatusCode(statusCode)) return this.remoteImplementationError();\n const code = this.text(errorCode);\n // The one coded failure webpieces names: rebuilt as its own type so a receiver sees\n // \"upgrade\", never a generic coded 4xx (issue #1172).\n if (ApiClientTooOldError.matches(statusCode, code))\n return new ApiClientTooOldError(message);\n return new ApiCodedError(message, statusCode, code);\n }\n\n // webpieces-disable no-function-outside-class -- concrete normalization for malformed remote errors\n private static remoteImplementationError(): ApiImplementationError {\n return new ApiImplementationError('Internal Error', undefined, true);\n }\n\n // webpieces-disable no-function-outside-class -- caller-safe message selection\n private static publicMessage(error: Error, kind: ApiErrorKind): string {\n if (error instanceof ApiEndUserError) return error.message.slice(0, 4096);\n return this.message(kind);\n }\n\n // webpieces-disable no-function-outside-class -- wire string bound; webpieces-disable no-any-unknown -- wire values require validation\n private static text(value: unknown): string | undefined {\n return typeof value === 'string' ? value.slice(0, 4096) : undefined;\n }\n\n /** Tolerant: a peer that sends nothing, or a status outside {@link EdgeHttpStatus}, decodes as undefined. */\n // webpieces-disable no-function-outside-class -- wire status bound; webpieces-disable no-any-unknown -- wire values require validation\n private static edgeStatus(value: unknown): EdgeHttpStatus | undefined {\n return ApiEndUserError.isEdgeHttpStatus(value) ? value : undefined;\n }\n\n // webpieces-disable no-function-outside-class -- wire number bound; webpieces-disable no-any-unknown -- wire values require validation\n private static wait(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0\n ? Math.min(value, 86400)\n : undefined;\n }\n\n // webpieces-disable no-function-outside-class -- allowlisted generic messages; webpieces-disable no-any-unknown -- discriminator is validated by switch\n private static message(kind: unknown): string {\n switch (kind) {\n case 'end-user':\n return 'End User Error';\n case 'bad-request':\n return 'Bad Request';\n case 'unauthorized':\n return 'Unauthorized';\n case 'forbidden':\n return 'Forbidden';\n case 'not-found':\n return 'Not Found';\n case 'endpoint-not-found':\n return 'Endpoint Not Found';\n case 'request-timeout':\n return 'Request Timeout';\n case 'rate-limited':\n return 'Rate Limited';\n case 'conflict':\n return 'Conflict';\n case 'unprocessable':\n return 'Unprocessable Content';\n case 'precondition-failed':\n return 'Precondition Failed';\n case 'unsupported-media-type':\n return 'Unsupported Media Type';\n case 'not-implemented':\n return 'Not Implemented';\n case 'coded':\n return 'Request Failed';\n case 'dependency':\n return 'Dependency Error';\n case 'bad-gateway':\n return 'Bad Gateway';\n case 'unavailable':\n return 'Service Unavailable';\n case 'dependency-timeout':\n return 'Dependency Timeout';\n case 'dependency-backoff':\n return 'Dependency Unavailable';\n case 'retries-exhausted':\n return 'Retries Exhausted';\n case 'connection':\n return 'Connection Error';\n default:\n return 'Internal Error';\n }\n }\n\n private static readonly kinds: ReadonlySet<ApiErrorKind> = new Set<ApiErrorKind>([\n 'end-user',\n 'bad-request',\n 'unauthorized',\n 'forbidden',\n 'not-found',\n 'endpoint-not-found',\n 'request-timeout',\n 'rate-limited',\n 'conflict',\n 'unprocessable',\n 'precondition-failed',\n 'unsupported-media-type',\n 'not-implemented',\n 'coded',\n 'implementation',\n 'dependency',\n 'bad-gateway',\n 'unavailable',\n 'dependency-timeout',\n 'dependency-backoff',\n 'retries-exhausted',\n 'connection',\n ]);\n}\n"]}
@@ -14,7 +14,10 @@ import { ClientRole } from './ClientRole';
14
14
  * so this process's failure metrics stay clean and page the right team.
15
15
  * - **an {@link ApiDependencyError} coming back -> rethrown AS-IS.** The fault is already attributed,
16
16
  * somewhere further downstream; this hop adds nothing by re-wrapping it, and re-wrapping would bury
17
- * the original message one `cause` deeper on every hop of a chain.
17
+ * the original message one `cause` deeper on every hop of a chain. That includes
18
+ * `ApiRetriesExhaustedError` (502, `kind: 'retries-exhausted'`): it IS an `ApiDependencyError`, so it
19
+ * passes through as itself, is classified `CallOutcome.FAILURE`, and the upstream does not retry a
20
+ * dependency that a lower layer already retried.
18
21
  * - **408/429/502/503/504 preserve retry-relevant semantics.** Timeout, backoff, bad-gateway and
19
22
  * unavailable must not collapse into the generic non-retryable dependency bucket.
20
23
  * - **266 / `end-user` -> {@link ApiEndUserError}**, the intended user-facing message, published
@@ -69,7 +72,8 @@ export declare class ReceivedApiErrorRule {
69
72
  * - {@link ApiClientTooOldError}: an upgrade, never a bug, on every hop and for every role.
70
73
  * - a 401 {@link ApiUnauthorizedError} received by an END-USER client: the credential it sent IS
71
74
  * the user's session, so this is "log in again". A SERVER's 401 is its own bug and falls through.
72
- * - a dependency failure: already attributed further downstream.
75
+ * - a dependency failure: already attributed further downstream (including a peer's
76
+ * `ApiRetriesExhaustedError`, a subclass of `ApiDependencyError`).
73
77
  */
74
78
  private static passesThrough;
75
79
  }
@@ -17,7 +17,10 @@ const ClientRole_1 = require("./ClientRole");
17
17
  * so this process's failure metrics stay clean and page the right team.
18
18
  * - **an {@link ApiDependencyError} coming back -> rethrown AS-IS.** The fault is already attributed,
19
19
  * somewhere further downstream; this hop adds nothing by re-wrapping it, and re-wrapping would bury
20
- * the original message one `cause` deeper on every hop of a chain.
20
+ * the original message one `cause` deeper on every hop of a chain. That includes
21
+ * `ApiRetriesExhaustedError` (502, `kind: 'retries-exhausted'`): it IS an `ApiDependencyError`, so it
22
+ * passes through as itself, is classified `CallOutcome.FAILURE`, and the upstream does not retry a
23
+ * dependency that a lower layer already retried.
21
24
  * - **408/429/502/503/504 preserve retry-relevant semantics.** Timeout, backoff, bad-gateway and
22
25
  * unavailable must not collapse into the generic non-retryable dependency bucket.
23
26
  * - **266 / `end-user` -> {@link ApiEndUserError}**, the intended user-facing message, published
@@ -103,7 +106,8 @@ class ReceivedApiErrorRule {
103
106
  * - {@link ApiClientTooOldError}: an upgrade, never a bug, on every hop and for every role.
104
107
  * - a 401 {@link ApiUnauthorizedError} received by an END-USER client: the credential it sent IS
105
108
  * the user's session, so this is "log in again". A SERVER's 401 is its own bug and falls through.
106
- * - a dependency failure: already attributed further downstream.
109
+ * - a dependency failure: already attributed further downstream (including a peer's
110
+ * `ApiRetriesExhaustedError`, a subclass of `ApiDependencyError`).
107
111
  */
108
112
  // webpieces-disable no-function-outside-class -- stateless shared rule, called by both protocol translators
109
113
  static passesThrough(role, statusCode, decoded) {
@@ -1 +1 @@
1
- {"version":3,"file":"ReceivedApiErrorRule.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/errors/ReceivedApiErrorRule.ts"],"names":[],"mappings":";;;AAAA,yCAWoB;AACpB,6CAA0C;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAa,oBAAoB;IAC7B;;;;;;;;;OASG;IACH,4GAA4G;IAC5G,MAAM,CAAC,KAAK,CACR,IAAgB,EAChB,UAAkB,EAClB,OAAe,EACf,OAAkB,EAClB,iBAA0B;QAE1B,gGAAgG;QAChG,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YACrB,OAAO,OAAO,YAAY,0BAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,0BAAe,CAAC,OAAO,CAAC,CAAC;QACvF,CAAC;QACD,IACI,OAAO,KAAK,SAAS;YACrB,oBAAoB,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAC/D,CAAC;YACC,OAAO,OAAO,CAAC;QACnB,CAAC;QACD,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YAC3C,OAAO,IAAI,oCAAyB,CAChC,kCAAkC,UAAU,sBAAsB,OAAO,EAAE,EAC3E,OAAO,CACV,CAAC;QACN,CAAC;QACD,IAAI,UAAU,KAAK,GAAG,IAAI,CAAC,UAAU,KAAK,GAAG,IAAI,iBAAiB,KAAK,SAAS,CAAC,EAAE,CAAC;YAChF,OAAO,IAAI,oCAAyB,CAChC,wDAAwD,UAAU,IAAI;gBAClE,oBAAoB,OAAO,EAAE,EACjC,iBAAiB,EACjB,OAAO,CACV,CAAC;QACN,CAAC;QACD,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YACrB,OAAO,IAAI,6BAAkB,CACzB,+CAA+C,OAAO,EAAE,EACxD,OAAO,CACV,CAAC;QACN,CAAC;QACD,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YACrB,OAAO,IAAI,8BAAmB,CAC1B,0DAA0D,OAAO,EAAE,EACnE,OAAO,CACV,CAAC;QACN,CAAC;QACD,IAAI,UAAU,IAAI,GAAG,EAAE,CAAC;YACpB,OAAO,IAAI,6BAAkB,CACzB,uBAAuB,UAAU,qCAAqC;gBAClE,oBAAoB,OAAO,EAAE,EACjC,OAAO,CACV,CAAC;QACN,CAAC;QACD,OAAO,IAAI,iCAAsB,CAC7B,uBAAuB,UAAU,oDAAoD;YACjF,iFAAiF;YACjF,mEAAmE;YACnE,oBAAoB,OAAO,EAAE,EACjC,OAAO,CACV,CAAC;IACN,CAAC;IAED;;;;;;;OAOG;IACH,4GAA4G;IACpG,MAAM,CAAC,aAAa,CAAC,IAAgB,EAAE,UAAkB,EAAE,OAAc;QAC7E,IAAI,OAAO,YAAY,+BAAoB;YAAE,OAAO,IAAI,CAAC;QACzD,IAAI,UAAU,KAAK,GAAG,IAAI,OAAO,YAAY,+BAAoB,EAAE,CAAC;YAChE,OAAO,IAAI,KAAK,uBAAU,CAAC,eAAe,CAAC;QAC/C,CAAC;QACD,OAAO,CACH,OAAO,YAAY,6BAAkB;YACrC,OAAO,YAAY,6BAAkB;YACrC,OAAO,YAAY,8BAAmB;YACtC,OAAO,YAAY,oCAAyB;YAC5C,OAAO,YAAY,oCAAyB,CAC/C,CAAC;IACN,CAAC;CACJ;AA7FD,oDA6FC","sourcesContent":["import {\n ApiBadGatewayError,\n ApiClientTooOldError,\n ApiDependencyBackoffError,\n ApiDependencyError,\n ApiDependencyTimeoutError,\n ApiEndUserError,\n ApiError,\n ApiImplementationError,\n ApiUnauthorizedError,\n ApiUnavailableError,\n} from './ApiError';\nimport { ClientRole } from './ClientRole';\n\n/**\n * THE rule for an error this process RECEIVED from another one — identical over HTTP and over IPC,\n * which is why it lives here and not in either protocol's translator.\n *\n * A status a peer answered describes OUR request to it. It is never, by itself, an answer for our\n * own caller, and the two ways it can be wrong have OPPOSITE owners:\n *\n * - **ordinary caller-error status (4xx-equivalent) -> {@link ApiImplementationError}.** WE sent a bad request,\n * called a path that does not exist, or presented the wrong credentials. MY bug. The peer that\n * answered 404 is correct.\n * - **ordinary server-error status (5xx-equivalent) -> {@link ApiDependencyError}.** THEY broke. Not my bug,\n * so this process's failure metrics stay clean and page the right team.\n * - **an {@link ApiDependencyError} coming back -> rethrown AS-IS.** The fault is already attributed,\n * somewhere further downstream; this hop adds nothing by re-wrapping it, and re-wrapping would bury\n * the original message one `cause` deeper on every hop of a chain.\n * - **408/429/502/503/504 preserve retry-relevant semantics.** Timeout, backoff, bad-gateway and\n * unavailable must not collapse into the generic non-retryable dependency bucket.\n * - **266 / `end-user` -> {@link ApiEndUserError}**, the intended user-facing message, published\n * verbatim. It is the one message the taxonomy lets a peer write for a human. `ApiErrorCodec` has\n * already rebuilt the registered SUBCLASS for its `errorCode` (see `EndUserErrorRegistry`).\n * - **426 / `client-too-old` -> {@link ApiClientTooOldError}, rethrown AS-IS, on EVERY hop and for\n * every {@link ClientRole}.** It is not \"I called wrong\", it is \"the build on the calling side is\n * older than the server allows\", and every receiver must see \"upgrade\", never a bug report.\n *\n * # Why this is UNIFORM, in a browser as much as in a server — with ONE exception, 401\n *\n * An earlier design made the browser pass a received status through unchanged on the theory that\n * \"the browser IS the user's agent, so a 404 is the user's answer\". That is wrong. If a browser\n * received a 404 the browser called the wrong path — that is the BROWSER's bug, and the code that\n * catches it wants to know that, not to show a user a spinner. Read in a browser this reads exactly\n * as intended: `ApiImplementationError` means the client has a bug, `ApiDependencyError` means the\n * server has one.\n *\n * **401 is the exception, and it is why the rule takes a {@link ClientRole}.** A received 401 means\n * \"the credential I presented was rejected\", and WHOSE credential that was depends on who I am:\n *\n * - an {@link ClientRole.END_USER_CLIENT} (browser bundle, Expo shell, remote MCP client) presented\n * the USER's session, so a 401 carrying the webpieces `unauthorized` payload is decoded as\n * {@link ApiUnauthorizedError}: \"log in again\" (and, for a remote MCP client, \"go find the\n * authorization server\"), not a bug.\n * - a {@link ClientRole.SERVER} presented ITS OWN credential, so a 401 stays\n * {@link ApiImplementationError} -> a 500 to its own caller. Relaying it as 401 would tell the\n * browser \"your session expired\" and log the user out for a backend misconfiguration, and it would\n * stop paging the team whose credential is broken.\n *\n * Every other status reads the same in both roles, so the role is consulted for 401 alone. The role\n * has no default: each client states it at setup (see {@link ClientRole}).\n *\n * An app that genuinely wants a peer's status relayed as its OWN typed error says so out loud, in one\n * greppable place, by throwing it from its registered translator's `fromWire`.\n */\nexport class ReceivedApiErrorRule {\n /**\n * @param role - who received it, declared at the client's setup. Decides what a 401 means.\n * @param statusCode - the HTTP status the peer published. IPC has no wire status, so it derives\n * the equivalent from the payload's `kind` through the same `ApiErrorHttpStatus` table — one\n * table, so the two protocols cannot drift apart.\n * @param message - the text to carry forward: the decoded payload's message, else whatever the\n * transport could salvage from the body.\n * @param decoded - the peer's own typed error when the body was a webpieces `ApiErrorPayload`.\n * Absent for a foreign responder.\n */\n // webpieces-disable no-function-outside-class -- stateless shared rule, called by both protocol translators\n static adapt(\n role: ClientRole,\n statusCode: number,\n message: string,\n decoded?: ApiError,\n retryAfterSeconds?: number,\n ): Error {\n // 266 is protocol SUCCESS carrying the actor's own answer — the one message published verbatim.\n if (statusCode === 266) {\n return decoded instanceof ApiEndUserError ? decoded : new ApiEndUserError(message);\n }\n if (\n decoded !== undefined &&\n ReceivedApiErrorRule.passesThrough(role, statusCode, decoded)\n ) {\n return decoded;\n }\n if (statusCode === 408 || statusCode === 504) {\n return new ApiDependencyTimeoutError(\n `dependency timed out with HTTP ${statusCode}. Downstream said: ${message}`,\n decoded,\n );\n }\n if (statusCode === 429 || (statusCode === 503 && retryAfterSeconds !== undefined)) {\n return new ApiDependencyBackoffError(\n `dependency asked this service to back off after HTTP ${statusCode}. ` +\n `Downstream said: ${message}`,\n retryAfterSeconds,\n decoded,\n );\n }\n if (statusCode === 502) {\n return new ApiBadGatewayError(\n `gateway answered HTTP 502. Downstream said: ${message}`,\n decoded,\n );\n }\n if (statusCode === 503) {\n return new ApiUnavailableError(\n `dependency is unavailable (HTTP 503). Downstream said: ${message}`,\n decoded,\n );\n }\n if (statusCode >= 500) {\n return new ApiDependencyError(\n `dependency answered ${statusCode} — it is broken, not this service. ` +\n `Downstream said: ${message}`,\n decoded,\n );\n }\n return new ApiImplementationError(\n `dependency answered ${statusCode}. That status describes OUR request to it, not an ` +\n `answer for our caller, so this service owns it — check the path, the base URL, ` +\n `whether the dependency is deployed, and our service credentials. ` +\n `Downstream said: ${message}`,\n decoded,\n );\n }\n\n /**\n * The peer's own typed error, rethrown AS-IS, when re-classifying it would change its meaning:\n *\n * - {@link ApiClientTooOldError}: an upgrade, never a bug, on every hop and for every role.\n * - a 401 {@link ApiUnauthorizedError} received by an END-USER client: the credential it sent IS\n * the user's session, so this is \"log in again\". A SERVER's 401 is its own bug and falls through.\n * - a dependency failure: already attributed further downstream.\n */\n // webpieces-disable no-function-outside-class -- stateless shared rule, called by both protocol translators\n private static passesThrough(role: ClientRole, statusCode: number, decoded: Error): boolean {\n if (decoded instanceof ApiClientTooOldError) return true;\n if (statusCode === 401 && decoded instanceof ApiUnauthorizedError) {\n return role === ClientRole.END_USER_CLIENT;\n }\n return (\n decoded instanceof ApiDependencyError ||\n decoded instanceof ApiBadGatewayError ||\n decoded instanceof ApiUnavailableError ||\n decoded instanceof ApiDependencyTimeoutError ||\n decoded instanceof ApiDependencyBackoffError\n );\n }\n}\n"]}
1
+ {"version":3,"file":"ReceivedApiErrorRule.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/errors/ReceivedApiErrorRule.ts"],"names":[],"mappings":";;;AAAA,yCAWoB;AACpB,6CAA0C;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAa,oBAAoB;IAC7B;;;;;;;;;OASG;IACH,4GAA4G;IAC5G,MAAM,CAAC,KAAK,CACR,IAAgB,EAChB,UAAkB,EAClB,OAAe,EACf,OAAkB,EAClB,iBAA0B;QAE1B,gGAAgG;QAChG,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YACrB,OAAO,OAAO,YAAY,0BAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,0BAAe,CAAC,OAAO,CAAC,CAAC;QACvF,CAAC;QACD,IACI,OAAO,KAAK,SAAS;YACrB,oBAAoB,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAC/D,CAAC;YACC,OAAO,OAAO,CAAC;QACnB,CAAC;QACD,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YAC3C,OAAO,IAAI,oCAAyB,CAChC,kCAAkC,UAAU,sBAAsB,OAAO,EAAE,EAC3E,OAAO,CACV,CAAC;QACN,CAAC;QACD,IAAI,UAAU,KAAK,GAAG,IAAI,CAAC,UAAU,KAAK,GAAG,IAAI,iBAAiB,KAAK,SAAS,CAAC,EAAE,CAAC;YAChF,OAAO,IAAI,oCAAyB,CAChC,wDAAwD,UAAU,IAAI;gBAClE,oBAAoB,OAAO,EAAE,EACjC,iBAAiB,EACjB,OAAO,CACV,CAAC;QACN,CAAC;QACD,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YACrB,OAAO,IAAI,6BAAkB,CACzB,+CAA+C,OAAO,EAAE,EACxD,OAAO,CACV,CAAC;QACN,CAAC;QACD,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YACrB,OAAO,IAAI,8BAAmB,CAC1B,0DAA0D,OAAO,EAAE,EACnE,OAAO,CACV,CAAC;QACN,CAAC;QACD,IAAI,UAAU,IAAI,GAAG,EAAE,CAAC;YACpB,OAAO,IAAI,6BAAkB,CACzB,uBAAuB,UAAU,qCAAqC;gBAClE,oBAAoB,OAAO,EAAE,EACjC,OAAO,CACV,CAAC;QACN,CAAC;QACD,OAAO,IAAI,iCAAsB,CAC7B,uBAAuB,UAAU,oDAAoD;YACjF,iFAAiF;YACjF,mEAAmE;YACnE,oBAAoB,OAAO,EAAE,EACjC,OAAO,CACV,CAAC;IACN,CAAC;IAED;;;;;;;;OAQG;IACH,4GAA4G;IACpG,MAAM,CAAC,aAAa,CAAC,IAAgB,EAAE,UAAkB,EAAE,OAAc;QAC7E,IAAI,OAAO,YAAY,+BAAoB;YAAE,OAAO,IAAI,CAAC;QACzD,IAAI,UAAU,KAAK,GAAG,IAAI,OAAO,YAAY,+BAAoB,EAAE,CAAC;YAChE,OAAO,IAAI,KAAK,uBAAU,CAAC,eAAe,CAAC;QAC/C,CAAC;QACD,OAAO,CACH,OAAO,YAAY,6BAAkB;YACrC,OAAO,YAAY,6BAAkB;YACrC,OAAO,YAAY,8BAAmB;YACtC,OAAO,YAAY,oCAAyB;YAC5C,OAAO,YAAY,oCAAyB,CAC/C,CAAC;IACN,CAAC;CACJ;AA9FD,oDA8FC","sourcesContent":["import {\n ApiBadGatewayError,\n ApiClientTooOldError,\n ApiDependencyBackoffError,\n ApiDependencyError,\n ApiDependencyTimeoutError,\n ApiEndUserError,\n ApiError,\n ApiImplementationError,\n ApiUnauthorizedError,\n ApiUnavailableError,\n} from './ApiError';\nimport { ClientRole } from './ClientRole';\n\n/**\n * THE rule for an error this process RECEIVED from another one — identical over HTTP and over IPC,\n * which is why it lives here and not in either protocol's translator.\n *\n * A status a peer answered describes OUR request to it. It is never, by itself, an answer for our\n * own caller, and the two ways it can be wrong have OPPOSITE owners:\n *\n * - **ordinary caller-error status (4xx-equivalent) -> {@link ApiImplementationError}.** WE sent a bad request,\n * called a path that does not exist, or presented the wrong credentials. MY bug. The peer that\n * answered 404 is correct.\n * - **ordinary server-error status (5xx-equivalent) -> {@link ApiDependencyError}.** THEY broke. Not my bug,\n * so this process's failure metrics stay clean and page the right team.\n * - **an {@link ApiDependencyError} coming back -> rethrown AS-IS.** The fault is already attributed,\n * somewhere further downstream; this hop adds nothing by re-wrapping it, and re-wrapping would bury\n * the original message one `cause` deeper on every hop of a chain. That includes\n * `ApiRetriesExhaustedError` (502, `kind: 'retries-exhausted'`): it IS an `ApiDependencyError`, so it\n * passes through as itself, is classified `CallOutcome.FAILURE`, and the upstream does not retry a\n * dependency that a lower layer already retried.\n * - **408/429/502/503/504 preserve retry-relevant semantics.** Timeout, backoff, bad-gateway and\n * unavailable must not collapse into the generic non-retryable dependency bucket.\n * - **266 / `end-user` -> {@link ApiEndUserError}**, the intended user-facing message, published\n * verbatim. It is the one message the taxonomy lets a peer write for a human. `ApiErrorCodec` has\n * already rebuilt the registered SUBCLASS for its `errorCode` (see `EndUserErrorRegistry`).\n * - **426 / `client-too-old` -> {@link ApiClientTooOldError}, rethrown AS-IS, on EVERY hop and for\n * every {@link ClientRole}.** It is not \"I called wrong\", it is \"the build on the calling side is\n * older than the server allows\", and every receiver must see \"upgrade\", never a bug report.\n *\n * # Why this is UNIFORM, in a browser as much as in a server — with ONE exception, 401\n *\n * An earlier design made the browser pass a received status through unchanged on the theory that\n * \"the browser IS the user's agent, so a 404 is the user's answer\". That is wrong. If a browser\n * received a 404 the browser called the wrong path — that is the BROWSER's bug, and the code that\n * catches it wants to know that, not to show a user a spinner. Read in a browser this reads exactly\n * as intended: `ApiImplementationError` means the client has a bug, `ApiDependencyError` means the\n * server has one.\n *\n * **401 is the exception, and it is why the rule takes a {@link ClientRole}.** A received 401 means\n * \"the credential I presented was rejected\", and WHOSE credential that was depends on who I am:\n *\n * - an {@link ClientRole.END_USER_CLIENT} (browser bundle, Expo shell, remote MCP client) presented\n * the USER's session, so a 401 carrying the webpieces `unauthorized` payload is decoded as\n * {@link ApiUnauthorizedError}: \"log in again\" (and, for a remote MCP client, \"go find the\n * authorization server\"), not a bug.\n * - a {@link ClientRole.SERVER} presented ITS OWN credential, so a 401 stays\n * {@link ApiImplementationError} -> a 500 to its own caller. Relaying it as 401 would tell the\n * browser \"your session expired\" and log the user out for a backend misconfiguration, and it would\n * stop paging the team whose credential is broken.\n *\n * Every other status reads the same in both roles, so the role is consulted for 401 alone. The role\n * has no default: each client states it at setup (see {@link ClientRole}).\n *\n * An app that genuinely wants a peer's status relayed as its OWN typed error says so out loud, in one\n * greppable place, by throwing it from its registered translator's `fromWire`.\n */\nexport class ReceivedApiErrorRule {\n /**\n * @param role - who received it, declared at the client's setup. Decides what a 401 means.\n * @param statusCode - the HTTP status the peer published. IPC has no wire status, so it derives\n * the equivalent from the payload's `kind` through the same `ApiErrorHttpStatus` table — one\n * table, so the two protocols cannot drift apart.\n * @param message - the text to carry forward: the decoded payload's message, else whatever the\n * transport could salvage from the body.\n * @param decoded - the peer's own typed error when the body was a webpieces `ApiErrorPayload`.\n * Absent for a foreign responder.\n */\n // webpieces-disable no-function-outside-class -- stateless shared rule, called by both protocol translators\n static adapt(\n role: ClientRole,\n statusCode: number,\n message: string,\n decoded?: ApiError,\n retryAfterSeconds?: number,\n ): Error {\n // 266 is protocol SUCCESS carrying the actor's own answer — the one message published verbatim.\n if (statusCode === 266) {\n return decoded instanceof ApiEndUserError ? decoded : new ApiEndUserError(message);\n }\n if (\n decoded !== undefined &&\n ReceivedApiErrorRule.passesThrough(role, statusCode, decoded)\n ) {\n return decoded;\n }\n if (statusCode === 408 || statusCode === 504) {\n return new ApiDependencyTimeoutError(\n `dependency timed out with HTTP ${statusCode}. Downstream said: ${message}`,\n decoded,\n );\n }\n if (statusCode === 429 || (statusCode === 503 && retryAfterSeconds !== undefined)) {\n return new ApiDependencyBackoffError(\n `dependency asked this service to back off after HTTP ${statusCode}. ` +\n `Downstream said: ${message}`,\n retryAfterSeconds,\n decoded,\n );\n }\n if (statusCode === 502) {\n return new ApiBadGatewayError(\n `gateway answered HTTP 502. Downstream said: ${message}`,\n decoded,\n );\n }\n if (statusCode === 503) {\n return new ApiUnavailableError(\n `dependency is unavailable (HTTP 503). Downstream said: ${message}`,\n decoded,\n );\n }\n if (statusCode >= 500) {\n return new ApiDependencyError(\n `dependency answered ${statusCode} — it is broken, not this service. ` +\n `Downstream said: ${message}`,\n decoded,\n );\n }\n return new ApiImplementationError(\n `dependency answered ${statusCode}. That status describes OUR request to it, not an ` +\n `answer for our caller, so this service owns it — check the path, the base URL, ` +\n `whether the dependency is deployed, and our service credentials. ` +\n `Downstream said: ${message}`,\n decoded,\n );\n }\n\n /**\n * The peer's own typed error, rethrown AS-IS, when re-classifying it would change its meaning:\n *\n * - {@link ApiClientTooOldError}: an upgrade, never a bug, on every hop and for every role.\n * - a 401 {@link ApiUnauthorizedError} received by an END-USER client: the credential it sent IS\n * the user's session, so this is \"log in again\". A SERVER's 401 is its own bug and falls through.\n * - a dependency failure: already attributed further downstream (including a peer's\n * `ApiRetriesExhaustedError`, a subclass of `ApiDependencyError`).\n */\n // webpieces-disable no-function-outside-class -- stateless shared rule, called by both protocol translators\n private static passesThrough(role: ClientRole, statusCode: number, decoded: Error): boolean {\n if (decoded instanceof ApiClientTooOldError) return true;\n if (statusCode === 401 && decoded instanceof ApiUnauthorizedError) {\n return role === ClientRole.END_USER_CLIENT;\n }\n return (\n decoded instanceof ApiDependencyError ||\n decoded instanceof ApiBadGatewayError ||\n decoded instanceof ApiUnavailableError ||\n decoded instanceof ApiDependencyTimeoutError ||\n decoded instanceof ApiDependencyBackoffError\n );\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { ApiError, ApiNotFoundError, ApiDependencyError, ApiBadGatewayError, ApiUnavailableError, ApiDependencyTimeoutError, ApiImplementationError, ApiRateLimitedError, ApiForbiddenError, ApiRequestTimeoutError, ApiConnectionError, ApiEndpointNotFoundError, ApiEndUserError, ApiBadRequestError, ApiUnauthorizedError, ApiDependencyBackoffError, ApiConflictError, ApiUnprocessableError, ApiPreconditionFailedError, ApiUnsupportedMediaTypeError, ApiNotImplementedError, ApiCodedError, ApiClientTooOldError, } from './ApiError';
1
+ export { ApiError, ApiNotFoundError, ApiDependencyError, ApiBadGatewayError, ApiUnavailableError, ApiDependencyTimeoutError, ApiImplementationError, ApiRateLimitedError, ApiForbiddenError, ApiRequestTimeoutError, ApiConnectionError, ApiEndpointNotFoundError, ApiEndUserError, ApiBadRequestError, ApiUnauthorizedError, ApiDependencyBackoffError, ApiRetriesExhaustedError, ApiConflictError, ApiUnprocessableError, ApiPreconditionFailedError, ApiUnsupportedMediaTypeError, ApiNotImplementedError, ApiCodedError, ApiClientTooOldError, } from './ApiError';
2
2
  export { ApiEndUserNotFoundError, ApiEndUserForbiddenError, ApiEndUserBadRequestError, } from './EndUserErrors';
3
3
  export { EndUserErrorCode } from './EndUserErrorCode';
4
4
  export { EndUserErrorRegistry } from './EndUserErrorRegistry';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = exports.ENTITY_NOT_FOUND = exports.ReceivedApiErrorRule = exports.ApiErrorBoundary = exports.ApiErrorPayload = exports.ApiErrorCodec = exports.RpcCallFailed = exports.ReportableApiFailure = exports.ApiCallSite = exports.ClientRole = exports.EndUserErrorRegistry = exports.EndUserErrorCode = exports.ApiEndUserBadRequestError = exports.ApiEndUserForbiddenError = exports.ApiEndUserNotFoundError = exports.ApiClientTooOldError = exports.ApiCodedError = exports.ApiNotImplementedError = exports.ApiUnsupportedMediaTypeError = exports.ApiPreconditionFailedError = exports.ApiUnprocessableError = exports.ApiConflictError = exports.ApiDependencyBackoffError = exports.ApiUnauthorizedError = exports.ApiBadRequestError = exports.ApiEndUserError = exports.ApiEndpointNotFoundError = exports.ApiConnectionError = exports.ApiRequestTimeoutError = exports.ApiForbiddenError = exports.ApiRateLimitedError = exports.ApiImplementationError = exports.ApiDependencyTimeoutError = exports.ApiUnavailableError = exports.ApiBadGatewayError = exports.ApiDependencyError = exports.ApiNotFoundError = exports.ApiError = void 0;
3
+ exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = exports.ENTITY_NOT_FOUND = exports.ReceivedApiErrorRule = exports.ApiErrorBoundary = exports.ApiErrorPayload = exports.ApiErrorCodec = exports.RpcCallFailed = exports.ReportableApiFailure = exports.ApiCallSite = exports.ClientRole = exports.EndUserErrorRegistry = exports.EndUserErrorCode = exports.ApiEndUserBadRequestError = exports.ApiEndUserForbiddenError = exports.ApiEndUserNotFoundError = exports.ApiClientTooOldError = exports.ApiCodedError = exports.ApiNotImplementedError = exports.ApiUnsupportedMediaTypeError = exports.ApiPreconditionFailedError = exports.ApiUnprocessableError = exports.ApiConflictError = exports.ApiRetriesExhaustedError = exports.ApiDependencyBackoffError = exports.ApiUnauthorizedError = exports.ApiBadRequestError = exports.ApiEndUserError = exports.ApiEndpointNotFoundError = exports.ApiConnectionError = exports.ApiRequestTimeoutError = exports.ApiForbiddenError = exports.ApiRateLimitedError = exports.ApiImplementationError = exports.ApiDependencyTimeoutError = exports.ApiUnavailableError = exports.ApiBadGatewayError = exports.ApiDependencyError = exports.ApiNotFoundError = exports.ApiError = void 0;
4
4
  var ApiError_1 = require("./ApiError");
5
5
  Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return ApiError_1.ApiError; } });
6
6
  Object.defineProperty(exports, "ApiNotFoundError", { enumerable: true, get: function () { return ApiError_1.ApiNotFoundError; } });
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "ApiEndUserError", { enumerable: true, get: funct
18
18
  Object.defineProperty(exports, "ApiBadRequestError", { enumerable: true, get: function () { return ApiError_1.ApiBadRequestError; } });
19
19
  Object.defineProperty(exports, "ApiUnauthorizedError", { enumerable: true, get: function () { return ApiError_1.ApiUnauthorizedError; } });
20
20
  Object.defineProperty(exports, "ApiDependencyBackoffError", { enumerable: true, get: function () { return ApiError_1.ApiDependencyBackoffError; } });
21
+ Object.defineProperty(exports, "ApiRetriesExhaustedError", { enumerable: true, get: function () { return ApiError_1.ApiRetriesExhaustedError; } });
21
22
  Object.defineProperty(exports, "ApiConflictError", { enumerable: true, get: function () { return ApiError_1.ApiConflictError; } });
22
23
  Object.defineProperty(exports, "ApiUnprocessableError", { enumerable: true, get: function () { return ApiError_1.ApiUnprocessableError; } });
23
24
  Object.defineProperty(exports, "ApiPreconditionFailedError", { enumerable: true, get: function () { return ApiError_1.ApiPreconditionFailedError; } });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/errors/index.ts"],"names":[],"mappings":";;;AAAA,uCAwBoB;AAvBhB,oGAAA,QAAQ,OAAA;AACR,4GAAA,gBAAgB,OAAA;AAChB,8GAAA,kBAAkB,OAAA;AAClB,8GAAA,kBAAkB,OAAA;AAClB,+GAAA,mBAAmB,OAAA;AACnB,qHAAA,yBAAyB,OAAA;AACzB,kHAAA,sBAAsB,OAAA;AACtB,+GAAA,mBAAmB,OAAA;AACnB,6GAAA,iBAAiB,OAAA;AACjB,kHAAA,sBAAsB,OAAA;AACtB,8GAAA,kBAAkB,OAAA;AAClB,oHAAA,wBAAwB,OAAA;AACxB,2GAAA,eAAe,OAAA;AACf,8GAAA,kBAAkB,OAAA;AAClB,gHAAA,oBAAoB,OAAA;AACpB,qHAAA,yBAAyB,OAAA;AACzB,4GAAA,gBAAgB,OAAA;AAChB,iHAAA,qBAAqB,OAAA;AACrB,sHAAA,0BAA0B,OAAA;AAC1B,wHAAA,4BAA4B,OAAA;AAC5B,kHAAA,sBAAsB,OAAA;AACtB,yGAAA,aAAa,OAAA;AACb,gHAAA,oBAAoB,OAAA;AAExB,iDAIyB;AAHrB,wHAAA,uBAAuB,OAAA;AACvB,yHAAA,wBAAwB,OAAA;AACxB,0HAAA,yBAAyB,OAAA;AAE7B,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAE7B,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AAEpB,+DAA6E;AAApE,4HAAA,oBAAoB,OAAA;AAAE,qHAAA,aAAa,OAAA;AAE5C,iDAAiE;AAAxD,8GAAA,aAAa,OAAA;AAAE,gHAAA,eAAe,OAAA;AACvC,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,yDAS6B;AARzB,qHAAA,gBAAgB,OAAA;AAChB,qHAAA,gBAAgB,OAAA;AAChB,gHAAA,WAAW,OAAA;AACX,iHAAA,YAAY,OAAA;AACZ,wHAAA,mBAAmB,OAAA;AACnB,iHAAA,YAAY,OAAA;AACZ,kHAAA,aAAa,OAAA;AACb,gHAAA,WAAW,OAAA","sourcesContent":["export {\n ApiError,\n ApiNotFoundError,\n ApiDependencyError,\n ApiBadGatewayError,\n ApiUnavailableError,\n ApiDependencyTimeoutError,\n ApiImplementationError,\n ApiRateLimitedError,\n ApiForbiddenError,\n ApiRequestTimeoutError,\n ApiConnectionError,\n ApiEndpointNotFoundError,\n ApiEndUserError,\n ApiBadRequestError,\n ApiUnauthorizedError,\n ApiDependencyBackoffError,\n ApiConflictError,\n ApiUnprocessableError,\n ApiPreconditionFailedError,\n ApiUnsupportedMediaTypeError,\n ApiNotImplementedError,\n ApiCodedError,\n ApiClientTooOldError,\n} from './ApiError';\nexport {\n ApiEndUserNotFoundError,\n ApiEndUserForbiddenError,\n ApiEndUserBadRequestError,\n} from './EndUserErrors';\nexport { EndUserErrorCode } from './EndUserErrorCode';\nexport { EndUserErrorRegistry } from './EndUserErrorRegistry';\nexport type { EndUserErrorFactory } from './EndUserErrorRegistry';\nexport { ClientRole } from './ClientRole';\nexport { ApiCallSite } from './ApiCallSite';\nexport type { CallSiteBoundary } from './ApiCallSite';\nexport { ReportableApiFailure, RpcCallFailed } from './ReportableApiFailure';\nexport type { ApiErrorKind, ApiStatusCode, EdgeHttpStatus } from './ApiError';\nexport { ApiErrorCodec, ApiErrorPayload } from './ApiErrorCodec';\nexport { ApiErrorBoundary } from './ApiErrorBoundary';\nexport { ReceivedApiErrorRule } from './ReceivedApiErrorRule';\nexport {\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './AuthErrorSubtypes';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/errors/index.ts"],"names":[],"mappings":";;;AAAA,uCAyBoB;AAxBhB,oGAAA,QAAQ,OAAA;AACR,4GAAA,gBAAgB,OAAA;AAChB,8GAAA,kBAAkB,OAAA;AAClB,8GAAA,kBAAkB,OAAA;AAClB,+GAAA,mBAAmB,OAAA;AACnB,qHAAA,yBAAyB,OAAA;AACzB,kHAAA,sBAAsB,OAAA;AACtB,+GAAA,mBAAmB,OAAA;AACnB,6GAAA,iBAAiB,OAAA;AACjB,kHAAA,sBAAsB,OAAA;AACtB,8GAAA,kBAAkB,OAAA;AAClB,oHAAA,wBAAwB,OAAA;AACxB,2GAAA,eAAe,OAAA;AACf,8GAAA,kBAAkB,OAAA;AAClB,gHAAA,oBAAoB,OAAA;AACpB,qHAAA,yBAAyB,OAAA;AACzB,oHAAA,wBAAwB,OAAA;AACxB,4GAAA,gBAAgB,OAAA;AAChB,iHAAA,qBAAqB,OAAA;AACrB,sHAAA,0BAA0B,OAAA;AAC1B,wHAAA,4BAA4B,OAAA;AAC5B,kHAAA,sBAAsB,OAAA;AACtB,yGAAA,aAAa,OAAA;AACb,gHAAA,oBAAoB,OAAA;AAExB,iDAIyB;AAHrB,wHAAA,uBAAuB,OAAA;AACvB,yHAAA,wBAAwB,OAAA;AACxB,0HAAA,yBAAyB,OAAA;AAE7B,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAE7B,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AAEpB,+DAA6E;AAApE,4HAAA,oBAAoB,OAAA;AAAE,qHAAA,aAAa,OAAA;AAE5C,iDAAiE;AAAxD,8GAAA,aAAa,OAAA;AAAE,gHAAA,eAAe,OAAA;AACvC,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,yDAS6B;AARzB,qHAAA,gBAAgB,OAAA;AAChB,qHAAA,gBAAgB,OAAA;AAChB,gHAAA,WAAW,OAAA;AACX,iHAAA,YAAY,OAAA;AACZ,wHAAA,mBAAmB,OAAA;AACnB,iHAAA,YAAY,OAAA;AACZ,kHAAA,aAAa,OAAA;AACb,gHAAA,WAAW,OAAA","sourcesContent":["export {\n ApiError,\n ApiNotFoundError,\n ApiDependencyError,\n ApiBadGatewayError,\n ApiUnavailableError,\n ApiDependencyTimeoutError,\n ApiImplementationError,\n ApiRateLimitedError,\n ApiForbiddenError,\n ApiRequestTimeoutError,\n ApiConnectionError,\n ApiEndpointNotFoundError,\n ApiEndUserError,\n ApiBadRequestError,\n ApiUnauthorizedError,\n ApiDependencyBackoffError,\n ApiRetriesExhaustedError,\n ApiConflictError,\n ApiUnprocessableError,\n ApiPreconditionFailedError,\n ApiUnsupportedMediaTypeError,\n ApiNotImplementedError,\n ApiCodedError,\n ApiClientTooOldError,\n} from './ApiError';\nexport {\n ApiEndUserNotFoundError,\n ApiEndUserForbiddenError,\n ApiEndUserBadRequestError,\n} from './EndUserErrors';\nexport { EndUserErrorCode } from './EndUserErrorCode';\nexport { EndUserErrorRegistry } from './EndUserErrorRegistry';\nexport type { EndUserErrorFactory } from './EndUserErrorRegistry';\nexport { ClientRole } from './ClientRole';\nexport { ApiCallSite } from './ApiCallSite';\nexport type { CallSiteBoundary } from './ApiCallSite';\nexport { ReportableApiFailure, RpcCallFailed } from './ReportableApiFailure';\nexport type { ApiErrorKind, ApiStatusCode, EdgeHttpStatus } from './ApiError';\nexport { ApiErrorCodec, ApiErrorPayload } from './ApiErrorCodec';\nexport { ApiErrorBoundary } from './ApiErrorBoundary';\nexport { ReceivedApiErrorRule } from './ReceivedApiErrorRule';\nexport {\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './AuthErrorSubtypes';\n"]}
@@ -42,7 +42,7 @@ import { ApiMethodInfo } from './ApiMethodInfo';
42
42
  export type ApiCallDirection = 'request' | 'response';
43
43
  /**
44
44
  * Response outcome. 'success' covers 2xx AND user errors (400/401/403/404/266 — a successfully
45
- * handled "you made a mistake"); 'failure' is a genuine server error. See {@link LogApiCallImpl.isUserError}.
45
+ * handled "you made a mistake"); 'failure' is a genuine server error. `success` = {@link CallOutcome.EXPECTED} from {@link ClientRegistry.classify}.
46
46
  */
47
47
  export type ApiResult = 'success' | 'failure';
48
48
  /** Re-exported from {@link ApiMethodInfo} (its true home) so existing `ApiSide` imports keep working. */
@@ -62,7 +62,7 @@ export declare class ApiCallInfo {
62
62
  * There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over
63
63
  * in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP
64
64
  * status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is
65
- * the transport-neutral outcome, exactly as {@link LogApiCallImpl.isUserError} classifies by
65
+ * the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
66
66
  * portable Error TYPE rather than by status code.
67
67
  */
68
68
  readonly durationMs?: number | undefined;
@@ -95,7 +95,7 @@ export declare class ApiCallInfo {
95
95
  * There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over
96
96
  * in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP
97
97
  * status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is
98
- * the transport-neutral outcome, exactly as {@link LogApiCallImpl.isUserError} classifies by
98
+ * the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
99
99
  * portable Error TYPE rather than by status code.
100
100
  */
101
101
  durationMs?: number | undefined,
@@ -23,7 +23,7 @@ class ApiCallInfo {
23
23
  * There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over
24
24
  * in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP
25
25
  * status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is
26
- * the transport-neutral outcome, exactly as {@link LogApiCallImpl.isUserError} classifies by
26
+ * the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by
27
27
  * portable Error TYPE rather than by status code.
28
28
  */
29
29
  durationMs,
@@ -1 +1 @@
1
- {"version":3,"file":"ApiCallInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallInfo.ts"],"names":[],"mappings":";;;AAsDA,MAAa,WAAW;IAIP;IACA;IAEA;IAYA;IAUA;IAGA;IAEA;IAjCb;IACI;oCACgC;IACvB,MAAqB,EACrB,IAAsB;IAC/B,sDAAsD;IAC7C,MAAkB;IAC3B;;;;;;;;;;OAUG;IACM,UAAmB;IAC5B;;;;;;;;OAQG;IACM,WAAoB;IAC7B;yFACqF;IAC5E,YAAqB;IAC9B,4FAA4F;IACnF,aAAsB;QA9BtB,WAAM,GAAN,MAAM,CAAe;QACrB,SAAI,GAAJ,IAAI,CAAkB;QAEtB,WAAM,GAAN,MAAM,CAAY;QAYlB,eAAU,GAAV,UAAU,CAAS;QAUnB,gBAAW,GAAX,WAAW,CAAS;QAGpB,iBAAY,GAAZ,YAAY,CAAS;QAErB,kBAAa,GAAb,aAAa,CAAS;IAChC,CAAC;CACP;AApCD,kCAoCC","sourcesContent":["import { ApiMethodInfo } from './ApiMethodInfo';\n\n/**\n * ApiCallInfo - the structured tag stamped into RequestContext around every API call\n * (by {@link LogApiCallImpl}), so ANY log line emitted during the call inherits a filterable\n * `api` object rather than only the req/resp text lines.\n *\n * The node logging backends (winston/bunyan) read this struct out of context via\n * `RequestContext.buildStructuredLogFields()` and emit it AS AN OBJECT under `jsonPayload.api`,\n * which unlocks GCP Cloud Logging filters like:\n * - `jsonPayload.api.method.side=\"client\"` — every outbound call this process made\n * - `jsonPayload.api.method.side=\"server\"` — every inbound call it handled\n * - `jsonPayload.api.method.apiClass=\"SaveApi\"` — one logical method, BOTH sides (client + server)\n * - `jsonPayload.api.result=\"failure\"` — failed exchanges only\n * - `jsonPayload.api.durationMs>1000` — slow calls, either side\n * - `jsonPayload.api.responseSize>100000` — the fat responses (the ones that get chunked)\n * - `jsonPayload.api.responseCount>100` — responses containing many logical items\n * - `jsonPayload.api:*` — \"API traffic only\" (tracing + the recorder)\n *\n * IMPORTANT: the field names here (and on the nested {@link ApiMethodInfo}) ARE the GCP field names —\n * rename a field and the filter renames with it. The identity lives NESTED under `api.method`\n * (`api.method.{side,apiClass,methodName,controllerName}`); `api.type` and `api.result` sit at the top.\n *\n * NOTE: the request `httpMethod`/`path` are NOT here — an inbound request stamps them as the separate\n * top-level logged keys `jsonPayload.httpMethod` / `jsonPayload.requestPath` (see\n * {@link WebpiecesCoreHeaders} + `RequestContextHeaders.fillFromRequest`). Outbound client calls have\n * no inbound path, so they carry only the `api` identity.\n *\n * Per-hop only: the underlying `API_CALL_INFO` ContextKey is NOT transferred over the wire, so a\n * downstream server stamps its own `side:'server'` rather than inheriting the caller's `side:'client'`.\n *\n * Per CLAUDE.md: data-only structures are classes, not interfaces.\n */\n\n/** Which half of the exchange this tag describes: the outgoing 'request' or the returning 'response'. */\n/**\n * Which HALF of one api call a log line describes.\n *\n * Renamed from `ApiType`, which collided with the `@ApiType(...)` CONTRACT decorator and read as if\n * it were about the kind of API rather than the direction of one line. Per\n * `.claude/rules/no-backwards-compat.md` the old spelling is deleted rather than aliased, so a caller\n * on the old name stops compiling and the error names this one.\n */\nexport type ApiCallDirection = 'request' | 'response';\n\n/**\n * Response outcome. 'success' covers 2xx AND user errors (400/401/403/404/266 — a successfully\n * handled \"you made a mistake\"); 'failure' is a genuine server error. See {@link LogApiCallImpl.isUserError}.\n */\nexport type ApiResult = 'success' | 'failure';\n\n/** Re-exported from {@link ApiMethodInfo} (its true home) so existing `ApiSide` imports keep working. */\nexport type { ApiSide } from './ApiMethodInfo';\n\nexport class ApiCallInfo {\n constructor(\n /** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under\n * `jsonPayload.api.method`. */\n readonly method: ApiMethodInfo,\n readonly type: ApiCallDirection,\n /** Response only — undefined on the 'request' tag. */\n readonly result?: ApiResult,\n /**\n * Wall-clock milliseconds the call took. RESPONSE tag only (undefined on 'request') — a\n * request has no duration yet. Present on BOTH the success and failure paths, so\n * `jsonPayload.api.durationMs>1000 AND api.result=\"failure\"` finds slow failures.\n *\n * There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over\n * in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP\n * status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is\n * the transport-neutral outcome, exactly as {@link LogApiCallImpl.isUserError} classifies by\n * portable Error TYPE rather than by status code.\n */\n readonly durationMs?: number,\n /**\n * Bytes of the serialized request DTO. Stamped on BOTH tags: the 'request' tag reports it as\n * soon as it is known, and the 'response' tag repeats it so one record shows the whole\n * exchange (`api.requestSize` + `api.responseSize` without a join).\n *\n * This is the TOTAL size of the body, measured BEFORE any log chunking — chunking is a\n * transport concern handled by the GCP backends, and a body split across 3 records still\n * reports its one true size here.\n */\n readonly requestSize?: number,\n /** Bytes of the serialized response. RESPONSE tag only, and only when the call succeeded —\n * a thrown error produced no response body to measure. Total size, pre-chunking. */\n readonly responseSize?: number,\n /** Number of logical items in a successful response. RESPONSE tag only, opt-in per call. */\n readonly responseCount?: number,\n ) {}\n}\n"]}
1
+ {"version":3,"file":"ApiCallInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallInfo.ts"],"names":[],"mappings":";;;AAsDA,MAAa,WAAW;IAIP;IACA;IAEA;IAYA;IAUA;IAGA;IAEA;IAjCb;IACI;oCACgC;IACvB,MAAqB,EACrB,IAAsB;IAC/B,sDAAsD;IAC7C,MAAkB;IAC3B;;;;;;;;;;OAUG;IACM,UAAmB;IAC5B;;;;;;;;OAQG;IACM,WAAoB;IAC7B;yFACqF;IAC5E,YAAqB;IAC9B,4FAA4F;IACnF,aAAsB;QA9BtB,WAAM,GAAN,MAAM,CAAe;QACrB,SAAI,GAAJ,IAAI,CAAkB;QAEtB,WAAM,GAAN,MAAM,CAAY;QAYlB,eAAU,GAAV,UAAU,CAAS;QAUnB,gBAAW,GAAX,WAAW,CAAS;QAGpB,iBAAY,GAAZ,YAAY,CAAS;QAErB,kBAAa,GAAb,aAAa,CAAS;IAChC,CAAC;CACP;AApCD,kCAoCC","sourcesContent":["import { ApiMethodInfo } from './ApiMethodInfo';\n\n/**\n * ApiCallInfo - the structured tag stamped into RequestContext around every API call\n * (by {@link LogApiCallImpl}), so ANY log line emitted during the call inherits a filterable\n * `api` object rather than only the req/resp text lines.\n *\n * The node logging backends (winston/bunyan) read this struct out of context via\n * `RequestContext.buildStructuredLogFields()` and emit it AS AN OBJECT under `jsonPayload.api`,\n * which unlocks GCP Cloud Logging filters like:\n * - `jsonPayload.api.method.side=\"client\"` — every outbound call this process made\n * - `jsonPayload.api.method.side=\"server\"` — every inbound call it handled\n * - `jsonPayload.api.method.apiClass=\"SaveApi\"` — one logical method, BOTH sides (client + server)\n * - `jsonPayload.api.result=\"failure\"` — failed exchanges only\n * - `jsonPayload.api.durationMs>1000` — slow calls, either side\n * - `jsonPayload.api.responseSize>100000` — the fat responses (the ones that get chunked)\n * - `jsonPayload.api.responseCount>100` — responses containing many logical items\n * - `jsonPayload.api:*` — \"API traffic only\" (tracing + the recorder)\n *\n * IMPORTANT: the field names here (and on the nested {@link ApiMethodInfo}) ARE the GCP field names —\n * rename a field and the filter renames with it. The identity lives NESTED under `api.method`\n * (`api.method.{side,apiClass,methodName,controllerName}`); `api.type` and `api.result` sit at the top.\n *\n * NOTE: the request `httpMethod`/`path` are NOT here — an inbound request stamps them as the separate\n * top-level logged keys `jsonPayload.httpMethod` / `jsonPayload.requestPath` (see\n * {@link WebpiecesCoreHeaders} + `RequestContextHeaders.fillFromRequest`). Outbound client calls have\n * no inbound path, so they carry only the `api` identity.\n *\n * Per-hop only: the underlying `API_CALL_INFO` ContextKey is NOT transferred over the wire, so a\n * downstream server stamps its own `side:'server'` rather than inheriting the caller's `side:'client'`.\n *\n * Per CLAUDE.md: data-only structures are classes, not interfaces.\n */\n\n/** Which half of the exchange this tag describes: the outgoing 'request' or the returning 'response'. */\n/**\n * Which HALF of one api call a log line describes.\n *\n * Renamed from `ApiType`, which collided with the `@ApiType(...)` CONTRACT decorator and read as if\n * it were about the kind of API rather than the direction of one line. Per\n * `.claude/rules/no-backwards-compat.md` the old spelling is deleted rather than aliased, so a caller\n * on the old name stops compiling and the error names this one.\n */\nexport type ApiCallDirection = 'request' | 'response';\n\n/**\n * Response outcome. 'success' covers 2xx AND user errors (400/401/403/404/266 — a successfully\n * handled \"you made a mistake\"); 'failure' is a genuine server error. `success` = {@link CallOutcome.EXPECTED} from {@link ClientRegistry.classify}.\n */\nexport type ApiResult = 'success' | 'failure';\n\n/** Re-exported from {@link ApiMethodInfo} (its true home) so existing `ApiSide` imports keep working. */\nexport type { ApiSide } from './ApiMethodInfo';\n\nexport class ApiCallInfo {\n constructor(\n /** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under\n * `jsonPayload.api.method`. */\n readonly method: ApiMethodInfo,\n readonly type: ApiCallDirection,\n /** Response only — undefined on the 'request' tag. */\n readonly result?: ApiResult,\n /**\n * Wall-clock milliseconds the call took. RESPONSE tag only (undefined on 'request') — a\n * request has no duration yet. Present on BOTH the success and failure paths, so\n * `jsonPayload.api.durationMs>1000 AND api.result=\"failure\"` finds slow failures.\n *\n * There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over\n * in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP\n * status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is\n * the transport-neutral outcome, exactly as {@link ClientRegistry.classify} (a {@link CallOutcome}) classifies by\n * portable Error TYPE rather than by status code.\n */\n readonly durationMs?: number,\n /**\n * Bytes of the serialized request DTO. Stamped on BOTH tags: the 'request' tag reports it as\n * soon as it is known, and the 'response' tag repeats it so one record shows the whole\n * exchange (`api.requestSize` + `api.responseSize` without a join).\n *\n * This is the TOTAL size of the body, measured BEFORE any log chunking — chunking is a\n * transport concern handled by the GCP backends, and a body split across 3 records still\n * reports its one true size here.\n */\n readonly requestSize?: number,\n /** Bytes of the serialized response. RESPONSE tag only, and only when the call succeeded —\n * a thrown error produced no response body to measure. Total size, pre-chunking. */\n readonly responseSize?: number,\n /** Number of logical items in a successful response. RESPONSE tag only, opt-in per call. */\n readonly responseCount?: number,\n ) {}\n}\n"]}
@@ -53,6 +53,9 @@ class ApiErrorHttpStatus {
53
53
  return statusCode ?? 500;
54
54
  case 'dependency':
55
55
  case 'bad-gateway':
56
+ // 502, never 503: a 503 invites generic clients and load balancers to retry, and
57
+ // retries-exhausted exists precisely so that retries happen at ONE layer.
58
+ case 'retries-exhausted':
56
59
  return 502;
57
60
  case 'unavailable':
58
61
  case 'dependency-backoff':
@@ -1 +1 @@
1
- {"version":3,"file":"ApiErrorHttpStatus.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiErrorHttpStatus.ts"],"names":[],"mappings":";;;AAaA,sFAAsF;AACtF,MAAa,kBAAkB;IAC3B;;;;OAIG;IACH,6EAA6E;IAC7E,MAAM,CAAC,OAAO,CAAC,KAAe;QAC1B,OAAO,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC;IACvC,CAAC;IAED;;;;OAIG;IACH,2FAA2F;IAC3F,MAAM,CAAC,OAAO,CAAC,IAAmB,EAAE,UAAmB;QACnD,QAAQ,IAAI,EAAE,CAAC;YACX,KAAK,UAAU;gBACX,OAAO,GAAG,CAAC;YACf,KAAK,aAAa;gBACd,OAAO,GAAG,CAAC;YACf,KAAK,cAAc;gBACf,OAAO,GAAG,CAAC;YACf,KAAK,WAAW;gBACZ,OAAO,GAAG,CAAC;YACf,KAAK,WAAW,CAAC;YACjB,KAAK,oBAAoB;gBACrB,OAAO,GAAG,CAAC;YACf,KAAK,iBAAiB;gBAClB,OAAO,GAAG,CAAC;YACf,KAAK,UAAU;gBACX,OAAO,GAAG,CAAC;YACf,KAAK,qBAAqB;gBACtB,OAAO,GAAG,CAAC;YACf,KAAK,wBAAwB;gBACzB,OAAO,GAAG,CAAC;YACf,KAAK,eAAe;gBAChB,OAAO,GAAG,CAAC;YACf,KAAK,cAAc;gBACf,OAAO,GAAG,CAAC;YACf,KAAK,gBAAgB;gBACjB,OAAO,GAAG,CAAC;YACf,KAAK,iBAAiB;gBAClB,OAAO,GAAG,CAAC;YACf,KAAK,OAAO;gBACR,oFAAoF;gBACpF,4CAA4C;gBAC5C,OAAO,UAAU,IAAI,GAAG,CAAC;YAC7B,KAAK,YAAY,CAAC;YAClB,KAAK,aAAa;gBACd,OAAO,GAAG,CAAC;YACf,KAAK,aAAa,CAAC;YACnB,KAAK,oBAAoB;gBACrB,OAAO,GAAG,CAAC;YACf,KAAK,oBAAoB;gBACrB,OAAO,GAAG,CAAC;QACnB,CAAC;IACL,CAAC;CACJ;AA5DD,gDA4DC","sourcesContent":["import { ApiError, ApiErrorKind } from '../errors/ApiError';\n\n/**\n * Every kind that HAS an HTTP status. `'connection'` is excluded on purpose: a caller-local\n * connection failure is never published as itself — `ApiErrorBoundary.encode` publishes it as\n * `implementation` — so asking this table for its status is a bug the COMPILER now rejects rather\n * than a `throw` at the bottom of a switch.\n */\nexport type PublishedKind = Exclude<ApiErrorKind, 'connection'>;\n\n/** An {@link ApiError} that {@link ApiErrorHttpStatus.hasCode} has proved carries an HTTP status. */\nexport type PublishedApiError = ApiError & { readonly kind: PublishedKind };\n\n/** HTTP-only adapter: semantic errors themselves have no protocol status property. */\nexport class ApiErrorHttpStatus {\n /**\n * CLIENT direction. A client that just DECODED a payload holds an `ApiError`, and needs to know\n * whether the status it received agrees with the kind the body claims. This narrows the error to\n * the kinds {@link codeFor} accepts, so the pair reads as one guarded call with no cast.\n */\n // webpieces-disable no-function-outside-class -- stateless mapping predicate\n static hasCode(error: ApiError): error is PublishedApiError {\n return error.kind !== 'connection';\n }\n\n /**\n * The ONE status table, keyed by the published kind. `statusCode` is the `'coded'` kind's own\n * status — it travels on {@link ApiErrorPayload.statusCode} and on `ApiCodedError.statusCode`,\n * so both directions can supply it without this table reaching back into the error object.\n */\n // webpieces-disable no-function-outside-class -- stateless mapping shared by HTTP adapters\n static codeFor(kind: PublishedKind, statusCode?: number): number {\n switch (kind) {\n case 'end-user':\n return 266;\n case 'bad-request':\n return 400;\n case 'unauthorized':\n return 401;\n case 'forbidden':\n return 403;\n case 'not-found':\n case 'endpoint-not-found':\n return 404;\n case 'request-timeout':\n return 408;\n case 'conflict':\n return 409;\n case 'precondition-failed':\n return 412;\n case 'unsupported-media-type':\n return 415;\n case 'unprocessable':\n return 422;\n case 'rate-limited':\n return 429;\n case 'implementation':\n return 500;\n case 'not-implemented':\n return 501;\n case 'coded':\n // A 'coded' failure whose status did not survive the wire is an internal failure to\n // this adapter, not a status it may invent.\n return statusCode ?? 500;\n case 'dependency':\n case 'bad-gateway':\n return 502;\n case 'unavailable':\n case 'dependency-backoff':\n return 503;\n case 'dependency-timeout':\n return 504;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"ApiErrorHttpStatus.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiErrorHttpStatus.ts"],"names":[],"mappings":";;;AAaA,sFAAsF;AACtF,MAAa,kBAAkB;IAC3B;;;;OAIG;IACH,6EAA6E;IAC7E,MAAM,CAAC,OAAO,CAAC,KAAe;QAC1B,OAAO,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC;IACvC,CAAC;IAED;;;;OAIG;IACH,2FAA2F;IAC3F,MAAM,CAAC,OAAO,CAAC,IAAmB,EAAE,UAAmB;QACnD,QAAQ,IAAI,EAAE,CAAC;YACX,KAAK,UAAU;gBACX,OAAO,GAAG,CAAC;YACf,KAAK,aAAa;gBACd,OAAO,GAAG,CAAC;YACf,KAAK,cAAc;gBACf,OAAO,GAAG,CAAC;YACf,KAAK,WAAW;gBACZ,OAAO,GAAG,CAAC;YACf,KAAK,WAAW,CAAC;YACjB,KAAK,oBAAoB;gBACrB,OAAO,GAAG,CAAC;YACf,KAAK,iBAAiB;gBAClB,OAAO,GAAG,CAAC;YACf,KAAK,UAAU;gBACX,OAAO,GAAG,CAAC;YACf,KAAK,qBAAqB;gBACtB,OAAO,GAAG,CAAC;YACf,KAAK,wBAAwB;gBACzB,OAAO,GAAG,CAAC;YACf,KAAK,eAAe;gBAChB,OAAO,GAAG,CAAC;YACf,KAAK,cAAc;gBACf,OAAO,GAAG,CAAC;YACf,KAAK,gBAAgB;gBACjB,OAAO,GAAG,CAAC;YACf,KAAK,iBAAiB;gBAClB,OAAO,GAAG,CAAC;YACf,KAAK,OAAO;gBACR,oFAAoF;gBACpF,4CAA4C;gBAC5C,OAAO,UAAU,IAAI,GAAG,CAAC;YAC7B,KAAK,YAAY,CAAC;YAClB,KAAK,aAAa,CAAC;YACnB,iFAAiF;YACjF,0EAA0E;YAC1E,KAAK,mBAAmB;gBACpB,OAAO,GAAG,CAAC;YACf,KAAK,aAAa,CAAC;YACnB,KAAK,oBAAoB;gBACrB,OAAO,GAAG,CAAC;YACf,KAAK,oBAAoB;gBACrB,OAAO,GAAG,CAAC;QACnB,CAAC;IACL,CAAC;CACJ;AA/DD,gDA+DC","sourcesContent":["import { ApiError, ApiErrorKind } from '../errors/ApiError';\n\n/**\n * Every kind that HAS an HTTP status. `'connection'` is excluded on purpose: a caller-local\n * connection failure is never published as itself — `ApiErrorBoundary.encode` publishes it as\n * `implementation` — so asking this table for its status is a bug the COMPILER now rejects rather\n * than a `throw` at the bottom of a switch.\n */\nexport type PublishedKind = Exclude<ApiErrorKind, 'connection'>;\n\n/** An {@link ApiError} that {@link ApiErrorHttpStatus.hasCode} has proved carries an HTTP status. */\nexport type PublishedApiError = ApiError & { readonly kind: PublishedKind };\n\n/** HTTP-only adapter: semantic errors themselves have no protocol status property. */\nexport class ApiErrorHttpStatus {\n /**\n * CLIENT direction. A client that just DECODED a payload holds an `ApiError`, and needs to know\n * whether the status it received agrees with the kind the body claims. This narrows the error to\n * the kinds {@link codeFor} accepts, so the pair reads as one guarded call with no cast.\n */\n // webpieces-disable no-function-outside-class -- stateless mapping predicate\n static hasCode(error: ApiError): error is PublishedApiError {\n return error.kind !== 'connection';\n }\n\n /**\n * The ONE status table, keyed by the published kind. `statusCode` is the `'coded'` kind's own\n * status — it travels on {@link ApiErrorPayload.statusCode} and on `ApiCodedError.statusCode`,\n * so both directions can supply it without this table reaching back into the error object.\n */\n // webpieces-disable no-function-outside-class -- stateless mapping shared by HTTP adapters\n static codeFor(kind: PublishedKind, statusCode?: number): number {\n switch (kind) {\n case 'end-user':\n return 266;\n case 'bad-request':\n return 400;\n case 'unauthorized':\n return 401;\n case 'forbidden':\n return 403;\n case 'not-found':\n case 'endpoint-not-found':\n return 404;\n case 'request-timeout':\n return 408;\n case 'conflict':\n return 409;\n case 'precondition-failed':\n return 412;\n case 'unsupported-media-type':\n return 415;\n case 'unprocessable':\n return 422;\n case 'rate-limited':\n return 429;\n case 'implementation':\n return 500;\n case 'not-implemented':\n return 501;\n case 'coded':\n // A 'coded' failure whose status did not survive the wire is an internal failure to\n // this adapter, not a status it may invent.\n return statusCode ?? 500;\n case 'dependency':\n case 'bad-gateway':\n // 502, never 503: a 503 invites generic clients and load balancers to retry, and\n // retries-exhausted exists precisely so that retries happen at ONE layer.\n case 'retries-exhausted':\n return 502;\n case 'unavailable':\n case 'dependency-backoff':\n return 503;\n case 'dependency-timeout':\n return 504;\n }\n }\n}\n"]}