@webpieces/http-client-core 0.4.772 → 0.4.774

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.772",
3
+ "version": "0.4.774",
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.772"
24
+ "@webpieces/core-util": "0.4.774"
25
25
  }
26
26
  }
@@ -0,0 +1,12 @@
1
+ /** Minimal structural byte-stream types that do not force DOM streams into React Native .d.ts files. */
2
+ export interface ByteReadResult {
3
+ readonly done: boolean;
4
+ readonly value?: Uint8Array;
5
+ }
6
+ export interface ByteStreamReader {
7
+ read(): Promise<ByteReadResult>;
8
+ releaseLock(): void;
9
+ }
10
+ export interface ByteReadableStream {
11
+ getReader(): ByteStreamReader;
12
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=ByteStream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ByteStream.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/ByteStream.ts"],"names":[],"mappings":"","sourcesContent":["/** Minimal structural byte-stream types that do not force DOM streams into React Native .d.ts files. */\nexport interface ByteReadResult {\n readonly done: boolean;\n readonly value?: Uint8Array;\n}\n\nexport interface ByteStreamReader {\n read(): Promise<ByteReadResult>;\n releaseLock(): void;\n}\n\nexport interface ByteReadableStream {\n getReader(): ByteStreamReader;\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import { ApiError, 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
+ fail(error: ApiError, correlation?: StreamCorrelation, options?: StreamFailureOptions): Promise<void>;
17
+ complete(): Promise<void>;
18
+ cancel(reason?: unknown): Promise<void>;
19
+ transportFailed(reason: unknown): Promise<void>;
20
+ private write;
21
+ private closeWriter;
22
+ private requireWritable;
23
+ }
@@ -0,0 +1,85 @@
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.requestEventClass, value, 'request');
33
+ await this.write(new core_util_1.StreamEnvelope('event', value, undefined, correlation));
34
+ }
35
+ async fail(error, correlation, options) {
36
+ this.requireWritable();
37
+ const terminal = options?.terminal ?? true;
38
+ await this.write(new core_util_1.StreamEnvelope('failure', undefined, core_util_1.ApiErrorCodec.encode(error), correlation, terminal));
39
+ if (terminal)
40
+ await this.closeWriter();
41
+ }
42
+ async complete() {
43
+ this.requireWritable();
44
+ await this.write(new core_util_1.StreamEnvelope('complete'));
45
+ await this.closeWriter();
46
+ }
47
+ // webpieces-disable no-any-unknown -- AbortSignal reasons are platform-defined
48
+ async cancel(reason) {
49
+ if (this.terminal || this.cancelled)
50
+ return;
51
+ this.cancelled = true;
52
+ this.terminal = true;
53
+ this.abort(reason);
54
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- abort may race a fetch-owned stream close
55
+ try {
56
+ await this.writer.abort(reason);
57
+ }
58
+ catch (err) {
59
+ const error = (0, core_util_1.toError)(err);
60
+ void error;
61
+ }
62
+ }
63
+ // webpieces-disable no-any-unknown -- a transport failure carries the platform cause
64
+ async transportFailed(reason) {
65
+ await this.cancel(reason);
66
+ }
67
+ async write(envelope) {
68
+ await this.ready;
69
+ const line = `${StreamEnvelopeCodec_1.StreamEnvelopeCodec.encode(envelope)}\n`;
70
+ // WritableStream.write resolves only when the fetch consumer accepts the chunk.
71
+ await this.writer.write(this.encoder.encode(line));
72
+ }
73
+ async closeWriter() {
74
+ this.terminal = true;
75
+ await this.ready;
76
+ await this.writer.close();
77
+ }
78
+ requireWritable() {
79
+ if (this.terminal || this.cancelled) {
80
+ throw new core_util_1.StreamTransportError('Cannot write after stream termination.');
81
+ }
82
+ }
83
+ }
84
+ exports.NdjsonRequestStream = NdjsonRequestStream;
85
+ //# sourceMappingURL=NdjsonRequestStream.js.map
@@ -0,0 +1 @@
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 ApiError,\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 async fail(\n error: ApiError,\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,8 +1,10 @@
1
1
  import { AuthMeta, DestinationTrust, RouteMetadata, LogApiCallImpl } from '@webpieces/core-util';
2
2
  import { ApiPrototype } from './ApiPrototype';
3
3
  import { ClientFilterDefinition } from './ClientFilter';
4
+ import { ClientRequest } from './ClientRequest';
4
5
  import { RequestOutcome } from './RequestOutcome';
5
6
  import { TranslatedFailure } from './TranslatedFailure';
7
+ import { ByteReadableStream } from './ByteStream';
6
8
  /**
7
9
  * ProxyClient - the HTTP call engine behind one API contract's client proxy.
8
10
  *
@@ -127,6 +129,10 @@ export declare abstract class ProxyClient {
127
129
  * @param callId - `ApiName.methodName`, so a rewritten message can still name the call
128
130
  */
129
131
  protected abstract adaptDownstreamFailure(failure: TranslatedFailure, callId: string): Error;
132
+ /** Whether fetch can read the response while its streaming request body remains open. */
133
+ protected abstract supportsConcurrentDuplexFetch(): boolean;
134
+ /** Environment-owned full-duplex transport after the shared filter chain has prepared it. */
135
+ protected abstract sendStreamingTransport(request: ClientRequest, signal: AbortSignal, body: ByteReadableStream): Promise<Response>;
130
136
  /**
131
137
  * Fires before the logical call's attempts, once per RPC — the progress "start marker". Symmetric with
132
138
  * {@link onRequestEnd}: every start is followed by exactly one end, on every path, so a listener
@@ -179,6 +185,14 @@ export declare abstract class ProxyClient {
179
185
  private refuseEndpointNoClientCanCall;
180
186
  /** One logical call: one lifecycle pair and log entry across all strategy attempts. */
181
187
  makeRequest(route: RouteMetadata, args: unknown[]): Promise<unknown>;
188
+ /** Open a typed stream without bypassing the ordinary context/auth/filter request pipeline. */
189
+ private makeStreamingRequest;
190
+ private responseStream;
191
+ /** One streaming handshake. Subsequent events stay on this established transport. */
192
+ private executeStreamingCall;
193
+ private openStreamingTransport;
194
+ /** Fresh filter-visible request metadata; the live request body is transport-owned. */
195
+ private prepareStreamingRequest;
182
196
  private executeCall;
183
197
  /** Fresh mutable request for every attempt, including URL, headers, auth and body. */
184
198
  private prepareRequest;
@@ -198,6 +212,8 @@ export declare abstract class ProxyClient {
198
212
  * will, rather than a raw platform reject.
199
213
  */
200
214
  private sendOnce;
215
+ /** Node fetch's streaming upload option. Browser callers are refused before reaching here. */
216
+ private sendStreamingOnce;
201
217
  /** Body consumption is inside the attempt deadline, including non-JSON error bodies. */
202
218
  private readResponse;
203
219
  /** Preserve empty, JSON, and protocol text bodies for caller-owned full responses. */
@@ -7,6 +7,17 @@ const ClientErrorTranslator_1 = require("./ClientErrorTranslator");
7
7
  const HttpResponseDtoFactory_1 = require("./HttpResponseDtoFactory");
8
8
  const RequestOutcome_1 = require("./RequestOutcome");
9
9
  const ResponseBodyReader_1 = require("./ResponseBodyReader");
10
+ const NdjsonRequestStream_1 = require("./NdjsonRequestStream");
11
+ const SseResponseStream_1 = require("./SseResponseStream");
12
+ const StreamingCapabilityError_1 = require("./StreamingCapabilityError");
13
+ class OpenedStreamingTransport {
14
+ response;
15
+ upload;
16
+ constructor(response, upload) {
17
+ this.response = response;
18
+ this.upload = upload;
19
+ }
20
+ }
10
21
  /**
11
22
  * ProxyClient - the HTTP call engine behind one API contract's client proxy.
12
23
  *
@@ -216,10 +227,95 @@ class ProxyClient {
216
227
  // webpieces-disable no-any-unknown -- request and response DTOs are erased at the proxy boundary
217
228
  async makeRequest(route, args) {
218
229
  this.refuseEndpointNoClientCanCall(route);
230
+ if (route.streaming)
231
+ return this.makeStreamingRequest(route, args);
219
232
  const mapped = core_util_1.HttpContractMapper.toWire(route.path, route.parameterBindings, route.bodyParameterIndex, args);
220
233
  const logValue = mapped.body === undefined ? args : mapped.body;
221
234
  return this.execute(route, logValue, () => this.executeCall(route, args));
222
235
  }
236
+ /** Open a typed stream without bypassing the ordinary context/auth/filter request pipeline. */
237
+ // webpieces-disable no-any-unknown -- generated proxy arguments are runtime-validated here
238
+ async makeStreamingRequest(route, args) {
239
+ if (!this.supportsConcurrentDuplexFetch()) {
240
+ throw new StreamingCapabilityError_1.StreamingCapabilityError('browser', 'Fetch request streaming is half-duplex and has no protocol-compatible full-duplex fallback.');
241
+ }
242
+ const destination = this.responseStream(args);
243
+ return this.execute(route, 'stream-open', () => this.executeStreamingCall(route, destination));
244
+ }
245
+ // webpieces-disable no-any-unknown -- generated proxy arguments are runtime-validated here
246
+ responseStream(args) {
247
+ const candidate = args[0];
248
+ if (args.length !== 1 || typeof candidate !== 'object' || candidate === null) {
249
+ throw new core_util_1.StreamTransportError(`${this.apiName} streaming methods require exactly one ResponseStream argument.`);
250
+ }
251
+ // webpieces-disable no-any-unknown -- reflected method argument is narrowed by the method checks below
252
+ const record = candidate;
253
+ if (typeof record['event'] !== 'function' ||
254
+ typeof record['fail'] !== 'function' ||
255
+ typeof record['complete'] !== 'function' ||
256
+ typeof record['onCancel'] !== 'function') {
257
+ throw new core_util_1.StreamTransportError(`${this.apiName} streaming method argument does not implement ResponseStream.`);
258
+ }
259
+ return candidate;
260
+ }
261
+ /** One streaming handshake. Subsequent events stay on this established transport. */
262
+ async executeStreamingCall(route, destination) {
263
+ this.onRequestStart(route);
264
+ let response;
265
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- lifecycle reports the original handshake failure
266
+ try {
267
+ const requestStream = await core_util_1.CallRegistry.execute(this.apiClass, route.methodName, (timeoutMs) => core_util_1.CallDeadline.run(timeoutMs, new core_util_1.CallContext(this.apiName, route.methodName), async (deadlineSignal) => {
268
+ const result = await this.openStreamingTransport(route, destination, deadlineSignal);
269
+ response = result.response;
270
+ return result.upload;
271
+ }), 30_000);
272
+ this.onRequestEnd(route, new RequestOutcome_1.RequestOutcome(true, response?.status ?? 0, response?.headers));
273
+ return requestStream;
274
+ }
275
+ catch (err) {
276
+ const error = (0, core_util_1.toError)(err);
277
+ this.onRequestEnd(route, new RequestOutcome_1.RequestOutcome(false, response?.status ?? 0, response?.headers, error));
278
+ throw err;
279
+ }
280
+ }
281
+ async openStreamingTransport(route, destination, deadlineSignal) {
282
+ const metadata = route.streaming;
283
+ if (!metadata)
284
+ throw new core_util_1.StreamTransportError('Streaming metadata disappeared.');
285
+ const request = await this.prepareStreamingRequest(route);
286
+ const controller = new AbortController();
287
+ deadlineSignal.addEventListener('abort', () => controller.abort(), { once: true });
288
+ const upload = new NdjsonRequestStream_1.NdjsonRequestStream(metadata,
289
+ // webpieces-disable no-any-unknown -- AbortController accepts a platform-defined cancellation reason
290
+ (reason) => controller.abort(reason));
291
+ const response = await this.chain.execute(request, () => this.sendStreamingOnce(request, controller.signal, upload.body));
292
+ if (!response.ok) {
293
+ await upload.transportFailed(new core_util_1.StreamTransportError(`Streaming handshake failed with HTTP ${response.status}.`));
294
+ await this.readResponse(response, route);
295
+ throw new core_util_1.StreamTransportError('Streaming handshake was rejected.');
296
+ }
297
+ const contentType = response.headers.get('content-type') ?? '';
298
+ if (!contentType.toLowerCase().startsWith('text/event-stream')) {
299
+ const error = new core_util_1.StreamTransportError(`Streaming response requires text/event-stream, received '${contentType || 'missing'}'.`);
300
+ await upload.transportFailed(error);
301
+ throw error;
302
+ }
303
+ void new SseResponseStream_1.SseResponseStream()
304
+ .consume(response, destination, metadata, upload)
305
+ .catch(() => undefined);
306
+ return new OpenedStreamingTransport(response, upload);
307
+ }
308
+ /** Fresh filter-visible request metadata; the live request body is transport-owned. */
309
+ async prepareStreamingRequest(route) {
310
+ const baseUrl = await this.resolveBaseUrl();
311
+ const headers = new Map();
312
+ headers.set('Content-Type', 'application/x-ndjson');
313
+ headers.set('Accept', 'text/event-stream');
314
+ const context = this.outboundContextHeaders(core_util_1.DestinationTrust.forAuthMode(route.authMeta?.mode));
315
+ for (const entry of context.entries())
316
+ headers.set(entry[0], entry[1]);
317
+ return new ClientRequest_1.ClientRequest(route, this.apiName, baseUrl, headers, undefined, undefined);
318
+ }
223
319
  // webpieces-disable no-any-unknown -- response DTO is erased at the proxy boundary
224
320
  async executeCall(route, args) {
225
321
  this.onRequestStart(route);
@@ -329,6 +425,18 @@ class ProxyClient {
329
425
  throw this.networkRejectClassifier.toNetworkError(error, request.url);
330
426
  }
331
427
  }
428
+ /** Node fetch's streaming upload option. Browser callers are refused before reaching here. */
429
+ async sendStreamingOnce(request, signal, body) {
430
+ core_util_1.CallDeadline.throwIfAborted(signal);
431
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- platform rejects are normalized below
432
+ try {
433
+ return await this.sendStreamingTransport(request, signal, body);
434
+ }
435
+ catch (err) {
436
+ const error = (0, core_util_1.toError)(err);
437
+ throw this.networkRejectClassifier.toNetworkError(error, request.url);
438
+ }
439
+ }
332
440
  /** Body consumption is inside the attempt deadline, including non-JSON error bodies. */
333
441
  // webpieces-disable no-any-unknown -- response DTO is erased at the proxy boundary
334
442
  async readResponse(response, route) {
@@ -1 +1 @@
1
- {"version":3,"file":"ProxyClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/ProxyClient.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isApiPath,\n getEndpoints,\n AuthMeta,\n DestinationTrust,\n RouteMetadata,\n LogApiCallImpl,\n ApiMethodInfo,\n toError,\n NetworkRejectClassifier,\n HttpContractMapper,\n RouteMetadataFactory,\n FilterChain,\n CallRegistry,\n CallDeadline,\n CallContext,\n} from '@webpieces/core-util';\nimport { ApiPrototype } from './ApiPrototype';\nimport { ClientFilterDefinition } from './ClientFilter';\nimport { ClientRequest } from './ClientRequest';\nimport { ClientErrorTranslator } from './ClientErrorTranslator';\nimport { HttpResponseDtoFactory } from './HttpResponseDtoFactory';\nimport { RequestOutcome } from './RequestOutcome';\nimport { ResponseBodyReader } from './ResponseBodyReader';\nimport { TranslatedFailure } from './TranslatedFailure';\n\n/**\n * ProxyClient - the HTTP call engine behind one API contract's client proxy.\n *\n * Contains ONLY what a browser can run: the route map built from the contract's decorators, URL\n * assembly, `fetch`, error translation, and logging. It holds no context object, no credentials,\n * and no recorder — it ASKS ITSELF for those through the hooks below, and each subclass answers\n * from its own environment.\n *\n * That is why the class is abstract rather than parameterized by a collaborator: a shared\n * header-provider seam would drag Node's AsyncLocalStorage vocabulary into a browser bundle and the\n * browser's store vocabulary into a server, and neither has any use for the other.\n *\n * NodeProxyClient (@webpieces/http-client-node) -> RequestContext, Secrets, mintIdToken, recording\n * BrowserProxyClient (@webpieces/http-client-browser) -> an app-held store, no credentials, no recording\n *\n * TWO-PHASE: collaborators arrive on the subclass constructor (so a DI container can supply them),\n * while the per-client state — which contract, which target — arrives on the subclass's `init`,\n * which calls {@link initRoutes}. That is what lets a factory hold a `Provider<ProxyClient>` and\n * hand out a fresh, independently-configured client per contract.\n */\nexport abstract class ProxyClient {\n // Assigned by initRoutes(), which every subclass's init() calls immediately after construction.\n private routeMap!: Map<string, RouteMetadata>;\n private apiName!: string;\n private apiClass!: ApiPrototype<object>;\n\n /**\n * The OUTBOUND filter chain, built once at bind time from {@link clientFilters} and reused for\n * every call. Built once rather than per call because a filter is STATELESS by contract (the\n * per-call state is the {@link ClientRequest} the chain is handed), exactly as on the server.\n */\n private chain!: FilterChain<ClientRequest, Response>;\n\n /**\n * The app's own filters, as handed to `createRpcClient`. Set by {@link initRoutes} BEFORE it\n * calls {@link clientFilters}, so an environment's built-ins may read the app's intent off them\n * — @webpieces/http-client-node takes the SSRF policy from an installed `ContextBaseUrlFilter`\n * that way, which keeps the one legitimate relaxation at the same construction site as the\n * decision to be re-pointable at all.\n */\n protected appFilters: ClientFilterDefinition[] = [];\n\n // Stateless + dependency-free, so the browser bundle keeps no DI on the fetch path.\n private readonly networkRejectClassifier = new NetworkRejectClassifier();\n\n // Same shape and same reason: stateless, so it is constructed here rather than injected.\n private readonly bodyReader = new ResponseBodyReader();\n\n /**\n * fetch `Response` -> the transport-neutral {@link HttpResponseDto} an app's `ErrorTranslators`\n * sees. Normalising HERE is what makes `fromWire` receive the identical shape in node and in the\n * browser: both environments share this class, and this is the only place either builds a DTO.\n */\n private readonly responseDtoFactory = new HttpResponseDtoFactory();\n\n /**\n * @param logApiCall - built by the SUBCLASS's package around that environment's ApiCallContext\n * (node: RequestContextApiCallContext; browser: BrowserApiCallContext). REQUIRED, with no\n * default: core-util cannot construct either one, and a default here would have to reach for a\n * process-global — which is exactly the throw-on-first-call this parameter deleted.\n */\n constructor(protected readonly logApiCall: LogApiCallImpl) {}\n\n // ---------------------------------------------------------------- environment hooks\n\n /** The callee's base URL. Async because a server may derive it from container metadata. */\n protected abstract resolveBaseUrl(): Promise<string>;\n\n /**\n * Context headers to put on the wire. Server reads RequestContext; browser reads its store.\n *\n * `destination` is derived from THIS route's auth mode and decides whether TRUSTED context keys\n * (`x-user-id`, `x-org-id`, `x-webpieces-roles`) may ride along — see {@link DestinationTrust}.\n * It is a required argument on purpose: a defaulted \"send everything\" would put the permissive\n * answer one keystroke away and make the safe one opt-in.\n *\n * RENAMED from `outboundHeaders()` in the same change that added `destination`, and the rename IS\n * the migration. TypeScript accepts an override that declares FEWER parameters than its base, so a\n * downstream `protected override outboundHeaders(): Map<string, string>` would have kept compiling\n * and silently ignored the gate — the permissive behaviour surviving as a second spelling. Against\n * the NEW name that subclass fails twice over: `override` names a member the base no longer has,\n * and this abstract member is left unimplemented.\n */\n protected abstract outboundContextHeaders(destination: DestinationTrust): Map<string, string>;\n\n /**\n * Run the call. The default just logs it. Test-case RECORDING is a server concept, so\n * NodeProxyClient overrides this to capture the call when a recorder is in the context.\n *\n * Context fields are NOT passed in: a logging backend stamps them onto every record itself.\n */\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n protected async execute(\n route: RouteMetadata,\n requestDto: unknown,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n method: () => Promise<unknown>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n ): Promise<unknown> {\n // apiClass = the CONTRACT name (this.apiName, e.g. 'SaveApi') so this client log line MATCHES\n // the server's for the same call. A client has no impl class, so controllerName is omitted.\n const info = new ApiMethodInfo(\n 'client',\n this.apiName,\n route.methodName,\n undefined,\n route.mask,\n );\n return this.logApiCall.execute(info, requestDto, method);\n }\n\n /**\n * Reject, at bind time, an endpoint this environment cannot satisfy — e.g. a browser cannot\n * mint the OIDC token an @WpAuthOidc endpoint demands. Surfacing it here beats failing on the\n * first call in production. The default accepts everything.\n */\n protected assertEndpointSupported(_authMeta: AuthMeta | undefined, _methodName: string): void {}\n\n /**\n * The FRAMEWORK filters this environment installs on every client it builds, BENEATH whatever\n * the app passed to `createRpcClient`. The default installs none, so the browser runs the exact\n * code path it ran before the chain existed.\n *\n * \"Beneath\" is not a priority — see {@link initRoutes}. These are the filters that must judge\n * and sign what is ACTUALLY about to be sent, so no app priority may be allowed to get under\n * them: @webpieces/http-client-node installs its SSRF guard and its outbound-auth minter here,\n * and both would be defeated by an app filter that re-pointed the URL below them. Neither\n * concept can live in this class, because reading a RequestContext, resolving DNS and minting\n * an OIDC token are all things a browser bundle must never contain.\n */\n protected clientFilters(): ClientFilterDefinition[] {\n return [];\n }\n\n /**\n * Adapt a translated downstream failure into the error THIS environment's caller should see.\n *\n * THE INVARIANT, and the reason this hook exists at all:\n *\n * A status received from a downstream dependency describes OUR request to it. It is never the\n * status we return to OUR caller. The server that answered 404 is correct; the server that\n * asked for a route that does not exist is broken, and must say so as a 500.\n *\n * That invariant reads differently in the two environments, which is exactly why the ISOMORPHIC\n * {@link ClientErrorTranslator} cannot settle it:\n * - BROWSER: the client IS the end user's agent, so the downstream IS the answer. Pass it through\n * unchanged.\n * - NODE: server-to-server. A 4xx from a dependency is a caller-side defect (wrong path, wrong\n * base URL, an undeployed dependency, bad service credentials), so the caller owns it as a 500.\n *\n * ABSTRACT, not a defaulted pass-through, for the same reason\n * {@link outboundContextHeaders} takes a required `destination`: a permissive default puts the\n * wrong answer one keystroke away. A new environment subclass must SAY which of the two it is,\n * and there are exactly two subclasses in the repo, so the compile error is the migration.\n *\n * @param failure - the translated error, its provenance (app-registered vs built-in), and the\n * downstream status\n * @param callId - `ApiName.methodName`, so a rewritten message can still name the call\n */\n protected abstract adaptDownstreamFailure(failure: TranslatedFailure, callId: string): Error;\n\n /**\n * Fires before the logical call's attempts, once per RPC — the progress \"start marker\". Symmetric with\n * {@link onRequestEnd}: every start is followed by exactly one end, on every path, so a listener\n * can drive a counter (bar on / bar off) without leaking a permanently-spinning bar.\n *\n * The default is a no-op, so every existing subclass is unaffected.\n */\n protected onRequestStart(_route: RouteMetadata): void {}\n\n /**\n * Fires exactly ONCE after the call settles, on EVERY path (2xx, HTTP error, network reject) —\n * the \"stop marker\", carrying how it settled.\n *\n * Subsumes the older header-only hook: this is the ONLY place the `fetch` Response — and thus its\n * `Headers` — exists, so an app that needs to read a response header (e.g. a server-version stamp\n * for client↔server version matching) reads `outcome.headers` after settlement\n * and on both the ok and error paths. `outcome.ok`/`outcome.error` add the success-or-error\n * signal the header-only seam could not give.\n *\n * The default is a no-op, so every existing subclass is unaffected.\n */\n protected onRequestEnd(_route: RouteMetadata, _outcome: RequestOutcome): void {}\n\n // ---------------------------------------------------------------- contract binding\n\n /**\n * Bind this client to one API contract: read @ApiPath/@Endpoint/@Auth* off the prototype and\n * build the route map once. Each subclass's `init(api, config)` stores its own config, then\n * calls this.\n *\n * @param appFilters the app's OUTBOUND filters for this client, from `createRpcClient`. They are\n * merged with {@link clientFilters} and sorted by priority, highest OUTERMOST.\n * @throws Error if the prototype lacks @ApiPath, or declares an endpoint this environment\n * cannot satisfy (see {@link assertEndpointSupported}).\n */\n protected initRoutes(\n apiPrototype: ApiPrototype<object>,\n appFilters: ClientFilterDefinition[],\n ): void {\n this.appFilters = appFilters;\n this.apiClass = apiPrototype;\n if (!isApiPath(apiPrototype)) {\n const className = apiPrototype.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiPath()`);\n }\n\n const endpoints = getEndpoints(apiPrototype) || {};\n\n // apiName as the class name so client logs read \"SaveApi.save\", not \"undefined.save\"\n this.apiName = apiPrototype.name || 'UnknownApi';\n\n this.routeMap = new Map<string, RouteMetadata>();\n for (const methodName of Object.keys(endpoints)) {\n // One shared factory joins and validates method/path/query/body metadata for every\n // transport, rather than letting each generated client reinterpret the decorators.\n const route = RouteMetadataFactory.create(apiPrototype, methodName);\n const authMeta = route.authMeta;\n this.assertEndpointSupported(authMeta, methodName);\n this.routeMap.set(methodName, route);\n }\n\n // APP filters first (highest priority OUTERMOST, matching the server's FilterMatcher), then\n // the framework built-ins, ALWAYS innermost. Two separate sorts rather than one over the\n // union, deliberately: an app priority orders app filters against each other and nothing\n // else, so no number an app can type — however large — gets underneath the SSRF guard or the\n // credential minter. A single sorted list would make \"displace the guard\" a matter of typing\n // a bigger integer, and a security control an app can outrank by accident is not a control.\n //\n // Sorted here, once, so FilterChain itself never sorts — priority lives on the DEFINITION,\n // not on the filter.\n const byPriority = (a: ClientFilterDefinition, b: ClientFilterDefinition): number =>\n b.priority - a.priority;\n const ordered = [\n ...[...this.appFilters].sort(byPriority),\n ...[...this.clientFilters()].sort(byPriority),\n ];\n this.chain = new FilterChain<ClientRequest, Response>(\n ordered.map((definition: ClientFilterDefinition) => definition.filter),\n );\n }\n\n /** The contract's class name, for logs and recordings. */\n protected contractName(): string {\n return this.apiName;\n }\n\n /** Check if a route exists for the given method name. */\n hasRoute(methodName: string): boolean {\n return this.routeMap.has(methodName);\n }\n\n /**\n * Get route metadata for a method name.\n * @throws Error if no route found\n */\n getRoute(methodName: string): RouteMetadata {\n const route = this.routeMap.get(methodName);\n if (!route) {\n throw new Error(`No route found for method ${methodName}`);\n }\n return route;\n }\n\n // ---------------------------------------------------------------- the call\n\n /**\n * FAIL FAST, PER METHOD, at call time: some endpoints exist for a caller that is not us, and this\n * proxy could only ever build a request they are obliged to reject. Refusing here rather than at\n * bind time means an api that MIXES such endpoints with normal ones still yields a working client\n * for the normal ones; only calling the un-callable method throws.\n *\n * @throws Error naming the endpoint, what it declared, and who its real caller is.\n */\n private refuseEndpointNoClientCanCall(route: RouteMetadata): void {\n const authMode = route.authMeta?.mode;\n // @WpAuthApiKey: the credential is a CUSTOMER-held key, and the header carrying it is the app's\n // ApiKeyHook's choice, so this client has nothing to send and the call is a guaranteed 401.\n if (authMode?.kind === 'apikey') {\n throw new Error(\n `${this.apiName}.${route.methodName} is @WpAuthApiKey('${authMode.regime}') — only the partner ` +\n `holding that api key can call it, and the header carrying it is the app's ApiKeyHook's choice, ` +\n `so a webpieces client has no credential to send.`,\n );\n }\n // @WpAuthWebhook is DELIBERATELY absent from this list. It used to be here, on the assumption\n // that the vendor is always somebody else — but `@WpAuthWebhook(name)` names a signing SCHEME,\n // not a direction, and for an OUTBOUND partner webhook WE are the vendor. The environment's\n // outbound-auth filter asks its bound signer to produce the signature, which is the exact\n // mirror of the inbound WebhookAuthCallback that verifies one.\n }\n\n /** One logical call: one lifecycle pair and log entry across all strategy attempts. */\n // webpieces-disable no-any-unknown -- request and response DTOs are erased at the proxy boundary\n async makeRequest(route: RouteMetadata, args: unknown[]): Promise<unknown> {\n this.refuseEndpointNoClientCanCall(route);\n const mapped = HttpContractMapper.toWire(\n route.path,\n route.parameterBindings,\n route.bodyParameterIndex,\n args,\n );\n const logValue = mapped.body === undefined ? args : mapped.body;\n return this.execute(route, logValue, () => this.executeCall(route, args));\n }\n\n // webpieces-disable no-any-unknown -- response DTO is erased at the proxy boundary\n private async executeCall(route: RouteMetadata, args: unknown[]): Promise<unknown> {\n this.onRequestStart(route);\n let response: Response | undefined;\n // webpieces-disable no-any-unknown -- response DTO is erased at the proxy boundary\n let result: unknown;\n // webpieces-disable no-unmanaged-exceptions -- report one logical END, preserving the original thrown value\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n result = await CallRegistry.execute(\n this.apiClass,\n route.methodName,\n (timeoutMs: number) => {\n response = undefined;\n return CallDeadline.run(\n timeoutMs,\n new CallContext(this.apiName, route.methodName),\n async (signal: AbortSignal) => {\n const request = await this.prepareRequest(route, args);\n CallDeadline.throwIfAborted(signal);\n const received = await this.chain.execute(request, () =>\n this.sendOnce(request, signal),\n );\n CallDeadline.throwIfAborted(signal);\n response = received;\n return this.readResponse(received, route);\n },\n );\n },\n 30_000,\n );\n } catch (err: unknown) {\n const error = toError(err);\n this.onRequestEnd(\n route,\n new RequestOutcome(false, response?.status ?? 0, response?.headers, error),\n );\n throw err;\n }\n this.onRequestEnd(\n route,\n new RequestOutcome(true, response?.status ?? 0, response?.headers),\n );\n return result;\n }\n\n /** Fresh mutable request for every attempt, including URL, headers, auth and body. */\n // webpieces-disable no-any-unknown -- request DTO is erased at the proxy boundary\n private async prepareRequest(route: RouteMetadata, args: unknown[]): Promise<ClientRequest> {\n const baseUrl = await this.resolveBaseUrl();\n const mapped = HttpContractMapper.toWire(\n route.path,\n route.parameterBindings,\n route.bodyParameterIndex,\n args,\n );\n const headers = new Map<string, string>();\n const body = this.serializeBody(route, mapped.body, headers);\n const context = this.outboundContextHeaders(\n DestinationTrust.forAuthMode(route.authMeta?.mode),\n );\n for (const entry of context.entries()) headers.set(entry[0], entry[1]);\n return new ClientRequest(\n route,\n this.apiName,\n baseUrl,\n headers,\n body,\n mapped.body,\n mapped.path,\n );\n }\n\n /** Serialize exactly the encoding the endpoint declared; GET is always bodyless. */\n // webpieces-disable no-any-unknown -- request DTO type is erased at the generated proxy boundary\n private serializeBody(\n route: RouteMetadata,\n requestDto: unknown,\n headers: Map<string, string>,\n ): string | undefined {\n if (route.httpMethod === 'GET' || requestDto === undefined) return undefined;\n if (route.formPost) {\n headers.set('Content-Type', 'application/x-www-form-urlencoded');\n return this.serializeForm(requestDto, route);\n }\n headers.set('Content-Type', 'application/json');\n return JSON.stringify(requestDto);\n }\n\n /** Flat form DTO -> deterministic urlencoded bytes, repeating array-valued fields. */\n // webpieces-disable no-any-unknown -- form DTO fields are contract-owned and heterogeneous\n private serializeForm(requestDto: unknown, route: RouteMetadata): string {\n if (requestDto === null || typeof requestDto !== 'object' || Array.isArray(requestDto)) {\n throw new Error(\n `${this.apiName}.${route.methodName} declares formPost:true, so its body must be a flat object.`,\n );\n }\n const params = new URLSearchParams();\n // webpieces-disable no-any-unknown -- checked object is narrowed to its contract-owned field bag\n for (const key of Object.keys(requestDto as Record<string, unknown>).sort()) {\n // webpieces-disable no-any-unknown -- checked object is narrowed to its contract-owned field bag\n const value = (requestDto as Record<string, unknown>)[key];\n if (value === undefined || value === null) continue;\n const values = Array.isArray(value) ? value : [value];\n for (const item of values) {\n if (item !== undefined && item !== null) params.append(key, String(item));\n }\n }\n return params.toString();\n }\n\n /**\n * ONE transmission — the bottom of the filter chain, and the only place `fetch` is called.\n *\n * Everything it sends comes off the {@link ClientRequest} as the chain left it, so a filter's\n * edits to the url, the headers or the serialized body are exactly what goes on the wire. It may\n * run more than once for a single RPC when a filter follows a redirect.\n *\n * A network reject (offline, DNS, CORS preflight) is classified into a typed ApiConnectionError here (a\n * genuine bug passes through untouched) so that filters above see the same typed error the caller\n * will, rather than a raw platform reject.\n */\n private async sendOnce(request: ClientRequest, signal: AbortSignal): Promise<Response> {\n CallDeadline.throwIfAborted(signal);\n const options: RequestInit = {\n method: request.route.httpMethod,\n signal,\n headers: request.headersAsRecord(),\n redirect:\n request.route.responseType === 'full' || !request.followRedirects\n ? 'manual'\n : 'follow',\n };\n if (request.body !== undefined) {\n options.body = request.body;\n }\n // webpieces-disable no-unmanaged-exceptions -- classify a network reject, then rethrow it typed\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-fetch -- this IS the generated-client implementation the rule points everyone to\n return await fetch(request.url, options);\n } catch (err: unknown) {\n const error = toError(err);\n throw this.networkRejectClassifier.toNetworkError(error, request.url);\n }\n }\n\n /** Body consumption is inside the attempt deadline, including non-JSON error bodies. */\n // webpieces-disable no-any-unknown -- response DTO is erased at the proxy boundary\n private async readResponse(response: Response, route: RouteMetadata): Promise<unknown> {\n const callId = `${this.apiName}.${route.methodName}`;\n if (route.responseType === 'full') {\n return this.responseDtoFactory.fromFetch(\n response,\n await this.readFullResponseBody(response),\n );\n }\n // 266 is protocol success, but its body represents an expected user exception.\n if (response.ok && response.status !== 266) {\n if (!this.bodyReader.isJson(response)) {\n throw new Error(\n this.bodyReader.describeForeignBody(response, callId, await response.text()),\n );\n }\n return response.json();\n }\n const protocolError = await this.bodyReader.readErrorBody(response, callId);\n const translated = ClientErrorTranslator.translateError(\n this.responseDtoFactory.fromFetch(response, protocolError),\n );\n throw this.adaptDownstreamFailure(translated, callId);\n }\n\n /** Preserve empty, JSON, and protocol text bodies for caller-owned full responses. */\n // webpieces-disable no-any-unknown -- a full response deliberately preserves the caller-owned body\n private async readFullResponseBody(response: Response): Promise<unknown> {\n if (response.status === 204 || response.status === 304) return undefined;\n const text = await response.text();\n if (text === '') return undefined;\n if (!this.bodyReader.isJson(response)) return text;\n // webpieces-disable no-any-unknown -- parsed JSON is returned untouched to the typed contract caller\n return JSON.parse(text) as unknown;\n }\n}\n"]}
1
+ {"version":3,"file":"ProxyClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/ProxyClient.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isApiPath,\n getEndpoints,\n AuthMeta,\n DestinationTrust,\n RouteMetadata,\n LogApiCallImpl,\n ApiMethodInfo,\n toError,\n NetworkRejectClassifier,\n HttpContractMapper,\n RouteMetadataFactory,\n FilterChain,\n CallRegistry,\n CallDeadline,\n CallContext,\n DtoValue,\n RequestStream,\n ResponseStream,\n StreamTransportError,\n} from '@webpieces/core-util';\nimport { ApiPrototype } from './ApiPrototype';\nimport { ClientFilterDefinition } from './ClientFilter';\nimport { ClientRequest } from './ClientRequest';\nimport { ClientErrorTranslator } from './ClientErrorTranslator';\nimport { HttpResponseDtoFactory } from './HttpResponseDtoFactory';\nimport { RequestOutcome } from './RequestOutcome';\nimport { ResponseBodyReader } from './ResponseBodyReader';\nimport { TranslatedFailure } from './TranslatedFailure';\nimport { NdjsonRequestStream } from './NdjsonRequestStream';\nimport { SseResponseStream } from './SseResponseStream';\nimport { StreamingCapabilityError } from './StreamingCapabilityError';\nimport { ByteReadableStream } from './ByteStream';\n\nclass OpenedStreamingTransport {\n constructor(\n readonly response: Response,\n readonly upload: NdjsonRequestStream,\n ) {}\n}\n\n/**\n * ProxyClient - the HTTP call engine behind one API contract's client proxy.\n *\n * Contains ONLY what a browser can run: the route map built from the contract's decorators, URL\n * assembly, `fetch`, error translation, and logging. It holds no context object, no credentials,\n * and no recorder — it ASKS ITSELF for those through the hooks below, and each subclass answers\n * from its own environment.\n *\n * That is why the class is abstract rather than parameterized by a collaborator: a shared\n * header-provider seam would drag Node's AsyncLocalStorage vocabulary into a browser bundle and the\n * browser's store vocabulary into a server, and neither has any use for the other.\n *\n * NodeProxyClient (@webpieces/http-client-node) -> RequestContext, Secrets, mintIdToken, recording\n * BrowserProxyClient (@webpieces/http-client-browser) -> an app-held store, no credentials, no recording\n *\n * TWO-PHASE: collaborators arrive on the subclass constructor (so a DI container can supply them),\n * while the per-client state — which contract, which target — arrives on the subclass's `init`,\n * which calls {@link initRoutes}. That is what lets a factory hold a `Provider<ProxyClient>` and\n * hand out a fresh, independently-configured client per contract.\n */\nexport abstract class ProxyClient {\n // Assigned by initRoutes(), which every subclass's init() calls immediately after construction.\n private routeMap!: Map<string, RouteMetadata>;\n private apiName!: string;\n private apiClass!: ApiPrototype<object>;\n\n /**\n * The OUTBOUND filter chain, built once at bind time from {@link clientFilters} and reused for\n * every call. Built once rather than per call because a filter is STATELESS by contract (the\n * per-call state is the {@link ClientRequest} the chain is handed), exactly as on the server.\n */\n private chain!: FilterChain<ClientRequest, Response>;\n\n /**\n * The app's own filters, as handed to `createRpcClient`. Set by {@link initRoutes} BEFORE it\n * calls {@link clientFilters}, so an environment's built-ins may read the app's intent off them\n * — @webpieces/http-client-node takes the SSRF policy from an installed `ContextBaseUrlFilter`\n * that way, which keeps the one legitimate relaxation at the same construction site as the\n * decision to be re-pointable at all.\n */\n protected appFilters: ClientFilterDefinition[] = [];\n\n // Stateless + dependency-free, so the browser bundle keeps no DI on the fetch path.\n private readonly networkRejectClassifier = new NetworkRejectClassifier();\n\n // Same shape and same reason: stateless, so it is constructed here rather than injected.\n private readonly bodyReader = new ResponseBodyReader();\n\n /**\n * fetch `Response` -> the transport-neutral {@link HttpResponseDto} an app's `ErrorTranslators`\n * sees. Normalising HERE is what makes `fromWire` receive the identical shape in node and in the\n * browser: both environments share this class, and this is the only place either builds a DTO.\n */\n private readonly responseDtoFactory = new HttpResponseDtoFactory();\n\n /**\n * @param logApiCall - built by the SUBCLASS's package around that environment's ApiCallContext\n * (node: RequestContextApiCallContext; browser: BrowserApiCallContext). REQUIRED, with no\n * default: core-util cannot construct either one, and a default here would have to reach for a\n * process-global — which is exactly the throw-on-first-call this parameter deleted.\n */\n constructor(protected readonly logApiCall: LogApiCallImpl) {}\n\n // ---------------------------------------------------------------- environment hooks\n\n /** The callee's base URL. Async because a server may derive it from container metadata. */\n protected abstract resolveBaseUrl(): Promise<string>;\n\n /**\n * Context headers to put on the wire. Server reads RequestContext; browser reads its store.\n *\n * `destination` is derived from THIS route's auth mode and decides whether TRUSTED context keys\n * (`x-user-id`, `x-org-id`, `x-webpieces-roles`) may ride along — see {@link DestinationTrust}.\n * It is a required argument on purpose: a defaulted \"send everything\" would put the permissive\n * answer one keystroke away and make the safe one opt-in.\n *\n * RENAMED from `outboundHeaders()` in the same change that added `destination`, and the rename IS\n * the migration. TypeScript accepts an override that declares FEWER parameters than its base, so a\n * downstream `protected override outboundHeaders(): Map<string, string>` would have kept compiling\n * and silently ignored the gate — the permissive behaviour surviving as a second spelling. Against\n * the NEW name that subclass fails twice over: `override` names a member the base no longer has,\n * and this abstract member is left unimplemented.\n */\n protected abstract outboundContextHeaders(destination: DestinationTrust): Map<string, string>;\n\n /**\n * Run the call. The default just logs it. Test-case RECORDING is a server concept, so\n * NodeProxyClient overrides this to capture the call when a recorder is in the context.\n *\n * Context fields are NOT passed in: a logging backend stamps them onto every record itself.\n */\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n protected async execute(\n route: RouteMetadata,\n requestDto: unknown,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n method: () => Promise<unknown>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n ): Promise<unknown> {\n // apiClass = the CONTRACT name (this.apiName, e.g. 'SaveApi') so this client log line MATCHES\n // the server's for the same call. A client has no impl class, so controllerName is omitted.\n const info = new ApiMethodInfo(\n 'client',\n this.apiName,\n route.methodName,\n undefined,\n route.mask,\n );\n return this.logApiCall.execute(info, requestDto, method);\n }\n\n /**\n * Reject, at bind time, an endpoint this environment cannot satisfy — e.g. a browser cannot\n * mint the OIDC token an @WpAuthOidc endpoint demands. Surfacing it here beats failing on the\n * first call in production. The default accepts everything.\n */\n protected assertEndpointSupported(_authMeta: AuthMeta | undefined, _methodName: string): void {}\n\n /**\n * The FRAMEWORK filters this environment installs on every client it builds, BENEATH whatever\n * the app passed to `createRpcClient`. The default installs none, so the browser runs the exact\n * code path it ran before the chain existed.\n *\n * \"Beneath\" is not a priority — see {@link initRoutes}. These are the filters that must judge\n * and sign what is ACTUALLY about to be sent, so no app priority may be allowed to get under\n * them: @webpieces/http-client-node installs its SSRF guard and its outbound-auth minter here,\n * and both would be defeated by an app filter that re-pointed the URL below them. Neither\n * concept can live in this class, because reading a RequestContext, resolving DNS and minting\n * an OIDC token are all things a browser bundle must never contain.\n */\n protected clientFilters(): ClientFilterDefinition[] {\n return [];\n }\n\n /**\n * Adapt a translated downstream failure into the error THIS environment's caller should see.\n *\n * THE INVARIANT, and the reason this hook exists at all:\n *\n * A status received from a downstream dependency describes OUR request to it. It is never the\n * status we return to OUR caller. The server that answered 404 is correct; the server that\n * asked for a route that does not exist is broken, and must say so as a 500.\n *\n * That invariant reads differently in the two environments, which is exactly why the ISOMORPHIC\n * {@link ClientErrorTranslator} cannot settle it:\n * - BROWSER: the client IS the end user's agent, so the downstream IS the answer. Pass it through\n * unchanged.\n * - NODE: server-to-server. A 4xx from a dependency is a caller-side defect (wrong path, wrong\n * base URL, an undeployed dependency, bad service credentials), so the caller owns it as a 500.\n *\n * ABSTRACT, not a defaulted pass-through, for the same reason\n * {@link outboundContextHeaders} takes a required `destination`: a permissive default puts the\n * wrong answer one keystroke away. A new environment subclass must SAY which of the two it is,\n * and there are exactly two subclasses in the repo, so the compile error is the migration.\n *\n * @param failure - the translated error, its provenance (app-registered vs built-in), and the\n * downstream status\n * @param callId - `ApiName.methodName`, so a rewritten message can still name the call\n */\n protected abstract adaptDownstreamFailure(failure: TranslatedFailure, callId: string): Error;\n\n /** Whether fetch can read the response while its streaming request body remains open. */\n protected abstract supportsConcurrentDuplexFetch(): boolean;\n\n /** Environment-owned full-duplex transport after the shared filter chain has prepared it. */\n protected abstract sendStreamingTransport(\n request: ClientRequest,\n signal: AbortSignal,\n body: ByteReadableStream,\n ): Promise<Response>;\n\n /**\n * Fires before the logical call's attempts, once per RPC — the progress \"start marker\". Symmetric with\n * {@link onRequestEnd}: every start is followed by exactly one end, on every path, so a listener\n * can drive a counter (bar on / bar off) without leaking a permanently-spinning bar.\n *\n * The default is a no-op, so every existing subclass is unaffected.\n */\n protected onRequestStart(_route: RouteMetadata): void {}\n\n /**\n * Fires exactly ONCE after the call settles, on EVERY path (2xx, HTTP error, network reject) —\n * the \"stop marker\", carrying how it settled.\n *\n * Subsumes the older header-only hook: this is the ONLY place the `fetch` Response — and thus its\n * `Headers` — exists, so an app that needs to read a response header (e.g. a server-version stamp\n * for client↔server version matching) reads `outcome.headers` after settlement\n * and on both the ok and error paths. `outcome.ok`/`outcome.error` add the success-or-error\n * signal the header-only seam could not give.\n *\n * The default is a no-op, so every existing subclass is unaffected.\n */\n protected onRequestEnd(_route: RouteMetadata, _outcome: RequestOutcome): void {}\n\n // ---------------------------------------------------------------- contract binding\n\n /**\n * Bind this client to one API contract: read @ApiPath/@Endpoint/@Auth* off the prototype and\n * build the route map once. Each subclass's `init(api, config)` stores its own config, then\n * calls this.\n *\n * @param appFilters the app's OUTBOUND filters for this client, from `createRpcClient`. They are\n * merged with {@link clientFilters} and sorted by priority, highest OUTERMOST.\n * @throws Error if the prototype lacks @ApiPath, or declares an endpoint this environment\n * cannot satisfy (see {@link assertEndpointSupported}).\n */\n protected initRoutes(\n apiPrototype: ApiPrototype<object>,\n appFilters: ClientFilterDefinition[],\n ): void {\n this.appFilters = appFilters;\n this.apiClass = apiPrototype;\n if (!isApiPath(apiPrototype)) {\n const className = apiPrototype.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiPath()`);\n }\n\n const endpoints = getEndpoints(apiPrototype) || {};\n\n // apiName as the class name so client logs read \"SaveApi.save\", not \"undefined.save\"\n this.apiName = apiPrototype.name || 'UnknownApi';\n\n this.routeMap = new Map<string, RouteMetadata>();\n for (const methodName of Object.keys(endpoints)) {\n // One shared factory joins and validates method/path/query/body metadata for every\n // transport, rather than letting each generated client reinterpret the decorators.\n const route = RouteMetadataFactory.create(apiPrototype, methodName);\n const authMeta = route.authMeta;\n this.assertEndpointSupported(authMeta, methodName);\n this.routeMap.set(methodName, route);\n }\n\n // APP filters first (highest priority OUTERMOST, matching the server's FilterMatcher), then\n // the framework built-ins, ALWAYS innermost. Two separate sorts rather than one over the\n // union, deliberately: an app priority orders app filters against each other and nothing\n // else, so no number an app can type — however large — gets underneath the SSRF guard or the\n // credential minter. A single sorted list would make \"displace the guard\" a matter of typing\n // a bigger integer, and a security control an app can outrank by accident is not a control.\n //\n // Sorted here, once, so FilterChain itself never sorts — priority lives on the DEFINITION,\n // not on the filter.\n const byPriority = (a: ClientFilterDefinition, b: ClientFilterDefinition): number =>\n b.priority - a.priority;\n const ordered = [\n ...[...this.appFilters].sort(byPriority),\n ...[...this.clientFilters()].sort(byPriority),\n ];\n this.chain = new FilterChain<ClientRequest, Response>(\n ordered.map((definition: ClientFilterDefinition) => definition.filter),\n );\n }\n\n /** The contract's class name, for logs and recordings. */\n protected contractName(): string {\n return this.apiName;\n }\n\n /** Check if a route exists for the given method name. */\n hasRoute(methodName: string): boolean {\n return this.routeMap.has(methodName);\n }\n\n /**\n * Get route metadata for a method name.\n * @throws Error if no route found\n */\n getRoute(methodName: string): RouteMetadata {\n const route = this.routeMap.get(methodName);\n if (!route) {\n throw new Error(`No route found for method ${methodName}`);\n }\n return route;\n }\n\n // ---------------------------------------------------------------- the call\n\n /**\n * FAIL FAST, PER METHOD, at call time: some endpoints exist for a caller that is not us, and this\n * proxy could only ever build a request they are obliged to reject. Refusing here rather than at\n * bind time means an api that MIXES such endpoints with normal ones still yields a working client\n * for the normal ones; only calling the un-callable method throws.\n *\n * @throws Error naming the endpoint, what it declared, and who its real caller is.\n */\n private refuseEndpointNoClientCanCall(route: RouteMetadata): void {\n const authMode = route.authMeta?.mode;\n // @WpAuthApiKey: the credential is a CUSTOMER-held key, and the header carrying it is the app's\n // ApiKeyHook's choice, so this client has nothing to send and the call is a guaranteed 401.\n if (authMode?.kind === 'apikey') {\n throw new Error(\n `${this.apiName}.${route.methodName} is @WpAuthApiKey('${authMode.regime}') — only the partner ` +\n `holding that api key can call it, and the header carrying it is the app's ApiKeyHook's choice, ` +\n `so a webpieces client has no credential to send.`,\n );\n }\n // @WpAuthWebhook is DELIBERATELY absent from this list. It used to be here, on the assumption\n // that the vendor is always somebody else — but `@WpAuthWebhook(name)` names a signing SCHEME,\n // not a direction, and for an OUTBOUND partner webhook WE are the vendor. The environment's\n // outbound-auth filter asks its bound signer to produce the signature, which is the exact\n // mirror of the inbound WebhookAuthCallback that verifies one.\n }\n\n /** One logical call: one lifecycle pair and log entry across all strategy attempts. */\n // webpieces-disable no-any-unknown -- request and response DTOs are erased at the proxy boundary\n async makeRequest(route: RouteMetadata, args: unknown[]): Promise<unknown> {\n this.refuseEndpointNoClientCanCall(route);\n if (route.streaming) return this.makeStreamingRequest(route, args);\n const mapped = HttpContractMapper.toWire(\n route.path,\n route.parameterBindings,\n route.bodyParameterIndex,\n args,\n );\n const logValue = mapped.body === undefined ? args : mapped.body;\n return this.execute(route, logValue, () => this.executeCall(route, args));\n }\n\n /** Open a typed stream without bypassing the ordinary context/auth/filter request pipeline. */\n // webpieces-disable no-any-unknown -- generated proxy arguments are runtime-validated here\n private async makeStreamingRequest(route: RouteMetadata, args: unknown[]): Promise<unknown> {\n if (!this.supportsConcurrentDuplexFetch()) {\n throw new StreamingCapabilityError(\n 'browser',\n 'Fetch request streaming is half-duplex and has no protocol-compatible full-duplex fallback.',\n );\n }\n const destination = this.responseStream(args);\n return this.execute(route, 'stream-open', () =>\n this.executeStreamingCall(route, destination),\n );\n }\n\n // webpieces-disable no-any-unknown -- generated proxy arguments are runtime-validated here\n private responseStream(args: unknown[]): ResponseStream<DtoValue> {\n const candidate = args[0];\n if (args.length !== 1 || typeof candidate !== 'object' || candidate === null) {\n throw new StreamTransportError(\n `${this.apiName} streaming methods require exactly one ResponseStream argument.`,\n );\n }\n // webpieces-disable no-any-unknown -- reflected method argument is narrowed by the method checks below\n const record = candidate as Record<string, unknown>;\n if (\n typeof record['event'] !== 'function' ||\n typeof record['fail'] !== 'function' ||\n typeof record['complete'] !== 'function' ||\n typeof record['onCancel'] !== 'function'\n ) {\n throw new StreamTransportError(\n `${this.apiName} streaming method argument does not implement ResponseStream.`,\n );\n }\n return candidate as ResponseStream<DtoValue>;\n }\n\n /** One streaming handshake. Subsequent events stay on this established transport. */\n private async executeStreamingCall(\n route: RouteMetadata,\n destination: ResponseStream<DtoValue>,\n ): Promise<RequestStream<DtoValue>> {\n this.onRequestStart(route);\n let response: Response | undefined;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- lifecycle reports the original handshake failure\n try {\n const requestStream = await CallRegistry.execute(\n this.apiClass,\n route.methodName,\n (timeoutMs: number) =>\n CallDeadline.run(\n timeoutMs,\n new CallContext(this.apiName, route.methodName),\n async (deadlineSignal: AbortSignal) => {\n const result = await this.openStreamingTransport(\n route,\n destination,\n deadlineSignal,\n );\n response = result.response;\n return result.upload;\n },\n ),\n 30_000,\n );\n this.onRequestEnd(\n route,\n new RequestOutcome(true, response?.status ?? 0, response?.headers),\n );\n return requestStream;\n } catch (err: unknown) {\n const error = toError(err);\n this.onRequestEnd(\n route,\n new RequestOutcome(false, response?.status ?? 0, response?.headers, error),\n );\n throw err;\n }\n }\n\n private async openStreamingTransport(\n route: RouteMetadata,\n destination: ResponseStream<DtoValue>,\n deadlineSignal: AbortSignal,\n ): Promise<OpenedStreamingTransport> {\n const metadata = route.streaming;\n if (!metadata) throw new StreamTransportError('Streaming metadata disappeared.');\n const request = await this.prepareStreamingRequest(route);\n const controller = new AbortController();\n deadlineSignal.addEventListener('abort', (): void => controller.abort(), { once: true });\n const upload = new NdjsonRequestStream(\n metadata,\n // webpieces-disable no-any-unknown -- AbortController accepts a platform-defined cancellation reason\n (reason?: unknown) => controller.abort(reason),\n );\n const response = await this.chain.execute(request, () =>\n this.sendStreamingOnce(request, controller.signal, upload.body),\n );\n if (!response.ok) {\n await upload.transportFailed(\n new StreamTransportError(\n `Streaming handshake failed with HTTP ${response.status}.`,\n ),\n );\n await this.readResponse(response, route);\n throw new StreamTransportError('Streaming handshake was rejected.');\n }\n const contentType = response.headers.get('content-type') ?? '';\n if (!contentType.toLowerCase().startsWith('text/event-stream')) {\n const error = new StreamTransportError(\n `Streaming response requires text/event-stream, received '${contentType || 'missing'}'.`,\n );\n await upload.transportFailed(error);\n throw error;\n }\n void new SseResponseStream()\n .consume(response, destination, metadata, upload)\n .catch(() => undefined);\n return new OpenedStreamingTransport(response, upload);\n }\n\n /** Fresh filter-visible request metadata; the live request body is transport-owned. */\n private async prepareStreamingRequest(route: RouteMetadata): Promise<ClientRequest> {\n const baseUrl = await this.resolveBaseUrl();\n const headers = new Map<string, string>();\n headers.set('Content-Type', 'application/x-ndjson');\n headers.set('Accept', 'text/event-stream');\n const context = this.outboundContextHeaders(\n DestinationTrust.forAuthMode(route.authMeta?.mode),\n );\n for (const entry of context.entries()) headers.set(entry[0], entry[1]);\n return new ClientRequest(route, this.apiName, baseUrl, headers, undefined, undefined);\n }\n\n // webpieces-disable no-any-unknown -- response DTO is erased at the proxy boundary\n private async executeCall(route: RouteMetadata, args: unknown[]): Promise<unknown> {\n this.onRequestStart(route);\n let response: Response | undefined;\n // webpieces-disable no-any-unknown -- response DTO is erased at the proxy boundary\n let result: unknown;\n // webpieces-disable no-unmanaged-exceptions -- report one logical END, preserving the original thrown value\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n result = await CallRegistry.execute(\n this.apiClass,\n route.methodName,\n (timeoutMs: number) => {\n response = undefined;\n return CallDeadline.run(\n timeoutMs,\n new CallContext(this.apiName, route.methodName),\n async (signal: AbortSignal) => {\n const request = await this.prepareRequest(route, args);\n CallDeadline.throwIfAborted(signal);\n const received = await this.chain.execute(request, () =>\n this.sendOnce(request, signal),\n );\n CallDeadline.throwIfAborted(signal);\n response = received;\n return this.readResponse(received, route);\n },\n );\n },\n 30_000,\n );\n } catch (err: unknown) {\n const error = toError(err);\n this.onRequestEnd(\n route,\n new RequestOutcome(false, response?.status ?? 0, response?.headers, error),\n );\n throw err;\n }\n this.onRequestEnd(\n route,\n new RequestOutcome(true, response?.status ?? 0, response?.headers),\n );\n return result;\n }\n\n /** Fresh mutable request for every attempt, including URL, headers, auth and body. */\n // webpieces-disable no-any-unknown -- request DTO is erased at the proxy boundary\n private async prepareRequest(route: RouteMetadata, args: unknown[]): Promise<ClientRequest> {\n const baseUrl = await this.resolveBaseUrl();\n const mapped = HttpContractMapper.toWire(\n route.path,\n route.parameterBindings,\n route.bodyParameterIndex,\n args,\n );\n const headers = new Map<string, string>();\n const body = this.serializeBody(route, mapped.body, headers);\n const context = this.outboundContextHeaders(\n DestinationTrust.forAuthMode(route.authMeta?.mode),\n );\n for (const entry of context.entries()) headers.set(entry[0], entry[1]);\n return new ClientRequest(\n route,\n this.apiName,\n baseUrl,\n headers,\n body,\n mapped.body,\n mapped.path,\n );\n }\n\n /** Serialize exactly the encoding the endpoint declared; GET is always bodyless. */\n // webpieces-disable no-any-unknown -- request DTO type is erased at the generated proxy boundary\n private serializeBody(\n route: RouteMetadata,\n requestDto: unknown,\n headers: Map<string, string>,\n ): string | undefined {\n if (route.httpMethod === 'GET' || requestDto === undefined) return undefined;\n if (route.formPost) {\n headers.set('Content-Type', 'application/x-www-form-urlencoded');\n return this.serializeForm(requestDto, route);\n }\n headers.set('Content-Type', 'application/json');\n return JSON.stringify(requestDto);\n }\n\n /** Flat form DTO -> deterministic urlencoded bytes, repeating array-valued fields. */\n // webpieces-disable no-any-unknown -- form DTO fields are contract-owned and heterogeneous\n private serializeForm(requestDto: unknown, route: RouteMetadata): string {\n if (requestDto === null || typeof requestDto !== 'object' || Array.isArray(requestDto)) {\n throw new Error(\n `${this.apiName}.${route.methodName} declares formPost:true, so its body must be a flat object.`,\n );\n }\n const params = new URLSearchParams();\n // webpieces-disable no-any-unknown -- checked object is narrowed to its contract-owned field bag\n for (const key of Object.keys(requestDto as Record<string, unknown>).sort()) {\n // webpieces-disable no-any-unknown -- checked object is narrowed to its contract-owned field bag\n const value = (requestDto as Record<string, unknown>)[key];\n if (value === undefined || value === null) continue;\n const values = Array.isArray(value) ? value : [value];\n for (const item of values) {\n if (item !== undefined && item !== null) params.append(key, String(item));\n }\n }\n return params.toString();\n }\n\n /**\n * ONE transmission — the bottom of the filter chain, and the only place `fetch` is called.\n *\n * Everything it sends comes off the {@link ClientRequest} as the chain left it, so a filter's\n * edits to the url, the headers or the serialized body are exactly what goes on the wire. It may\n * run more than once for a single RPC when a filter follows a redirect.\n *\n * A network reject (offline, DNS, CORS preflight) is classified into a typed ApiConnectionError here (a\n * genuine bug passes through untouched) so that filters above see the same typed error the caller\n * will, rather than a raw platform reject.\n */\n private async sendOnce(request: ClientRequest, signal: AbortSignal): Promise<Response> {\n CallDeadline.throwIfAborted(signal);\n const options: RequestInit = {\n method: request.route.httpMethod,\n signal,\n headers: request.headersAsRecord(),\n redirect:\n request.route.responseType === 'full' || !request.followRedirects\n ? 'manual'\n : 'follow',\n };\n if (request.body !== undefined) {\n options.body = request.body;\n }\n // webpieces-disable no-unmanaged-exceptions -- classify a network reject, then rethrow it typed\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-fetch -- this IS the generated-client implementation the rule points everyone to\n return await fetch(request.url, options);\n } catch (err: unknown) {\n const error = toError(err);\n throw this.networkRejectClassifier.toNetworkError(error, request.url);\n }\n }\n\n /** Node fetch's streaming upload option. Browser callers are refused before reaching here. */\n private async sendStreamingOnce(\n request: ClientRequest,\n signal: AbortSignal,\n body: ByteReadableStream,\n ): Promise<Response> {\n CallDeadline.throwIfAborted(signal);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- platform rejects are normalized below\n try {\n return await this.sendStreamingTransport(request, signal, body);\n } catch (err: unknown) {\n const error = toError(err);\n throw this.networkRejectClassifier.toNetworkError(error, request.url);\n }\n }\n\n /** Body consumption is inside the attempt deadline, including non-JSON error bodies. */\n // webpieces-disable no-any-unknown -- response DTO is erased at the proxy boundary\n private async readResponse(response: Response, route: RouteMetadata): Promise<unknown> {\n const callId = `${this.apiName}.${route.methodName}`;\n if (route.responseType === 'full') {\n return this.responseDtoFactory.fromFetch(\n response,\n await this.readFullResponseBody(response),\n );\n }\n // 266 is protocol success, but its body represents an expected user exception.\n if (response.ok && response.status !== 266) {\n if (!this.bodyReader.isJson(response)) {\n throw new Error(\n this.bodyReader.describeForeignBody(response, callId, await response.text()),\n );\n }\n return response.json();\n }\n const protocolError = await this.bodyReader.readErrorBody(response, callId);\n const translated = ClientErrorTranslator.translateError(\n this.responseDtoFactory.fromFetch(response, protocolError),\n );\n throw this.adaptDownstreamFailure(translated, callId);\n }\n\n /** Preserve empty, JSON, and protocol text bodies for caller-owned full responses. */\n // webpieces-disable no-any-unknown -- a full response deliberately preserves the caller-owned body\n private async readFullResponseBody(response: Response): Promise<unknown> {\n if (response.status === 204 || response.status === 304) return undefined;\n const text = await response.text();\n if (text === '') return undefined;\n if (!this.bodyReader.isJson(response)) return text;\n // webpieces-disable no-any-unknown -- parsed JSON is returned untouched to the typed contract caller\n return JSON.parse(text) as unknown;\n }\n}\n"]}
@@ -0,0 +1,17 @@
1
+ /** One fully delimited Server-Sent Event. `data:` fields are joined with a newline. */
2
+ export declare class SseEvent {
3
+ readonly data: string;
4
+ readonly event?: string | undefined;
5
+ constructor(data: string, event?: string | undefined);
6
+ }
7
+ /** Incremental SSE parser. It deliberately ignores comments, id, retry and unknown fields. */
8
+ export declare class SseEventParser {
9
+ private readonly decoder;
10
+ private buffered;
11
+ private eventName;
12
+ private dataLines;
13
+ feed(bytes: Uint8Array | string): SseEvent[];
14
+ finish(): SseEvent[];
15
+ private drainLines;
16
+ private acceptLine;
17
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SseEventParser = exports.SseEvent = void 0;
4
+ const core_util_1 = require("@webpieces/core-util");
5
+ const Utf8Codec_1 = require("./Utf8Codec");
6
+ /** One fully delimited Server-Sent Event. `data:` fields are joined with a newline. */
7
+ class SseEvent {
8
+ data;
9
+ event;
10
+ constructor(data, event) {
11
+ this.data = data;
12
+ this.event = event;
13
+ }
14
+ }
15
+ exports.SseEvent = SseEvent;
16
+ /** Incremental SSE parser. It deliberately ignores comments, id, retry and unknown fields. */
17
+ class SseEventParser {
18
+ decoder = new Utf8Codec_1.Utf8Codec();
19
+ buffered = '';
20
+ eventName;
21
+ dataLines = [];
22
+ feed(bytes) {
23
+ this.buffered += typeof bytes === 'string' ? bytes : this.decoder.decode(bytes, true);
24
+ return this.drainLines(false);
25
+ }
26
+ finish() {
27
+ this.buffered += this.decoder.decode(undefined, false);
28
+ return this.drainLines(true);
29
+ }
30
+ drainLines(finished) {
31
+ const events = [];
32
+ let newline = this.buffered.indexOf('\n');
33
+ while (newline >= 0) {
34
+ const rawLine = this.buffered.slice(0, newline);
35
+ this.buffered = this.buffered.slice(newline + 1);
36
+ this.acceptLine(rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine, events);
37
+ newline = this.buffered.indexOf('\n');
38
+ }
39
+ if (finished && this.buffered !== '') {
40
+ const rawLine = this.buffered;
41
+ this.buffered = '';
42
+ this.acceptLine(rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine, events);
43
+ }
44
+ if (finished && this.dataLines.length > 0) {
45
+ throw new core_util_1.StreamTransportError('SSE response ended in the middle of an event.');
46
+ }
47
+ return events;
48
+ }
49
+ acceptLine(line, events) {
50
+ if (line === '') {
51
+ if (this.dataLines.length > 0) {
52
+ events.push(new SseEvent(this.dataLines.join('\n'), this.eventName));
53
+ }
54
+ this.dataLines = [];
55
+ this.eventName = undefined;
56
+ return;
57
+ }
58
+ if (line.startsWith(':'))
59
+ return;
60
+ const separator = line.indexOf(':');
61
+ const field = separator < 0 ? line : line.slice(0, separator);
62
+ let value = separator < 0 ? '' : line.slice(separator + 1);
63
+ if (value.startsWith(' '))
64
+ value = value.slice(1);
65
+ if (field === 'data')
66
+ this.dataLines.push(value);
67
+ if (field === 'event')
68
+ this.eventName = value;
69
+ }
70
+ }
71
+ exports.SseEventParser = SseEventParser;
72
+ //# sourceMappingURL=SseEventParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SseEventParser.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/SseEventParser.ts"],"names":[],"mappings":"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","sourcesContent":["import { StreamTransportError } from '@webpieces/core-util';\nimport { Utf8Codec } from './Utf8Codec';\n\n/** One fully delimited Server-Sent Event. `data:` fields are joined with a newline. */\nexport class SseEvent {\n constructor(\n public readonly data: string,\n public readonly event?: string,\n ) {}\n}\n\n/** Incremental SSE parser. It deliberately ignores comments, id, retry and unknown fields. */\nexport class SseEventParser {\n private readonly decoder = new Utf8Codec();\n private buffered = '';\n private eventName: string | undefined;\n private dataLines: string[] = [];\n\n feed(bytes: Uint8Array | string): SseEvent[] {\n this.buffered += typeof bytes === 'string' ? bytes : this.decoder.decode(bytes, true);\n return this.drainLines(false);\n }\n\n finish(): SseEvent[] {\n this.buffered += this.decoder.decode(undefined, false);\n return this.drainLines(true);\n }\n\n private drainLines(finished: boolean): SseEvent[] {\n const events: SseEvent[] = [];\n let newline = this.buffered.indexOf('\\n');\n while (newline >= 0) {\n const rawLine = this.buffered.slice(0, newline);\n this.buffered = this.buffered.slice(newline + 1);\n this.acceptLine(rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine, events);\n newline = this.buffered.indexOf('\\n');\n }\n if (finished && this.buffered !== '') {\n const rawLine = this.buffered;\n this.buffered = '';\n this.acceptLine(rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine, events);\n }\n if (finished && this.dataLines.length > 0) {\n throw new StreamTransportError('SSE response ended in the middle of an event.');\n }\n return events;\n }\n\n private acceptLine(line: string, events: SseEvent[]): void {\n if (line === '') {\n if (this.dataLines.length > 0) {\n events.push(new SseEvent(this.dataLines.join('\\n'), this.eventName));\n }\n this.dataLines = [];\n this.eventName = undefined;\n return;\n }\n if (line.startsWith(':')) return;\n const separator = line.indexOf(':');\n const field = separator < 0 ? line : line.slice(0, separator);\n let value = separator < 0 ? '' : line.slice(separator + 1);\n if (value.startsWith(' ')) value = value.slice(1);\n if (field === 'data') this.dataLines.push(value);\n if (field === 'event') this.eventName = value;\n }\n}\n"]}
@@ -0,0 +1,9 @@
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
+ }
@@ -0,0 +1,75 @@
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.responseEventClass, 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
@@ -0,0 +1 @@
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.responseEventClass, 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"]}
@@ -0,0 +1,7 @@
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
+ }
@@ -0,0 +1,67 @@
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
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,5 @@
1
+ import { StreamTransportError } from '@webpieces/core-util';
2
+ /** Raised before opening a stream when the runtime cannot safely do bidirectional fetch. */
3
+ export declare class StreamingCapabilityError extends StreamTransportError {
4
+ constructor(runtime: 'browser' | 'node', detail: string, options?: ErrorOptions);
5
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamingCapabilityError = void 0;
4
+ const core_util_1 = require("@webpieces/core-util");
5
+ /** Raised before opening a stream when the runtime cannot safely do bidirectional fetch. */
6
+ class StreamingCapabilityError extends core_util_1.StreamTransportError {
7
+ constructor(runtime, detail, options) {
8
+ super(`${runtime} cannot open this typed duplex HTTP stream: ${detail} ` +
9
+ 'The transport requires a streaming NDJSON request body and a concurrently readable SSE response.', undefined, undefined, options?.cause instanceof Error ? options.cause : undefined);
10
+ }
11
+ }
12
+ exports.StreamingCapabilityError = StreamingCapabilityError;
13
+ //# sourceMappingURL=StreamingCapabilityError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamingCapabilityError.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/StreamingCapabilityError.ts"],"names":[],"mappings":";;;AAAA,oDAA4D;AAE5D,4FAA4F;AAC5F,MAAa,wBAAyB,SAAQ,gCAAoB;IAC9D,YAAY,OAA2B,EAAE,MAAc,EAAE,OAAsB;QAC3E,KAAK,CACD,GAAG,OAAO,+CAA+C,MAAM,GAAG;YAC9D,kGAAkG,EACtG,SAAS,EACT,SAAS,EACT,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAC9D,CAAC;IACN,CAAC;CACJ;AAVD,4DAUC","sourcesContent":["import { StreamTransportError } from '@webpieces/core-util';\n\n/** Raised before opening a stream when the runtime cannot safely do bidirectional fetch. */\nexport class StreamingCapabilityError extends StreamTransportError {\n constructor(runtime: 'browser' | 'node', detail: string, options?: ErrorOptions) {\n super(\n `${runtime} cannot open this typed duplex HTTP stream: ${detail} ` +\n 'The transport requires a streaming NDJSON request body and a concurrently readable SSE response.',\n undefined,\n undefined,\n options?.cause instanceof Error ? options.cause : undefined,\n );\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ /** Small dependency-free UTF-8 codec for browser, Node, and React Native bundles. */
2
+ export declare class Utf8Codec {
3
+ private pending;
4
+ encode(value: string): Uint8Array;
5
+ decode(bytes?: Uint8Array, stream?: boolean): string;
6
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Utf8Codec = void 0;
4
+ /** Small dependency-free UTF-8 codec for browser, Node, and React Native bundles. */
5
+ class Utf8Codec {
6
+ pending = [];
7
+ encode(value) {
8
+ const bytes = [];
9
+ for (const character of value) {
10
+ const point = character.codePointAt(0) ?? 0;
11
+ if (point <= 0x7f) {
12
+ bytes.push(point);
13
+ }
14
+ else if (point <= 0x7ff) {
15
+ bytes.push(0xc0 | (point >> 6), 0x80 | (point & 0x3f));
16
+ }
17
+ else if (point <= 0xffff) {
18
+ bytes.push(0xe0 | (point >> 12), 0x80 | ((point >> 6) & 0x3f), 0x80 | (point & 0x3f));
19
+ }
20
+ else {
21
+ bytes.push(0xf0 | (point >> 18), 0x80 | ((point >> 12) & 0x3f), 0x80 | ((point >> 6) & 0x3f), 0x80 | (point & 0x3f));
22
+ }
23
+ }
24
+ return Uint8Array.from(bytes);
25
+ }
26
+ decode(bytes, stream = false) {
27
+ const input = [...this.pending, ...(bytes ?? [])];
28
+ this.pending = [];
29
+ let output = '';
30
+ let index = 0;
31
+ while (index < input.length) {
32
+ const first = input[index];
33
+ const length = first <= 0x7f
34
+ ? 1
35
+ : first >= 0xc2 && first <= 0xdf
36
+ ? 2
37
+ : first >= 0xe0 && first <= 0xef
38
+ ? 3
39
+ : first >= 0xf0 && first <= 0xf4
40
+ ? 4
41
+ : 0;
42
+ if (length === 0) {
43
+ output += '\ufffd';
44
+ index++;
45
+ continue;
46
+ }
47
+ if (index + length > input.length) {
48
+ if (stream)
49
+ this.pending = input.slice(index);
50
+ else
51
+ output += '\ufffd';
52
+ break;
53
+ }
54
+ let point = length === 1 ? first : first & (0x7f >> length);
55
+ let valid = true;
56
+ for (let offset = 1; offset < length; offset++) {
57
+ const continuation = input[index + offset];
58
+ if ((continuation & 0xc0) !== 0x80) {
59
+ valid = false;
60
+ break;
61
+ }
62
+ point = (point << 6) | (continuation & 0x3f);
63
+ }
64
+ const minimum = length === 1 ? 0 : length === 2 ? 0x80 : length === 3 ? 0x800 : 0x10000;
65
+ if (!valid ||
66
+ point < minimum ||
67
+ point > 0x10ffff ||
68
+ (point >= 0xd800 && point <= 0xdfff)) {
69
+ output += '\ufffd';
70
+ index++;
71
+ continue;
72
+ }
73
+ output += String.fromCodePoint(point);
74
+ index += length;
75
+ }
76
+ return output;
77
+ }
78
+ }
79
+ exports.Utf8Codec = Utf8Codec;
80
+ //# sourceMappingURL=Utf8Codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Utf8Codec.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-core/src/Utf8Codec.ts"],"names":[],"mappings":"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","sourcesContent":["/** Small dependency-free UTF-8 codec for browser, Node, and React Native bundles. */\nexport class Utf8Codec {\n private pending: number[] = [];\n\n encode(value: string): Uint8Array {\n const bytes: number[] = [];\n for (const character of value) {\n const point = character.codePointAt(0) ?? 0;\n if (point <= 0x7f) {\n bytes.push(point);\n } else if (point <= 0x7ff) {\n bytes.push(0xc0 | (point >> 6), 0x80 | (point & 0x3f));\n } else if (point <= 0xffff) {\n bytes.push(\n 0xe0 | (point >> 12),\n 0x80 | ((point >> 6) & 0x3f),\n 0x80 | (point & 0x3f),\n );\n } else {\n bytes.push(\n 0xf0 | (point >> 18),\n 0x80 | ((point >> 12) & 0x3f),\n 0x80 | ((point >> 6) & 0x3f),\n 0x80 | (point & 0x3f),\n );\n }\n }\n return Uint8Array.from(bytes);\n }\n\n decode(bytes?: Uint8Array, stream = false): string {\n const input = [...this.pending, ...(bytes ?? [])];\n this.pending = [];\n let output = '';\n let index = 0;\n while (index < input.length) {\n const first = input[index];\n const length =\n first <= 0x7f\n ? 1\n : first >= 0xc2 && first <= 0xdf\n ? 2\n : first >= 0xe0 && first <= 0xef\n ? 3\n : first >= 0xf0 && first <= 0xf4\n ? 4\n : 0;\n if (length === 0) {\n output += '\\ufffd';\n index++;\n continue;\n }\n if (index + length > input.length) {\n if (stream) this.pending = input.slice(index);\n else output += '\\ufffd';\n break;\n }\n let point = length === 1 ? first : first & (0x7f >> length);\n let valid = true;\n for (let offset = 1; offset < length; offset++) {\n const continuation = input[index + offset];\n if ((continuation & 0xc0) !== 0x80) {\n valid = false;\n break;\n }\n point = (point << 6) | (continuation & 0x3f);\n }\n const minimum = length === 1 ? 0 : length === 2 ? 0x80 : length === 3 ? 0x800 : 0x10000;\n if (\n !valid ||\n point < minimum ||\n point > 0x10ffff ||\n (point >= 0xd800 && point <= 0xdfff)\n ) {\n output += '\\ufffd';\n index++;\n continue;\n }\n output += String.fromCodePoint(point);\n index += length;\n }\n return output;\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -36,3 +36,10 @@ export { ResponseBodyReader } from './ResponseBodyReader';
36
36
  export { ClientRequest } from './ClientRequest';
37
37
  export { ClientFilterDefinition } from './ClientFilter';
38
38
  export type { ClientFilter } from './ClientFilter';
39
+ export { NdjsonRequestStream } from './NdjsonRequestStream';
40
+ export { SseEvent, SseEventParser } from './SseEventParser';
41
+ export { SseResponseStream } from './SseResponseStream';
42
+ export { StreamEnvelopeCodec } from './StreamEnvelopeCodec';
43
+ export { StreamingCapabilityError } from './StreamingCapabilityError';
44
+ export { Utf8Codec } from './Utf8Codec';
45
+ export type { ByteReadableStream, ByteStreamReader, ByteReadResult } from './ByteStream';
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.ClientFilterDefinition = exports.ClientRequest = exports.ResponseBodyReader = exports.TranslatedFailure = exports.HttpResponseDtoFactory = exports.UnexpectedApiResponseError = exports.ClientErrorTranslator = exports.buildClientProxy = exports.RequestOutcome = exports.ProxyClient = void 0;
29
+ exports.Utf8Codec = exports.StreamingCapabilityError = exports.StreamEnvelopeCodec = exports.SseResponseStream = exports.SseEventParser = exports.SseEvent = exports.NdjsonRequestStream = exports.ClientFilterDefinition = exports.ClientRequest = exports.ResponseBodyReader = exports.TranslatedFailure = exports.HttpResponseDtoFactory = exports.UnexpectedApiResponseError = 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,4 +52,19 @@ var ClientRequest_1 = require("./ClientRequest");
52
52
  Object.defineProperty(exports, "ClientRequest", { enumerable: true, get: function () { return ClientRequest_1.ClientRequest; } });
53
53
  var ClientFilter_1 = require("./ClientFilter");
54
54
  Object.defineProperty(exports, "ClientFilterDefinition", { enumerable: true, get: function () { return ClientFilter_1.ClientFilterDefinition; } });
55
+ // Generic streaming wire adapters: NDJSON uploads, request-scoped SSE downloads, and a typed
56
+ // capability failure for runtimes that cannot safely keep both fetch halves open concurrently.
57
+ var NdjsonRequestStream_1 = require("./NdjsonRequestStream");
58
+ Object.defineProperty(exports, "NdjsonRequestStream", { enumerable: true, get: function () { return NdjsonRequestStream_1.NdjsonRequestStream; } });
59
+ var SseEventParser_1 = require("./SseEventParser");
60
+ Object.defineProperty(exports, "SseEvent", { enumerable: true, get: function () { return SseEventParser_1.SseEvent; } });
61
+ Object.defineProperty(exports, "SseEventParser", { enumerable: true, get: function () { return SseEventParser_1.SseEventParser; } });
62
+ var SseResponseStream_1 = require("./SseResponseStream");
63
+ Object.defineProperty(exports, "SseResponseStream", { enumerable: true, get: function () { return SseResponseStream_1.SseResponseStream; } });
64
+ var StreamEnvelopeCodec_1 = require("./StreamEnvelopeCodec");
65
+ Object.defineProperty(exports, "StreamEnvelopeCodec", { enumerable: true, get: function () { return StreamEnvelopeCodec_1.StreamEnvelopeCodec; } });
66
+ var StreamingCapabilityError_1 = require("./StreamingCapabilityError");
67
+ Object.defineProperty(exports, "StreamingCapabilityError", { enumerable: true, get: function () { return StreamingCapabilityError_1.StreamingCapabilityError; } });
68
+ var Utf8Codec_1 = require("./Utf8Codec");
69
+ Object.defineProperty(exports, "Utf8Codec", { enumerable: true, get: function () { return Utf8Codec_1.Utf8Codec; } });
55
70
  //# sourceMappingURL=index.js.map
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,2EAA0E;AAAjE,wIAAA,0BAA0B,OAAA;AACnC,gGAAgG;AAChG,kFAAkF;AAClF,mEAAkE;AAAzD,gIAAA,sBAAsB,OAAA;AAC/B,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAC3B,kGAAkG;AAClG,kFAAkF;AAClF,gEAAgE;AAChE,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,+CAAwD;AAA/C,sHAAA,sBAAsB,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';\nexport { UnexpectedApiResponseError } from './UnexpectedApiResponseError';\n// The CLIENT-side transport boundary: a fetch Response becomes the ONE HttpResponseDto an app's\n// ErrorTranslators sees, so node and browser hand `fromWire` the identical shape.\nexport { HttpResponseDtoFactory } from './HttpResponseDtoFactory';\nexport { TranslatedFailure } from './TranslatedFailure';\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 { ClientFilterDefinition } from './ClientFilter';\nexport type { ClientFilter } from './ClientFilter';\n"]}
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,2EAA0E;AAAjE,wIAAA,0BAA0B,OAAA;AACnC,gGAAgG;AAChG,kFAAkF;AAClF,mEAAkE;AAAzD,gIAAA,sBAAsB,OAAA;AAC/B,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAC3B,kGAAkG;AAClG,kFAAkF;AAClF,gEAAgE;AAChE,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,+CAAwD;AAA/C,sHAAA,sBAAsB,OAAA;AAE/B,6FAA6F;AAC7F,+FAA+F;AAC/F,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,mDAA4D;AAAnD,0GAAA,QAAQ,OAAA;AAAE,gHAAA,cAAc,OAAA;AACjC,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,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';\nexport { UnexpectedApiResponseError } from './UnexpectedApiResponseError';\n// The CLIENT-side transport boundary: a fetch Response becomes the ONE HttpResponseDto an app's\n// ErrorTranslators sees, so node and browser hand `fromWire` the identical shape.\nexport { HttpResponseDtoFactory } from './HttpResponseDtoFactory';\nexport { TranslatedFailure } from './TranslatedFailure';\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 { 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 { NdjsonRequestStream } from './NdjsonRequestStream';\nexport { SseEvent, SseEventParser } from './SseEventParser';\nexport { SseResponseStream } from './SseResponseStream';\nexport { StreamEnvelopeCodec } from './StreamEnvelopeCodec';\nexport { StreamingCapabilityError } from './StreamingCapabilityError';\nexport { Utf8Codec } from './Utf8Codec';\nexport type { ByteReadableStream, ByteStreamReader, ByteReadResult } from './ByteStream';\n"]}