@optionfactory/fml 9.0.0-rc14 → 9.0.0-rc16

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.
Files changed (43) hide show
  1. package/README.md +16 -88
  2. package/dist/client-errors.iife.js +17 -26
  3. package/dist/client-errors.iife.js.map +1 -1
  4. package/dist/client-errors.iife.min.js.map +1 -1
  5. package/dist/custom-elements.json +70 -30
  6. package/dist/fml.css +20 -16
  7. package/dist/fml.css.map +1 -1
  8. package/dist/fml.iife.js +3612 -2138
  9. package/dist/fml.iife.js.map +1 -1
  10. package/dist/fml.iife.min.js +1 -1
  11. package/dist/fml.iife.min.js.map +1 -1
  12. package/dist/ftl.d.mts +395 -224
  13. package/dist/ftl.iife.js +400 -330
  14. package/dist/ftl.iife.js.map +1 -1
  15. package/dist/ftl.iife.min.js +1 -1
  16. package/dist/ftl.iife.min.js.map +1 -1
  17. package/dist/ftl.min.mjs +1 -1
  18. package/dist/ftl.min.mjs.map +1 -1
  19. package/dist/ftl.mjs +400 -330
  20. package/dist/ftl.mjs.map +1 -1
  21. package/dist/ful.css +20 -16
  22. package/dist/ful.css.map +1 -1
  23. package/dist/ful.d.mts +2601 -678
  24. package/dist/ful.iife.js +3042 -1610
  25. package/dist/ful.iife.js.map +1 -1
  26. package/dist/ful.iife.min.js +1 -1
  27. package/dist/ful.iife.min.js.map +1 -1
  28. package/dist/ful.min.mjs +1 -1
  29. package/dist/ful.min.mjs.map +1 -1
  30. package/dist/ful.mjs +3042 -1611
  31. package/dist/ful.mjs.map +1 -1
  32. package/dist/httpc.d.mts +139 -121
  33. package/dist/httpc.iife.js +137 -166
  34. package/dist/httpc.iife.js.map +1 -1
  35. package/dist/httpc.iife.min.js +1 -1
  36. package/dist/httpc.iife.min.js.map +1 -1
  37. package/dist/httpc.min.mjs +1 -1
  38. package/dist/httpc.min.mjs.map +1 -1
  39. package/dist/httpc.mjs +137 -166
  40. package/dist/httpc.mjs.map +1 -1
  41. package/dist/vscode.html-custom-data.json +36 -18
  42. package/dist/web-types.json +67 -35
  43. package/package.json +1 -1
