@webpieces/http-client-core 0.4.788 → 0.4.790
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-client-core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.790",
|
|
4
4
|
"description": "Isomorphic core of the webpieces HTTP client: the decorator-driven ProxyClient, error translation, and the Proxy trap shared by http-client-node and http-client-browser",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,6 +21,6 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@webpieces/core-util": "0.4.
|
|
24
|
+
"@webpieces/core-util": "0.4.790"
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -8,7 +8,10 @@ const UnexpectedApiResponseError_1 = require("./UnexpectedApiResponseError");
|
|
|
8
8
|
class ClientErrorTranslator {
|
|
9
9
|
// webpieces-disable no-function-outside-class -- pure stateless mapping shared by browser and node
|
|
10
10
|
static translateError(response) {
|
|
11
|
-
|
|
11
|
+
// `undefined` here is PROVENANCE, not a fallback: `TranslatedFailure.appRegistered` is what
|
|
12
|
+
// `NodeProxyClient.adaptDownstreamFailure` reads to tell an app's deliberate claim on a
|
|
13
|
+
// status apart from the framework's generic default for it.
|
|
14
|
+
const custom = core_util_1.ClientRegistry.getErrorTranslators()?.fromWire(response);
|
|
12
15
|
if (custom !== undefined)
|
|
13
16
|
return new TranslatedFailure_1.TranslatedFailure(custom, true, response.status.code);
|
|
14
17
|
return new TranslatedFailure_1.TranslatedFailure(this.builtInError(response), false, response.status.code);
|
|
@@ -18,8 +21,12 @@ class ClientErrorTranslator {
|
|
|
18
21
|
static builtInError(response) {
|
|
19
22
|
if (core_util_1.ApiErrorCodec.isPayload(response.body)) {
|
|
20
23
|
const decoded = core_util_1.ApiErrorCodec.decode(response.body);
|
|
24
|
+
// `statusCode` is the 'coded' kind's own status, the one thing the shared table cannot
|
|
25
|
+
// know. This side legitimately holds an `ApiError` — it just decoded one — which is why
|
|
26
|
+
// `hasCode` stays: it narrows the kind to the published subset `codeFor` accepts.
|
|
27
|
+
const statusCode = decoded instanceof core_util_1.ApiCodedError ? decoded.statusCode : undefined;
|
|
21
28
|
if (core_util_1.ApiErrorHttpStatus.hasCode(decoded) &&
|
|
22
|
-
core_util_1.ApiErrorHttpStatus.
|
|
29
|
+
core_util_1.ApiErrorHttpStatus.codeFor(decoded.kind, statusCode) === response.status.code)
|
|
23
30
|
return decoded;
|
|
24
31
|
return new core_util_1.ApiImplementationError('Internal Error', undefined, true);
|
|
25
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/ClientErrorTranslator.ts"],"names":[],"mappings":";;;AAAA,oDAsB8B;AAC9B,2DAAwD;AACxD,6EAA0E;AAE1E,yEAAyE;AACzE,MAAa,qBAAqB;IAC9B,mGAAmG;IACnG,MAAM,CAAC,cAAc,CAAC,QAAyB;QAC3C,MAAM,MAAM,GAAG,0BAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"ClientErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/ClientErrorTranslator.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApiBadRequestError,\n ApiCodedError,\n ApiConflictError,\n ApiDependencyError,\n ApiDependencyTimeoutError,\n ApiEndUserError,\n ApiErrorCodec,\n ApiForbiddenError,\n ApiImplementationError,\n ApiNotFoundError,\n ApiNotImplementedError,\n ApiPreconditionFailedError,\n ApiRateLimitedError,\n ApiRequestTimeoutError,\n ApiUnauthorizedError,\n ApiUnavailableError,\n ApiUnprocessableError,\n ApiUnsupportedMediaTypeError,\n ClientRegistry,\n ApiErrorHttpStatus,\n HttpResponseDto,\n} from '@webpieces/core-util';\nimport { TranslatedFailure } from './TranslatedFailure';\nimport { UnexpectedApiResponseError } from './UnexpectedApiResponseError';\n\n/** Reconstructs transport-neutral API failures from an HTTP response. */\nexport class ClientErrorTranslator {\n // webpieces-disable no-function-outside-class -- pure stateless mapping shared by browser and node\n static translateError(response: HttpResponseDto): TranslatedFailure {\n // `undefined` here is PROVENANCE, not a fallback: `TranslatedFailure.appRegistered` is what\n // `NodeProxyClient.adaptDownstreamFailure` reads to tell an app's deliberate claim on a\n // status apart from the framework's generic default for it.\n const custom = ClientRegistry.getErrorTranslators()?.fromWire(response);\n if (custom !== undefined) return new TranslatedFailure(custom, true, response.status.code);\n return new TranslatedFailure(this.builtInError(response), false, response.status.code);\n }\n\n /** Semantic body kind is authoritative when it agrees with the HTTP adapter status. */\n // webpieces-disable no-function-outside-class -- public delegable default mapping\n static builtInError(response: HttpResponseDto): Error {\n if (ApiErrorCodec.isPayload(response.body)) {\n const decoded = ApiErrorCodec.decode(response.body);\n // `statusCode` is the 'coded' kind's own status, the one thing the shared table cannot\n // know. This side legitimately holds an `ApiError` — it just decoded one — which is why\n // `hasCode` stays: it narrows the kind to the published subset `codeFor` accepts.\n const statusCode = decoded instanceof ApiCodedError ? decoded.statusCode : undefined;\n if (\n ApiErrorHttpStatus.hasCode(decoded) &&\n ApiErrorHttpStatus.codeFor(decoded.kind, statusCode) === response.status.code\n )\n return decoded;\n return new ApiImplementationError('Internal Error', undefined, true);\n }\n return this.fromStatus(\n response.status.code,\n this.fallbackMessage(response.body, response.status.reason),\n );\n }\n\n /** Non-Webpieces responders: named statuses map to their class, any other 100-599 to ApiCodedError. */\n // webpieces-disable no-function-outside-class -- fallback for non-Webpieces HTTP responders\n private static fromStatus(statusCode: number, message: string): Error {\n switch (statusCode) {\n case 266:\n return new ApiEndUserError(message);\n case 400:\n return new ApiBadRequestError(message);\n case 401:\n return new ApiUnauthorizedError(message);\n case 403:\n return new ApiForbiddenError(message);\n case 404:\n return new ApiNotFoundError(message);\n case 408:\n return new ApiRequestTimeoutError(message);\n case 409:\n return new ApiConflictError(message);\n case 412:\n return new ApiPreconditionFailedError(message);\n case 415:\n return new ApiUnsupportedMediaTypeError(message);\n case 422:\n return new ApiUnprocessableError(message);\n case 429:\n return new ApiRateLimitedError(message);\n case 500:\n return new ApiImplementationError('Internal Error', undefined, true);\n case 501:\n return new ApiNotImplementedError(message);\n case 502:\n return new ApiDependencyError(message);\n case 503:\n return new ApiUnavailableError(message);\n case 504:\n return new ApiDependencyTimeoutError(message);\n default:\n // Any other real HTTP status keeps its code; only a value no HTTP status can hold is unexpected.\n if (ApiCodedError.isStatusCode(statusCode))\n return new ApiCodedError(message, statusCode);\n return new UnexpectedApiResponseError(statusCode, message);\n }\n }\n\n // webpieces-disable no-any-unknown -- HTTP response bodies are app-owned until this boundary safely inspects them\n // webpieces-disable no-function-outside-class -- bounds diagnostic text from a foreign responder\n private static fallbackMessage(body: unknown, reason: string): string {\n if (typeof body === 'object' && body !== null) {\n const message = Object.getOwnPropertyDescriptor(body, 'message')?.value;\n if (typeof message === 'string' && message.length > 0) return message.slice(0, 4096);\n }\n return reason || 'Request Failed';\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DtoValue, RequestStream, StreamCorrelation, StreamFailureOptions, StreamingEndpointMetadata } from '@webpieces/core-util';
|
|
2
2
|
import { ByteReadableStream } from './ByteStream';
|
|
3
3
|
/** NDJSON upload half: exactly one JSON envelope plus LF per acknowledged write. */
|
|
4
4
|
export declare class NdjsonRequestStream implements RequestStream<DtoValue> {
|
|
@@ -13,7 +13,12 @@ export declare class NdjsonRequestStream implements RequestStream<DtoValue> {
|
|
|
13
13
|
private cancelled;
|
|
14
14
|
constructor(metadata: StreamingEndpointMetadata, abort: (reason?: unknown) => void);
|
|
15
15
|
event(value: DtoValue, correlation?: StreamCorrelation): Promise<void>;
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Takes `Error`: webpieces never demands its own `ApiError` taxonomy from an application. The
|
|
18
|
+
* faithful codec encode, for the reason {@link ResponseStream.fail} documents — a
|
|
19
|
+
* `StreamTransportError` must reach the peer as kind `connection`.
|
|
20
|
+
*/
|
|
21
|
+
fail(error: Error, correlation?: StreamCorrelation, options?: StreamFailureOptions): Promise<void>;
|
|
17
22
|
complete(): Promise<void>;
|
|
18
23
|
cancel(reason?: unknown): Promise<void>;
|
|
19
24
|
transportFailed(reason: unknown): Promise<void>;
|
|
@@ -32,6 +32,11 @@ class NdjsonRequestStream {
|
|
|
32
32
|
this.validator.validate(this.metadata.requestEventClass, value, 'request');
|
|
33
33
|
await this.write(new core_util_1.StreamEnvelope('event', value, undefined, correlation));
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Takes `Error`: webpieces never demands its own `ApiError` taxonomy from an application. The
|
|
37
|
+
* faithful codec encode, for the reason {@link ResponseStream.fail} documents — a
|
|
38
|
+
* `StreamTransportError` must reach the peer as kind `connection`.
|
|
39
|
+
*/
|
|
35
40
|
async fail(error, correlation, options) {
|
|
36
41
|
this.requireWritable();
|
|
37
42
|
const terminal = options?.terminal ?? true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NdjsonRequestStream.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/NdjsonRequestStream.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"NdjsonRequestStream.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/NdjsonRequestStream.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApiErrorCodec,\n DtoValue,\n RequestStream,\n StreamCorrelation,\n StreamEnvelope,\n StreamEventValidator,\n StreamFailureOptions,\n StreamingEndpointMetadata,\n StreamTransportError,\n toError,\n} from '@webpieces/core-util';\nimport { StreamEnvelopeCodec } from './StreamEnvelopeCodec';\nimport { Utf8Codec } from './Utf8Codec';\nimport { ByteReadableStream } from './ByteStream';\n\n/** NDJSON upload half: exactly one JSON envelope plus LF per acknowledged write. */\nexport class NdjsonRequestStream implements RequestStream<DtoValue> {\n readonly body: ByteReadableStream;\n private readonly writer: WritableStreamDefaultWriter<Uint8Array>;\n private readonly encoder = new Utf8Codec();\n private readonly validator = new StreamEventValidator();\n private readonly ready: Promise<void>;\n private terminal = false;\n private cancelled = false;\n\n constructor(\n private readonly metadata: StreamingEndpointMetadata,\n // webpieces-disable no-any-unknown -- AbortSignal reasons are platform-defined\n private readonly abort: (reason?: unknown) => void,\n ) {\n const transport = new TransformStream<Uint8Array, Uint8Array>();\n this.body = transport.readable;\n this.writer = transport.writable.getWriter();\n // A blank NDJSON line is a transport preamble. It makes Node/undici send request headers so\n // the server can establish the SSE response before the caller has its request writer.\n this.ready = this.writer.write(this.encoder.encode('\\n'));\n }\n\n async event(value: DtoValue, correlation?: StreamCorrelation): Promise<void> {\n this.requireWritable();\n this.validator.validate(this.metadata.requestEventClass, value, 'request');\n await this.write(new StreamEnvelope('event', value, undefined, correlation));\n }\n\n /**\n * Takes `Error`: webpieces never demands its own `ApiError` taxonomy from an application. The\n * faithful codec encode, for the reason {@link ResponseStream.fail} documents — a\n * `StreamTransportError` must reach the peer as kind `connection`.\n */\n async fail(\n error: Error,\n correlation?: StreamCorrelation,\n options?: StreamFailureOptions,\n ): Promise<void> {\n this.requireWritable();\n const terminal = options?.terminal ?? true;\n await this.write(\n new StreamEnvelope<DtoValue>(\n 'failure',\n undefined,\n ApiErrorCodec.encode(error),\n correlation,\n terminal,\n ),\n );\n if (terminal) await this.closeWriter();\n }\n\n async complete(): Promise<void> {\n this.requireWritable();\n await this.write(new StreamEnvelope<DtoValue>('complete'));\n await this.closeWriter();\n }\n\n // webpieces-disable no-any-unknown -- AbortSignal reasons are platform-defined\n async cancel(reason?: unknown): Promise<void> {\n if (this.terminal || this.cancelled) return;\n this.cancelled = true;\n this.terminal = true;\n this.abort(reason);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- abort may race a fetch-owned stream close\n try {\n await this.writer.abort(reason);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n // webpieces-disable no-any-unknown -- a transport failure carries the platform cause\n async transportFailed(reason: unknown): Promise<void> {\n await this.cancel(reason);\n }\n\n private async write(envelope: StreamEnvelope<DtoValue>): Promise<void> {\n await this.ready;\n const line = `${StreamEnvelopeCodec.encode(envelope)}\\n`;\n // WritableStream.write resolves only when the fetch consumer accepts the chunk.\n await this.writer.write(this.encoder.encode(line));\n }\n\n private async closeWriter(): Promise<void> {\n this.terminal = true;\n await this.ready;\n await this.writer.close();\n }\n\n private requireWritable(): void {\n if (this.terminal || this.cancelled) {\n throw new StreamTransportError('Cannot write after stream termination.');\n }\n }\n}\n"]}
|