@webpieces/http-client-core 0.4.838 → 0.4.839
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-client-core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.839",
|
|
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.839"
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DtoValue, RequestStream, RouteMetadata } from '@webpieces/core-util';
|
|
2
|
+
import { ClientRequest } from './ClientRequest';
|
|
3
|
+
import { StreamingBaseUrl, StreamingContextHeaders, StreamingReadError } from './ResponseStreamingCall';
|
|
4
|
+
import { ByteReadableStream } from './ByteStream';
|
|
5
|
+
export type DuplexStreamingSend = (request: ClientRequest, signal: AbortSignal, body: ByteReadableStream) => Promise<Response>;
|
|
6
|
+
export declare class OpenedJsonlExchange {
|
|
7
|
+
readonly response: Response;
|
|
8
|
+
readonly upload: RequestStream<DtoValue, DtoValue>;
|
|
9
|
+
constructor(response: Response, upload: RequestStream<DtoValue, DtoValue>);
|
|
10
|
+
}
|
|
11
|
+
/** Opens the REQUEST/FULL JSONL exchange and resolves only after its typed acknowledgement. */
|
|
12
|
+
export declare class DuplexStreamingCall {
|
|
13
|
+
private readonly apiName;
|
|
14
|
+
private readonly baseUrl;
|
|
15
|
+
private readonly contextHeaders;
|
|
16
|
+
private readonly send;
|
|
17
|
+
private readonly readError;
|
|
18
|
+
private readonly responses;
|
|
19
|
+
constructor(apiName: string, baseUrl: StreamingBaseUrl, contextHeaders: StreamingContextHeaders, send: DuplexStreamingSend, readError: StreamingReadError);
|
|
20
|
+
open(route: RouteMetadata, args: unknown[], deadlineSignal: AbortSignal): Promise<OpenedJsonlExchange>;
|
|
21
|
+
private destination;
|
|
22
|
+
private guardDestination;
|
|
23
|
+
private request;
|
|
24
|
+
private requireContentType;
|
|
25
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DuplexStreamingCall = exports.OpenedJsonlExchange = void 0;
|
|
4
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
+
const ClientRequest_1 = require("./ClientRequest");
|
|
6
|
+
const JsonlRequestStream_1 = require("./JsonlRequestStream");
|
|
7
|
+
const JsonlResponseStream_1 = require("./JsonlResponseStream");
|
|
8
|
+
const ResponseStreamingRequestFactory_1 = require("./ResponseStreamingRequestFactory");
|
|
9
|
+
class OpenedJsonlExchange {
|
|
10
|
+
response;
|
|
11
|
+
upload;
|
|
12
|
+
constructor(response, upload) {
|
|
13
|
+
this.response = response;
|
|
14
|
+
this.upload = upload;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.OpenedJsonlExchange = OpenedJsonlExchange;
|
|
18
|
+
/** Opens the REQUEST/FULL JSONL exchange and resolves only after its typed acknowledgement. */
|
|
19
|
+
class DuplexStreamingCall {
|
|
20
|
+
apiName;
|
|
21
|
+
baseUrl;
|
|
22
|
+
contextHeaders;
|
|
23
|
+
send;
|
|
24
|
+
readError;
|
|
25
|
+
responses = new JsonlResponseStream_1.JsonlResponseStream();
|
|
26
|
+
constructor(apiName, baseUrl, contextHeaders, send, readError) {
|
|
27
|
+
this.apiName = apiName;
|
|
28
|
+
this.baseUrl = baseUrl;
|
|
29
|
+
this.contextHeaders = contextHeaders;
|
|
30
|
+
this.send = send;
|
|
31
|
+
this.readError = readError;
|
|
32
|
+
}
|
|
33
|
+
// webpieces-disable no-any-unknown -- generated proxy arguments are runtime-validated here
|
|
34
|
+
async open(route, args, deadlineSignal) {
|
|
35
|
+
const destination = this.destination(route, args);
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
deadlineSignal.addEventListener('abort', () => controller.abort(), { once: true });
|
|
38
|
+
const upload = new JsonlRequestStream_1.JsonlRequestStream(route, args[0], (error) => controller.abort(error));
|
|
39
|
+
const request = await this.request(route, args[0]);
|
|
40
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- handshake failures must release both transport directions
|
|
41
|
+
try {
|
|
42
|
+
const response = await this.send(request, controller.signal, upload.body);
|
|
43
|
+
if (!response.ok || response.status === 266) {
|
|
44
|
+
await this.readError(response, route);
|
|
45
|
+
throw new core_util_1.StreamTransportError('Streaming handshake was rejected.');
|
|
46
|
+
}
|
|
47
|
+
this.requireContentType(response);
|
|
48
|
+
const guarded = this.guardDestination(destination, upload);
|
|
49
|
+
const initial = await this.responses.consume(route, response, guarded, (error) => upload.cancel(error));
|
|
50
|
+
upload.setInitialResponse(initial);
|
|
51
|
+
return new OpenedJsonlExchange(response, upload);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const error = (0, core_util_1.toError)(err);
|
|
55
|
+
await upload.transportFailed(error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// webpieces-disable no-any-unknown -- structural stream boundary
|
|
60
|
+
destination(route, args) {
|
|
61
|
+
const expected = route.streaming?.direction === core_util_1.StreamDirection.FULL ? 2 : 1;
|
|
62
|
+
if (args.length !== expected) {
|
|
63
|
+
throw new core_util_1.StreamTransportError(`${this.apiName}.${route.methodName} ${route.streaming?.direction.toUpperCase()} streaming requires ${expected} argument(s).`);
|
|
64
|
+
}
|
|
65
|
+
if (expected === 1) {
|
|
66
|
+
return {
|
|
67
|
+
event: async () => {
|
|
68
|
+
throw new core_util_1.StreamTransportError('REQUEST streaming cannot receive response events.');
|
|
69
|
+
},
|
|
70
|
+
close: async () => undefined,
|
|
71
|
+
cancel: async () => undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const candidate = args[1];
|
|
75
|
+
if (typeof candidate !== 'object' || candidate === null) {
|
|
76
|
+
throw new core_util_1.StreamTransportError('FULL streaming response destination is not an object.');
|
|
77
|
+
}
|
|
78
|
+
return new ResponseStreamingRequestFactory_1.ResponseStreamingRequestFactory().destination(candidate);
|
|
79
|
+
}
|
|
80
|
+
guardDestination(destination, upload) {
|
|
81
|
+
return {
|
|
82
|
+
event: (value) => destination.event(value),
|
|
83
|
+
close: () => destination.close(),
|
|
84
|
+
cancel: async (error) => {
|
|
85
|
+
if (error)
|
|
86
|
+
await upload.transportFailed(error);
|
|
87
|
+
await destination.cancel(error);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async request(route, initial) {
|
|
92
|
+
const headers = new Map();
|
|
93
|
+
headers.set('Content-Type', 'application/x-webpieces-jsonl');
|
|
94
|
+
headers.set('Accept', 'application/x-webpieces-jsonl');
|
|
95
|
+
for (const entry of this.contextHeaders(route))
|
|
96
|
+
headers.set(entry[0], entry[1]);
|
|
97
|
+
return new ClientRequest_1.ClientRequest(route, this.apiName, await this.baseUrl(), headers, undefined, initial);
|
|
98
|
+
}
|
|
99
|
+
requireContentType(response) {
|
|
100
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
101
|
+
if (!contentType.toLowerCase().startsWith('application/x-webpieces-jsonl')) {
|
|
102
|
+
throw new core_util_1.StreamTransportError(`Streaming response requires application/x-webpieces-jsonl, received '${contentType || 'missing'}'.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.DuplexStreamingCall = DuplexStreamingCall;
|
|
107
|
+
//# sourceMappingURL=DuplexStreamingCall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DuplexStreamingCall.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/DuplexStreamingCall.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DtoValue,\n RequestStream,\n ResponseStream,\n RouteMetadata,\n StreamDirection,\n StreamTransportError,\n toError,\n} from '@webpieces/core-util';\nimport { ClientRequest } from './ClientRequest';\nimport { JsonlRequestStream } from './JsonlRequestStream';\nimport { JsonlResponseStream } from './JsonlResponseStream';\nimport {\n StreamingBaseUrl,\n StreamingContextHeaders,\n StreamingReadError,\n} from './ResponseStreamingCall';\nimport { ByteReadableStream } from './ByteStream';\nimport { ResponseStreamingRequestFactory } from './ResponseStreamingRequestFactory';\n\nexport type DuplexStreamingSend = (\n request: ClientRequest,\n signal: AbortSignal,\n body: ByteReadableStream,\n) => Promise<Response>;\n\nexport class OpenedJsonlExchange {\n constructor(\n readonly response: Response,\n readonly upload: RequestStream<DtoValue, DtoValue>,\n ) {}\n}\n\n/** Opens the REQUEST/FULL JSONL exchange and resolves only after its typed acknowledgement. */\nexport class DuplexStreamingCall {\n private readonly responses = new JsonlResponseStream();\n\n constructor(\n private readonly apiName: string,\n private readonly baseUrl: StreamingBaseUrl,\n private readonly contextHeaders: StreamingContextHeaders,\n private readonly send: DuplexStreamingSend,\n private readonly readError: StreamingReadError,\n ) {}\n\n // webpieces-disable no-any-unknown -- generated proxy arguments are runtime-validated here\n async open(\n route: RouteMetadata,\n args: unknown[],\n deadlineSignal: AbortSignal,\n ): Promise<OpenedJsonlExchange> {\n const destination = this.destination(route, args);\n const controller = new AbortController();\n deadlineSignal.addEventListener('abort', (): void => controller.abort(), { once: true });\n const upload = new JsonlRequestStream(route, args[0] as DtoValue, (error: Error): void =>\n controller.abort(error),\n );\n const request = await this.request(route, args[0] as DtoValue);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- handshake failures must release both transport directions\n try {\n const response = await this.send(request, controller.signal, upload.body);\n if (!response.ok || response.status === 266) {\n await this.readError(response, route);\n throw new StreamTransportError('Streaming handshake was rejected.');\n }\n this.requireContentType(response);\n const guarded = this.guardDestination(destination, upload);\n const initial = await this.responses.consume(\n route,\n response,\n guarded,\n (error: Error): Promise<void> => upload.cancel(error),\n );\n upload.setInitialResponse(initial);\n return new OpenedJsonlExchange(response, upload);\n } catch (err: unknown) {\n const error = toError(err);\n await upload.transportFailed(error);\n throw error;\n }\n }\n\n // webpieces-disable no-any-unknown -- structural stream boundary\n private destination(route: RouteMetadata, args: unknown[]): ResponseStream<DtoValue> {\n const expected = route.streaming?.direction === StreamDirection.FULL ? 2 : 1;\n if (args.length !== expected) {\n throw new StreamTransportError(\n `${this.apiName}.${route.methodName} ${route.streaming?.direction.toUpperCase()} streaming requires ${expected} argument(s).`,\n );\n }\n if (expected === 1) {\n return {\n event: async (): Promise<void> => {\n throw new StreamTransportError(\n 'REQUEST streaming cannot receive response events.',\n );\n },\n close: async (): Promise<void> => undefined,\n cancel: async (): Promise<void> => undefined,\n };\n }\n const candidate = args[1];\n if (typeof candidate !== 'object' || candidate === null) {\n throw new StreamTransportError('FULL streaming response destination is not an object.');\n }\n return new ResponseStreamingRequestFactory().destination(candidate);\n }\n\n private guardDestination(\n destination: ResponseStream<DtoValue>,\n upload: JsonlRequestStream,\n ): ResponseStream<DtoValue> {\n return {\n event: (value: DtoValue): Promise<void> => destination.event(value),\n close: (): Promise<void> => destination.close(),\n cancel: async (error?: Error): Promise<void> => {\n if (error) await upload.transportFailed(error);\n await destination.cancel(error);\n },\n };\n }\n\n private async request(route: RouteMetadata, initial: DtoValue): Promise<ClientRequest> {\n const headers = new Map<string, string>();\n headers.set('Content-Type', 'application/x-webpieces-jsonl');\n headers.set('Accept', 'application/x-webpieces-jsonl');\n for (const entry of this.contextHeaders(route)) headers.set(entry[0], entry[1]);\n return new ClientRequest(\n route,\n this.apiName,\n await this.baseUrl(),\n headers,\n undefined,\n initial,\n );\n }\n\n private requireContentType(response: Response): void {\n const contentType = response.headers.get('content-type') ?? '';\n if (!contentType.toLowerCase().startsWith('application/x-webpieces-jsonl')) {\n throw new StreamTransportError(\n `Streaming response requires application/x-webpieces-jsonl, received '${contentType || 'missing'}'.`,\n );\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ByteReadableStream } from './ByteStream';
|
|
2
|
+
import { ClientRequest } from './ClientRequest';
|
|
3
|
+
/** Thin transport boundary after generated mapping, validation, and outbound filters. */
|
|
4
|
+
export interface HttpTransport {
|
|
5
|
+
send(request: ClientRequest, signal: AbortSignal, body?: ByteReadableStream): Promise<Response>;
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpTransport.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/HttpTransport.ts"],"names":[],"mappings":"","sourcesContent":["import { ByteReadableStream } from './ByteStream';\nimport { ClientRequest } from './ClientRequest';\n\n/** Thin transport boundary after generated mapping, validation, and outbound filters. */\nexport interface HttpTransport {\n send(request: ClientRequest, signal: AbortSignal, body?: ByteReadableStream): Promise<Response>;\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DtoValue, RequestStream, RouteMetadata } from '@webpieces/core-util';
|
|
2
|
+
import { ByteReadableStream } from './ByteStream';
|
|
3
|
+
/** Ordered writable request half of application/x-webpieces-jsonl. */
|
|
4
|
+
export declare class JsonlRequestStream implements RequestStream<DtoValue, DtoValue> {
|
|
5
|
+
private readonly route;
|
|
6
|
+
private readonly abortTransport;
|
|
7
|
+
readonly body: ByteReadableStream;
|
|
8
|
+
private readonly writer;
|
|
9
|
+
private readonly codec;
|
|
10
|
+
private readonly validator;
|
|
11
|
+
private closed;
|
|
12
|
+
private failure?;
|
|
13
|
+
private initialResponse?;
|
|
14
|
+
private readonly initialWrite;
|
|
15
|
+
private rejectFailure;
|
|
16
|
+
private readonly failureSignal;
|
|
17
|
+
constructor(route: RouteMetadata, initialRequest: DtoValue, abortTransport: (error: Error) => void);
|
|
18
|
+
setInitialResponse(value: DtoValue): void;
|
|
19
|
+
getInitialResponse(): DtoValue;
|
|
20
|
+
event(value: DtoValue): Promise<void>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
cancel(error?: Error): Promise<void>;
|
|
23
|
+
transportFailed(error: Error): Promise<void>;
|
|
24
|
+
private requireWritable;
|
|
25
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JsonlRequestStream = void 0;
|
|
4
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
+
const Utf8Codec_1 = require("./Utf8Codec");
|
|
6
|
+
/** Ordered writable request half of application/x-webpieces-jsonl. */
|
|
7
|
+
class JsonlRequestStream {
|
|
8
|
+
route;
|
|
9
|
+
abortTransport;
|
|
10
|
+
body;
|
|
11
|
+
writer;
|
|
12
|
+
codec = new Utf8Codec_1.Utf8Codec();
|
|
13
|
+
validator = new core_util_1.StreamEventValidator();
|
|
14
|
+
closed = false;
|
|
15
|
+
failure;
|
|
16
|
+
initialResponse;
|
|
17
|
+
initialWrite;
|
|
18
|
+
rejectFailure;
|
|
19
|
+
failureSignal = new Promise((_resolve, reject) => {
|
|
20
|
+
this.rejectFailure = reject;
|
|
21
|
+
});
|
|
22
|
+
constructor(route, initialRequest, abortTransport) {
|
|
23
|
+
this.route = route;
|
|
24
|
+
this.abortTransport = abortTransport;
|
|
25
|
+
void this.failureSignal.catch(() => undefined);
|
|
26
|
+
if (route.streaming?.initialRequestSchema) {
|
|
27
|
+
this.validator.validate(route.streaming.initialRequestSchema, initialRequest, 'request');
|
|
28
|
+
}
|
|
29
|
+
const transport = new TransformStream();
|
|
30
|
+
this.body = transport.readable;
|
|
31
|
+
this.writer = transport.writable.getWriter();
|
|
32
|
+
this.initialWrite = this.writer.write(this.codec.encode(`${JSON.stringify(initialRequest)}\n`));
|
|
33
|
+
void this.initialWrite.catch((error) => {
|
|
34
|
+
this.failure ??= error;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
setInitialResponse(value) {
|
|
38
|
+
this.initialResponse = value;
|
|
39
|
+
}
|
|
40
|
+
getInitialResponse() {
|
|
41
|
+
if (this.initialResponse === undefined) {
|
|
42
|
+
throw new core_util_1.StreamTransportError('Initial response has not arrived.');
|
|
43
|
+
}
|
|
44
|
+
return this.initialResponse;
|
|
45
|
+
}
|
|
46
|
+
async event(value) {
|
|
47
|
+
this.requireWritable();
|
|
48
|
+
if (this.route.streaming?.requestSchema) {
|
|
49
|
+
this.validator.validate(this.route.streaming.requestSchema, value, 'request');
|
|
50
|
+
}
|
|
51
|
+
await Promise.race([this.initialWrite, this.failureSignal]);
|
|
52
|
+
await Promise.race([
|
|
53
|
+
this.writer.write(this.codec.encode(`${JSON.stringify(value)}\n`)),
|
|
54
|
+
this.failureSignal,
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
async close() {
|
|
58
|
+
this.requireWritable();
|
|
59
|
+
this.closed = true;
|
|
60
|
+
await Promise.race([this.initialWrite, this.failureSignal]);
|
|
61
|
+
await Promise.race([this.writer.close(), this.failureSignal]);
|
|
62
|
+
}
|
|
63
|
+
async cancel(error) {
|
|
64
|
+
if (this.failure)
|
|
65
|
+
return;
|
|
66
|
+
if (!error || this.closed) {
|
|
67
|
+
await this.transportFailed(error ?? new core_util_1.StreamDisconnectedError('Local stream cancellation.'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- failed control delivery must abort the transport
|
|
71
|
+
try {
|
|
72
|
+
const record = new core_util_1.StreamErrorControl().encode(error);
|
|
73
|
+
this.failure = error;
|
|
74
|
+
this.rejectFailure(error);
|
|
75
|
+
await this.initialWrite;
|
|
76
|
+
await this.writer.write(this.codec.encode(record));
|
|
77
|
+
this.closed = true;
|
|
78
|
+
await this.writer.close();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const error = (0, core_util_1.toError)(err);
|
|
82
|
+
this.failure = undefined;
|
|
83
|
+
await this.transportFailed(error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async transportFailed(error) {
|
|
88
|
+
if (this.failure)
|
|
89
|
+
return;
|
|
90
|
+
this.failure = error;
|
|
91
|
+
this.rejectFailure(error);
|
|
92
|
+
this.closed = true;
|
|
93
|
+
this.abortTransport(error);
|
|
94
|
+
void this.writer.abort(error).catch(() => undefined);
|
|
95
|
+
}
|
|
96
|
+
requireWritable() {
|
|
97
|
+
if (this.failure)
|
|
98
|
+
throw this.failure;
|
|
99
|
+
if (this.closed)
|
|
100
|
+
throw new core_util_1.StreamTransportError('Cannot write after stream termination.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.JsonlRequestStream = JsonlRequestStream;
|
|
104
|
+
//# sourceMappingURL=JsonlRequestStream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JsonlRequestStream.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/JsonlRequestStream.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DtoValue,\n RequestStream,\n RouteMetadata,\n StreamTransportError,\n StreamEventValidator,\n StreamErrorControl,\n StreamDisconnectedError,\n toError,\n} from '@webpieces/core-util';\nimport { ByteReadableStream } from './ByteStream';\nimport { Utf8Codec } from './Utf8Codec';\n\n/** Ordered writable request half of application/x-webpieces-jsonl. */\nexport class JsonlRequestStream implements RequestStream<DtoValue, DtoValue> {\n readonly body: ByteReadableStream;\n private readonly writer: WritableStreamDefaultWriter<Uint8Array>;\n private readonly codec = new Utf8Codec();\n private readonly validator = new StreamEventValidator();\n private closed = false;\n private failure?: Error;\n private initialResponse?: DtoValue;\n private readonly initialWrite: Promise<void>;\n private rejectFailure!: (error: Error) => void;\n private readonly failureSignal = new Promise<never>(\n (_resolve: (value: never) => void, reject: (error: Error) => void): void => {\n this.rejectFailure = reject;\n },\n );\n\n constructor(\n private readonly route: RouteMetadata,\n initialRequest: DtoValue,\n private readonly abortTransport: (error: Error) => void,\n ) {\n void this.failureSignal.catch((): void => undefined);\n if (route.streaming?.initialRequestSchema) {\n this.validator.validate(\n route.streaming.initialRequestSchema,\n initialRequest,\n 'request',\n );\n }\n const transport = new TransformStream<Uint8Array, Uint8Array>();\n this.body = transport.readable;\n this.writer = transport.writable.getWriter();\n this.initialWrite = this.writer.write(\n this.codec.encode(`${JSON.stringify(initialRequest)}\\n`),\n );\n void this.initialWrite.catch((error: Error): void => {\n this.failure ??= error;\n });\n }\n\n setInitialResponse(value: DtoValue): void {\n this.initialResponse = value;\n }\n\n getInitialResponse(): DtoValue {\n if (this.initialResponse === undefined) {\n throw new StreamTransportError('Initial response has not arrived.');\n }\n return this.initialResponse;\n }\n\n async event(value: DtoValue): Promise<void> {\n this.requireWritable();\n if (this.route.streaming?.requestSchema) {\n this.validator.validate(this.route.streaming.requestSchema, value, 'request');\n }\n await Promise.race([this.initialWrite, this.failureSignal]);\n await Promise.race([\n this.writer.write(this.codec.encode(`${JSON.stringify(value)}\\n`)),\n this.failureSignal,\n ]);\n }\n\n async close(): Promise<void> {\n this.requireWritable();\n this.closed = true;\n await Promise.race([this.initialWrite, this.failureSignal]);\n await Promise.race([this.writer.close(), this.failureSignal]);\n }\n\n async cancel(error?: Error): Promise<void> {\n if (this.failure) return;\n if (!error || this.closed) {\n await this.transportFailed(\n error ?? new StreamDisconnectedError('Local stream cancellation.'),\n );\n return;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- failed control delivery must abort the transport\n try {\n const record = new StreamErrorControl().encode(error);\n this.failure = error;\n this.rejectFailure(error);\n await this.initialWrite;\n await this.writer.write(this.codec.encode(record));\n this.closed = true;\n await this.writer.close();\n } catch (err: unknown) {\n const error = toError(err);\n this.failure = undefined;\n await this.transportFailed(error);\n throw error;\n }\n }\n\n async transportFailed(error: Error): Promise<void> {\n if (this.failure) return;\n this.failure = error;\n this.rejectFailure(error);\n this.closed = true;\n this.abortTransport(error);\n void this.writer.abort(error).catch((): void => undefined);\n }\n\n private requireWritable(): void {\n if (this.failure) throw this.failure;\n if (this.closed) throw new StreamTransportError('Cannot write after stream termination.');\n }\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DtoValue, ResponseStream, RouteMetadata } from '@webpieces/core-util';
|
|
2
|
+
/** First raw JSONL record resolves the call; subsequent records obey ordered consumer backpressure. */
|
|
3
|
+
export declare class JsonlResponseStream {
|
|
4
|
+
private readonly validator;
|
|
5
|
+
private readonly controls;
|
|
6
|
+
consume(route: RouteMetadata, response: Response, destination: ResponseStream<DtoValue>, onConsumerFailure?: (error: Error) => Promise<void>): Promise<DtoValue>;
|
|
7
|
+
cancel(destination: ResponseStream<DtoValue>, error: Error): Promise<void>;
|
|
8
|
+
private finish;
|
|
9
|
+
private deliver;
|
|
10
|
+
private parseData;
|
|
11
|
+
private checkLength;
|
|
12
|
+
private read;
|
|
13
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JsonlResponseStream = void 0;
|
|
4
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
+
const Utf8Codec_1 = require("./Utf8Codec");
|
|
6
|
+
/** First raw JSONL record resolves the call; subsequent records obey ordered consumer backpressure. */
|
|
7
|
+
class JsonlResponseStream {
|
|
8
|
+
validator = new core_util_1.StreamEventValidator();
|
|
9
|
+
controls = new core_util_1.StreamErrorControl();
|
|
10
|
+
async consume(route, response, destination, onConsumerFailure) {
|
|
11
|
+
if (!response.body)
|
|
12
|
+
throw new core_util_1.StreamTransportError('Streaming response has no readable body.');
|
|
13
|
+
const reader = response.body.getReader();
|
|
14
|
+
const codec = new Utf8Codec_1.Utf8Codec();
|
|
15
|
+
let pending = '';
|
|
16
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- handshake failure cancels and releases the response reader
|
|
17
|
+
try {
|
|
18
|
+
for (;;) {
|
|
19
|
+
const chunk = await this.read(reader);
|
|
20
|
+
pending += codec.decode(chunk.value, !chunk.done);
|
|
21
|
+
const newline = pending.indexOf('\n');
|
|
22
|
+
if (newline >= 0) {
|
|
23
|
+
const line = pending.slice(0, newline).replace(/\r$/, '');
|
|
24
|
+
pending = pending.slice(newline + 1);
|
|
25
|
+
if (line === '')
|
|
26
|
+
continue;
|
|
27
|
+
const initial = this.parseData(line);
|
|
28
|
+
if (initial === null)
|
|
29
|
+
throw new core_util_1.StreamProtocolError('Initial response must be non-null.');
|
|
30
|
+
if (route.streaming?.initialResponseSchema) {
|
|
31
|
+
this.validator.validate(route.streaming.initialResponseSchema, initial, 'response');
|
|
32
|
+
}
|
|
33
|
+
void this.finish(reader, codec, pending, route, destination, onConsumerFailure).catch(
|
|
34
|
+
// webpieces-disable no-any-unknown -- asynchronous transport failure is normalized at the boundary
|
|
35
|
+
async (err) => {
|
|
36
|
+
const error = (0, core_util_1.toError)(err);
|
|
37
|
+
// The call has already resolved: cancel reports the primary failure.
|
|
38
|
+
// A throwing cancellation callback cannot be reported back through
|
|
39
|
+
// that same callback; contain it to avoid an unhandled background rejection.
|
|
40
|
+
await this.cancel(destination, error).catch(() => undefined);
|
|
41
|
+
});
|
|
42
|
+
return initial;
|
|
43
|
+
}
|
|
44
|
+
this.checkLength(pending);
|
|
45
|
+
if (chunk.done)
|
|
46
|
+
throw new core_util_1.StreamProtocolError('Streaming response ended before its initial response.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const error = (0, core_util_1.toError)(err);
|
|
51
|
+
await reader.cancel(error).catch(() => undefined);
|
|
52
|
+
reader.releaseLock();
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async cancel(destination, error) {
|
|
57
|
+
await destination.cancel(error);
|
|
58
|
+
}
|
|
59
|
+
async finish(reader, codec, initialPending, route, destination, onConsumerFailure) {
|
|
60
|
+
let pending = initialPending;
|
|
61
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- reader ownership ends on clean EOF or protocol failure
|
|
62
|
+
try {
|
|
63
|
+
for (;;) {
|
|
64
|
+
let newline = pending.indexOf('\n');
|
|
65
|
+
while (newline >= 0) {
|
|
66
|
+
const line = pending.slice(0, newline).replace(/\r$/, '');
|
|
67
|
+
pending = pending.slice(newline + 1);
|
|
68
|
+
if (line !== '')
|
|
69
|
+
await this.deliver(line, route, destination, onConsumerFailure);
|
|
70
|
+
newline = pending.indexOf('\n');
|
|
71
|
+
}
|
|
72
|
+
this.checkLength(pending);
|
|
73
|
+
const chunk = await this.read(reader);
|
|
74
|
+
pending += codec.decode(chunk.value, !chunk.done);
|
|
75
|
+
if (chunk.done) {
|
|
76
|
+
if (pending !== '')
|
|
77
|
+
throw new core_util_1.StreamProtocolError('JSONL response ended mid-record.');
|
|
78
|
+
await destination.close();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await reader.cancel().catch(() => undefined);
|
|
85
|
+
reader.releaseLock();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async deliver(line, route, destination, onConsumerFailure) {
|
|
89
|
+
const value = this.parseData(line);
|
|
90
|
+
if (route.streaming?.responseSchema)
|
|
91
|
+
this.validator.validate(route.streaming.responseSchema, value, 'response');
|
|
92
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a client handler error uses the still-open request control channel
|
|
93
|
+
try {
|
|
94
|
+
await destination.event(value);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const error = (0, core_util_1.toError)(err);
|
|
98
|
+
await onConsumerFailure?.(error);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
parseData(line) {
|
|
103
|
+
this.checkLength(line);
|
|
104
|
+
if (line.charCodeAt(0) === 0x1e)
|
|
105
|
+
throw this.controls.decode(line);
|
|
106
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted JSONL boundary
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(line);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const error = (0, core_util_1.toError)(err);
|
|
112
|
+
throw new core_util_1.StreamProtocolError('Malformed Webpieces JSONL record.', undefined, undefined, error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
checkLength(line) {
|
|
116
|
+
if (line.length > 1024 * 1024)
|
|
117
|
+
throw new core_util_1.StreamProtocolError('JSONL response record exceeds 1048576 characters.');
|
|
118
|
+
}
|
|
119
|
+
async read(reader) {
|
|
120
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- native read failures have no remote application error DTO
|
|
121
|
+
try {
|
|
122
|
+
return await reader.read();
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
const error = (0, core_util_1.toError)(err);
|
|
126
|
+
throw new core_util_1.StreamDisconnectedError('Streaming response transport disconnected.', undefined, undefined, error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.JsonlResponseStream = JsonlResponseStream;
|
|
131
|
+
//# sourceMappingURL=JsonlResponseStream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JsonlResponseStream.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/JsonlResponseStream.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DtoValue,\n ResponseStream,\n RouteMetadata,\n StreamErrorControl,\n StreamEventValidator,\n StreamProtocolError,\n StreamTransportError,\n toError,\n StreamDisconnectedError,\n} from '@webpieces/core-util';\nimport { Utf8Codec } from './Utf8Codec';\nimport { ByteReadResult } from './ByteStream';\n\n/** First raw JSONL record resolves the call; subsequent records obey ordered consumer backpressure. */\nexport class JsonlResponseStream {\n private readonly validator = new StreamEventValidator();\n private readonly controls = new StreamErrorControl();\n\n async consume(\n route: RouteMetadata,\n response: Response,\n destination: ResponseStream<DtoValue>,\n onConsumerFailure?: (error: Error) => Promise<void>,\n ): Promise<DtoValue> {\n if (!response.body)\n throw new StreamTransportError('Streaming response has no readable body.');\n const reader = response.body.getReader();\n const codec = new Utf8Codec();\n let pending = '';\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- handshake failure cancels and releases the response reader\n try {\n for (;;) {\n const chunk = await this.read(reader);\n pending += codec.decode(chunk.value, !chunk.done);\n const newline = pending.indexOf('\\n');\n if (newline >= 0) {\n const line = pending.slice(0, newline).replace(/\\r$/, '');\n pending = pending.slice(newline + 1);\n if (line === '') continue;\n const initial = this.parseData(line);\n if (initial === null)\n throw new StreamProtocolError('Initial response must be non-null.');\n if (route.streaming?.initialResponseSchema) {\n this.validator.validate(\n route.streaming.initialResponseSchema,\n initial,\n 'response',\n );\n }\n void this.finish(\n reader,\n codec,\n pending,\n route,\n destination,\n onConsumerFailure,\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- post-open failures belong to the response consumer and are reported through cancel\n ).catch(\n // webpieces-disable no-any-unknown -- asynchronous transport failure is normalized at the boundary\n async (err: unknown): Promise<void> => {\n const error = toError(err);\n // The call has already resolved: cancel reports the primary failure.\n // A throwing cancellation callback cannot be reported back through\n // that same callback; contain it to avoid an unhandled background rejection.\n await this.cancel(destination, error).catch((): void => undefined);\n },\n );\n return initial;\n }\n this.checkLength(pending);\n if (chunk.done)\n throw new StreamProtocolError(\n 'Streaming response ended before its initial response.',\n );\n }\n } catch (err: unknown) {\n const error = toError(err);\n await reader.cancel(error).catch((): void => undefined);\n reader.releaseLock();\n throw error;\n }\n }\n\n async cancel(destination: ResponseStream<DtoValue>, error: Error): Promise<void> {\n await destination.cancel(error);\n }\n\n private async finish(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n codec: Utf8Codec,\n initialPending: string,\n route: RouteMetadata,\n destination: ResponseStream<DtoValue>,\n onConsumerFailure?: (error: Error) => Promise<void>,\n ): Promise<void> {\n let pending = initialPending;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- reader ownership ends on clean EOF or protocol failure\n try {\n for (;;) {\n let newline = pending.indexOf('\\n');\n while (newline >= 0) {\n const line = pending.slice(0, newline).replace(/\\r$/, '');\n pending = pending.slice(newline + 1);\n if (line !== '')\n await this.deliver(line, route, destination, onConsumerFailure);\n newline = pending.indexOf('\\n');\n }\n this.checkLength(pending);\n const chunk = await this.read(reader);\n pending += codec.decode(chunk.value, !chunk.done);\n if (chunk.done) {\n if (pending !== '')\n throw new StreamProtocolError('JSONL response ended mid-record.');\n await destination.close();\n return;\n }\n }\n } finally {\n await reader.cancel().catch((): void => undefined);\n reader.releaseLock();\n }\n }\n\n private async deliver(\n line: string,\n route: RouteMetadata,\n destination: ResponseStream<DtoValue>,\n onConsumerFailure?: (error: Error) => Promise<void>,\n ): Promise<void> {\n const value = this.parseData(line);\n if (route.streaming?.responseSchema)\n this.validator.validate(route.streaming.responseSchema, value, 'response');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a client handler error uses the still-open request control channel\n try {\n await destination.event(value);\n } catch (err: unknown) {\n const error = toError(err);\n await onConsumerFailure?.(error);\n throw error;\n }\n }\n\n private parseData(line: string): DtoValue {\n this.checkLength(line);\n if (line.charCodeAt(0) === 0x1e) throw this.controls.decode(line);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted JSONL boundary\n try {\n return JSON.parse(line) as DtoValue;\n } catch (err: unknown) {\n const error = toError(err);\n throw new StreamProtocolError(\n 'Malformed Webpieces JSONL record.',\n undefined,\n undefined,\n error,\n );\n }\n }\n\n private checkLength(line: string): void {\n if (line.length > 1024 * 1024)\n throw new StreamProtocolError('JSONL response record exceeds 1048576 characters.');\n }\n\n private async read(reader: ReadableStreamDefaultReader<Uint8Array>): Promise<ByteReadResult> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- native read failures have no remote application error DTO\n try {\n return await reader.read();\n } catch (err: unknown) {\n const error = toError(err);\n throw new StreamDisconnectedError(\n 'Streaming response transport disconnected.',\n undefined,\n undefined,\n error,\n );\n }\n }\n}\n"]}
|
package/src/ProxyClient.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DestinationTrust, RouteMetadata, LogApiCallImpl } from '@webpieces/core-util';
|
|
2
2
|
import { ApiPrototype } from './ApiPrototype';
|
|
3
3
|
import { ClientFilterDefinition } from './ClientFilter';
|
|
4
4
|
import { ClientRequest } from './ClientRequest';
|
|
@@ -93,7 +93,7 @@ export declare abstract class ProxyClient {
|
|
|
93
93
|
* mint the OIDC token an @WpAuthOidc endpoint demands. Surfacing it here beats failing on the
|
|
94
94
|
* first call in production. The default accepts everything.
|
|
95
95
|
*/
|
|
96
|
-
protected assertEndpointSupported(
|
|
96
|
+
protected assertEndpointSupported(_route: RouteMetadata): void;
|
|
97
97
|
/**
|
|
98
98
|
* The FRAMEWORK filters this environment installs on every client it builds, BENEATH whatever
|
|
99
99
|
* the app passed to `createRpcClient`. The default installs none, so the browser runs the exact
|
|
@@ -176,23 +176,13 @@ export declare abstract class ProxyClient {
|
|
|
176
176
|
* @throws Error naming the endpoint, what it declared, and who its real caller is.
|
|
177
177
|
*/
|
|
178
178
|
private refuseEndpointNoClientCanCall;
|
|
179
|
-
/** One logical call: one lifecycle pair and log entry across all strategy attempts. */
|
|
180
179
|
makeRequest(route: RouteMetadata, args: unknown[]): Promise<unknown>;
|
|
181
|
-
/** Open a typed stream without bypassing the ordinary context/auth/filter request pipeline. */
|
|
182
180
|
private makeStreamingRequest;
|
|
183
|
-
private
|
|
181
|
+
private makeResponseStreamingRequest;
|
|
184
182
|
/** One streaming handshake. Subsequent events stay on this established transport. */
|
|
185
183
|
private executeStreamingCall;
|
|
186
|
-
/**
|
|
187
|
-
* Hand a settled response's headers to {@link acceptResponseContext}, with the SAME
|
|
188
|
-
* {@link DestinationTrust} the request was built with. One private helper rather than the same
|
|
189
|
-
* four lines on each of the four settle paths, because a path that forgot it would silently stop
|
|
190
|
-
* propagating context upward with nothing failing.
|
|
191
|
-
*/
|
|
184
|
+
/** Hand settled response headers to the context using the request's destination trust. */
|
|
192
185
|
private readResponseContext;
|
|
193
|
-
private openStreamingTransport;
|
|
194
|
-
/** Fresh filter-visible request metadata; the live request body is transport-owned. */
|
|
195
|
-
private prepareStreamingRequest;
|
|
196
186
|
private executeCall;
|
|
197
187
|
/** Fresh mutable request for every attempt, including URL, headers, auth and body. */
|
|
198
188
|
private prepareRequest;
|
|
@@ -207,7 +197,7 @@ export declare abstract class ProxyClient {
|
|
|
207
197
|
* genuine bug passes through untouched) so that filters above see the same typed error the caller
|
|
208
198
|
* will, rather than a raw platform reject.
|
|
209
199
|
*/
|
|
210
|
-
|
|
200
|
+
protected sendOnce(request: ClientRequest, signal: AbortSignal): Promise<Response>;
|
|
211
201
|
/** Node fetch's streaming upload option. Browser callers are refused before reaching here. */
|
|
212
202
|
private sendStreamingOnce;
|
|
213
203
|
/** Body consumption is inside the attempt deadline, including non-JSON error bodies. */
|