@webpieces/core-util 0.4.790 → 0.4.791

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 (50) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/errors/ReceivedApiErrorRule.d.ts +43 -0
  4. package/src/errors/ReceivedApiErrorRule.js +65 -0
  5. package/src/errors/ReceivedApiErrorRule.js.map +1 -0
  6. package/src/errors/index.d.ts +1 -0
  7. package/src/errors/index.js +3 -1
  8. package/src/errors/index.js.map +1 -1
  9. package/src/http/ClientRegistry.d.ts +29 -18
  10. package/src/http/ClientRegistry.js +37 -27
  11. package/src/http/ClientRegistry.js.map +1 -1
  12. package/src/http/ErrorTranslator.d.ts +115 -0
  13. package/src/http/{ErrorTranslators.js → ErrorTranslator.js} +1 -1
  14. package/src/http/ErrorTranslator.js.map +1 -0
  15. package/src/http/FailureClassifier.d.ts +1 -1
  16. package/src/http/FailureClassifier.js.map +1 -1
  17. package/src/http/HttpResponseDto.d.ts +1 -1
  18. package/src/http/HttpResponseDto.js +1 -1
  19. package/src/http/HttpResponseDto.js.map +1 -1
  20. package/src/http/ServiceInfo.d.ts +1 -1
  21. package/src/http/ServiceInfo.js +1 -1
  22. package/src/http/ServiceInfo.js.map +1 -1
  23. package/src/http/Surface.d.ts +39 -0
  24. package/src/http/Surface.js +28 -0
  25. package/src/http/Surface.js.map +1 -0
  26. package/src/http/WebpiecesCoreHeaders.d.ts +18 -0
  27. package/src/http/WebpiecesCoreHeaders.js +18 -0
  28. package/src/http/WebpiecesCoreHeaders.js.map +1 -1
  29. package/src/http/WebpiecesDefaultErrorTranslator.d.ts +70 -0
  30. package/src/http/WebpiecesDefaultErrorTranslator.js +136 -0
  31. package/src/http/WebpiecesDefaultErrorTranslator.js.map +1 -0
  32. package/src/index.d.ts +5 -1
  33. package/src/index.js +15 -3
  34. package/src/index.js.map +1 -1
  35. package/src/ipc/IpcConnection.js +6 -8
  36. package/src/ipc/IpcConnection.js.map +1 -1
  37. package/src/ipc/IpcErrorTranslator.d.ts +39 -0
  38. package/src/ipc/IpcErrorTranslator.js +3 -0
  39. package/src/ipc/IpcErrorTranslator.js.map +1 -0
  40. package/src/ipc/IpcRegistry.d.ts +55 -0
  41. package/src/ipc/IpcRegistry.js +73 -0
  42. package/src/ipc/IpcRegistry.js.map +1 -0
  43. package/src/ipc/WebpiecesDefaultIpcErrorTranslator.d.ts +28 -0
  44. package/src/ipc/WebpiecesDefaultIpcErrorTranslator.js +49 -0
  45. package/src/ipc/WebpiecesDefaultIpcErrorTranslator.js.map +1 -0
  46. package/src/ipc/index.d.ts +4 -0
  47. package/src/ipc/index.js +9 -1
  48. package/src/ipc/index.js.map +1 -1
  49. package/src/http/ErrorTranslators.d.ts +0 -106
  50. package/src/http/ErrorTranslators.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebpiecesDefaultIpcErrorTranslator.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/ipc/WebpiecesDefaultIpcErrorTranslator.ts"],"names":[],"mappings":";;;AAAA,iEAA8D;AAC9D,2DAAyE;AACzE,iDAAmD;AACnD,yEAAsE;AACtE,mEAAgE;AAIhE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,kCAAkC;IAC1B,QAAQ,GAAG,IAAI,mCAAgB,EAAE,CAAC;IAEnD,MAAM,CAAC,KAAY;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,QAAQ,CAAC,KAAe;QACpB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO;QACX,CAAC;QACD,MAAM,OAAO,GAAG,6BAAa,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClD,2FAA2F;QAC3F,8FAA8F;QAC9F,MAAM,UAAU,GAAG,OAAO,YAAY,wBAAa,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QACrF,MAAM,UAAU,GAAG,uCAAkB,CAAC,OAAO,CAAC,OAAO,CAAC;YAClD,CAAC,CAAC,uCAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC;YACtD,CAAC,CAAC,GAAG,CAAC;QACV,MAAM,2CAAoB,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;CACJ;AApBD,gFAoBC;AAED,oFAAoF;AACvE,QAAA,sCAAsC,GAAG,IAAI,kCAAkC,EAAE,CAAC","sourcesContent":["import { ApiErrorBoundary } from '../errors/ApiErrorBoundary';\nimport { ApiErrorCodec, ApiErrorPayload } from '../errors/ApiErrorCodec';\nimport { ApiCodedError } from '../errors/ApiError';\nimport { ReceivedApiErrorRule } from '../errors/ReceivedApiErrorRule';\nimport { ApiErrorHttpStatus } from '../http/ApiErrorHttpStatus';\nimport { IpcErrorTranslator } from './IpcErrorTranslator';\nimport { IpcReply } from './IpcProtocol';\n\n/**\n * THE webpieces default {@link IpcErrorTranslator}, and what {@link IpcRegistry} holds until an app\n * installs its own.\n *\n * `toWire` is exactly what the two IPC receivers did inline before this seam existed — the shared\n * {@link ApiErrorBoundary}, so the `ApiConnectionError` -> `implementation` rule survives untouched.\n *\n * `fromWire` is a BEHAVIOUR CHANGE and a deliberate one. `IpcClientFactory` used to rethrow\n * `ApiErrorCodec.decode(reply.error)` verbatim, which handed the caller the PEER's error type as if\n * this process had produced it. IPC now applies the SAME {@link ReceivedApiErrorRule} as HTTP: a\n * caller-error kind coming back means I sent a bad IPC request (MY bug ->\n * `ApiImplementationError`), a server-side kind means the peer broke (NOT my bug ->\n * `ApiDependencyError`), an incoming `ApiDependencyError` is already attributed and rethrows as-is,\n * and an `ApiEndUserError` is the actor's own answer and passes through.\n *\n * The two protocols share ONE rule object rather than two copies for the obvious reason: an app that\n * moves a call from IPC to HTTP or back must not thereby change which team gets paged.\n */\nexport class WebpiecesDefaultIpcErrorTranslator implements IpcErrorTranslator {\n private readonly boundary = new ApiErrorBoundary();\n\n toWire(error: Error): ApiErrorPayload {\n return this.boundary.encode(error);\n }\n\n fromWire(reply: IpcReply): void {\n if (reply.type === 'success') {\n return;\n }\n const decoded = ApiErrorCodec.decode(reply.error);\n // IPC has no wire status, so the equivalent is derived from the payload's own kind through\n // the ONE status table HTTP uses. Same table, same verdict, no second ladder to keep in step.\n const statusCode = decoded instanceof ApiCodedError ? decoded.statusCode : undefined;\n const equivalent = ApiErrorHttpStatus.hasCode(decoded)\n ? ApiErrorHttpStatus.codeFor(decoded.kind, statusCode)\n : 500;\n throw ReceivedApiErrorRule.adapt(equivalent, decoded.message, decoded);\n }\n}\n\n/** Process-wide built-in instance — stateless, so one shared instance is enough. */\nexport const WEBPIECES_DEFAULT_IPC_ERROR_TRANSLATOR = new WebpiecesDefaultIpcErrorTranslator();\n"]}
@@ -4,6 +4,10 @@ export type { IpcApiType, IpcEndpointKind, IpcEndpointOptions } from './IpcDecor
4
4
  export { IpcCallContext, IpcRequest, IpcSuccess, IpcFailure, IpcProtocol } from './IpcProtocol';
5
5
  export type { IpcReply, IpcMessage } from './IpcProtocol';
6
6
  export { IpcConnection, IpcConnectionOptions, IpcTransportError, IpcErrors } from './IpcConnection';
7
+ export { IpcRegistry, IpcClientErrorTranslator } from './IpcRegistry';
8
+ export type { IpcErrorTranslator } from './IpcErrorTranslator';
9
+ export { WebpiecesDefaultIpcErrorTranslator, WEBPIECES_DEFAULT_IPC_ERROR_TRANSLATOR, } from './WebpiecesDefaultIpcErrorTranslator';
10
+ export { ApiErrorPayload } from '../errors/ApiErrorCodec';
7
11
  export type { IpcTransport, IpcScheduler, IpcErrorOwner } from './IpcConnection';
8
12
  export { IpcCallLogger } from './IpcLogging';
9
13
  export type { IpcLogging } from './IpcLogging';
package/src/ipc/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toError = exports.ApiCallTimeoutError = exports.LogApiCallImpl = exports.ApiMethodInfo = exports.MaskLog = exports.MaskSpec = exports.IpcCallLogger = exports.IpcErrors = exports.IpcTransportError = exports.IpcConnectionOptions = exports.IpcConnection = exports.IpcProtocol = exports.IpcFailure = exports.IpcSuccess = exports.IpcRequest = exports.IpcCallContext = exports.IPC_METADATA_KEYS = exports.assertNotInternalApi = exports.assertInternalApi = exports.getIpcMaskSpec = exports.getIpcEndpointKind = exports.getIpcEndpoints = exports.getIpcApiId = exports.isInternalApi = exports.WpIpcEndpoint = exports.WpInternal = exports.IpcIdentity = void 0;
3
+ exports.toError = exports.ApiCallTimeoutError = exports.LogApiCallImpl = exports.ApiMethodInfo = exports.MaskLog = exports.MaskSpec = exports.IpcCallLogger = exports.ApiErrorPayload = exports.WEBPIECES_DEFAULT_IPC_ERROR_TRANSLATOR = exports.WebpiecesDefaultIpcErrorTranslator = exports.IpcClientErrorTranslator = exports.IpcRegistry = exports.IpcErrors = exports.IpcTransportError = exports.IpcConnectionOptions = exports.IpcConnection = exports.IpcProtocol = exports.IpcFailure = exports.IpcSuccess = exports.IpcRequest = exports.IpcCallContext = exports.IPC_METADATA_KEYS = exports.assertNotInternalApi = exports.assertInternalApi = exports.getIpcMaskSpec = exports.getIpcEndpointKind = exports.getIpcEndpoints = exports.getIpcApiId = exports.isInternalApi = exports.WpIpcEndpoint = exports.WpInternal = exports.IpcIdentity = void 0;
4
4
  var IpcIdentity_1 = require("./IpcIdentity");
5
5
  Object.defineProperty(exports, "IpcIdentity", { enumerable: true, get: function () { return IpcIdentity_1.IpcIdentity; } });
6
6
  var IpcDecorators_1 = require("./IpcDecorators");
@@ -25,6 +25,14 @@ Object.defineProperty(exports, "IpcConnection", { enumerable: true, get: functio
25
25
  Object.defineProperty(exports, "IpcConnectionOptions", { enumerable: true, get: function () { return IpcConnection_1.IpcConnectionOptions; } });
26
26
  Object.defineProperty(exports, "IpcTransportError", { enumerable: true, get: function () { return IpcConnection_1.IpcTransportError; } });
27
27
  Object.defineProperty(exports, "IpcErrors", { enumerable: true, get: function () { return IpcConnection_1.IpcErrors; } });
28
+ var IpcRegistry_1 = require("./IpcRegistry");
29
+ Object.defineProperty(exports, "IpcRegistry", { enumerable: true, get: function () { return IpcRegistry_1.IpcRegistry; } });
30
+ Object.defineProperty(exports, "IpcClientErrorTranslator", { enumerable: true, get: function () { return IpcRegistry_1.IpcClientErrorTranslator; } });
31
+ var WebpiecesDefaultIpcErrorTranslator_1 = require("./WebpiecesDefaultIpcErrorTranslator");
32
+ Object.defineProperty(exports, "WebpiecesDefaultIpcErrorTranslator", { enumerable: true, get: function () { return WebpiecesDefaultIpcErrorTranslator_1.WebpiecesDefaultIpcErrorTranslator; } });
33
+ Object.defineProperty(exports, "WEBPIECES_DEFAULT_IPC_ERROR_TRANSLATOR", { enumerable: true, get: function () { return WebpiecesDefaultIpcErrorTranslator_1.WEBPIECES_DEFAULT_IPC_ERROR_TRANSLATOR; } });
34
+ var ApiErrorCodec_1 = require("../errors/ApiErrorCodec");
35
+ Object.defineProperty(exports, "ApiErrorPayload", { enumerable: true, get: function () { return ApiErrorCodec_1.ApiErrorPayload; } });
28
36
  var IpcLogging_1 = require("./IpcLogging");
29
37
  Object.defineProperty(exports, "IpcCallLogger", { enumerable: true, get: function () { return IpcLogging_1.IpcCallLogger; } });
30
38
  var LogFieldMask_1 = require("../http/LogFieldMask");
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/ipc/index.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,iDAWyB;AAVrB,2GAAA,UAAU,OAAA;AACV,8GAAA,aAAa,OAAA;AACb,8GAAA,aAAa,OAAA;AACb,4GAAA,WAAW,OAAA;AACX,gHAAA,eAAe,OAAA;AACf,mHAAA,kBAAkB,OAAA;AAClB,+GAAA,cAAc,OAAA;AACd,kHAAA,iBAAiB,OAAA;AACjB,qHAAA,oBAAoB,OAAA;AACpB,kHAAA,iBAAiB,OAAA;AAGrB,6CAAgG;AAAvF,6GAAA,cAAc,OAAA;AAAE,yGAAA,UAAU,OAAA;AAAE,yGAAA,UAAU,OAAA;AAAE,yGAAA,UAAU,OAAA;AAAE,0GAAA,WAAW,OAAA;AAExE,iDAAoG;AAA3F,8GAAA,aAAa,OAAA;AAAE,qHAAA,oBAAoB,OAAA;AAAE,kHAAA,iBAAiB,OAAA;AAAE,0GAAA,SAAS,OAAA;AAE1E,2CAA6C;AAApC,2GAAA,aAAa,OAAA;AAEtB,qDAAgD;AAAvC,wGAAA,QAAQ,OAAA;AACjB,iDAA6C;AAApC,qGAAA,OAAO,OAAA;AAChB,uDAAsD;AAA7C,8GAAA,aAAa,OAAA;AACtB,iDAAoD;AAA3C,4GAAA,cAAc,OAAA;AAEvB,mEAAkE;AAAzD,0HAAA,mBAAmB,OAAA;AAE5B,gDAA4C;AAAnC,qGAAA,OAAO,OAAA","sourcesContent":["export { IpcIdentity } from './IpcIdentity';\nexport {\n WpInternal,\n WpIpcEndpoint,\n isInternalApi,\n getIpcApiId,\n getIpcEndpoints,\n getIpcEndpointKind,\n getIpcMaskSpec,\n assertInternalApi,\n assertNotInternalApi,\n IPC_METADATA_KEYS,\n} from './IpcDecorators';\nexport type { IpcApiType, IpcEndpointKind, IpcEndpointOptions } from './IpcDecorators';\nexport { IpcCallContext, IpcRequest, IpcSuccess, IpcFailure, IpcProtocol } from './IpcProtocol';\nexport type { IpcReply, IpcMessage } from './IpcProtocol';\nexport { IpcConnection, IpcConnectionOptions, IpcTransportError, IpcErrors } from './IpcConnection';\nexport type { IpcTransport, IpcScheduler, IpcErrorOwner } from './IpcConnection';\nexport { IpcCallLogger } from './IpcLogging';\nexport type { IpcLogging } from './IpcLogging';\nexport { MaskSpec } from '../http/LogFieldMask';\nexport { MaskLog } from '../http/decorators';\nexport { ApiMethodInfo } from '../http/ApiMethodInfo';\nexport { LogApiCallImpl } from '../http/LogApiCall';\nexport type { ApiCallContext } from '../http/ApiCallContext';\nexport { ApiCallTimeoutError } from '../http/ApiCallTimeoutError';\n\nexport { toError } from '../lib/errorUtils';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/ipc/index.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,iDAWyB;AAVrB,2GAAA,UAAU,OAAA;AACV,8GAAA,aAAa,OAAA;AACb,8GAAA,aAAa,OAAA;AACb,4GAAA,WAAW,OAAA;AACX,gHAAA,eAAe,OAAA;AACf,mHAAA,kBAAkB,OAAA;AAClB,+GAAA,cAAc,OAAA;AACd,kHAAA,iBAAiB,OAAA;AACjB,qHAAA,oBAAoB,OAAA;AACpB,kHAAA,iBAAiB,OAAA;AAGrB,6CAAgG;AAAvF,6GAAA,cAAc,OAAA;AAAE,yGAAA,UAAU,OAAA;AAAE,yGAAA,UAAU,OAAA;AAAE,yGAAA,UAAU,OAAA;AAAE,0GAAA,WAAW,OAAA;AAExE,iDAAoG;AAA3F,8GAAA,aAAa,OAAA;AAAE,qHAAA,oBAAoB,OAAA;AAAE,kHAAA,iBAAiB,OAAA;AAAE,0GAAA,SAAS,OAAA;AAC1E,6CAAsE;AAA7D,0GAAA,WAAW,OAAA;AAAE,uHAAA,wBAAwB,OAAA;AAE9C,2FAG8C;AAF1C,wJAAA,kCAAkC,OAAA;AAClC,4JAAA,sCAAsC,OAAA;AAE1C,yDAA0D;AAAjD,gHAAA,eAAe,OAAA;AAExB,2CAA6C;AAApC,2GAAA,aAAa,OAAA;AAEtB,qDAAgD;AAAvC,wGAAA,QAAQ,OAAA;AACjB,iDAA6C;AAApC,qGAAA,OAAO,OAAA;AAChB,uDAAsD;AAA7C,8GAAA,aAAa,OAAA;AACtB,iDAAoD;AAA3C,4GAAA,cAAc,OAAA;AAEvB,mEAAkE;AAAzD,0HAAA,mBAAmB,OAAA;AAE5B,gDAA4C;AAAnC,qGAAA,OAAO,OAAA","sourcesContent":["export { IpcIdentity } from './IpcIdentity';\nexport {\n WpInternal,\n WpIpcEndpoint,\n isInternalApi,\n getIpcApiId,\n getIpcEndpoints,\n getIpcEndpointKind,\n getIpcMaskSpec,\n assertInternalApi,\n assertNotInternalApi,\n IPC_METADATA_KEYS,\n} from './IpcDecorators';\nexport type { IpcApiType, IpcEndpointKind, IpcEndpointOptions } from './IpcDecorators';\nexport { IpcCallContext, IpcRequest, IpcSuccess, IpcFailure, IpcProtocol } from './IpcProtocol';\nexport type { IpcReply, IpcMessage } from './IpcProtocol';\nexport { IpcConnection, IpcConnectionOptions, IpcTransportError, IpcErrors } from './IpcConnection';\nexport { IpcRegistry, IpcClientErrorTranslator } from './IpcRegistry';\nexport type { IpcErrorTranslator } from './IpcErrorTranslator';\nexport {\n WebpiecesDefaultIpcErrorTranslator,\n WEBPIECES_DEFAULT_IPC_ERROR_TRANSLATOR,\n} from './WebpiecesDefaultIpcErrorTranslator';\nexport { ApiErrorPayload } from '../errors/ApiErrorCodec';\nexport type { IpcTransport, IpcScheduler, IpcErrorOwner } from './IpcConnection';\nexport { IpcCallLogger } from './IpcLogging';\nexport type { IpcLogging } from './IpcLogging';\nexport { MaskSpec } from '../http/LogFieldMask';\nexport { MaskLog } from '../http/decorators';\nexport { ApiMethodInfo } from '../http/ApiMethodInfo';\nexport { LogApiCallImpl } from '../http/LogApiCall';\nexport type { ApiCallContext } from '../http/ApiCallContext';\nexport { ApiCallTimeoutError } from '../http/ApiCallTimeoutError';\n\nexport { toError } from '../lib/errorUtils';\n"]}
@@ -1,106 +0,0 @@
1
- import { HttpResponseDto } from './HttpResponseDto';
2
- /**
3
- * ErrorTranslators - ONE symmetric place an app owns error translation, in BOTH directions, over the
4
- * WHOLE response.
5
- *
6
- * An app implements this ONCE and registers it ONCE per process via
7
- * {@link ClientRegistry.setErrorTranslators} — on the server AND in the browser. Its `toWire` runs on
8
- * the SERVER (`ExpressWrapper.handleError`) and its `fromWire` runs on every CLIENT in that process
9
- * (`ClientErrorTranslator.translateError`, shared by `http-client-node` and `http-client-browser`).
10
- * The payoff is type symmetry across the wire: the server throws `OrderNotFoundError` and the caller
11
- * CATCHES `OrderNotFoundError`, instead of decoding a status code by hand at every call site.
12
- *
13
- * # Both halves speak {@link HttpResponseDto}, and that is the point
14
- *
15
- * `toWire` PRODUCES exactly what `fromWire` CONSUMES. Reading the two methods against each other in
16
- * one file is what makes a mistake visible, which is why this is one object rather than two
17
- * separately-registered functions.
18
- *
19
- * # The whole response, not a status plus a body
20
- *
21
- * The previous contract could express only `(statusCode, protocolError)`, so an app could not put its
22
- * own trace id, `Retry-After`, `WWW-Authenticate` or a cookie on an error response. A translator now
23
- * returns the status code, the reason phrase, the header LIST and the body — everything.
24
- *
25
- * # `toWire` REPLACES the default; it declines by DELEGATING to it
26
- *
27
- * `toWire` never returns `undefined`. An error in, the exact wire shape out — the same contract every
28
- * other outbound boundary in the framework has. A registered translator is THE server-side
29
- * translator for this process, so "not mine" is spelled by calling the webpieces default, which is a
30
- * public class it already has: `new ApiErrorHttpMapper('gui').toResponse(error)`
31
- * (`@webpieces/http-server`) — pass `'edge'` instead when this router was configured with
32
- * `setEndUserStatus('edge')`, so a delegated end-user error keeps that router's published status.
33
- *
34
- * That is one call, it is visible at the call site, and it composes: an app that wants to DECORATE
35
- * the default rather than replace it does so by calling it and editing the result. A `next` parameter
36
- * was considered and rejected — it buys nothing this does not, and it puts the framework back in the
37
- * business of sequencing somebody else's precedence.
38
- *
39
- * # `fromWire` KEEPS `undefined`, and it is not the same question
40
- *
41
- * On the CLIENT half, `undefined` is not "fall back" — it is PROVENANCE. `TranslatedFailure`
42
- * carries `appRegistered`, and `NodeProxyClient.adaptDownstreamFailure` reads it to decide whether a
43
- * downstream 4xx is relayed as the app's own typed error or wrapped as this service's 500. An
44
- * `ApiNotFoundError` the built-in 404 branch produced and one an app CLAIMED are identical as
45
- * values and mean opposite things, so the claim has to be expressible. Returning
46
- * `ClientErrorTranslator.builtInError(response)` instead would silently assert the app claimed every
47
- * status it has an opinion about, and the server-to-server 4xx-to-500 invariant would stop firing.
48
- *
49
- * There is exactly ONE registered ErrorTranslators per process (a `set`, not an `add` to a list):
50
- * precedence between an app's own layers is the APP's to compose explicitly inside its `toWire`,
51
- * rather than something hidden in registration order inside the framework.
52
- *
53
- * # Not covered: 404 / unknown route
54
- *
55
- * A request that matches no route has no route context and never reaches this seam. That is
56
- * deliberate and out of scope.
57
- *
58
- * This is a business-logic contract (methods, not data), so it is an interface per the webpieces
59
- * guidelines.
60
- *
61
- * ```ts
62
- * class OrderErrorPayload {
63
- * constructor(public message: string, public errorCode: string) {}
64
- * }
65
- *
66
- * export class OrderErrorTranslators implements ErrorTranslators {
67
- * private readonly fallback = new ApiErrorHttpMapper('gui');
68
- *
69
- * toWire(error: Error): HttpResponseDto {
70
- * if (!(error instanceof OrderNotFoundError)) {
71
- * return this.fallback.toResponse(error); // not mine -> webpieces default
72
- * }
73
- * const body = new OrderErrorPayload(error.message, 'ORDER_NOT_FOUND');
74
- * return new HttpResponseDto(
75
- * new HttpResponseStatus(460, 'Order Not Found'),
76
- * [new HttpHeader('x-order-trace', error.traceId)],
77
- * body,
78
- * );
79
- * }
80
- *
81
- * fromWire(response: HttpResponseDto): Error | undefined {
82
- * if (response.status.code !== 460) {
83
- * return undefined; // not claimed -> webpieces' built-in mapping
84
- * }
85
- * return new OrderNotFoundError(String(response.body));
86
- * }
87
- * }
88
- *
89
- * // startup, ONCE per process (server AND browser)
90
- * ClientRegistry.setErrorTranslators(new OrderErrorTranslators());
91
- * ```
92
- */
93
- export interface ErrorTranslators {
94
- /**
95
- * SERVER: exception -> the ENTIRE response (status code, reason phrase, headers, body). Always a
96
- * response; decline by returning the webpieces default's own (`ApiErrorHttpMapper.toResponse`).
97
- */
98
- toWire(error: Error): HttpResponseDto;
99
- /**
100
- * CLIENT: the ENTIRE response -> a typed exception. Receives the SAME shape `toWire` produces,
101
- * normalised from whichever transport read it. `undefined` => this app does not CLAIM this
102
- * status; webpieces' built-in status-to-type mapping answers instead, and the failure is marked
103
- * as not app-registered (see the class doc — that flag is what an environment hook acts on).
104
- */
105
- fromWire(response: HttpResponseDto): Error | undefined;
106
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"ErrorTranslators.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ErrorTranslators.ts"],"names":[],"mappings":"","sourcesContent":["import { HttpResponseDto } from './HttpResponseDto';\n\n/**\n * ErrorTranslators - ONE symmetric place an app owns error translation, in BOTH directions, over the\n * WHOLE response.\n *\n * An app implements this ONCE and registers it ONCE per process via\n * {@link ClientRegistry.setErrorTranslators} — on the server AND in the browser. Its `toWire` runs on\n * the SERVER (`ExpressWrapper.handleError`) and its `fromWire` runs on every CLIENT in that process\n * (`ClientErrorTranslator.translateError`, shared by `http-client-node` and `http-client-browser`).\n * The payoff is type symmetry across the wire: the server throws `OrderNotFoundError` and the caller\n * CATCHES `OrderNotFoundError`, instead of decoding a status code by hand at every call site.\n *\n * # Both halves speak {@link HttpResponseDto}, and that is the point\n *\n * `toWire` PRODUCES exactly what `fromWire` CONSUMES. Reading the two methods against each other in\n * one file is what makes a mistake visible, which is why this is one object rather than two\n * separately-registered functions.\n *\n * # The whole response, not a status plus a body\n *\n * The previous contract could express only `(statusCode, protocolError)`, so an app could not put its\n * own trace id, `Retry-After`, `WWW-Authenticate` or a cookie on an error response. A translator now\n * returns the status code, the reason phrase, the header LIST and the body — everything.\n *\n * # `toWire` REPLACES the default; it declines by DELEGATING to it\n *\n * `toWire` never returns `undefined`. An error in, the exact wire shape out — the same contract every\n * other outbound boundary in the framework has. A registered translator is THE server-side\n * translator for this process, so \"not mine\" is spelled by calling the webpieces default, which is a\n * public class it already has: `new ApiErrorHttpMapper('gui').toResponse(error)`\n * (`@webpieces/http-server`) — pass `'edge'` instead when this router was configured with\n * `setEndUserStatus('edge')`, so a delegated end-user error keeps that router's published status.\n *\n * That is one call, it is visible at the call site, and it composes: an app that wants to DECORATE\n * the default rather than replace it does so by calling it and editing the result. A `next` parameter\n * was considered and rejected — it buys nothing this does not, and it puts the framework back in the\n * business of sequencing somebody else's precedence.\n *\n * # `fromWire` KEEPS `undefined`, and it is not the same question\n *\n * On the CLIENT half, `undefined` is not \"fall back\" — it is PROVENANCE. `TranslatedFailure`\n * carries `appRegistered`, and `NodeProxyClient.adaptDownstreamFailure` reads it to decide whether a\n * downstream 4xx is relayed as the app's own typed error or wrapped as this service's 500. An\n * `ApiNotFoundError` the built-in 404 branch produced and one an app CLAIMED are identical as\n * values and mean opposite things, so the claim has to be expressible. Returning\n * `ClientErrorTranslator.builtInError(response)` instead would silently assert the app claimed every\n * status it has an opinion about, and the server-to-server 4xx-to-500 invariant would stop firing.\n *\n * There is exactly ONE registered ErrorTranslators per process (a `set`, not an `add` to a list):\n * precedence between an app's own layers is the APP's to compose explicitly inside its `toWire`,\n * rather than something hidden in registration order inside the framework.\n *\n * # Not covered: 404 / unknown route\n *\n * A request that matches no route has no route context and never reaches this seam. That is\n * deliberate and out of scope.\n *\n * This is a business-logic contract (methods, not data), so it is an interface per the webpieces\n * guidelines.\n *\n * ```ts\n * class OrderErrorPayload {\n * constructor(public message: string, public errorCode: string) {}\n * }\n *\n * export class OrderErrorTranslators implements ErrorTranslators {\n * private readonly fallback = new ApiErrorHttpMapper('gui');\n *\n * toWire(error: Error): HttpResponseDto {\n * if (!(error instanceof OrderNotFoundError)) {\n * return this.fallback.toResponse(error); // not mine -> webpieces default\n * }\n * const body = new OrderErrorPayload(error.message, 'ORDER_NOT_FOUND');\n * return new HttpResponseDto(\n * new HttpResponseStatus(460, 'Order Not Found'),\n * [new HttpHeader('x-order-trace', error.traceId)],\n * body,\n * );\n * }\n *\n * fromWire(response: HttpResponseDto): Error | undefined {\n * if (response.status.code !== 460) {\n * return undefined; // not claimed -> webpieces' built-in mapping\n * }\n * return new OrderNotFoundError(String(response.body));\n * }\n * }\n *\n * // startup, ONCE per process (server AND browser)\n * ClientRegistry.setErrorTranslators(new OrderErrorTranslators());\n * ```\n */\nexport interface ErrorTranslators {\n /**\n * SERVER: exception -> the ENTIRE response (status code, reason phrase, headers, body). Always a\n * response; decline by returning the webpieces default's own (`ApiErrorHttpMapper.toResponse`).\n */\n toWire(error: Error): HttpResponseDto;\n\n /**\n * CLIENT: the ENTIRE response -> a typed exception. Receives the SAME shape `toWire` produces,\n * normalised from whichever transport read it. `undefined` => this app does not CLAIM this\n * status; webpieces' built-in status-to-type mapping answers instead, and the failure is marked\n * as not app-registered (see the class doc — that flag is what an environment hook acts on).\n */\n fromWire(response: HttpResponseDto): Error | undefined;\n}\n"]}