@optionfactory/fml 9.0.0-rc3 → 9.0.0-rc30
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 -88
- package/dist/client-errors.iife.js +40 -49
- 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 +483 -50
- package/dist/fml.css +25 -20
- package/dist/fml.css.map +1 -1
- package/dist/fml.iife.js +5688 -2542
- 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 +398 -227
- package/dist/ftl.iife.js +682 -1016
- 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 +682 -1016
- package/dist/ftl.mjs.map +1 -1
- package/dist/ful.css +25 -20
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +3043 -546
- package/dist/ful.iife.js +4875 -1387
- 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 +4873 -1389
- package/dist/ful.mjs.map +1 -1
- package/dist/httpc.d.mts +143 -123
- package/dist/httpc.iife.js +163 -175
- 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 +163 -175
- package/dist/httpc.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +234 -38
- package/dist/web-types.json +489 -50
- package/package.json +2 -2
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) {
|
|
@@ -168,12 +155,20 @@ var httpc = (function (exports) {
|
|
|
168
155
|
});
|
|
169
156
|
}
|
|
170
157
|
/**
|
|
171
|
-
* @param {Iterable<number
|
|
172
|
-
*
|
|
173
|
-
* @
|
|
158
|
+
* @param {Iterable<number>|ArrayBuffer|ArrayBufferView} bytes octets, or
|
|
159
|
+
* a buffer or view read as its bytes
|
|
160
|
+
* @param {boolean} [upper] uppercase digits instead of lowercase
|
|
161
|
+
* @returns {string} two digits per byte
|
|
174
162
|
*/
|
|
175
163
|
static encode(bytes, upper) {
|
|
176
|
-
|
|
164
|
+
let octets = /** @type {Iterable<number>} */ (bytes);
|
|
165
|
+
if (bytes instanceof ArrayBuffer) {
|
|
166
|
+
octets = new Uint8Array(bytes);
|
|
167
|
+
}
|
|
168
|
+
if (ArrayBuffer.isView(bytes)) {
|
|
169
|
+
octets = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
170
|
+
}
|
|
171
|
+
return Array.from(octets)
|
|
177
172
|
.map((b) => b.toString(16))
|
|
178
173
|
.map((b) => (upper ? b.toUpperCase() : b))
|
|
179
174
|
.map((o) => o.padStart(2, '0'))
|
|
@@ -189,19 +184,22 @@ var httpc = (function (exports) {
|
|
|
189
184
|
this.#type = type;
|
|
190
185
|
this.#subtype = subtype;
|
|
191
186
|
}
|
|
187
|
+
/** @returns {string} `type/subtype`, lowercase */
|
|
192
188
|
get normalized() {
|
|
193
189
|
return `${this.#type}/${this.#subtype}`;
|
|
194
190
|
}
|
|
191
|
+
/** @returns {string} */
|
|
195
192
|
get type() {
|
|
196
193
|
return this.#type;
|
|
197
194
|
}
|
|
195
|
+
/** @returns {string} */
|
|
198
196
|
get subtype() {
|
|
199
197
|
return this.#subtype;
|
|
200
198
|
}
|
|
201
199
|
/**
|
|
202
200
|
* Parses a Content-Type header value into its type/subtype pair, dropping any parameter.
|
|
203
201
|
* @param {string|null|undefined} v
|
|
204
|
-
* @returns
|
|
202
|
+
* @returns {MediaType} `unknown/unknown` for a missing or malformed value
|
|
205
203
|
*/
|
|
206
204
|
static parse(v) {
|
|
207
205
|
if (!v) {
|
|
@@ -251,28 +249,36 @@ var httpc = (function (exports) {
|
|
|
251
249
|
return new HttpClientError(this.message, this.status, Failure.dropProblemsContext(this.problems, prefix), this);
|
|
252
250
|
}
|
|
253
251
|
/**
|
|
254
|
-
*
|
|
255
|
-
* same shape, and the server's arrive already shaped from the wire.
|
|
252
|
+
* A problem with no context and no details.
|
|
256
253
|
* @param {string} type
|
|
257
254
|
* @param {string} reason
|
|
255
|
+
* @returns {{ type: string; context: null; reason: string; details: null; }}
|
|
258
256
|
*/
|
|
259
257
|
static problem(type, reason) {
|
|
260
258
|
return { type, context: null, reason, details: null };
|
|
261
259
|
}
|
|
262
260
|
/**
|
|
263
|
-
*
|
|
261
|
+
* A failure with status 0 and one problem of the given type, whose reason is
|
|
262
|
+
* the cause's message.
|
|
264
263
|
* @param {string} type
|
|
265
264
|
* @param {any} cause
|
|
266
|
-
* @returns
|
|
265
|
+
* @returns {HttpClientError}
|
|
267
266
|
*/
|
|
268
267
|
static of(type, cause) {
|
|
269
268
|
const reason = String(cause?.message ?? cause ?? 'unknown failure');
|
|
270
269
|
return new HttpClientError(reason, 0, [HttpClientError.problem(type, reason)], cause);
|
|
271
270
|
}
|
|
272
271
|
/**
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
272
|
+
* The failure an error response stands for, keeping its status. An
|
|
273
|
+
* `application/failures+json` body is the problems list; an
|
|
274
|
+
* `application/problem+json` body contributes its non-empty `problems`
|
|
275
|
+
* array, or one GENERIC_PROBLEM made of whichever of its title and detail
|
|
276
|
+
* it has. Any other body is one GENERIC_PROBLEM carrying the body text. A
|
|
277
|
+
* json body that does not decode, or decodes to the wrong shape or to an
|
|
278
|
+
* empty failures array, and a body that cannot be read at all, each
|
|
279
|
+
* become one GENERIC_PROBLEM saying so.
|
|
280
|
+
* @param {Response} response consumed
|
|
281
|
+
* @returns {Promise<HttpClientError>}
|
|
276
282
|
*/
|
|
277
283
|
static async fromResponse(response) {
|
|
278
284
|
switch (MediaType.parse(response.headers.get('Content-Type')).normalized) {
|
|
@@ -282,7 +288,10 @@ var httpc = (function (exports) {
|
|
|
282
288
|
return HttpClientError.#undecodable(response);
|
|
283
289
|
}
|
|
284
290
|
if (!Array.isArray(data)) {
|
|
285
|
-
return HttpClientError.#undecodable(response, 'as a failures array');
|
|
291
|
+
return HttpClientError.#undecodable(response, 'does not decode as a failures array');
|
|
292
|
+
}
|
|
293
|
+
if (data.length === 0) {
|
|
294
|
+
return HttpClientError.#undecodable(response, 'carries no failures');
|
|
286
295
|
}
|
|
287
296
|
const message = `${response.status} ${response.statusText}: ${data.length} failures`;
|
|
288
297
|
return new HttpClientError(message, response.status, data);
|
|
@@ -293,13 +302,18 @@ var httpc = (function (exports) {
|
|
|
293
302
|
return HttpClientError.#undecodable(response);
|
|
294
303
|
}
|
|
295
304
|
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
296
|
-
return HttpClientError.#undecodable(response, 'as a problem object');
|
|
305
|
+
return HttpClientError.#undecodable(response, 'does not decode as a problem object');
|
|
297
306
|
}
|
|
298
|
-
const
|
|
307
|
+
const text = [data.title, data.detail].filter((part) => part !== undefined && part !== null).join(' ');
|
|
308
|
+
const message = text
|
|
309
|
+
? `${response.status} ${response.statusText}: ${text}`
|
|
310
|
+
: `${response.status} ${response.statusText}`;
|
|
299
311
|
return new HttpClientError(
|
|
300
312
|
message,
|
|
301
313
|
response.status,
|
|
302
|
-
data.problems
|
|
314
|
+
Array.isArray(data.problems) && data.problems.length > 0
|
|
315
|
+
? data.problems
|
|
316
|
+
: [HttpClientError.problem('GENERIC_PROBLEM', message)],
|
|
303
317
|
);
|
|
304
318
|
}
|
|
305
319
|
default: {
|
|
@@ -307,24 +321,17 @@ var httpc = (function (exports) {
|
|
|
307
321
|
}
|
|
308
322
|
}
|
|
309
323
|
}
|
|
310
|
-
/** marks a body whose json() rejected, telling it apart from a body decoding to json null */
|
|
311
324
|
static #unreadable = Symbol('unreadable body');
|
|
312
325
|
/**
|
|
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
326
|
* @param {Response} response
|
|
317
|
-
* @param {string} [
|
|
327
|
+
* @param {string} [flaw] what is wrong with the body
|
|
318
328
|
*/
|
|
319
|
-
static #undecodable(response,
|
|
329
|
+
static #undecodable(response, flaw = 'does not decode as json') {
|
|
320
330
|
const mediaType = MediaType.parse(response.headers.get('Content-Type')).normalized;
|
|
321
|
-
const message = `${response.status} ${response.statusText}: the ${mediaType} body
|
|
331
|
+
const message = `${response.status} ${response.statusText}: the ${mediaType} body ${flaw}`;
|
|
322
332
|
return new HttpClientError(message, response.status, [HttpClientError.problem('GENERIC_PROBLEM', message)]);
|
|
323
333
|
}
|
|
324
334
|
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
335
|
const text = await response.text().catch(() => null);
|
|
329
336
|
const message =
|
|
330
337
|
text === null
|
|
@@ -337,19 +344,14 @@ var httpc = (function (exports) {
|
|
|
337
344
|
const metaContent = (name) =>
|
|
338
345
|
globalThis.document?.querySelector(`meta[name="${name}"]`)?.getAttribute('content') ?? undefined;
|
|
339
346
|
|
|
340
|
-
/**
|
|
341
|
-
* @implements {HttpInterceptor}
|
|
342
|
-
*/
|
|
343
347
|
/**
|
|
344
348
|
* Sends the csrf header named by the page's `_csrf_header` meta, with the token
|
|
345
349
|
* from `_csrf`. Both are read per request, so metas replaced after the client
|
|
346
350
|
* was built are honoured, and the header is sent to the page's own origin only.
|
|
351
|
+
* @implements {HttpInterceptor}
|
|
347
352
|
*/
|
|
348
353
|
class CsrfTokenInterceptor {
|
|
349
354
|
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
355
|
if (url.origin !== (globalThis.window?.location?.origin ?? url.origin)) {
|
|
354
356
|
return await chain.proceed(url, request);
|
|
355
357
|
}
|
|
@@ -361,13 +363,12 @@ var httpc = (function (exports) {
|
|
|
361
363
|
return await chain.proceed(url, request);
|
|
362
364
|
}
|
|
363
365
|
}
|
|
364
|
-
/**
|
|
365
|
-
* @implements {HttpInterceptor}
|
|
366
|
-
*/
|
|
367
366
|
/**
|
|
368
367
|
* Navigates to a login url when a response comes back 401. The promise it
|
|
369
368
|
* returns never settles, so callers keep waiting while the page unloads
|
|
370
|
-
* instead of showing a failure
|
|
369
|
+
* instead of showing a failure; where the navigation is blocked, as by a
|
|
370
|
+
* beforeunload prompt, they stay pending until the page leaves.
|
|
371
|
+
* @implements {HttpInterceptor}
|
|
371
372
|
*/
|
|
372
373
|
class RedirectOnUnauthorizedInterceptor {
|
|
373
374
|
#redirectUri;
|
|
@@ -381,10 +382,6 @@ var httpc = (function (exports) {
|
|
|
381
382
|
const response = await chain.proceed(url, request);
|
|
382
383
|
if (response.status === 401) {
|
|
383
384
|
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
385
|
return new Promise(() => {});
|
|
389
386
|
}
|
|
390
387
|
return response;
|
|
@@ -400,38 +397,48 @@ var httpc = (function (exports) {
|
|
|
400
397
|
constructor() {
|
|
401
398
|
this.#interceptors = [];
|
|
402
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Adds the CsrfTokenInterceptor.
|
|
402
|
+
* @returns {HttpClientBuilder} this builder
|
|
403
|
+
*/
|
|
403
404
|
withCsrfToken() {
|
|
404
405
|
this.#interceptors.push(new CsrfTokenInterceptor());
|
|
405
406
|
return this;
|
|
406
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Adds the RedirectOnUnauthorizedInterceptor.
|
|
410
|
+
* @param {string} redirectUri where a 401 navigates to
|
|
411
|
+
* @returns {HttpClientBuilder} this builder
|
|
412
|
+
*/
|
|
407
413
|
withRedirectOnUnauthorized(redirectUri) {
|
|
408
414
|
this.#interceptors.push(new RedirectOnUnauthorizedInterceptor(redirectUri));
|
|
409
415
|
return this;
|
|
410
416
|
}
|
|
411
417
|
/**
|
|
418
|
+
* Adds interceptors, run in the order given after the ones added before.
|
|
412
419
|
* @param {...HttpInterceptor} interceptors
|
|
420
|
+
* @returns {HttpClientBuilder} this builder
|
|
413
421
|
*/
|
|
414
422
|
withInterceptors(...interceptors) {
|
|
415
423
|
this.#interceptors.push(...interceptors);
|
|
416
424
|
return this;
|
|
417
425
|
}
|
|
426
|
+
/** @returns {HttpClient} a client running the interceptors added so far */
|
|
418
427
|
build() {
|
|
419
|
-
return new HttpClient(this.#interceptors);
|
|
428
|
+
return new HttpClient([...this.#interceptors]);
|
|
420
429
|
}
|
|
421
430
|
}
|
|
422
431
|
|
|
423
432
|
/**
|
|
433
|
+
* The last interceptor in every chain, performing the request with fetch. A
|
|
434
|
+
* fetch rejection becomes an HttpClientError with a CONNECTION_PROBLEM.
|
|
424
435
|
* @implements {HttpInterceptor}
|
|
425
436
|
*/
|
|
426
|
-
/** The last interceptor in every chain: the one that performs the request. */
|
|
427
437
|
class HttpCall {
|
|
428
438
|
async intercept(url, request, chain) {
|
|
429
439
|
try {
|
|
430
440
|
return await fetch(url, request);
|
|
431
441
|
} 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
442
|
throw HttpClientError.of('CONNECTION_PROBLEM', ex);
|
|
436
443
|
}
|
|
437
444
|
}
|
|
@@ -442,19 +449,18 @@ var httpc = (function (exports) {
|
|
|
442
449
|
#interceptors;
|
|
443
450
|
#current;
|
|
444
451
|
/**
|
|
445
|
-
*
|
|
446
452
|
* @param {HttpInterceptor[]} interceptors
|
|
447
|
-
* @param {number} current
|
|
453
|
+
* @param {number} current the index of the interceptor `proceed` runs
|
|
448
454
|
*/
|
|
449
455
|
constructor(interceptors, current) {
|
|
450
456
|
this.#interceptors = interceptors;
|
|
451
457
|
this.#current = current;
|
|
452
458
|
}
|
|
453
459
|
/**
|
|
454
|
-
*
|
|
460
|
+
* Runs the next interceptor.
|
|
455
461
|
* @param {URL} url
|
|
456
462
|
* @param {RequestInit} request
|
|
457
|
-
* @returns {Promise<Response>}
|
|
463
|
+
* @returns {Promise<Response>}
|
|
458
464
|
*/
|
|
459
465
|
async proceed(url, request) {
|
|
460
466
|
const interceptor = this.#interceptors[this.#current];
|
|
@@ -488,12 +494,14 @@ var httpc = (function (exports) {
|
|
|
488
494
|
this.#interceptors = interceptors || [];
|
|
489
495
|
}
|
|
490
496
|
/**
|
|
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
|
|
497
|
+
* Performs an HTTP exchange through the client's interceptors, then the
|
|
498
|
+
* given ones.
|
|
499
|
+
* @param {string} uri the request url, relative to the page's
|
|
500
|
+
* @param {RequestInit|undefined} options fetch options
|
|
501
|
+
* @param {HttpInterceptor[]|undefined} interceptors run for this exchange only
|
|
502
|
+
* @returns {Promise<Response>} the response, whatever its status; rejecting
|
|
503
|
+
* with an HttpClientError CONNECTION_PROBLEM when the transport fails, or
|
|
504
|
+
* with what an interceptor throws
|
|
497
505
|
*/
|
|
498
506
|
async exchange(uri, options, interceptors) {
|
|
499
507
|
const is = [...this.#interceptors, ...(interceptors || []), new HttpCall()];
|
|
@@ -503,58 +511,58 @@ var httpc = (function (exports) {
|
|
|
503
511
|
return await chain.proceed(url, request);
|
|
504
512
|
}
|
|
505
513
|
/**
|
|
506
|
-
*
|
|
507
|
-
* @param {string} method
|
|
508
|
-
* @param {string} uri
|
|
509
|
-
* @returns {HttpRequestBuilder}
|
|
514
|
+
* Starts a request with any method.
|
|
515
|
+
* @param {string} method
|
|
516
|
+
* @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
|
|
517
|
+
* @returns {HttpRequestBuilder}
|
|
510
518
|
*/
|
|
511
519
|
request(method, uri) {
|
|
512
520
|
return HttpRequestBuilder.create(this, method, uri);
|
|
513
521
|
}
|
|
514
522
|
/**
|
|
515
|
-
*
|
|
516
|
-
* @param {string} uri
|
|
517
|
-
* @returns {HttpRequestBuilder}
|
|
523
|
+
* Starts a GET request.
|
|
524
|
+
* @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
|
|
525
|
+
* @returns {HttpRequestBuilder}
|
|
518
526
|
*/
|
|
519
527
|
get(uri) {
|
|
520
528
|
return HttpRequestBuilder.create(this, 'GET', uri);
|
|
521
529
|
}
|
|
522
530
|
/**
|
|
523
|
-
*
|
|
524
|
-
* @param {string} uri
|
|
525
|
-
* @returns {HttpRequestBuilder}
|
|
531
|
+
* Starts a HEAD request.
|
|
532
|
+
* @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
|
|
533
|
+
* @returns {HttpRequestBuilder}
|
|
526
534
|
*/
|
|
527
535
|
head(uri) {
|
|
528
536
|
return HttpRequestBuilder.create(this, 'HEAD', uri);
|
|
529
537
|
}
|
|
530
538
|
/**
|
|
531
|
-
*
|
|
532
|
-
* @param {string} uri
|
|
533
|
-
* @returns {HttpRequestBuilder}
|
|
539
|
+
* Starts a POST request.
|
|
540
|
+
* @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
|
|
541
|
+
* @returns {HttpRequestBuilder}
|
|
534
542
|
*/
|
|
535
543
|
post(uri) {
|
|
536
544
|
return HttpRequestBuilder.create(this, 'POST', uri);
|
|
537
545
|
}
|
|
538
546
|
/**
|
|
539
|
-
*
|
|
540
|
-
* @param {string} uri
|
|
541
|
-
* @returns {HttpRequestBuilder}
|
|
547
|
+
* Starts a PUT request.
|
|
548
|
+
* @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
|
|
549
|
+
* @returns {HttpRequestBuilder}
|
|
542
550
|
*/
|
|
543
551
|
put(uri) {
|
|
544
552
|
return HttpRequestBuilder.create(this, 'PUT', uri);
|
|
545
553
|
}
|
|
546
554
|
/**
|
|
547
|
-
*
|
|
548
|
-
* @param {string} uri
|
|
549
|
-
* @returns {HttpRequestBuilder}
|
|
555
|
+
* Starts a PATCH request.
|
|
556
|
+
* @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
|
|
557
|
+
* @returns {HttpRequestBuilder}
|
|
550
558
|
*/
|
|
551
559
|
patch(uri) {
|
|
552
560
|
return HttpRequestBuilder.create(this, 'PATCH', uri);
|
|
553
561
|
}
|
|
554
562
|
/**
|
|
555
|
-
*
|
|
556
|
-
* @param {string} uri
|
|
557
|
-
* @returns {HttpRequestBuilder}
|
|
563
|
+
* Starts a DELETE request.
|
|
564
|
+
* @param {string} uri the request url, relative to the page's; a query and a fragment in it are kept
|
|
565
|
+
* @returns {HttpRequestBuilder}
|
|
558
566
|
*/
|
|
559
567
|
delete(uri) {
|
|
560
568
|
return HttpRequestBuilder.create(this, 'DELETE', uri);
|
|
@@ -565,7 +573,7 @@ var httpc = (function (exports) {
|
|
|
565
573
|
* Reads the response body as the given type, wrapping a failed read as an UNMARSHALING_PROBLEM.
|
|
566
574
|
* @param {Response} response
|
|
567
575
|
* @param {'text'|'json'|'blob'|'arrayBuffer'} type
|
|
568
|
-
* @returns
|
|
576
|
+
* @returns {Promise<any>}
|
|
569
577
|
*/
|
|
570
578
|
const unmarshal = async (response, type) => {
|
|
571
579
|
try {
|
|
@@ -576,9 +584,7 @@ var httpc = (function (exports) {
|
|
|
576
584
|
};
|
|
577
585
|
|
|
578
586
|
/**
|
|
579
|
-
*
|
|
580
|
-
* normalizing through `Headers`/`URLSearchParams` first would stringify them to
|
|
581
|
-
* "null"/"undefined" instead of removing the key.
|
|
587
|
+
* The entries of a headers or params initializer with nullish values kept as they are.
|
|
582
588
|
* @param {any} source
|
|
583
589
|
* @returns {Iterable<[string, any]>}
|
|
584
590
|
*/
|
|
@@ -612,15 +618,14 @@ var httpc = (function (exports) {
|
|
|
612
618
|
#interceptors;
|
|
613
619
|
#fragment;
|
|
614
620
|
/**
|
|
615
|
-
*
|
|
621
|
+
* A builder for the url: its query becomes the initial params, split at the
|
|
622
|
+
* first `?`, and its fragment, from the first `#`, is kept for the final url.
|
|
616
623
|
* @param {HttpClient} client
|
|
617
|
-
* @param {string} method
|
|
618
|
-
* @param {string} uri
|
|
619
|
-
* @returns {HttpRequestBuilder}
|
|
624
|
+
* @param {string} method
|
|
625
|
+
* @param {string} uri
|
|
626
|
+
* @returns {HttpRequestBuilder}
|
|
620
627
|
*/
|
|
621
628
|
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
629
|
const hashIndex = uri.indexOf('#');
|
|
625
630
|
const fragment = hashIndex === -1 ? '' : uri.slice(hashIndex);
|
|
626
631
|
const withoutFragment = hashIndex === -1 ? uri : uri.slice(0, hashIndex);
|
|
@@ -638,10 +643,9 @@ var httpc = (function (exports) {
|
|
|
638
643
|
);
|
|
639
644
|
}
|
|
640
645
|
/**
|
|
641
|
-
* Creates an HttpRequestBuilder.
|
|
642
646
|
* @param {HttpClient} client
|
|
643
|
-
* @param {string} method
|
|
644
|
-
* @param {string} uri
|
|
647
|
+
* @param {string} method
|
|
648
|
+
* @param {string} uri the url without query and fragment
|
|
645
649
|
* @param {URLSearchParams} params
|
|
646
650
|
* @param {Headers} headers
|
|
647
651
|
* @param {any} body
|
|
@@ -661,24 +665,20 @@ var httpc = (function (exports) {
|
|
|
661
665
|
this.#fragment = fragment;
|
|
662
666
|
}
|
|
663
667
|
/**
|
|
664
|
-
*
|
|
668
|
+
* Sets each header, replacing a header of the same name; a null or undefined value removes it.
|
|
665
669
|
* @param {HeadersInit|Record<string,string|null|undefined>} hs
|
|
666
670
|
* @returns {HttpRequestBuilder} this builder
|
|
667
671
|
*/
|
|
668
672
|
headers(hs) {
|
|
669
673
|
for (const [k, v] of rawEntries(hs)) {
|
|
670
|
-
|
|
671
|
-
this.#headers.delete(k);
|
|
672
|
-
} else {
|
|
673
|
-
this.#headers.set(k, v);
|
|
674
|
-
}
|
|
674
|
+
this.header(k, v);
|
|
675
675
|
}
|
|
676
676
|
return this;
|
|
677
677
|
}
|
|
678
678
|
/**
|
|
679
|
-
*
|
|
679
|
+
* Sets a header, replacing one of the same name; a null or undefined value removes it.
|
|
680
680
|
* @param {string} k
|
|
681
|
-
* @param {string} v
|
|
681
|
+
* @param {string|null|undefined} v
|
|
682
682
|
* @returns {HttpRequestBuilder} this builder
|
|
683
683
|
*/
|
|
684
684
|
header(k, v) {
|
|
@@ -690,7 +690,7 @@ var httpc = (function (exports) {
|
|
|
690
690
|
return this;
|
|
691
691
|
}
|
|
692
692
|
/**
|
|
693
|
-
*
|
|
693
|
+
* Sets each query parameter in place, replacing a parameter of the same name; a null or undefined value removes it.
|
|
694
694
|
* @param {URLSearchParams|Record<string,string|null|undefined>|string[][]|string} ps
|
|
695
695
|
* @returns {HttpRequestBuilder} this builder
|
|
696
696
|
*/
|
|
@@ -705,20 +705,14 @@ var httpc = (function (exports) {
|
|
|
705
705
|
return this;
|
|
706
706
|
}
|
|
707
707
|
/**
|
|
708
|
-
*
|
|
708
|
+
* 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
709
|
* @param {string} k
|
|
710
710
|
* @param {...string} vs
|
|
711
711
|
* @returns {HttpRequestBuilder} this builder
|
|
712
712
|
*/
|
|
713
713
|
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
714
|
this.#params.delete(k);
|
|
717
|
-
const
|
|
718
|
-
if (values.length === 0) {
|
|
719
|
-
return this;
|
|
720
|
-
}
|
|
721
|
-
for (const v of values) {
|
|
715
|
+
for (const v of vs.filter((v) => v != null)) {
|
|
722
716
|
this.#params.append(k, v);
|
|
723
717
|
}
|
|
724
718
|
return this;
|
|
@@ -753,6 +747,7 @@ var httpc = (function (exports) {
|
|
|
753
747
|
* Sets the request body as a FormData configured using the callback.
|
|
754
748
|
* `Content-Type: multipart/form-data` header is automatically added by fetch if not explicitly set.
|
|
755
749
|
* @param {(c: HttpMultipartRequestCustomizer) => void} callback
|
|
750
|
+
* @returns {HttpRequestBuilder} this builder
|
|
756
751
|
*/
|
|
757
752
|
multipart(callback) {
|
|
758
753
|
const formData = new FormData();
|
|
@@ -762,7 +757,7 @@ var httpc = (function (exports) {
|
|
|
762
757
|
return this;
|
|
763
758
|
}
|
|
764
759
|
/**
|
|
765
|
-
*
|
|
760
|
+
* Merges fetch options into the request's.
|
|
766
761
|
* @param {Omit<RequestInit,"headers"|"method"|"body">} kvs
|
|
767
762
|
* @returns {HttpRequestBuilder} this builder
|
|
768
763
|
*/
|
|
@@ -783,8 +778,8 @@ var httpc = (function (exports) {
|
|
|
783
778
|
return this;
|
|
784
779
|
}
|
|
785
780
|
/**
|
|
786
|
-
* Adds interceptors
|
|
787
|
-
* @param {[
|
|
781
|
+
* Adds interceptors run for this request only, after the client's.
|
|
782
|
+
* @param {HttpInterceptor[]} is
|
|
788
783
|
* @returns {HttpRequestBuilder} this builder
|
|
789
784
|
*/
|
|
790
785
|
interceptors(is) {
|
|
@@ -794,8 +789,8 @@ var httpc = (function (exports) {
|
|
|
794
789
|
return this;
|
|
795
790
|
}
|
|
796
791
|
/**
|
|
797
|
-
* Adds
|
|
798
|
-
* @param {HttpInterceptor} i
|
|
792
|
+
* Adds one interceptor run for this request only, after the client's.
|
|
793
|
+
* @param {HttpInterceptor} i
|
|
799
794
|
* @returns {HttpRequestBuilder} this builder
|
|
800
795
|
*/
|
|
801
796
|
interceptor(i) {
|
|
@@ -803,8 +798,8 @@ var httpc = (function (exports) {
|
|
|
803
798
|
return this;
|
|
804
799
|
}
|
|
805
800
|
/**
|
|
806
|
-
*
|
|
807
|
-
* @returns {Promise<Response>}
|
|
801
|
+
* Sends the request and answers the Response, whatever its status.
|
|
802
|
+
* @returns {Promise<Response>} rejecting as `HttpClient.exchange` does
|
|
808
803
|
*/
|
|
809
804
|
async exchange() {
|
|
810
805
|
const query = this.#params.size ? `?${this.#params}` : '';
|
|
@@ -817,8 +812,12 @@ var httpc = (function (exports) {
|
|
|
817
812
|
return await this.#client.exchange(`${this.#uri}${query}${this.#fragment}`, opts, this.#interceptors);
|
|
818
813
|
}
|
|
819
814
|
/**
|
|
820
|
-
*
|
|
821
|
-
*
|
|
815
|
+
* Sends the request and answers the Response when its status is 200-299.
|
|
816
|
+
* Always rejects with an HttpClientError: `HttpClientError.fromResponse`
|
|
817
|
+
* for an error status, CONNECTION_PROBLEM for a transport failure, and
|
|
818
|
+
* UNEXPECTED_PROBLEM, carrying the error as its cause, for anything an
|
|
819
|
+
* interceptor throws that is not already a Failure.
|
|
820
|
+
* @returns {Promise<Response>}
|
|
822
821
|
*/
|
|
823
822
|
async fetch() {
|
|
824
823
|
try {
|
|
@@ -831,45 +830,40 @@ var httpc = (function (exports) {
|
|
|
831
830
|
if (ex instanceof Failure) {
|
|
832
831
|
throw ex;
|
|
833
832
|
}
|
|
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
833
|
throw HttpClientError.of('UNEXPECTED_PROBLEM', ex);
|
|
839
834
|
}
|
|
840
835
|
}
|
|
841
836
|
/**
|
|
842
|
-
*
|
|
843
|
-
* @returns {Promise<string>}
|
|
837
|
+
* `fetch()`, then the body as text; a body that cannot be read rejects with UNMARSHALING_PROBLEM.
|
|
838
|
+
* @returns {Promise<string>}
|
|
844
839
|
*/
|
|
845
840
|
async fetchText() {
|
|
846
841
|
const response = await this.fetch();
|
|
847
842
|
return await unmarshal(response, 'text');
|
|
848
843
|
}
|
|
849
844
|
/**
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
* @returns {Promise<any>}
|
|
845
|
+
* `fetch()`, then the body parsed as json. A 204 yields null without reading
|
|
846
|
+
* the body; any other body that does not parse rejects with UNMARSHALING_PROBLEM.
|
|
847
|
+
* @returns {Promise<any>}
|
|
853
848
|
*/
|
|
854
849
|
async fetchJson() {
|
|
855
850
|
const response = await this.fetch();
|
|
856
851
|
if (response.status === 204) {
|
|
857
|
-
//a 204 declares no content: there is no body to decode
|
|
858
852
|
return null;
|
|
859
853
|
}
|
|
860
854
|
return await unmarshal(response, 'json');
|
|
861
855
|
}
|
|
862
856
|
/**
|
|
863
|
-
*
|
|
864
|
-
* @returns {Promise<Blob>}
|
|
857
|
+
* `fetch()`, then the body as a Blob; a body that cannot be read rejects with UNMARSHALING_PROBLEM.
|
|
858
|
+
* @returns {Promise<Blob>}
|
|
865
859
|
*/
|
|
866
860
|
async fetchBlob() {
|
|
867
861
|
const response = await this.fetch();
|
|
868
862
|
return await unmarshal(response, 'blob');
|
|
869
863
|
}
|
|
870
864
|
/**
|
|
871
|
-
*
|
|
872
|
-
* @returns {Promise<ArrayBuffer>}
|
|
865
|
+
* `fetch()`, then the body as an ArrayBuffer; a body that cannot be read rejects with UNMARSHALING_PROBLEM.
|
|
866
|
+
* @returns {Promise<ArrayBuffer>}
|
|
873
867
|
*/
|
|
874
868
|
async fetchArrayBuffer() {
|
|
875
869
|
const response = await this.fetch();
|
|
@@ -880,10 +874,7 @@ var httpc = (function (exports) {
|
|
|
880
874
|
/** Builds a multipart body: text fields, json parts, and single or repeated blobs. */
|
|
881
875
|
class HttpMultipartRequestCustomizer {
|
|
882
876
|
#formData;
|
|
883
|
-
/**
|
|
884
|
-
*
|
|
885
|
-
* @param {FormData} formData
|
|
886
|
-
*/
|
|
877
|
+
/** @param {FormData} formData the body being built */
|
|
887
878
|
constructor(formData) {
|
|
888
879
|
this.#formData = formData;
|
|
889
880
|
}
|
|
@@ -891,33 +882,30 @@ var httpc = (function (exports) {
|
|
|
891
882
|
* Appends a value to the FormData.
|
|
892
883
|
* @param {string} name
|
|
893
884
|
* @param {*} value
|
|
894
|
-
* @returns this builder
|
|
885
|
+
* @returns {HttpMultipartRequestCustomizer} this builder
|
|
895
886
|
*/
|
|
896
887
|
field(name, value) {
|
|
897
888
|
this.#formData.append(name, value);
|
|
898
889
|
return this;
|
|
899
890
|
}
|
|
900
891
|
/**
|
|
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.
|
|
892
|
+
* Appends a Blob to the FormData. Without a filename a File keeps its own
|
|
893
|
+
* and any other Blob is named "blob".
|
|
905
894
|
* @param {string} name
|
|
906
895
|
* @param {Blob} value
|
|
907
|
-
* @param {string
|
|
908
|
-
* @returns this builder
|
|
896
|
+
* @param {string} [filename]
|
|
897
|
+
* @returns {HttpMultipartRequestCustomizer} this builder
|
|
909
898
|
*/
|
|
910
899
|
blob(name, value, filename) {
|
|
911
900
|
this.#formData.append(name, value, filename);
|
|
912
901
|
return this;
|
|
913
902
|
}
|
|
914
903
|
/**
|
|
915
|
-
* Appends
|
|
916
|
-
*
|
|
917
|
-
* The default filename for File objects is the file's filename.
|
|
904
|
+
* Appends each Blob under the same name, a File keeping its own filename and
|
|
905
|
+
* any other Blob named "blob".
|
|
918
906
|
* @param {string} name
|
|
919
907
|
* @param {Blob[]} values
|
|
920
|
-
* @returns this builder
|
|
908
|
+
* @returns {HttpMultipartRequestCustomizer} this builder
|
|
921
909
|
*/
|
|
922
910
|
blobs(name, values) {
|
|
923
911
|
for (const v of values) {
|
|
@@ -926,11 +914,11 @@ var httpc = (function (exports) {
|
|
|
926
914
|
return this;
|
|
927
915
|
}
|
|
928
916
|
/**
|
|
929
|
-
* Appends
|
|
917
|
+
* Appends the value serialized as an `application/json` blob.
|
|
930
918
|
* @param {string} name
|
|
931
919
|
* @param {any} value
|
|
932
|
-
* @param {string
|
|
933
|
-
* @returns this builder
|
|
920
|
+
* @param {string} [filename]
|
|
921
|
+
* @returns {HttpMultipartRequestCustomizer} this builder
|
|
934
922
|
*/
|
|
935
923
|
json(name, value, filename) {
|
|
936
924
|
const blob = new Blob([JSON.stringify(value)], { type: 'application/json' });
|