@webpieces/http-client-core 0.4.838 → 0.4.840
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 +2 -2
- package/src/DuplexStreamingCall.d.ts +25 -0
- package/src/DuplexStreamingCall.js +107 -0
- package/src/DuplexStreamingCall.js.map +1 -0
- package/src/HttpTransport.d.ts +6 -0
- package/src/HttpTransport.js +3 -0
- package/src/HttpTransport.js.map +1 -0
- package/src/JsonlRequestStream.d.ts +25 -0
- package/src/JsonlRequestStream.js +104 -0
- package/src/JsonlRequestStream.js.map +1 -0
- package/src/JsonlResponseStream.d.ts +13 -0
- package/src/JsonlResponseStream.js +131 -0
- package/src/JsonlResponseStream.js.map +1 -0
- package/src/ProxyClient.d.ts +5 -15
- package/src/ProxyClient.js +24 -78
- package/src/ProxyClient.js.map +1 -1
- package/src/ResponseStreamingCall.d.ts +26 -0
- package/src/ResponseStreamingCall.js +86 -0
- package/src/ResponseStreamingCall.js.map +1 -0
- package/src/ResponseStreamingRequestFactory.d.ts +8 -0
- package/src/ResponseStreamingRequestFactory.js +39 -0
- package/src/ResponseStreamingRequestFactory.js.map +1 -0
- package/src/index.d.ts +1 -3
- package/src/index.js +1 -7
- package/src/index.js.map +1 -1
- package/src/NdjsonRequestStream.d.ts +0 -28
- package/src/NdjsonRequestStream.js +0 -90
- package/src/NdjsonRequestStream.js.map +0 -1
- package/src/SseResponseStream.d.ts +0 -9
- package/src/SseResponseStream.js +0 -75
- package/src/SseResponseStream.js.map +0 -1
- package/src/StreamEnvelopeCodec.d.ts +0 -7
- package/src/StreamEnvelopeCodec.js +0 -67
- package/src/StreamEnvelopeCodec.js.map +0 -1
package/src/index.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* a browser bundle, and nothing browser-only (a ContextReader store) reaches a server.
|
|
27
27
|
*/
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.Utf8Codec = exports.StreamingCapabilityError = exports.
|
|
29
|
+
exports.Utf8Codec = exports.StreamingCapabilityError = exports.SseEventParser = exports.SseEvent = exports.ClientFilterDefinition = exports.ClientRequest = exports.ResponseBodyReader = exports.RequestBodySerializer = exports.HttpResponseDtoFactory = exports.ClientErrorTranslator = exports.buildClientProxy = exports.RequestOutcome = exports.ProxyClient = void 0;
|
|
30
30
|
var ProxyClient_1 = require("./ProxyClient");
|
|
31
31
|
Object.defineProperty(exports, "ProxyClient", { enumerable: true, get: function () { return ProxyClient_1.ProxyClient; } });
|
|
32
32
|
var RequestOutcome_1 = require("./RequestOutcome");
|
|
@@ -52,15 +52,9 @@ var ClientFilter_1 = require("./ClientFilter");
|
|
|
52
52
|
Object.defineProperty(exports, "ClientFilterDefinition", { enumerable: true, get: function () { return ClientFilter_1.ClientFilterDefinition; } });
|
|
53
53
|
// Generic streaming wire adapters: NDJSON uploads, request-scoped SSE downloads, and a typed
|
|
54
54
|
// capability failure for runtimes that cannot safely keep both fetch halves open concurrently.
|
|
55
|
-
var NdjsonRequestStream_1 = require("./NdjsonRequestStream");
|
|
56
|
-
Object.defineProperty(exports, "NdjsonRequestStream", { enumerable: true, get: function () { return NdjsonRequestStream_1.NdjsonRequestStream; } });
|
|
57
55
|
var SseEventParser_1 = require("./SseEventParser");
|
|
58
56
|
Object.defineProperty(exports, "SseEvent", { enumerable: true, get: function () { return SseEventParser_1.SseEvent; } });
|
|
59
57
|
Object.defineProperty(exports, "SseEventParser", { enumerable: true, get: function () { return SseEventParser_1.SseEventParser; } });
|
|
60
|
-
var SseResponseStream_1 = require("./SseResponseStream");
|
|
61
|
-
Object.defineProperty(exports, "SseResponseStream", { enumerable: true, get: function () { return SseResponseStream_1.SseResponseStream; } });
|
|
62
|
-
var StreamEnvelopeCodec_1 = require("./StreamEnvelopeCodec");
|
|
63
|
-
Object.defineProperty(exports, "StreamEnvelopeCodec", { enumerable: true, get: function () { return StreamEnvelopeCodec_1.StreamEnvelopeCodec; } });
|
|
64
58
|
var StreamingCapabilityError_1 = require("./StreamingCapabilityError");
|
|
65
59
|
Object.defineProperty(exports, "StreamingCapabilityError", { enumerable: true, get: function () { return StreamingCapabilityError_1.StreamingCapabilityError; } });
|
|
66
60
|
var Utf8Codec_1 = require("./Utf8Codec");
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;;;AAEH,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AAEvB,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,gGAAgG;AAChG,iFAAiF;AACjF,mEAAkE;AAAzD,gIAAA,sBAAsB,OAAA;AAC/B,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAC3B,kGAAkG;AAClG,kFAAkF;AAClF,gEAAgE;AAChE,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;;;AAEH,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AAEvB,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,gGAAgG;AAChG,iFAAiF;AACjF,mEAAkE;AAAzD,gIAAA,sBAAsB,OAAA;AAC/B,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAC3B,kGAAkG;AAClG,kFAAkF;AAClF,gEAAgE;AAChE,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AAEtB,+CAAwD;AAA/C,sHAAA,sBAAsB,OAAA;AAE/B,6FAA6F;AAC7F,+FAA+F;AAC/F,mDAA4D;AAAnD,0GAAA,QAAQ,OAAA;AAAE,gHAAA,cAAc,OAAA;AACjC,uEAAsE;AAA7D,oIAAA,wBAAwB,OAAA;AACjC,yCAAwC;AAA/B,sGAAA,SAAS,OAAA","sourcesContent":["/**\n * @webpieces/http-client-core\n *\n * The ISOMORPHIC core of the webpieces HTTP client — everything that reads an API contract's\n * decorators and turns a method call into an HTTP request, with no opinion about where the\n * magic context comes from or whether a DI container exists.\n *\n * You almost certainly want one of its two environment packages instead:\n * - Server: @webpieces/http-client-node (inversify-wired, reads RequestContext, mints OIDC)\n * - Browser: @webpieces/http-client-browser (no DI — React or Angular, app-managed context store)\n *\n * Architecture:\n * ```\n * http-api (defines the contract)\n * ^\n * +-- http-routing (server: contract -> handlers)\n * +-- http-client-core (contract -> HTTP requests) <- YOU ARE HERE\n * +-- http-client-node (RequestContext + Secrets + OIDC + inversify factory)\n * +-- http-client-browser (app-held store + plain factory, no DI)\n * ```\n *\n * There is no context/credential/recording seam here at all: ProxyClient is ABSTRACT and asks its\n * subclass for the base URL, the context headers, the log map, the outbound credential, and the\n * recorder. Nothing server-only (RequestContext, Secrets, mintIdToken, TestCaseRecorder) can reach\n * a browser bundle, and nothing browser-only (a ContextReader store) reaches a server.\n */\n\nexport { ProxyClient } from './ProxyClient';\nexport { RequestOutcome } from './RequestOutcome';\nexport type { ApiPrototype } from './ApiPrototype';\nexport { buildClientProxy } from './buildClientProxy';\nexport { ClientErrorTranslator } from './ClientErrorTranslator';\n// The CLIENT-side transport boundary: a fetch Response becomes the ONE HttpResponseDto an app's\n// ErrorTranslator sees, so node and browser hand `fromWire` the identical shape.\nexport { HttpResponseDtoFactory } from './HttpResponseDtoFactory';\nexport { RequestBodySerializer } from './RequestBodySerializer';\nexport { ResponseBodyReader } from './ResponseBodyReader';\n// The OUTBOUND filter chain: the mutable request a filter edits, and one registration of a filter\n// at a priority. The `Filter`/`Service`/`FilterChain` abstraction itself lives in\n// @webpieces/core-util, shared with the server's inbound chain.\nexport { ClientRequest } from './ClientRequest';\nexport type { HttpTransport } from './HttpTransport';\nexport { ClientFilterDefinition } from './ClientFilter';\nexport type { ClientFilter } from './ClientFilter';\n// Generic streaming wire adapters: NDJSON uploads, request-scoped SSE downloads, and a typed\n// capability failure for runtimes that cannot safely keep both fetch halves open concurrently.\nexport { SseEvent, SseEventParser } from './SseEventParser';\nexport { StreamingCapabilityError } from './StreamingCapabilityError';\nexport { Utf8Codec } from './Utf8Codec';\nexport type { ByteReadableStream, ByteStreamReader, ByteReadResult } from './ByteStream';\n"]}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { DtoValue, RequestStream, StreamCorrelation, StreamFailureOptions, StreamingEndpointMetadata } from '@webpieces/core-util';
|
|
2
|
-
import { ByteReadableStream } from './ByteStream';
|
|
3
|
-
/** NDJSON upload half: exactly one JSON envelope plus LF per acknowledged write. */
|
|
4
|
-
export declare class NdjsonRequestStream implements RequestStream<DtoValue> {
|
|
5
|
-
private readonly metadata;
|
|
6
|
-
private readonly abort;
|
|
7
|
-
readonly body: ByteReadableStream;
|
|
8
|
-
private readonly writer;
|
|
9
|
-
private readonly encoder;
|
|
10
|
-
private readonly validator;
|
|
11
|
-
private readonly ready;
|
|
12
|
-
private terminal;
|
|
13
|
-
private cancelled;
|
|
14
|
-
constructor(metadata: StreamingEndpointMetadata, abort: (reason?: unknown) => void);
|
|
15
|
-
event(value: DtoValue, correlation?: StreamCorrelation): Promise<void>;
|
|
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>;
|
|
22
|
-
complete(): Promise<void>;
|
|
23
|
-
cancel(reason?: unknown): Promise<void>;
|
|
24
|
-
transportFailed(reason: unknown): Promise<void>;
|
|
25
|
-
private write;
|
|
26
|
-
private closeWriter;
|
|
27
|
-
private requireWritable;
|
|
28
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NdjsonRequestStream = void 0;
|
|
4
|
-
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
-
const StreamEnvelopeCodec_1 = require("./StreamEnvelopeCodec");
|
|
6
|
-
const Utf8Codec_1 = require("./Utf8Codec");
|
|
7
|
-
/** NDJSON upload half: exactly one JSON envelope plus LF per acknowledged write. */
|
|
8
|
-
class NdjsonRequestStream {
|
|
9
|
-
metadata;
|
|
10
|
-
abort;
|
|
11
|
-
body;
|
|
12
|
-
writer;
|
|
13
|
-
encoder = new Utf8Codec_1.Utf8Codec();
|
|
14
|
-
validator = new core_util_1.StreamEventValidator();
|
|
15
|
-
ready;
|
|
16
|
-
terminal = false;
|
|
17
|
-
cancelled = false;
|
|
18
|
-
constructor(metadata,
|
|
19
|
-
// webpieces-disable no-any-unknown -- AbortSignal reasons are platform-defined
|
|
20
|
-
abort) {
|
|
21
|
-
this.metadata = metadata;
|
|
22
|
-
this.abort = abort;
|
|
23
|
-
const transport = new TransformStream();
|
|
24
|
-
this.body = transport.readable;
|
|
25
|
-
this.writer = transport.writable.getWriter();
|
|
26
|
-
// A blank NDJSON line is a transport preamble. It makes Node/undici send request headers so
|
|
27
|
-
// the server can establish the SSE response before the caller has its request writer.
|
|
28
|
-
this.ready = this.writer.write(this.encoder.encode('\n'));
|
|
29
|
-
}
|
|
30
|
-
async event(value, correlation) {
|
|
31
|
-
this.requireWritable();
|
|
32
|
-
this.validator.validate(this.metadata.requestSchema, value, 'request');
|
|
33
|
-
await this.write(new core_util_1.StreamEnvelope('event', value, undefined, correlation));
|
|
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
|
-
*/
|
|
40
|
-
async fail(error, correlation, options) {
|
|
41
|
-
this.requireWritable();
|
|
42
|
-
const terminal = options?.terminal ?? true;
|
|
43
|
-
await this.write(new core_util_1.StreamEnvelope('failure', undefined, core_util_1.ApiErrorCodec.encode(error), correlation, terminal));
|
|
44
|
-
if (terminal)
|
|
45
|
-
await this.closeWriter();
|
|
46
|
-
}
|
|
47
|
-
async complete() {
|
|
48
|
-
this.requireWritable();
|
|
49
|
-
await this.write(new core_util_1.StreamEnvelope('complete'));
|
|
50
|
-
await this.closeWriter();
|
|
51
|
-
}
|
|
52
|
-
// webpieces-disable no-any-unknown -- AbortSignal reasons are platform-defined
|
|
53
|
-
async cancel(reason) {
|
|
54
|
-
if (this.terminal || this.cancelled)
|
|
55
|
-
return;
|
|
56
|
-
this.cancelled = true;
|
|
57
|
-
this.terminal = true;
|
|
58
|
-
this.abort(reason);
|
|
59
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- abort may race a fetch-owned stream close
|
|
60
|
-
try {
|
|
61
|
-
await this.writer.abort(reason);
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
const error = (0, core_util_1.toError)(err);
|
|
65
|
-
void error;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// webpieces-disable no-any-unknown -- a transport failure carries the platform cause
|
|
69
|
-
async transportFailed(reason) {
|
|
70
|
-
await this.cancel(reason);
|
|
71
|
-
}
|
|
72
|
-
async write(envelope) {
|
|
73
|
-
await this.ready;
|
|
74
|
-
const line = `${StreamEnvelopeCodec_1.StreamEnvelopeCodec.encode(envelope)}\n`;
|
|
75
|
-
// WritableStream.write resolves only when the fetch consumer accepts the chunk.
|
|
76
|
-
await this.writer.write(this.encoder.encode(line));
|
|
77
|
-
}
|
|
78
|
-
async closeWriter() {
|
|
79
|
-
this.terminal = true;
|
|
80
|
-
await this.ready;
|
|
81
|
-
await this.writer.close();
|
|
82
|
-
}
|
|
83
|
-
requireWritable() {
|
|
84
|
-
if (this.terminal || this.cancelled) {
|
|
85
|
-
throw new core_util_1.StreamTransportError('Cannot write after stream termination.');
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
exports.NdjsonRequestStream = NdjsonRequestStream;
|
|
90
|
-
//# sourceMappingURL=NdjsonRequestStream.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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.requestSchema, 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"]}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { DtoValue, ResponseStream, StreamingEndpointMetadata } from '@webpieces/core-util';
|
|
2
|
-
import { NdjsonRequestStream } from './NdjsonRequestStream';
|
|
3
|
-
/** Consumes generic Webpieces stream envelopes from one request-scoped SSE response. */
|
|
4
|
-
export declare class SseResponseStream {
|
|
5
|
-
private readonly validator;
|
|
6
|
-
consume(response: Response, destination: ResponseStream<DtoValue>, metadata: StreamingEndpointMetadata, requestStream: NdjsonRequestStream): Promise<void>;
|
|
7
|
-
private dispatchAll;
|
|
8
|
-
private transportFailure;
|
|
9
|
-
}
|
package/src/SseResponseStream.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SseResponseStream = void 0;
|
|
4
|
-
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
-
const SseEventParser_1 = require("./SseEventParser");
|
|
6
|
-
const StreamEnvelopeCodec_1 = require("./StreamEnvelopeCodec");
|
|
7
|
-
/** Consumes generic Webpieces stream envelopes from one request-scoped SSE response. */
|
|
8
|
-
class SseResponseStream {
|
|
9
|
-
validator = new core_util_1.StreamEventValidator();
|
|
10
|
-
async consume(response, destination, metadata, requestStream) {
|
|
11
|
-
const body = response.body;
|
|
12
|
-
if (!body) {
|
|
13
|
-
await this.transportFailure(new core_util_1.StreamTransportError('Streaming response has no readable body.'), destination, requestStream);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const parser = new SseEventParser_1.SseEventParser();
|
|
17
|
-
const reader = body.getReader();
|
|
18
|
-
let terminated = false;
|
|
19
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- all wire/reader failures are translated below
|
|
20
|
-
try {
|
|
21
|
-
while (!terminated) {
|
|
22
|
-
const part = await reader.read();
|
|
23
|
-
if (part.done)
|
|
24
|
-
break;
|
|
25
|
-
terminated = await this.dispatchAll(parser.feed(part.value), destination, metadata);
|
|
26
|
-
}
|
|
27
|
-
if (!terminated) {
|
|
28
|
-
terminated = await this.dispatchAll(parser.finish(), destination, metadata);
|
|
29
|
-
}
|
|
30
|
-
if (!terminated) {
|
|
31
|
-
throw new core_util_1.StreamTransportError('SSE response closed before a terminal failure or complete envelope.');
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
const error = (0, core_util_1.toError)(err);
|
|
36
|
-
const transportError = error instanceof core_util_1.StreamTransportError
|
|
37
|
-
? error
|
|
38
|
-
: new core_util_1.StreamTransportError('SSE response transport failed.', undefined, undefined, error);
|
|
39
|
-
await this.transportFailure(transportError, destination, requestStream);
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
reader.releaseLock();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
async dispatchAll(events, destination, metadata) {
|
|
46
|
-
for (const event of events) {
|
|
47
|
-
if (event.event !== undefined && event.event !== 'message')
|
|
48
|
-
continue;
|
|
49
|
-
const envelope = StreamEnvelopeCodec_1.StreamEnvelopeCodec.decode(event.data);
|
|
50
|
-
if (envelope.kind === 'event') {
|
|
51
|
-
this.validator.validate(metadata.responseSchema, envelope.value, 'response');
|
|
52
|
-
await destination.event(envelope.value, envelope.correlation);
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (envelope.kind === 'failure') {
|
|
56
|
-
const error = core_util_1.ApiErrorCodec.decode(envelope.error);
|
|
57
|
-
await destination.fail(error, envelope.correlation, {
|
|
58
|
-
terminal: envelope.terminal,
|
|
59
|
-
});
|
|
60
|
-
if (envelope.terminal)
|
|
61
|
-
return true;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
await destination.complete();
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
async transportFailure(error, destination, requestStream) {
|
|
70
|
-
await requestStream.transportFailed(error);
|
|
71
|
-
await destination.fail(error, error.correlation, { terminal: true });
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
exports.SseResponseStream = SseResponseStream;
|
|
75
|
-
//# sourceMappingURL=SseResponseStream.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SseResponseStream.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/SseResponseStream.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApiErrorCodec,\n DtoValue,\n ResponseStream,\n StreamEventValidator,\n StreamingEndpointMetadata,\n StreamTransportError,\n toError,\n} from '@webpieces/core-util';\nimport { NdjsonRequestStream } from './NdjsonRequestStream';\nimport { SseEvent, SseEventParser } from './SseEventParser';\nimport { StreamEnvelopeCodec } from './StreamEnvelopeCodec';\n\n/** Consumes generic Webpieces stream envelopes from one request-scoped SSE response. */\nexport class SseResponseStream {\n private readonly validator = new StreamEventValidator();\n\n async consume(\n response: Response,\n destination: ResponseStream<DtoValue>,\n metadata: StreamingEndpointMetadata,\n requestStream: NdjsonRequestStream,\n ): Promise<void> {\n const body = response.body;\n if (!body) {\n await this.transportFailure(\n new StreamTransportError('Streaming response has no readable body.'),\n destination,\n requestStream,\n );\n return;\n }\n const parser = new SseEventParser();\n const reader = body.getReader();\n let terminated = false;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- all wire/reader failures are translated below\n try {\n while (!terminated) {\n const part = await reader.read();\n if (part.done) break;\n terminated = await this.dispatchAll(parser.feed(part.value), destination, metadata);\n }\n if (!terminated) {\n terminated = await this.dispatchAll(parser.finish(), destination, metadata);\n }\n if (!terminated) {\n throw new StreamTransportError(\n 'SSE response closed before a terminal failure or complete envelope.',\n );\n }\n } catch (err: unknown) {\n const error = toError(err);\n const transportError =\n error instanceof StreamTransportError\n ? error\n : new StreamTransportError(\n 'SSE response transport failed.',\n undefined,\n undefined,\n error,\n );\n await this.transportFailure(transportError, destination, requestStream);\n } finally {\n reader.releaseLock();\n }\n }\n\n private async dispatchAll(\n events: readonly SseEvent[],\n destination: ResponseStream<DtoValue>,\n metadata: StreamingEndpointMetadata,\n ): Promise<boolean> {\n for (const event of events) {\n if (event.event !== undefined && event.event !== 'message') continue;\n const envelope = StreamEnvelopeCodec.decode(event.data);\n if (envelope.kind === 'event') {\n this.validator.validate(metadata.responseSchema, envelope.value, 'response');\n await destination.event(envelope.value as DtoValue, envelope.correlation);\n continue;\n }\n if (envelope.kind === 'failure') {\n const error = ApiErrorCodec.decode(envelope.error);\n await destination.fail(error, envelope.correlation, {\n terminal: envelope.terminal,\n });\n if (envelope.terminal) return true;\n continue;\n }\n await destination.complete();\n return true;\n }\n return false;\n }\n\n private async transportFailure(\n error: StreamTransportError,\n destination: ResponseStream<DtoValue>,\n requestStream: NdjsonRequestStream,\n ): Promise<void> {\n await requestStream.transportFailed(error);\n await destination.fail(error, error.correlation, { terminal: true });\n }\n}\n"]}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { DtoValue, StreamEnvelope } from '@webpieces/core-util';
|
|
2
|
-
/** Strict JSON codec shared by the NDJSON request writer and SSE response reader. */
|
|
3
|
-
export declare class StreamEnvelopeCodec {
|
|
4
|
-
static encode(envelope: StreamEnvelope<DtoValue>): string;
|
|
5
|
-
static decode(json: string): StreamEnvelope<DtoValue>;
|
|
6
|
-
private static correlation;
|
|
7
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StreamEnvelopeCodec = void 0;
|
|
4
|
-
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
-
/** Strict JSON codec shared by the NDJSON request writer and SSE response reader. */
|
|
6
|
-
class StreamEnvelopeCodec {
|
|
7
|
-
// webpieces-disable no-function-outside-class -- stateless wire codec shared by client transports
|
|
8
|
-
static encode(envelope) {
|
|
9
|
-
return JSON.stringify(envelope);
|
|
10
|
-
}
|
|
11
|
-
// webpieces-disable no-function-outside-class -- stateless public wire codec; webpieces-disable no-any-unknown -- JSON.parse is narrowed below before it crosses the boundary
|
|
12
|
-
static decode(json) {
|
|
13
|
-
let value;
|
|
14
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- malformed wire data is translated to a typed transport error
|
|
15
|
-
try {
|
|
16
|
-
// webpieces-disable no-any-unknown -- JSON.parse result is narrowed immediately below
|
|
17
|
-
value = JSON.parse(json);
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
const error = (0, core_util_1.toError)(err);
|
|
21
|
-
throw new core_util_1.StreamTransportError('Stream frame is not valid JSON.', undefined, undefined, error);
|
|
22
|
-
}
|
|
23
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
24
|
-
throw new core_util_1.StreamTransportError('Stream frame must be a JSON object.');
|
|
25
|
-
}
|
|
26
|
-
// webpieces-disable no-any-unknown -- validated object remains an untrusted field bag until each field check
|
|
27
|
-
const record = value;
|
|
28
|
-
const kind = record['kind'];
|
|
29
|
-
if (kind !== 'event' && kind !== 'failure' && kind !== 'complete') {
|
|
30
|
-
throw new core_util_1.StreamTransportError('Stream frame has an invalid kind.');
|
|
31
|
-
}
|
|
32
|
-
const correlation = this.correlation(record['correlation']);
|
|
33
|
-
if (kind === 'event') {
|
|
34
|
-
return new core_util_1.StreamEnvelope('event', record['value'], undefined, correlation);
|
|
35
|
-
}
|
|
36
|
-
if (kind === 'complete')
|
|
37
|
-
return new core_util_1.StreamEnvelope('complete');
|
|
38
|
-
if (!core_util_1.ApiErrorCodec.isPayload(record['error'])) {
|
|
39
|
-
throw new core_util_1.StreamTransportError('Failure stream frame has no valid Webpieces error payload.', correlation);
|
|
40
|
-
}
|
|
41
|
-
if (typeof record['terminal'] !== 'boolean') {
|
|
42
|
-
throw new core_util_1.StreamTransportError('Failure stream frame must declare its terminal disposition.', correlation);
|
|
43
|
-
}
|
|
44
|
-
return new core_util_1.StreamEnvelope('failure', undefined, record['error'], correlation, record['terminal']);
|
|
45
|
-
}
|
|
46
|
-
// webpieces-disable no-function-outside-class -- private stateless wire-field validator; webpieces-disable no-any-unknown -- correlation is untrusted until narrowed here
|
|
47
|
-
static correlation(value) {
|
|
48
|
-
if (value === undefined)
|
|
49
|
-
return undefined;
|
|
50
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
51
|
-
throw new core_util_1.StreamTransportError('Stream correlation must be an object.');
|
|
52
|
-
}
|
|
53
|
-
// webpieces-disable no-any-unknown -- validated object remains untrusted until both fields are checked
|
|
54
|
-
const record = value;
|
|
55
|
-
const key = record['key'];
|
|
56
|
-
const requestId = record['requestId'];
|
|
57
|
-
if (typeof key !== 'string' && typeof key !== 'number') {
|
|
58
|
-
throw new core_util_1.StreamTransportError('Stream correlation key must be a string or number.');
|
|
59
|
-
}
|
|
60
|
-
if (requestId !== undefined && typeof requestId !== 'string') {
|
|
61
|
-
throw new core_util_1.StreamTransportError('Stream correlation requestId must be a string.');
|
|
62
|
-
}
|
|
63
|
-
return new core_util_1.StreamCorrelation(key, requestId);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
exports.StreamEnvelopeCodec = StreamEnvelopeCodec;
|
|
67
|
-
//# sourceMappingURL=StreamEnvelopeCodec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StreamEnvelopeCodec.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/StreamEnvelopeCodec.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApiErrorCodec,\n ApiErrorPayload,\n DtoValue,\n StreamCorrelation,\n StreamEnvelope,\n StreamTransportError,\n toError,\n} from '@webpieces/core-util';\n\n/** Strict JSON codec shared by the NDJSON request writer and SSE response reader. */\nexport class StreamEnvelopeCodec {\n // webpieces-disable no-function-outside-class -- stateless wire codec shared by client transports\n static encode(envelope: StreamEnvelope<DtoValue>): string {\n return JSON.stringify(envelope);\n }\n\n // webpieces-disable no-function-outside-class -- stateless public wire codec; webpieces-disable no-any-unknown -- JSON.parse is narrowed below before it crosses the boundary\n static decode(json: string): StreamEnvelope<DtoValue> {\n let value: unknown;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- malformed wire data is translated to a typed transport error\n try {\n // webpieces-disable no-any-unknown -- JSON.parse result is narrowed immediately below\n value = JSON.parse(json) as unknown;\n } catch (err: unknown) {\n const error = toError(err);\n throw new StreamTransportError(\n 'Stream frame is not valid JSON.',\n undefined,\n undefined,\n error,\n );\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new StreamTransportError('Stream frame must be a JSON object.');\n }\n // webpieces-disable no-any-unknown -- validated object remains an untrusted field bag until each field check\n const record = value as Record<string, unknown>;\n const kind = record['kind'];\n if (kind !== 'event' && kind !== 'failure' && kind !== 'complete') {\n throw new StreamTransportError('Stream frame has an invalid kind.');\n }\n const correlation = this.correlation(record['correlation']);\n if (kind === 'event') {\n return new StreamEnvelope<DtoValue>(\n 'event',\n record['value'] as DtoValue,\n undefined,\n correlation,\n );\n }\n if (kind === 'complete') return new StreamEnvelope<DtoValue>('complete');\n if (!ApiErrorCodec.isPayload(record['error'])) {\n throw new StreamTransportError(\n 'Failure stream frame has no valid Webpieces error payload.',\n correlation,\n );\n }\n if (typeof record['terminal'] !== 'boolean') {\n throw new StreamTransportError(\n 'Failure stream frame must declare its terminal disposition.',\n correlation,\n );\n }\n return new StreamEnvelope<DtoValue>(\n 'failure',\n undefined,\n record['error'] as ApiErrorPayload,\n correlation,\n record['terminal'],\n );\n }\n\n // webpieces-disable no-function-outside-class -- private stateless wire-field validator; webpieces-disable no-any-unknown -- correlation is untrusted until narrowed here\n private static correlation(value: unknown): StreamCorrelation | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new StreamTransportError('Stream correlation must be an object.');\n }\n // webpieces-disable no-any-unknown -- validated object remains untrusted until both fields are checked\n const record = value as Record<string, unknown>;\n const key = record['key'];\n const requestId = record['requestId'];\n if (typeof key !== 'string' && typeof key !== 'number') {\n throw new StreamTransportError('Stream correlation key must be a string or number.');\n }\n if (requestId !== undefined && typeof requestId !== 'string') {\n throw new StreamTransportError('Stream correlation requestId must be a string.');\n }\n return new StreamCorrelation(key, requestId);\n }\n}\n"]}
|