@@ -2,6 +2,9 @@ var httpc = (function (exports) {
2
2
  'use strict';
3
3
 
4
4
  /**
5
+ * One problem of a failure: its `type` (a code such as `FIELD_ERROR` or
6
+ * `GENERIC_PROBLEM`), the `context` naming the field it belongs to or null, the
7
+ * human `reason`, and optional `details`.
5
8
  * @typedef {{ type: string; context: string?; reason: string; details: any?; }} Problem
6
9
  */
7
10
  /**
@@ -11,10 +14,9 @@ var httpc = (function (exports) {
11
14
  */
12
15
  class Failure extends Error {
13
16
  /**
14
- *
15
17
  * @param {string} message
16
18
  * @param {Problem[]} problems
17
- * @param {*} cause
19
+ * @param {*} [cause]
18
20
  */
19
21
  constructor(message, problems, cause) {
20
22
  super(message, { cause });
@@ -31,6 +33,7 @@ var httpc = (function (exports) {
31
33
  return new Failure(this.message, Failure.dropProblemsContext(this.problems, prefix), this);
32
34
  }
33
35
  /**
36
+ * Copies of the problems, each context losing the prefix where it starts with it.
34
37
  * @param {Problem[]} problems
35
38
  * @param {string} prefix
36
39
  * @returns {Problem[]}
@@ -41,21 +44,6 @@ var httpc = (function (exports) {
41
44
  return { type, context: nctx, reason, details };
42
45
  });
43
46
  }
44
- /**
45
- * The one reading of a failure: its problems' reasons, one per line, or
46
- * the fallback when the value carries none. An empty problems array
47
- * carries nothing: the failure's own message reads instead.
48
- *
49
- * @param {any} cause
50
- * @param {string|null} [fallback]
51
- * @returns {string}
52
- */
53
- static problemsText(cause, fallback = null) {
54
- if (cause?.problems?.length) {
55
- return cause.problems.map((p) => `${p.reason}`).join('\n');
56
- }
57
- return fallback ?? `${cause?.message ?? cause}`;
58
- }
59
47
  }
60
48
 
61
49
  /**
@@ -65,6 +53,10 @@ var httpc = (function (exports) {
65
53
  * of corrupting silently.
66
54
  */
67
55
  class Base64 {
56
+ /** The RFC 4648 alphabet, with `+` and `/`. */
57
+ static STANDARD = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
58
+ /** The RFC 4648 url and filename safe alphabet, with `-` and `_`. */
59
+ static URL_SAFE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
68
60
  /**
69
61
  * @param {ArrayBuffer} arrayBuffer
70
62
  * @param {string} [dialect] one of Base64.STANDARD or Base64.URL_SAFE, URL_SAFE by default
@@ -90,14 +82,14 @@ var httpc = (function (exports) {
90
82
  return res;
91
83
  }
92
84
  /**
93
- * @param {string} str
85
+ * @param {string} str padded or unpadded
94
86
  * @param {string} [dialect] one of Base64.STANDARD or Base64.URL_SAFE, URL_SAFE by default
95
87
  * @returns {ArrayBuffer}
88
+ * @throws on padding anywhere but the tail or longer than two, on an
89
+ * impossible length, and on a character outside the dialect
96
90
  */
97
91
  static decode(str, dialect) {
98
92
  const d = dialect || Base64.URL_SAFE;
99
- //padding belongs at the tail only, two at most, and nothing outside the
100
- //dialect decodes: reject instead of corrupting silently
101
93
  let end = str.length;
102
94
  while (end > 0 && str.charAt(end - 1) === '=') {
103
95
  --end;
@@ -119,9 +111,6 @@ var httpc = (function (exports) {
119
111
 
120
112
  let vi = 0;
121
113
  let si = 0;
122
- //every group yields three bytes, but a padded input stops short of the last
123
- //one or two: writing them anyway would rely on typed arrays dropping writes
124
- //past their length
125
114
  while (vi < nbytes) {
126
115
  const v1 = d.indexOf(unpadded.charAt(si++));
127
116
  const v2 = d.indexOf(unpadded.charAt(si++));
@@ -142,16 +131,14 @@ var httpc = (function (exports) {
142
131
  }
143
132
  }
144
133
 
145
- Base64.STANDARD = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
146
- Base64.URL_SAFE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
147
-
148
134
  /**
149
135
  * Hex encoding and decoding over byte sequences, lowercase by default.
150
136
  */
151
137
  class Hex {
152
138
  /**
153
- * @param {string} hex
139
+ * @param {string} hex either case
154
140
  * @returns {Uint8Array}
141
+ * @throws on an odd length or a character that is not a hex digit
155
142
  */
156
143
  static decode(hex) {
157
144
  if (hex.length % 2 !== 0) {
@@ -169,8 +156,8 @@ var httpc = (function (exports) {
169
156
  }
170
157
  /**
171
158
  * @param {Iterable<number>} bytes
172
- * @param {boolean} [upper]
173
- * @returns {string}
159
+ * @param {boolean} [upper] uppercase digits instead of lowercase
160
+ * @returns {string} two digits per byte
174
161
  */
175
162
  static encode(bytes, upper) {
176
163
  return Array.from(bytes)
@@ -189,19 +176,22 @@ var httpc = (function (exports) {
189
176
  this.#type = type;
190
177
  this.#subtype = subtype;
191
178
  }
179
+ /** @returns {string} `type/subtype`, lowercase */
192
180
  get normalized() {
193
181
  return `${this.#type}/${this.#subtype}`;
194
182
  }
183
+ /** @returns {string} */
195
184
  get type() {
196
185
  return this.#type;
197
186
  }
187
+ /** @returns {string} */
198
188
  get subtype() {
199
189
  return this.#subtype;
200
190
  }
201
191
  /**
202
192
  * Parses a Content-Type header value into its type/subtype pair, dropping any parameter.
203
193
  * @param {string|null|undefined} v
204
- * @returns
194
+ * @returns {MediaType} `unknown/unknown` for a missing or malformed value
205
195
  */
206
196
  static parse(v) {
207
197
  if (!v) {
@@ -251,28 +241,35 @@ var httpc = (function (exports) {
251
241
  return new HttpClientError(this.message, this.status, Failure.dropProblemsContext(this.problems, prefix), this);
252
242
  }
253
243
  /**
254
- * One problem of the client's own making: the four the client mints are the
255
- * same shape, and the server's arrive already shaped from the wire.
244
+ * A problem with no context and no details.
256
245
  * @param {string} type
257
246
  * @param {string} reason
247
+ * @returns {{ type: string; context: null; reason: string; details: null; }}
258
248
  */
259
249
  static problem(type, reason) {
260
250
  return { type, context: null, reason, details: null };
261
251
  }
262
252
  /**
263
- * Creates a client failure carrying no status, wrapping the cause and its message.
253
+ * A failure with status 0 and one problem of the given type, whose reason is
254
+ * the cause's message.
264
255
  * @param {string} type
265
256
  * @param {any} cause
266
- * @returns
257
+ * @returns {HttpClientError}
267
258
  */
268
259
  static of(type, cause) {
269
260
  const reason = String(cause?.message ?? cause ?? 'unknown failure');
270
261
  return new HttpClientError(reason, 0, [HttpClientError.problem(type, reason)], cause);
271
262
  }
272
263
  /**
273
- * Creates an HttpClientError from a Response.
274
- * @param {Response} response
275
- * @returns an HttpClientError
264
+ * The failure an error response stands for, keeping its status. An
265
+ * `application/failures+json` body is the problems list; an
266
+ * `application/problem+json` body contributes its `problems`, or one
267
+ * GENERIC_PROBLEM made of its title and detail. Any other body is one
268
+ * GENERIC_PROBLEM carrying the body text. A json body that does not decode,
269
+ * or decodes to the wrong shape, and a body that cannot be read at all,
270
+ * each become one GENERIC_PROBLEM saying so.
271
+ * @param {Response} response consumed
272
+ * @returns {Promise<HttpClientError>}
276
273
  */
277
274
  static async fromResponse(response) {
278
275
  switch (MediaType.parse(response.headers.get('Content-Type')).normalized) {
@@ -307,14 +304,10 @@ var httpc = (function (exports) {
307
304
  }
308
305
  }
309
306
  }
310
- /** marks a body whose json() rejected, telling it apart from a body decoding to json null */
311
307
  static #unreadable = Symbol('unreadable body');
312
308
  /**
313
- * A json body that failed to decode, or that decoded to something other than
314
- * the declared contract, has consumed its stream: there is no text left to
315
- * embed, the status, the declared media type and the shape are the report.
316
309
  * @param {Response} response
317
- * @param {string} [as] - what the body does not decode as
310
+ * @param {string} [as] what the body does not decode as
318
311
  */
319
312
  static #undecodable(response, as = 'as json') {
320
313
  const mediaType = MediaType.parse(response.headers.get('Content-Type')).normalized;
@@ -322,9 +315,6 @@ var httpc = (function (exports) {
322
315
  return new HttpClientError(message, response.status, [HttpClientError.problem('GENERIC_PROBLEM', message)]);
323
316
  }
324
317
  static async #generic(response) {
325
- //a body that cannot be read (the connection cut mid-body) must not
326
- //masquerade as a connection problem: the response was served, its
327
- //status is the report
328
318
  const text = await response.text().catch(() => null);
329
319
  const message =
330
320
  text === null
@@ -337,19 +327,14 @@ var httpc = (function (exports) {
337
327
  const metaContent = (name) =>
338
328
  globalThis.document?.querySelector(`meta[name="${name}"]`)?.getAttribute('content') ?? undefined;
339
329
 
340
- /**
341
- * @implements {HttpInterceptor}
342
- */
343
330
  /**
344
331
  * Sends the csrf header named by the page's `_csrf_header` meta, with the token
345
332
  * from `_csrf`. Both are read per request, so metas replaced after the client
346
333
  * was built are honoured, and the header is sent to the page's own origin only.
334
+ * @implements {HttpInterceptor}
347
335
  */
348
336
  class CsrfTokenInterceptor {
349
337
  async intercept(url, request, chain) {
350
- //the token is the page's own: it travels to the page's origin only, and it
351
- //is read at request time, so metas landed after the client was built (a
352
- //login flow) are honored without a rebuild
353
338
  if (url.origin !== (globalThis.window?.location?.origin ?? url.origin)) {
354
339
  return await chain.proceed(url, request);
355
340
  }
@@ -361,13 +346,12 @@ var httpc = (function (exports) {
361
346
  return await chain.proceed(url, request);
362
347
  }
363
348
  }
364
- /**
365
- * @implements {HttpInterceptor}
366
- */
367
349
  /**
368
350
  * Navigates to a login url when a response comes back 401. The promise it
369
351
  * returns never settles, so callers keep waiting while the page unloads
370
- * instead of showing a failure nobody will be present to read.
352
+ * instead of showing a failure; where the navigation is blocked, as by a
353
+ * beforeunload prompt, they stay pending until the page leaves.
354
+ * @implements {HttpInterceptor}
371
355
  */
372
356
  class RedirectOnUnauthorizedInterceptor {
373
357
  #redirectUri;
@@ -381,10 +365,6 @@ var httpc = (function (exports) {
381
365
  const response = await chain.proceed(url, request);
382
366
  if (response.status === 401) {
383
367
  window.location.href = this.#redirectUri;
384
- //the page is navigating away: a promise that never settles keeps the
385
- //callers' spinners up instead of flashing a failure nobody will read.
386
- //Where the navigation is blocked (a beforeunload gate), they stay
387
- //pending until the page actually leaves
388
368
  return new Promise(() => {});
389
369
  }
390
370
  return response;
@@ -400,38 +380,48 @@ var httpc = (function (exports) {
400
380
  constructor() {
401
381
  this.#interceptors = [];
402
382
  }
383
+ /**
384
+ * Adds the CsrfTokenInterceptor.
385
+ * @returns {HttpClientBuilder} this builder
386
+ */
403
387
  withCsrfToken() {
404
388
  this.#interceptors.push(new CsrfTokenInterceptor());
405
389
  return this;
406
390
  }
391
+ /**
392
+ * Adds the RedirectOnUnauthorizedInterceptor.
393
+ * @param {string} redirectUri where a 401 navigates to
394
+ * @returns {HttpClientBuilder} this builder
395
+ */
407
396
  withRedirectOnUnauthorized(redirectUri) {
408
397
  this.#interceptors.push(new RedirectOnUnauthorizedInterceptor(redirectUri));
409
398
  return this;
410
399
  }
411
400
  /**
401
+ * Adds interceptors, run in the order given after the ones added before.
412
402
  * @param {...HttpInterceptor} interceptors
403
+ * @returns {HttpClientBuilder} this builder
413
404
  */
414
405
  withInterceptors(...interceptors) {
415
406
  this.#interceptors.push(...interceptors);
416
407
  return this;
417
408
  }
409
+ /** @returns {HttpClient} a client running the interceptors added so far */
418
410
  build() {
419
411
  return new HttpClient(this.#interceptors);
420
412
  }
421
413
  }
422
414
 
423
415
  /**
416
+ * The last interceptor in every chain, performing the request with fetch. A
417
+ * fetch rejection becomes an HttpClientError with a CONNECTION_PROBLEM.
424
418
  * @implements {HttpInterceptor}
425
419
  */
426
- /** The last interceptor in every chain: the one that performs the request. */
427
420
  class HttpCall {
428
421
  async intercept(url, request, chain) {
429
422
  try {
430
423
  return await fetch(url, request);
431
424
  } catch (ex) {
432
- //the one place a connection problem is a connection problem: the
433
- //transport itself refused to deliver. Everything above this is code,
434
- //and code that throws has a different story to tell
435
425
  throw HttpClientError.of('CONNECTION_PROBLEM', ex);
436
426
  }
437
427
  }
@@ -442,19 +432,18 @@ var httpc = (function (exports) {
442
432
  #interceptors;
443
433
  #current;
444
434
  /**
445
- *
446
435
  * @param {HttpInterceptor[]} interceptors
447
- * @param {number} current
436
+ * @param {number} current the index of the interceptor `proceed` runs
448
437
  */
449
438
  constructor(interceptors, current) {
450
439
  this.#interceptors = interceptors;
451
440
  this.#current = current;
452
441
  }
453
442
  /**
454
- *
443
+ * Runs the next interceptor.
455
444
  * @param {URL} url
456
445
  * @param {RequestInit} request
457
- * @returns {Promise<Response>} the response
446
+ * @returns {Promise<Response>}
458
447
  */
459
448
  async proceed(url, request) {
460
449
  const interceptor = this.#interceptors[this.#current];
@@ -488,12 +477,14 @@ var httpc = (function (exports) {
488
477
  this.#interceptors = interceptors || [];
489
478
  }
490
479
  /**
491
- * Performs an HTTP exchange.
492
- * @async
493
- * @param {string} uri - the (possibly relative) request url
494
- * @param {RequestInit|undefined} options - fetch options
495
- * @param {HttpInterceptor[]|undefined} interceptors - the HttpInterceptors to be registered for this exchange.
496
- * @returns {Promise<Response>} the response
480
+ * Performs an HTTP exchange through the client's interceptors, then the
481
+ * given ones.
482
+ * @param {string} uri the request url, relative to the page's
483
+ * @param {RequestInit|undefined} options fetch options
484
+ * @param {HttpInterceptor[]|undefined} interceptors run for this exchange only
485
+ * @returns {Promise<Response>} the response, whatever its status; rejecting
486
+ * with an HttpClientError CONNECTION_PROBLEM when the transport fails, or
487
+ * with what an interceptor throws
497
488
  */
498
489
  async exchange(uri, options, interceptors) {
499
490
  const is = [...this.#interceptors, ...(interceptors || []), new HttpCall()];
@@ -503,58 +494,58 @@ var httpc = (function (exports) {
503
494
  return await chain.proceed(url, request);
504
495
  }
505
496
  /**
506
- * Creates a request builder.
507
- * @param {string} method - the HTTP method to be used
508
- * @param {string} uri - the (possibly relative) request url
509
- * @returns {HttpRequestBuilder} the request builder
497
+ * Starts a request with any method.
498
+ * @param {string} method
499
+ * @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
500
+ * @returns {HttpRequestBuilder}
510
501
  */
511
502
  request(method, uri) {
512
503
  return HttpRequestBuilder.create(this, method, uri);
513
504
  }
514
505
  /**
515
- * Creates a request builder.
516
- * @param {string} uri - the (possibly relative) request url
517
- * @returns {HttpRequestBuilder} the request builder
506
+ * Starts a GET request.
507
+ * @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
508
+ * @returns {HttpRequestBuilder}
518
509
  */
519
510
  get(uri) {
520
511
  return HttpRequestBuilder.create(this, 'GET', uri);
521
512
  }
522
513
  /**
523
- * Creates a request builder.
524
- * @param {string} uri - the (possibly relative) request url
525
- * @returns {HttpRequestBuilder} the request builder
514
+ * Starts a HEAD request.
515
+ * @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
516
+ * @returns {HttpRequestBuilder}
526
517
  */
527
518
  head(uri) {
528
519
  return HttpRequestBuilder.create(this, 'HEAD', uri);
529
520
  }
530
521
  /**
531
- * Creates a request builder.
532
- * @param {string} uri - the (possibly relative) request url
533
- * @returns {HttpRequestBuilder} the request builder
522
+ * Starts a POST request.
523
+ * @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
524
+ * @returns {HttpRequestBuilder}
534
525
  */
535
526
  post(uri) {
536
527
  return HttpRequestBuilder.create(this, 'POST', uri);
537
528
  }
538
529
  /**
539
- * Creates a request builder.
540
- * @param {string} uri - the (possibly relative) request url
541
- * @returns {HttpRequestBuilder} the request builder
530
+ * Starts a PUT request.
531
+ * @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
532
+ * @returns {HttpRequestBuilder}
542
533
  */
543
534
  put(uri) {
544
535
  return HttpRequestBuilder.create(this, 'PUT', uri);
545
536
  }
546
537
  /**
547
- * Creates a request builder.
548
- * @param {string} uri - the (possibly relative) request url
549
- * @returns {HttpRequestBuilder} the request builder
538
+ * Starts a PATCH request.
539
+ * @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
540
+ * @returns {HttpRequestBuilder}
550
541
  */
551
542
  patch(uri) {
552
543
  return HttpRequestBuilder.create(this, 'PATCH', uri);
553
544
  }
554
545
  /**
555
- * Creates a request builder.
556
- * @param {string} uri - the (possibly relative) request url
557
- * @returns {HttpRequestBuilder} the request builder
546
+ * Starts a DELETE request.
547
+ * @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
548
+ * @returns {HttpRequestBuilder}
558
549
  */
559
550
  delete(uri) {
560
551
  return HttpRequestBuilder.create(this, 'DELETE', uri);
@@ -565,7 +556,7 @@ var httpc = (function (exports) {
565
556
  * Reads the response body as the given type, wrapping a failed read as an UNMARSHALING_PROBLEM.
566
557
  * @param {Response} response
567
558
  * @param {'text'|'json'|'blob'|'arrayBuffer'} type
568
- * @returns
559
+ * @returns {Promise<any>}
569
560
  */
570
561
  const unmarshal = async (response, type) => {
571
562
  try {
@@ -576,9 +567,7 @@ var httpc = (function (exports) {
576
567
  };
577
568
 
578
569
  /**
579
- * Yields the entries of a headers or params initializer preserving nullish values:
580
- * normalizing through `Headers`/`URLSearchParams` first would stringify them to
581
- * "null"/"undefined" instead of removing the key.
570
+ * The entries of a headers or params initializer with nullish values kept as they are.
582
571
  * @param {any} source
583
572
  * @returns {Iterable<[string, any]>}
584
573
  */
@@ -612,15 +601,14 @@ var httpc = (function (exports) {
612
601
  #interceptors;
613
602
  #fragment;
614
603
  /**
615
- * Creates an HttpRequestBuilder.
604
+ * A builder for the url: its query becomes the initial params, split at the
605
+ * first `?`, and its fragment, from the first `#`, is kept for the final url.
616
606
  * @param {HttpClient} client
617
- * @param {string} method - the HTTP method to be used
618
- * @param {string} uri - the (possibly relative) request url
619
- * @returns {HttpRequestBuilder} the builder
607
+ * @param {string} method
608
+ * @param {string} uri
609
+ * @returns {HttpRequestBuilder}
620
610
  */
621
611
  static create(client, method, uri) {
622
- //'/a#frag?p=1' parses as hash '#frag?p=1' with an empty query, and a '?'
623
- //may appear in a query itself: only the first of each splits
624
612
  const hashIndex = uri.indexOf('#');
625
613
  const fragment = hashIndex === -1 ? '' : uri.slice(hashIndex);
626
614
  const withoutFragment = hashIndex === -1 ? uri : uri.slice(0, hashIndex);
@@ -638,10 +626,9 @@ var httpc = (function (exports) {
638
626
  );
639
627
  }
640
628
  /**
641
- * Creates an HttpRequestBuilder.
642
629
  * @param {HttpClient} client
643
- * @param {string} method - the HTTP method to be used
644
- * @param {string} uri - the (possibly relative) request url
630
+ * @param {string} method
631
+ * @param {string} uri the url without query and fragment
645
632
  * @param {URLSearchParams} params
646
633
  * @param {Headers} headers
647
634
  * @param {any} body
@@ -661,24 +648,20 @@ var httpc = (function (exports) {
661
648
  this.#fragment = fragment;
662
649
  }
663
650
  /**
664
- * Add all passed headers to the request, overriding existing ones if that key already exists. Null and undefined values cause the key to be removed.
651
+ * Sets each header, replacing a header of the same name; a null or undefined value removes it.
665
652
  * @param {HeadersInit|Record<string,string|null|undefined>} hs
666
653
  * @returns {HttpRequestBuilder} this builder
667
654
  */
668
655
  headers(hs) {
669
656
  for (const [k, v] of rawEntries(hs)) {
670
- if (v == null) {
671
- this.#headers.delete(k);
672
- } else {
673
- this.#headers.set(k, v);
674
- }
657
+ this.header(k, v);
675
658
  }
676
659
  return this;
677
660
  }
678
661
  /**
679
- * Adds an header to the request, overriding it if it already exists. Null and undefined values cause the key to be removed
662
+ * Sets a header, replacing one of the same name; a null or undefined value removes it.
680
663
  * @param {string} k
681
- * @param {string} v
664
+ * @param {string|null|undefined} v
682
665
  * @returns {HttpRequestBuilder} this builder
683
666
  */
684
667
  header(k, v) {
@@ -690,7 +673,7 @@ var httpc = (function (exports) {
690
673
  return this;
691
674
  }
692
675
  /**
693
- * Add all query parameters to the request, overriding existing ones if that key already exists. Null and undefined values cause the key to be removed
676
+ * Sets each query parameter in place, replacing a parameter of the same name; a null or undefined value removes it.
694
677
  * @param {URLSearchParams|Record<string,string|null|undefined>|string[][]|string} ps
695
678
  * @returns {HttpRequestBuilder} this builder
696
679
  */
@@ -705,20 +688,14 @@ var httpc = (function (exports) {
705
688
  return this;
706
689
  }
707
690
  /**
708
- * Adds a query parameter to the request, overriding it if it already exists. An empty list, or one carrying only null and undefined values, causes the key to be removed; nullish entries among real values are skipped.
691
+ * Sets a query parameter to the values given, as repeated entries moved to the end of the query; nullish values are skipped, and none left removes the parameter.
709
692
  * @param {string} k
710
693
  * @param {...string} vs
711
694
  * @returns {HttpRequestBuilder} this builder
712
695
  */
713
696
  param(k, ...vs) {
714
- //overriding, as header, headers and params all do: pass every value in one
715
- //call to get a multi valued parameter
716
697
  this.#params.delete(k);
717
- const values = vs.filter((v) => v != null);
718
- if (values.length === 0) {
719
- return this;
720
- }
721
- for (const v of values) {
698
+ for (const v of vs.filter((v) => v != null)) {
722
699
  this.#params.append(k, v);
723
700
  }
724
701
  return this;
@@ -753,6 +730,7 @@ var httpc = (function (exports) {
753
730
  * Sets the request body as a FormData configured using the callback.
754
731
  * `Content-Type: multipart/form-data` header is automatically added by fetch if not explicitly set.
755
732
  * @param {(c: HttpMultipartRequestCustomizer) => void} callback
733
+ * @returns {HttpRequestBuilder} this builder
756
734
  */
757
735
  multipart(callback) {
758
736
  const formData = new FormData();
@@ -762,7 +740,7 @@ var httpc = (function (exports) {
762
740
  return this;
763
741
  }
764
742
  /**
765
- * Sets a fetch options for the request.
743
+ * Merges fetch options into the request's.
766
744
  * @param {Omit<RequestInit,"headers"|"method"|"body">} kvs
767
745
  * @returns {HttpRequestBuilder} this builder
768
746
  */
@@ -783,8 +761,8 @@ var httpc = (function (exports) {
783
761
  return this;
784
762
  }
785
763
  /**
786
- * Adds interceptors to the request.
787
- * @param {[HttpInterceptor]} is - the interceptor to be registered
764
+ * Adds interceptors run for this request only, after the client's.
765
+ * @param {HttpInterceptor[]} is
788
766
  * @returns {HttpRequestBuilder} this builder
789
767
  */
790
768
  interceptors(is) {
@@ -794,8 +772,8 @@ var httpc = (function (exports) {
794
772
  return this;
795
773
  }
796
774
  /**
797
- * Adds an interceptor to the request.
798
- * @param {HttpInterceptor} i - the interceptor to be registered
775
+ * Adds one interceptor run for this request only, after the client's.
776
+ * @param {HttpInterceptor} i
799
777
  * @returns {HttpRequestBuilder} this builder
800
778
  */
801
779
  interceptor(i) {
@@ -803,8 +781,8 @@ var httpc = (function (exports) {
803
781
  return this;
804
782
  }
805
783
  /**
806
- * Performs an HTTP exchange using the configured client, request and interceptors.
807
- * @returns {Promise<Response>} the response
784
+ * Sends the request and answers the Response, whatever its status.
785
+ * @returns {Promise<Response>} rejecting as `HttpClient.exchange` does
808
786
  */
809
787
  async exchange() {
810
788
  const query = this.#params.size ? `?${this.#params}` : '';
@@ -817,8 +795,12 @@ var httpc = (function (exports) {
817
795
  return await this.#client.exchange(`${this.#uri}${query}${this.#fragment}`, opts, this.#interceptors);
818
796
  }
819
797
  /**
820
- * Performs an HTTP exchange using the configured client request, and interceptors throwing a failure when response status is not in the 200-299 range.
821
- * @returns {Promise<Response>} the response
798
+ * Sends the request and answers the Response when its status is 200-299.
799
+ * Always rejects with an HttpClientError: `HttpClientError.fromResponse`
800
+ * for an error status, CONNECTION_PROBLEM for a transport failure, and
801
+ * UNEXPECTED_PROBLEM, carrying the error as its cause, for anything an
802
+ * interceptor throws that is not already a Failure.
803
+ * @returns {Promise<Response>}
822
804
  */
823
805
  async fetch() {
824
806
  try {
@@ -831,45 +813,40 @@ var httpc = (function (exports) {
831
813
  if (ex instanceof Failure) {
832
814
  throw ex;
833
815
  }
834
- //fetch() answers a Failure whatever happened, so a caller reading
835
- //`problems` never has to test the shape first. What reaches here is
836
- //not the transport, which labels its own failure below the chain: it
837
- //is a throw from the chain's own code, carried as the cause
838
816
  throw HttpClientError.of('UNEXPECTED_PROBLEM', ex);
839
817
  }
840
818
  }
841
819
  /**
842
- * Performs an HTTP exchange using the configured client request, and interceptors throwing a failure when response status is not in the 200-299 range.
843
- * @returns {Promise<string>} the response body, as text
820
+ * `fetch()`, then the body as text; a body that cannot be read rejects with UNMARSHALING_PROBLEM.
821
+ * @returns {Promise<string>}
844
822
  */
845
823
  async fetchText() {
846
824
  const response = await this.fetch();
847
825
  return await unmarshal(response, 'text');
848
826
  }
849
827
  /**
850
- * Performs an HTTP exchange using the configured client request, and interceptors throwing a failure when response status is not in the 200-299 range.
851
- * A 204 yields null without reading the body; any other empty body is an unmarshaling failure.
852
- * @returns {Promise<any>} the response body, deserialized as JSON
828
+ * `fetch()`, then the body parsed as json. A 204 yields null without reading
829
+ * the body; any other body that does not parse rejects with UNMARSHALING_PROBLEM.
830
+ * @returns {Promise<any>}
853
831
  */
854
832
  async fetchJson() {
855
833
  const response = await this.fetch();
856
834
  if (response.status === 204) {
857
- //a 204 declares no content: there is no body to decode
858
835
  return null;
859
836
  }
860
837
  return await unmarshal(response, 'json');
861
838
  }
862
839
  /**
863
- * Performs an HTTP exchange using the configured client request, and interceptors throwing a failure when response status is not in the 200-299 range.
864
- * @returns {Promise<Blob>} the response body, as a Blob
840
+ * `fetch()`, then the body as a Blob; a body that cannot be read rejects with UNMARSHALING_PROBLEM.
841
+ * @returns {Promise<Blob>}
865
842
  */
866
843
  async fetchBlob() {
867
844
  const response = await this.fetch();
868
845
  return await unmarshal(response, 'blob');
869
846
  }
870
847
  /**
871
- * Performs an HTTP exchange using the configured client request, and interceptors throwing a failure when response status is not in the 200-299 range.
872
- * @returns {Promise<ArrayBuffer>} the response body, as an ArrayBuffer
848
+ * `fetch()`, then the body as an ArrayBuffer; a body that cannot be read rejects with UNMARSHALING_PROBLEM.
849
+ * @returns {Promise<ArrayBuffer>}
873
850
  */
874
851
  async fetchArrayBuffer() {
875
852
  const response = await this.fetch();
@@ -880,10 +857,7 @@ var httpc = (function (exports) {
880
857
  /** Builds a multipart body: text fields, json parts, and single or repeated blobs. */
881
858
  class HttpMultipartRequestCustomizer {
882
859
  #formData;
883
- /**
884
- *
885
- * @param {FormData} formData
886
- */
860
+ /** @param {FormData} formData the body being built */
887
861
  constructor(formData) {
888
862
  this.#formData = formData;
889
863
  }
@@ -891,33 +865,30 @@ var httpc = (function (exports) {
891
865
  * Appends a value to the FormData.
892
866
  * @param {string} name
893
867
  * @param {*} value
894
- * @returns this builder
868
+ * @returns {HttpMultipartRequestCustomizer} this builder
895
869
  */
896
870
  field(name, value) {
897
871
  this.#formData.append(name, value);
898
872
  return this;
899
873
  }
900
874
  /**
901
- * Appends a Blob to the FormData.
902
- * If `filename` is omitted, FormData defaults are applied:
903
- * The default filename for Blob objects is "blob";
904
- * The default filename for File objects is the file's filename.
875
+ * Appends a Blob to the FormData. Without a filename a File keeps its own
876
+ * and any other Blob is named "blob".
905
877
  * @param {string} name
906
878
  * @param {Blob} value
907
- * @param {string|undefined} filename
908
- * @returns this builder
879
+ * @param {string} [filename]
880
+ * @returns {HttpMultipartRequestCustomizer} this builder
909
881
  */
910
882
  blob(name, value, filename) {
911
883
  this.#formData.append(name, value, filename);
912
884
  return this;
913
885
  }
914
886
  /**
915
- * Appends multiple Blobs to the FormData with the same name.
916
- * The default filename for Blob objects is "blob";
917
- * The default filename for File objects is the file's filename.
887
+ * Appends each Blob under the same name, a File keeping its own filename and
888
+ * any other Blob named "blob".
918
889
  * @param {string} name
919
890
  * @param {Blob[]} values
920
- * @returns this builder
891
+ * @returns {HttpMultipartRequestCustomizer} this builder
921
892
  */
922
893
  blobs(name, values) {
923
894
  for (const v of values) {
@@ -926,11 +897,11 @@ var httpc = (function (exports) {
926
897
  return this;
927
898
  }
928
899
  /**
929
- * Appends a JSON serialized blob to the FormData.
900
+ * Appends the value serialized as an `application/json` blob.
930
901
  * @param {string} name
931
902
  * @param {any} value
932
- * @param {string|undefined} filename
933
- * @returns this builder
903
+ * @param {string} [filename]
904
+ * @returns {HttpMultipartRequestCustomizer} this builder
934
905
  */
935
906
  json(name, value, filename) {
936
907
  const blob = new Blob([JSON.stringify(value)], { type: 'application/json' });