@zlink-systems/http-client 0.26.0 → 0.27.0

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.
@@ -62,222 +62,24 @@ var ZLINK_PACKET_JSON_CONTRACTS = Symbol.for("@zlink-systems/framework:packet-js
62
62
  // packages/framework/src/contracts/Handlers/Attributes.ts
63
63
  var ZLINK_DECORATOR_METADATA = Symbol.for("@zlink-systems/framework:decorator");
64
64
 
65
- // packages/http-client/src/runtime/http-client-errors.ts
66
- function responseBodySizeExceeded(message = "HTTP response exceeded the maximum body size") {
67
- return new ZLinkFrameworkException(4 /* Rejected */, message);
68
- }
69
- function redirectLimitExceeded(message = "HTTP request exceeded the redirect limit") {
70
- return new ZLinkFrameworkException(8 /* ProtocolError */, message);
71
- }
72
- function redirectFormatError(message) {
73
- return new ZLinkFrameworkException(8 /* ProtocolError */, message);
74
- }
75
-
76
- // packages/http-client/src/runtime/redirect-policy.ts
77
- var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
78
- function isRedirectStatus(status) {
79
- return REDIRECT_STATUSES.has(status);
80
- }
81
- function makeTarget(prefix, path) {
82
- if (prefix.length === 0 || prefix === "/") {
83
- return path;
84
- }
85
- return prefix.endsWith("/") ? prefix.slice(0, -1) + path : prefix + path;
86
- }
87
- function rewriteForRedirect(status, method, body) {
88
- if (status === 303 || (status === 301 || status === 302) && method === "POST") {
89
- return { method: "GET", body: void 0 };
90
- }
91
- return { method, body };
92
- }
93
- function resolveLocation(current, location) {
94
- try {
95
- if (location.startsWith("http://") || location.startsWith("https://")) {
96
- return new URL(location);
97
- }
98
- if (location.startsWith("//")) {
99
- return new URL(current.protocol + location);
100
- }
101
- if (location.startsWith("/")) {
102
- return new URL(current.origin + location);
103
- }
104
- } catch {
105
- throw redirectFormatError(`HTTP redirect location is malformed: ${location}`);
106
- }
107
- throw redirectFormatError(`HTTP redirect location is not supported: ${location}`);
108
- }
109
-
110
- // packages/http-client/src/runtime/retry-policy.ts
111
- function delayMsFor(attempt) {
112
- const ceiling = Math.min(1e3, 50 << Math.min(attempt, 5));
113
- return Math.floor(Math.random() * (ceiling + 1));
114
- }
115
- var RetryPolicy = class {
116
- constructor(options) {
117
- this.options = options;
118
- }
119
- async execute(spec, perform) {
120
- const maxRetries = spec.sink !== void 0 || spec.bodyProvider !== void 0 ? 0 : this.options.retryAttempts;
121
- const timeoutMs = spec.timeoutMs ?? this.options.timeoutMs;
122
- for (let attempt = 0; ; attempt++) {
123
- const controller = new AbortController();
124
- const timer = setTimeout(() => {
125
- controller.abort();
126
- }, timeoutMs);
127
- try {
128
- return await perform(spec, controller.signal);
129
- } catch (error) {
130
- const failure = mapFailure(error, controller.signal.aborted);
131
- if (isRetriableHttpFailure(failure) && attempt < maxRetries) {
132
- await delay(delayMsFor(attempt));
133
- continue;
134
- }
135
- throw failure;
136
- } finally {
137
- clearTimeout(timer);
138
- }
139
- }
140
- }
141
- };
142
- function mapFailure(error, aborted) {
143
- if (error instanceof ZLinkFrameworkException) {
144
- return error;
145
- }
146
- if (aborted || error instanceof Error && error.name === "AbortError") {
147
- return new ZLinkFrameworkException(
148
- 6 /* DeadlineExceeded */,
149
- "HTTP request exceeded timeout",
150
- error
151
- );
152
- }
153
- const message = error instanceof Error ? error.message : "HTTP transport failure";
154
- return new ZLinkFrameworkException(5 /* Unavailable */, message, error);
155
- }
156
- function isRetriableHttpFailure(error) {
157
- return error.kind === 5 /* Unavailable */ || error.kind === 6 /* DeadlineExceeded */;
158
- }
159
- function delay(ms) {
160
- return new Promise((resolve) => {
161
- setTimeout(resolve, ms);
162
- });
163
- }
164
-
165
- // packages/http-client/src/runtime/browser-runtime.ts
166
- var HttpClientRuntime = class {
167
- constructor(options) {
168
- this.options = options;
169
- rejectUnsupportedTransportOptions(options);
170
- this.retryPolicy = new RetryPolicy(options);
171
- }
172
- retryPolicy;
173
- async executeAsync(spec) {
174
- return await this.retryPolicy.execute(spec, (request, signal) => this.perform(request, signal));
175
- }
176
- async close() {
177
- }
178
- async perform(spec, signal) {
179
- const baseUri = new URL(this.options.baseUrl);
180
- const origin = baseUri.origin;
181
- let current = new URL(origin + makeTarget(baseUri.pathname, spec.target));
182
- let method = spec.method;
183
- let body = spec.body;
184
- let bodyProvider = spec.bodyProvider;
185
- let redirectsLeft = this.options.followRedirects;
186
- for (; ; ) {
187
- const headers = this.buildHeaders(
188
- spec,
189
- current.origin === origin,
190
- body !== void 0 || bodyProvider !== void 0
191
- );
192
- const response = await fetch(current, {
193
- method,
194
- headers,
195
- body: body ?? bodyStream(bodyProvider),
196
- credentials: this.options.cookies ? "include" : "same-origin",
197
- redirect: "manual",
198
- signal
199
- });
200
- const location = response.headers.get("location");
201
- if (this.options.followRedirects > 0 && isRedirectStatus(response.status) && location !== null) {
202
- if (redirectsLeft === 0) {
203
- await response.body?.cancel();
204
- throw redirectLimitExceeded();
205
- }
206
- redirectsLeft--;
207
- ({ method, body } = rewriteForRedirect(response.status, method, body));
208
- bodyProvider = void 0;
209
- await response.body?.cancel();
210
- current = resolveLocation(current, location);
211
- continue;
212
- }
213
- const headersResult = collectHeaders(response.headers);
214
- if (spec.sink !== void 0) {
215
- await streamResponse(response, spec.sink, this.options.maxResponseBodySize);
216
- return { status: response.status, headers: headersResult, body: "" };
217
- }
218
- const text = await response.text();
219
- if (new TextEncoder().encode(text).length > this.options.maxResponseBodySize) {
220
- throw responseBodySizeExceeded();
221
- }
222
- return { status: response.status, headers: headersResult, body: text };
223
- }
224
- }
225
- buildHeaders(spec, keepAuthorization, hasBody) {
226
- const headers = { accept: "application/json" };
227
- applyHeaders(headers, this.options.headers, keepAuthorization);
228
- applyHeaders(headers, spec.headers, keepAuthorization);
229
- if (!hasBody) delete headers["content-type"];
230
- return headers;
231
- }
232
- };
233
- function rejectUnsupportedTransportOptions(options) {
234
- if (options.trustCertificateFile !== void 0 || options.clientCertificate !== void 0 || options.proxy !== void 0) {
235
- throw new ZLinkFrameworkException(
236
- 8 /* ProtocolError */,
237
- "Browser HTTP clients cannot configure certificate files or a transport proxy."
238
- );
239
- }
240
- }
241
- function bodyStream(provider) {
242
- if (provider === void 0) return void 0;
243
- return new ReadableStream({
244
- pull(controller) {
245
- const chunk = provider();
246
- if (chunk === null) controller.close();
247
- else controller.enqueue(chunk);
248
- }
249
- });
250
- }
251
- async function streamResponse(response, sink, maximumSize) {
252
- if (response.body === null) return;
253
- const reader = response.body.getReader();
254
- let total = 0;
255
- for (; ; ) {
256
- const result = await reader.read();
257
- if (result.done) return;
258
- total += result.value.length;
259
- if (total > maximumSize) {
260
- await reader.cancel();
261
- throw responseBodySizeExceeded();
262
- }
263
- sink(result.value);
264
- }
265
- }
266
- function collectHeaders(headers) {
267
- const result = {};
268
- headers.forEach((value, name) => {
269
- result[name.toLowerCase()] = value;
270
- });
271
- return result;
272
- }
273
- function applyHeaders(target, source, keepAuthorization) {
274
- for (const [name, value] of Object.entries(source)) {
275
- const lower = name.toLowerCase();
276
- if (keepAuthorization || lower !== "authorization") target[lower] = value;
277
- }
278
- }
279
-
280
65
  // packages/http-client/src/runtime/text.ts
66
+ var HttpHeaderName = Object.freeze({
67
+ Accept: "accept",
68
+ Authorization: "authorization",
69
+ ContentType: "content-type",
70
+ Location: "location",
71
+ SetCookie: "set-cookie",
72
+ UserAgent: "user-agent",
73
+ AcceptEncoding: "accept-encoding",
74
+ Cookie: "cookie",
75
+ ContentEncoding: "content-encoding",
76
+ ContentLength: "content-length"
77
+ });
78
+ var HttpContentType = Object.freeze({
79
+ Json: "application/json",
80
+ UrlEncoded: "application/x-www-form-urlencoded",
81
+ Multipart: "multipart/form-data"
82
+ });
281
83
  function isBlank(value) {
282
84
  return value.length === 0 || /^[\s]*$/u.test(value);
283
85
  }
@@ -311,11 +113,14 @@ function percentEncode(value) {
311
113
  function basicAuthorization(user, password) {
312
114
  return "Basic " + Buffer.from(`${user}:${password}`, "utf8").toString("base64");
313
115
  }
116
+ var MULTIPART_BOUNDARY_PREFIX = "zlink-boundary-";
117
+ var MULTIPART_BOUNDARY_ENTROPY_CHARACTERS = 16;
314
118
  function makeMultipartBoundary() {
315
- return "zlink-boundary-" + Math.random().toString(16).slice(2).padEnd(16, "0").slice(0, 16);
119
+ return MULTIPART_BOUNDARY_PREFIX + Math.random().toString(16).slice(2).padEnd(MULTIPART_BOUNDARY_ENTROPY_CHARACTERS, "0").slice(0, MULTIPART_BOUNDARY_ENTROPY_CHARACTERS);
316
120
  }
317
121
 
318
122
  // packages/http-client/src/request-builder.ts
123
+ var HTTP_FAILURE_STATUS_MIN = 400;
319
124
  var ZLinkHttpRequestBuilder = class {
320
125
  constructor(client, method, path, clientFactory) {
321
126
  this.method = method;
@@ -364,10 +169,16 @@ var ZLinkHttpRequestBuilder = class {
364
169
  }
365
170
  return this.clientInstance;
366
171
  }
367
- async closeIfOwned() {
368
- if (this.ownsClient && this.clientInstance !== void 0) {
369
- await this.clientInstance.close().catch(() => void 0);
172
+ async execute(client, operation) {
173
+ if (!this.ownsClient) return operation;
174
+ const [outcome] = await Promise.allSettled([operation]);
175
+ try {
176
+ await client.close();
177
+ } catch (closeError) {
178
+ if (outcome.status === "fulfilled") throw closeError;
370
179
  }
180
+ if (outcome.status === "rejected") throw outcome.reason;
181
+ return outcome.value;
371
182
  }
372
183
  header(name, value) {
373
184
  requireNonBlank(name, "HTTP request header name is required");
@@ -394,11 +205,11 @@ var ZLinkHttpRequestBuilder = class {
394
205
  }
395
206
  requireNonBlank(contentType, "HTTP request body content type is required");
396
207
  this.bodyValue = value;
397
- this.headersValue["content-type"] = contentType;
208
+ this.headersValue[HttpHeaderName.ContentType] = contentType;
398
209
  return this;
399
210
  }
400
211
  this.bodyValue = JSON.stringify(value);
401
- this.headersValue["content-type"] ??= "application/json";
212
+ this.headersValue[HttpHeaderName.ContentType] ??= HttpContentType.Json;
402
213
  return this;
403
214
  }
404
215
  /**
@@ -414,7 +225,7 @@ var ZLinkHttpRequestBuilder = class {
414
225
  }
415
226
  requireNonBlank(contentType, "HTTP request body content type is required");
416
227
  this.bodyProviderValue = provider;
417
- this.headersValue["content-type"] = contentType;
228
+ this.headersValue[HttpHeaderName.ContentType] = contentType;
418
229
  return this;
419
230
  }
420
231
  form(name, value) {
@@ -438,11 +249,7 @@ var ZLinkHttpRequestBuilder = class {
438
249
  async submitRaw() {
439
250
  const request = this.makeRequest(void 0);
440
251
  const client = this.resolveClient();
441
- try {
442
- return await client.runtime.executeAsync(request);
443
- } finally {
444
- await this.closeIfOwned();
445
- }
252
+ return this.execute(client, client.runtime.executeAsync(request));
446
253
  }
447
254
  /**
448
255
  * Streams the response body to `sink` chunk by chunk instead of buffering it; the returned
@@ -457,11 +264,7 @@ var ZLinkHttpRequestBuilder = class {
457
264
  }
458
265
  const request = this.makeRequest(sink);
459
266
  const client = this.resolveClient();
460
- try {
461
- return await client.runtime.executeAsync(request);
462
- } finally {
463
- await this.closeIfOwned();
464
- }
267
+ return this.execute(client, client.runtime.executeAsync(request));
465
268
  }
466
269
  submit(callback) {
467
270
  const pending = this.executeTyped();
@@ -474,8 +277,13 @@ var ZLinkHttpRequestBuilder = class {
474
277
  );
475
278
  }
476
279
  async executeTyped() {
477
- const raw = await this.submitRaw();
478
- if (raw.status >= 400) {
280
+ const request = this.makeRequest(void 0);
281
+ const client = this.resolveClient();
282
+ return this.execute(client, this.decodeResponse(client.runtime.executeAsync(request)));
283
+ }
284
+ async decodeResponse(pending) {
285
+ const raw = await pending;
286
+ if (raw.status >= HTTP_FAILURE_STATUS_MIN) {
479
287
  throw new ZLinkFrameworkException(
480
288
  11 /* InternalFailure */,
481
289
  `HTTP request failed with status ${raw.status}`
@@ -544,12 +352,12 @@ var ZLinkHttpRequestBuilder = class {
544
352
  return { body: this.bodyValue, headers };
545
353
  }
546
354
  if (this.formValue.length > 0) {
547
- headers["content-type"] = "application/x-www-form-urlencoded";
355
+ headers[HttpHeaderName.ContentType] = HttpContentType.UrlEncoded;
548
356
  return { body: this.encodeFormBody(), headers };
549
357
  }
550
358
  if (this.multipartValue.length > 0) {
551
359
  const boundary = makeMultipartBoundary();
552
- headers["content-type"] = `multipart/form-data; boundary=${boundary}`;
360
+ headers[HttpHeaderName.ContentType] = `${HttpContentType.Multipart}; boundary=${boundary}`;
553
361
  return { body: this.encodeMultipartBody(boundary), headers };
554
362
  }
555
363
  return { body: void 0, headers };
@@ -608,6 +416,311 @@ function safeJsonReviver(key, value) {
608
416
  return value;
609
417
  }
610
418
 
419
+ // packages/http-client/src/runtime/http-client-errors.ts
420
+ function responseBodySizeExceeded(message = "HTTP response exceeded the maximum body size") {
421
+ return new ZLinkFrameworkException(4 /* Rejected */, message);
422
+ }
423
+ function redirectLimitExceeded(message = "HTTP request exceeded the redirect limit") {
424
+ return new ZLinkFrameworkException(8 /* ProtocolError */, message);
425
+ }
426
+ function redirectFormatError(message) {
427
+ return new ZLinkFrameworkException(8 /* ProtocolError */, message);
428
+ }
429
+
430
+ // packages/http-client/src/runtime/redirect-policy.ts
431
+ var RedirectStatus = {
432
+ MovedPermanently: 301,
433
+ Found: 302,
434
+ SeeOther: 303,
435
+ TemporaryRedirect: 307,
436
+ PermanentRedirect: 308
437
+ };
438
+ var REDIRECT_STATUSES = new Set(Object.values(RedirectStatus));
439
+ function isRedirectStatus(status) {
440
+ return REDIRECT_STATUSES.has(status);
441
+ }
442
+ function makeTarget(prefix, path) {
443
+ if (prefix.length === 0 || prefix === "/") {
444
+ return path;
445
+ }
446
+ return prefix.endsWith("/") ? prefix.slice(0, -1) + path : prefix + path;
447
+ }
448
+ function rewriteForRedirect(status, method, body) {
449
+ if (status === RedirectStatus.SeeOther || (status === RedirectStatus.MovedPermanently || status === RedirectStatus.Found) && method === "POST") {
450
+ return { method: "GET", body: void 0 };
451
+ }
452
+ return { method, body };
453
+ }
454
+ function resolveLocation(current, location) {
455
+ try {
456
+ if (location.startsWith("http://") || location.startsWith("https://")) {
457
+ return new URL(location);
458
+ }
459
+ if (location.startsWith("//")) {
460
+ return new URL(current.protocol + location);
461
+ }
462
+ if (location.startsWith("/")) {
463
+ return new URL(current.origin + location);
464
+ }
465
+ } catch {
466
+ throw redirectFormatError(`HTTP redirect location is malformed: ${location}`);
467
+ }
468
+ throw redirectFormatError(`HTTP redirect location is not supported: ${location}`);
469
+ }
470
+
471
+ // packages/http-client/src/runtime/retry-policy.ts
472
+ var INITIAL_RETRY_DELAY_MS = 50;
473
+ var MAX_RETRY_DELAY_MS = 1e3;
474
+ var MAX_RETRY_DELAY_SHIFT = Math.ceil(Math.log2(MAX_RETRY_DELAY_MS / INITIAL_RETRY_DELAY_MS));
475
+ function delayMsFor(attempt) {
476
+ const ceiling = Math.min(
477
+ MAX_RETRY_DELAY_MS,
478
+ INITIAL_RETRY_DELAY_MS << Math.min(attempt, MAX_RETRY_DELAY_SHIFT)
479
+ );
480
+ return Math.floor(Math.random() * (ceiling + 1));
481
+ }
482
+ var RetryPolicy = class {
483
+ constructor(options) {
484
+ this.options = options;
485
+ }
486
+ async execute(spec, perform) {
487
+ const maxRetries = spec.sink !== void 0 || spec.bodyProvider !== void 0 ? 0 : this.options.retryAttempts;
488
+ const timeoutMs = spec.timeoutMs ?? this.options.timeoutMs;
489
+ for (let attempt = 0; ; attempt++) {
490
+ const controller = new AbortController();
491
+ const timer = setTimeout(() => {
492
+ controller.abort();
493
+ }, timeoutMs);
494
+ try {
495
+ return await perform(spec, controller);
496
+ } catch (error) {
497
+ const failure = mapFailure(error, controller.signal.aborted, 1 /* Application */);
498
+ if (isRetriableHttpFailure(failure) && attempt < maxRetries) {
499
+ await delay(delayMsFor(attempt));
500
+ continue;
501
+ }
502
+ throw failure;
503
+ } finally {
504
+ clearTimeout(timer);
505
+ }
506
+ }
507
+ }
508
+ };
509
+ function mapFailure(error, aborted, stage) {
510
+ if (error instanceof ZLinkFrameworkException) {
511
+ return error;
512
+ }
513
+ if (stage === 0 /* Transport */ && aborted) {
514
+ const cause = new DOMException("HTTP request exceeded timeout", "TimeoutError");
515
+ return new ZLinkFrameworkException(
516
+ 6 /* DeadlineExceeded */,
517
+ cause.message,
518
+ cause
519
+ );
520
+ }
521
+ const message = error instanceof Error ? error.message : "HTTP execution failure";
522
+ return new ZLinkFrameworkException(
523
+ stage === 0 /* Transport */ ? 5 /* Unavailable */ : 11 /* InternalFailure */,
524
+ message,
525
+ error
526
+ );
527
+ }
528
+ function isRetriableHttpFailure(error) {
529
+ return error.kind === 5 /* Unavailable */ || error.kind === 6 /* DeadlineExceeded */;
530
+ }
531
+ function delay(ms) {
532
+ return new Promise((resolve) => {
533
+ setTimeout(resolve, ms);
534
+ });
535
+ }
536
+
537
+ // packages/http-client/src/runtime/bounded-response-body.ts
538
+ async function readResponseBody(stream, maximumSize, consume) {
539
+ let total = 0;
540
+ for await (const chunk of stream) {
541
+ total += chunk.length;
542
+ if (total > maximumSize) {
543
+ throw responseBodySizeExceeded();
544
+ }
545
+ consume(chunk);
546
+ }
547
+ }
548
+
549
+ // packages/http-client/src/runtime/browser-runtime.ts
550
+ var HttpClientRuntime = class {
551
+ constructor(options) {
552
+ this.options = options;
553
+ rejectUnsupportedTransportOptions(options);
554
+ this.retryPolicy = new RetryPolicy(options);
555
+ }
556
+ retryPolicy;
557
+ async executeAsync(spec) {
558
+ return await this.retryPolicy.execute(
559
+ spec,
560
+ (request, controller) => this.perform(request, controller)
561
+ );
562
+ }
563
+ async close() {
564
+ }
565
+ async perform(spec, controller) {
566
+ const signal = controller.signal;
567
+ const baseUri = new URL(this.options.baseUrl);
568
+ const origin = baseUri.origin;
569
+ let current = new URL(origin + makeTarget(baseUri.pathname, spec.target));
570
+ let method = spec.method;
571
+ let body = spec.body;
572
+ let bodyProvider = spec.bodyProvider;
573
+ let redirectsLeft = this.options.followRedirects;
574
+ for (; ; ) {
575
+ const headers = this.buildHeaders(
576
+ spec,
577
+ current.origin === origin,
578
+ body !== void 0 || bodyProvider !== void 0
579
+ );
580
+ let response;
581
+ try {
582
+ const init = {
583
+ method,
584
+ headers,
585
+ body: body ?? bodyStream(bodyProvider, controller),
586
+ duplex: "half",
587
+ credentials: this.options.cookies ? "include" : "same-origin",
588
+ redirect: "manual",
589
+ signal
590
+ };
591
+ response = await fetch(current, init);
592
+ } catch (error) {
593
+ throw mapFailure(error, signal.aborted, 0 /* Transport */);
594
+ }
595
+ const location = response.headers.get(HttpHeaderName.Location);
596
+ if (this.options.followRedirects > 0 && isRedirectStatus(response.status) && location !== null) {
597
+ if (redirectsLeft === 0) {
598
+ try {
599
+ await response.body?.cancel();
600
+ } catch (error) {
601
+ throw mapFailure(error, signal.aborted, 0 /* Transport */);
602
+ }
603
+ throw redirectLimitExceeded();
604
+ }
605
+ redirectsLeft--;
606
+ ({ method, body } = rewriteForRedirect(response.status, method, body));
607
+ bodyProvider = void 0;
608
+ try {
609
+ await response.body?.cancel();
610
+ } catch (error) {
611
+ throw mapFailure(error, signal.aborted, 0 /* Transport */);
612
+ }
613
+ current = resolveLocation(current, location);
614
+ continue;
615
+ }
616
+ const headersResult = collectHeaders(response.headers);
617
+ if (spec.sink !== void 0) {
618
+ const sink = spec.sink;
619
+ await readResponseBody(
620
+ responseChunks(response, signal),
621
+ this.options.maxResponseBodySize,
622
+ (chunk) => {
623
+ try {
624
+ sink(chunk);
625
+ } catch (error) {
626
+ throw mapFailure(error, signal.aborted, 1 /* Application */);
627
+ }
628
+ }
629
+ );
630
+ return { status: response.status, headers: headersResult, body: "" };
631
+ }
632
+ const decoder = new TextDecoder();
633
+ const text = [];
634
+ await readResponseBody(
635
+ responseChunks(response, signal),
636
+ this.options.maxResponseBodySize,
637
+ (chunk) => {
638
+ text.push(decoder.decode(chunk, { stream: true }));
639
+ }
640
+ );
641
+ text.push(decoder.decode());
642
+ return { status: response.status, headers: headersResult, body: text.join("") };
643
+ }
644
+ }
645
+ buildHeaders(spec, keepAuthorization, hasBody) {
646
+ const headers = {
647
+ [HttpHeaderName.Accept]: HttpContentType.Json
648
+ };
649
+ applyHeaders(headers, this.options.headers, keepAuthorization);
650
+ applyHeaders(headers, spec.headers, keepAuthorization);
651
+ if (!hasBody) delete headers[HttpHeaderName.ContentType];
652
+ return headers;
653
+ }
654
+ };
655
+ function rejectUnsupportedTransportOptions(options) {
656
+ if (options.trustCertificateFile !== void 0 || options.clientCertificate !== void 0 || options.proxy !== void 0) {
657
+ throw new ZLinkFrameworkException(
658
+ 8 /* ProtocolError */,
659
+ "Browser HTTP clients cannot configure certificate files or a transport proxy."
660
+ );
661
+ }
662
+ }
663
+ function bodyStream(provider, abortController) {
664
+ if (provider === void 0) return void 0;
665
+ return new ReadableStream({
666
+ pull(controller) {
667
+ let chunk;
668
+ try {
669
+ chunk = provider();
670
+ } catch (error) {
671
+ const failure = mapFailure(
672
+ error,
673
+ abortController.signal.aborted,
674
+ 1 /* Application */
675
+ );
676
+ abortController.abort(failure);
677
+ throw failure;
678
+ }
679
+ if (chunk === null) controller.close();
680
+ else controller.enqueue(chunk);
681
+ }
682
+ });
683
+ }
684
+ async function* responseChunks(response, signal) {
685
+ if (response.body === null) return;
686
+ const reader = response.body.getReader();
687
+ try {
688
+ for (; ; ) {
689
+ let result;
690
+ try {
691
+ result = await reader.read();
692
+ } catch (error) {
693
+ throw mapFailure(error, signal.aborted, 0 /* Transport */);
694
+ }
695
+ if (result.done) return;
696
+ yield result.value;
697
+ }
698
+ } finally {
699
+ try {
700
+ try {
701
+ await reader.cancel();
702
+ } catch (error) {
703
+ throw mapFailure(error, signal.aborted, 0 /* Transport */);
704
+ }
705
+ } finally {
706
+ reader.releaseLock();
707
+ }
708
+ }
709
+ }
710
+ function collectHeaders(headers) {
711
+ const result = {};
712
+ headers.forEach((value, name) => {
713
+ result[name.toLowerCase()] = value;
714
+ });
715
+ return result;
716
+ }
717
+ function applyHeaders(target, source, keepAuthorization) {
718
+ for (const [name, value] of Object.entries(source)) {
719
+ const lower = name.toLowerCase();
720
+ if (keepAuthorization || lower !== HttpHeaderName.Authorization) target[lower] = value;
721
+ }
722
+ }
723
+
611
724
  // packages/http-client/src/client.ts
612
725
  var ZLinkHttpClient = class {
613
726
  constructor(runtimeInstance, executionScheduler) {
@@ -648,10 +761,13 @@ var ZLinkHttpClient = class {
648
761
  await this.runtimeInstance.close();
649
762
  }
650
763
  };
764
+ var DEFAULT_HTTP_CLIENT_TIMEOUT_MS = 3e3;
765
+ var DEFAULT_HTTP_RESPONSE_BODY_SIZE = 16 * 1024 * 1024;
766
+ var DEFAULT_HTTP_MAX_REDIRECTS = 5;
651
767
  var ZLinkHttpClientBuilder = class {
652
768
  baseUrlValue = "";
653
- timeoutMsValue = 3e3;
654
- maxResponseBodySizeValue = 16 * 1024 * 1024;
769
+ timeoutMsValue = DEFAULT_HTTP_CLIENT_TIMEOUT_MS;
770
+ maxResponseBodySizeValue = DEFAULT_HTTP_RESPONSE_BODY_SIZE;
655
771
  headersValue = {};
656
772
  trustCertificateFileValue;
657
773
  clientCertificateValue;
@@ -679,12 +795,12 @@ var ZLinkHttpClientBuilder = class {
679
795
  }
680
796
  basicAuth(user, password) {
681
797
  requireNonBlank(user, "HTTP client basic auth user is required");
682
- this.headersValue["authorization"] = basicAuthorization(user, password);
798
+ this.headersValue[HttpHeaderName.Authorization] = basicAuthorization(user, password);
683
799
  return this;
684
800
  }
685
801
  bearerToken(token) {
686
802
  requireNonBlank(token, "HTTP client bearer token is required");
687
- this.headersValue["authorization"] = `Bearer ${token}`;
803
+ this.headersValue[HttpHeaderName.Authorization] = `Bearer ${token}`;
688
804
  return this;
689
805
  }
690
806
  maxResponseBodySize(bytes) {
@@ -708,7 +824,7 @@ var ZLinkHttpClientBuilder = class {
708
824
  this.clientCertificateValue = { certificatePath, keyPath };
709
825
  return this;
710
826
  }
711
- followRedirects(maxRedirects = 5) {
827
+ followRedirects(maxRedirects = DEFAULT_HTTP_MAX_REDIRECTS) {
712
828
  if (!(maxRedirects > 0)) {
713
829
  throw new ZLinkFrameworkException(
714
830
  8 /* ProtocolError */,