@optionfactory/fml 9.0.0-rc2 → 9.0.0-rc20
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/README.md +16 -89
- package/dist/client-errors.iife.js +23 -37
- package/dist/client-errors.iife.js.map +1 -1
- package/dist/client-errors.iife.min.js +1 -1
- package/dist/client-errors.iife.min.js.map +1 -1
- package/dist/custom-elements.json +459 -36
- package/dist/fml.css +24 -19
- package/dist/fml.css.map +1 -1
- package/dist/fml.iife.js +4646 -1779
- package/dist/fml.iife.js.map +1 -1
- package/dist/fml.iife.min.js +1 -1
- package/dist/fml.iife.min.js.map +1 -1
- package/dist/ftl.d.mts +395 -224
- package/dist/ftl.iife.js +402 -332
- package/dist/ftl.iife.js.map +1 -1
- package/dist/ftl.iife.min.js +1 -1
- package/dist/ftl.iife.min.js.map +1 -1
- package/dist/ftl.min.mjs +1 -1
- package/dist/ftl.min.mjs.map +1 -1
- package/dist/ftl.mjs +402 -332
- package/dist/ftl.mjs.map +1 -1
- package/dist/ful.css +24 -19
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +2801 -519
- package/dist/ful.iife.js +4096 -1275
- package/dist/ful.iife.js.map +1 -1
- package/dist/ful.iife.min.js +1 -1
- package/dist/ful.iife.min.js.map +1 -1
- package/dist/ful.min.mjs +1 -1
- package/dist/ful.min.mjs.map +1 -1
- package/dist/ful.mjs +4093 -1277
- package/dist/ful.mjs.map +1 -1
- package/dist/httpc.d.mts +139 -121
- package/dist/httpc.iife.js +137 -166
- package/dist/httpc.iife.js.map +1 -1
- package/dist/httpc.iife.min.js +1 -1
- package/dist/httpc.iife.min.js.map +1 -1
- package/dist/httpc.min.mjs +1 -1
- package/dist/httpc.min.mjs.map +1 -1
- package/dist/httpc.mjs +137 -166
- package/dist/httpc.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +215 -27
- package/dist/web-types.json +463 -36
- package/package.json +1 -1
package/dist/httpc.iife.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
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]
|
|
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
|
|
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>}
|
|
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
|
-
*
|
|
493
|
-
* @param {string} uri
|
|
494
|
-
* @param {RequestInit|undefined} options
|
|
495
|
-
* @param {HttpInterceptor[]|undefined} interceptors
|
|
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
|
-
*
|
|
507
|
-
* @param {string} method
|
|
508
|
-
* @param {string} uri
|
|
509
|
-
* @returns {HttpRequestBuilder}
|
|
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
|
-
*
|
|
516
|
-
* @param {string} uri
|
|
517
|
-
* @returns {HttpRequestBuilder}
|
|
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
|
-
*
|
|
524
|
-
* @param {string} uri
|
|
525
|
-
* @returns {HttpRequestBuilder}
|
|
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
|
-
*
|
|
532
|
-
* @param {string} uri
|
|
533
|
-
* @returns {HttpRequestBuilder}
|
|
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
|
-
*
|
|
540
|
-
* @param {string} uri
|
|
541
|
-
* @returns {HttpRequestBuilder}
|
|
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
|
-
*
|
|
548
|
-
* @param {string} uri
|
|
549
|
-
* @returns {HttpRequestBuilder}
|
|
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
|
-
*
|
|
556
|
-
* @param {string} uri
|
|
557
|
-
* @returns {HttpRequestBuilder}
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
618
|
-
* @param {string} uri
|
|
619
|
-
* @returns {HttpRequestBuilder}
|
|
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
|
|
644
|
-
* @param {string} uri
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
787
|
-
* @param {[
|
|
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
|
|
798
|
-
* @param {HttpInterceptor} i
|
|
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
|
-
*
|
|
807
|
-
* @returns {Promise<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
|
-
*
|
|
821
|
-
*
|
|
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
|
-
*
|
|
843
|
-
* @returns {Promise<string>}
|
|
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
|
-
*
|
|
851
|
-
*
|
|
852
|
-
* @returns {Promise<any>}
|
|
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
|
-
*
|
|
864
|
-
* @returns {Promise<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
|
-
*
|
|
872
|
-
* @returns {Promise<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
|
-
*
|
|
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
|
|
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
|
|
916
|
-
*
|
|
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
|
|
900
|
+
* Appends the value serialized as an `application/json` blob.
|
|
930
901
|
* @param {string} name
|
|
931
902
|
* @param {any} value
|
|
932
|
-
* @param {string
|
|
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' });
|