@brandup/ui-ajax 1.0.44 → 2.0.2

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 CHANGED
@@ -12,7 +12,7 @@ npm i @brandup/ui-ajax@latest
12
12
 
13
13
  ## AJAX request
14
14
 
15
- Simplify async ajax request method.
15
+ Simplify async ajax request method. `request` is the `fetch`-based, promise-returning API.
16
16
 
17
17
  ```
18
18
  import { request } from "@brandup/ui-ajax";
@@ -21,10 +21,13 @@ await request({
21
21
  url?: string | null;
22
22
  query?: QueryData | null;
23
23
  method?: AJAXMethod | null;
24
+ mode?: RequestMode;
25
+ credentials?: RequestCredentials;
24
26
  timeout?: number | null;
25
27
  headers?: { [key: string]: string } | null;
26
- type?: AJAXReqestType | null;
28
+ type?: AJAXRequestType | null;
27
29
  data?: string | object | Blob | FormData | HTMLFormElement | null;
30
+ abort?: AbortSignal;
28
31
  success?: ResponseDelegate | null;
29
32
  error?: ErrorDelegate | null;
30
33
  disableCache?: boolean | null;
@@ -42,15 +45,49 @@ await request({
42
45
  .catch(reason => console.error(reason));
43
46
  ```
44
47
 
48
+ The response body is parsed by `Content-Type`: JSON, `text/html`, `text/plain`, otherwise a `Blob`. The default `timeout` is `30000` ms and `credentials` defaults to `"include"`.
49
+
45
50
  ### Request cancellation
46
51
 
52
+ A request is aborted when its `timeout` elapses, when the `abort` option signals, or when the second `abortSignal` argument signals.
53
+
47
54
  ```
48
55
  import { request } from "@brandup/ui-ajax";
49
56
 
50
57
  const cancellation = new AbortController();
51
58
 
52
- await request({ }, cancellation.signal)
59
+ await request({ /* request options */ }, cancellation.signal)
53
60
  .catch(reason => console.error(reason));
61
+
62
+ cancellation.abort();
63
+ ```
64
+
65
+ ### Disable cache
66
+
67
+ Set `disableCache: true` to bypass HTTP caching. In `request` (fetch) this sends `cache: "no-store"`; in `ajaxRequest` (XHR) it appends a `_=<timestamp>` cache-busting query parameter.
68
+
69
+ ```
70
+ await request({ url: "/api/data", disableCache: true });
71
+ ```
72
+
73
+ ## AJAX request with XMLHttpRequest
74
+
75
+ `ajaxRequest` is the `XMLHttpRequest`-based API. It always sends credentials, delivers results via the `success`/`error` callbacks (it does not return a promise), and returns the underlying `XMLHttpRequest` so the call can be aborted. Unlike `request`, there is no `blob` response type.
76
+
77
+ ```
78
+ import { ajaxRequest } from "@brandup/ui-ajax";
79
+
80
+ const xhr = ajaxRequest({
81
+ url: "/api/data",
82
+ method: "POST",
83
+ data: { name: "value" },
84
+ success: response => {
85
+ // response.status, response.data, response.type, ...
86
+ },
87
+ error: (request, reason) => console.error(reason)
88
+ });
89
+
90
+ xhr.abort(); // cancel the request
54
91
  ```
55
92
 
56
93
  ## Queue requests
@@ -61,16 +98,33 @@ Sequential execution of AJAX requests.
61
98
  import { AjaxQueue } from "@brandup/ui-ajax";
62
99
 
63
100
  const queue = new AjaxQueue({
64
- canRequest?: (request: AjaxRequest) => void | boolean;
101
+ canRequest?: (request: AjaxRequest) => boolean | void;
65
102
  successRequest?: (request: AjaxRequest, response: AjaxResponse) => void;
66
103
  errorRequest?: (response: AjaxRequest, reason?: any) => void;
67
104
  });
68
105
 
69
- queue.push({ request options });
106
+ queue.push({ /* request options */ }); // enqueue a request (fire and forget)
107
+
108
+ queue.push({ /* request options */ }, abortSignal); // with per-request cancellation
70
109
 
71
- queue.reset(); // clear queue without abort current request
110
+ queue.reset(); // clear queue without aborting current request
72
111
 
73
112
  queue.reset(true); // abort current request and clear queue
74
113
 
75
- queue.destroy();
114
+ queue.destroy(); // clear queue, abort current request; further push() throws
115
+ ```
116
+
117
+ Queue state can be inspected via `queue.length` (waiting requests), `queue.isEmpty` (nothing waiting) and `queue.isFree` (nothing waiting and nothing executing).
118
+
119
+ ### Awaiting a queued request
120
+
121
+ `enqueue` queues a request like `push`, but returns a promise that resolves with the response (or rejects with the failure reason). The request's own `success`/`error` callbacks are still invoked.
122
+
123
+ ```
124
+ import { AjaxQueue } from "@brandup/ui-ajax";
125
+
126
+ const queue = new AjaxQueue();
127
+
128
+ const response = await queue.enqueue({ url: "/api/data" });
129
+ // response.status, response.data, ...
76
130
  ```
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+
3
+ var helpers = require('./helpers.js');
4
+ var internals = require('./internals.js');
5
+
6
+ /**
7
+ * Performs an AJAX request using `XMLHttpRequest`, always with credentials.
8
+ *
9
+ * The response body is parsed by `Content-Type` into JSON, `text/plain` or `text/html`;
10
+ * unlike the fetch-based {@link request}, there is no `blob` response type. `disableCache`
11
+ * appends a `_=<timestamp>` cache-busting query parameter (rather than `cache: "no-store"`).
12
+ * Results and errors are delivered through `options.success` / `options.error`; this
13
+ * function does not return a promise.
14
+ *
15
+ * @param options Request options. `GET` requests must not carry `data`. `timeout` of `0` disables the timeout.
16
+ * @returns The underlying `XMLHttpRequest`, which can be used to `abort()` the request.
17
+ */
18
+ const ajaxRequest = (options) => {
19
+ let url = options.url || location.href;
20
+ let { query } = options;
21
+ if (options.disableCache) {
22
+ if (!query)
23
+ query = {};
24
+ query["_"] = Date.now().toString();
25
+ }
26
+ url = helpers.addQuery(url, query);
27
+ const method = options.method ? options.method.toUpperCase() : "GET";
28
+ if (options.data && (method === "GET" || method === "HEAD"))
29
+ throw new Error(`${method} method does not support a request body.`);
30
+ internals.default.detectRequestType(options);
31
+ const prepared = internals.default.prepareRequest(options, options.data);
32
+ const xhr = new XMLHttpRequest();
33
+ xhr.withCredentials = true;
34
+ if (options.timeout === 0 || options.timeout)
35
+ xhr.timeout = options.timeout;
36
+ xhr.onreadystatechange = (_e) => {
37
+ if (xhr.readyState !== XMLHttpRequest.DONE)
38
+ return;
39
+ if (options.success) {
40
+ let responseData = null;
41
+ let responseType = "none";
42
+ const contentType = xhr.getResponseHeader("Content-Type");
43
+ if (xhr.response && contentType) {
44
+ if (contentType.includes("json")) {
45
+ responseType = "json";
46
+ try {
47
+ responseData = JSON.parse(xhr.responseText);
48
+ }
49
+ catch (e) {
50
+ if (options.error)
51
+ options.error(options, e);
52
+ return;
53
+ }
54
+ }
55
+ else if (contentType.includes("text/plain")) {
56
+ responseType = "text";
57
+ responseData = xhr.responseText;
58
+ }
59
+ else if (contentType.includes("text/html")) {
60
+ responseType = "html";
61
+ responseData = xhr.responseText;
62
+ }
63
+ }
64
+ const xhrRef = xhr;
65
+ const headers = {
66
+ get(name) {
67
+ return xhrRef.getResponseHeader(name);
68
+ },
69
+ has(name) {
70
+ return !!xhrRef.getResponseHeader(name);
71
+ },
72
+ forEach(callbackfn, thisArg) {
73
+ xhrRef.getAllResponseHeaders()
74
+ .trim()
75
+ .split(/[\r\n]+/)
76
+ .filter(Boolean)
77
+ .forEach(line => {
78
+ const idx = line.indexOf(": ");
79
+ const key = idx >= 0 ? line.substring(0, idx).toLowerCase() : line.toLowerCase();
80
+ const value = idx >= 0 ? line.substring(idx + 2) : "";
81
+ callbackfn.call(thisArg, value, key, {});
82
+ });
83
+ }
84
+ };
85
+ options.success({
86
+ status: xhr.status,
87
+ url: xhr.responseURL,
88
+ redirected: false,
89
+ type: responseType,
90
+ contentType,
91
+ headers,
92
+ data: responseData,
93
+ state: options.state
94
+ });
95
+ }
96
+ };
97
+ xhr.onabort = (_e) => {
98
+ if (options.error)
99
+ options.error(options, new Error("Request aborted"));
100
+ };
101
+ xhr.onerror = (_e) => {
102
+ if (options.error)
103
+ options.error(options, new Error("Request network error"));
104
+ };
105
+ xhr.ontimeout = (_e) => {
106
+ if (options.error)
107
+ options.error(options, new Error("Request timeout"));
108
+ };
109
+ xhr.open(method, url, true);
110
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
111
+ for (const key in prepared.headers) {
112
+ const value = prepared.headers[key];
113
+ if (!value)
114
+ continue;
115
+ xhr.setRequestHeader(key, value);
116
+ }
117
+ if (method === "GET")
118
+ xhr.send();
119
+ else
120
+ xhr.send(prepared.body);
121
+ return xhr;
122
+ };
123
+
124
+ exports.ajaxRequest = ajaxRequest;
125
+ //# sourceMappingURL=ajax-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ajax-request.js","sources":["../../../source/ajax-request.ts"],"sourcesContent":[null],"names":["helpers.addQuery","internals"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Builds a `URLSearchParams` from query data or a `FormData`.
5
+ *
6
+ * Array values produce repeated keys; `null` values are skipped.
7
+ *
8
+ * @param query Source parameters.
9
+ * @returns The populated `URLSearchParams` (empty when `query` is nullish).
10
+ */
11
+ const createQuery = (query) => {
12
+ const urlParams = new URLSearchParams();
13
+ if (!query)
14
+ return urlParams;
15
+ if (query instanceof FormData) {
16
+ query.forEach((value, key) => {
17
+ if (!key)
18
+ return;
19
+ urlParams.append(key, value.toString());
20
+ });
21
+ }
22
+ else {
23
+ for (const key in query) {
24
+ const val = query[key];
25
+ if (val === null)
26
+ continue;
27
+ if (Array.isArray(val))
28
+ val.forEach(v => urlParams.append(key, v));
29
+ else
30
+ urlParams.append(key, val);
31
+ }
32
+ }
33
+ return urlParams;
34
+ };
35
+ /**
36
+ * Appends query parameters to a URL, choosing `?` or `&` as appropriate.
37
+ *
38
+ * @param url Base URL.
39
+ * @param query Parameters to append; nothing is added when empty.
40
+ * @returns The resulting URL.
41
+ */
42
+ const addQuery = (url, query) => {
43
+ if (query) {
44
+ const urlParams = createQuery(query);
45
+ if (urlParams.size) {
46
+ if (url.indexOf("?") === -1)
47
+ url += "?";
48
+ else
49
+ url += "&";
50
+ url += urlParams.toString();
51
+ }
52
+ }
53
+ return url;
54
+ };
55
+ /**
56
+ * Encodes a `FormData` as an `application/x-www-form-urlencoded` string.
57
+ *
58
+ * @param data Form data to encode.
59
+ * @returns The URL-encoded form string.
60
+ */
61
+ const encodeForm = (data) => {
62
+ const query = createQuery(data);
63
+ return query.toString();
64
+ };
65
+
66
+ exports.addQuery = addQuery;
67
+ exports.createQuery = createQuery;
68
+ exports.encodeForm = encodeForm;
69
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sources":["../../../source/helpers.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAEA;;;;;;;AAOG;AACH,MAAM,WAAW,GAAG,CAAC,KAAmC,KAAI;AAC3D,IAAA,MAAM,SAAS,GAAG,IAAI,eAAe,EAAE;AAEvC,IAAA,IAAI,CAAC,KAAK;AACT,QAAA,OAAO,SAAS;AAEjB,IAAA,IAAI,KAAK,YAAY,QAAQ,EAAE;QAC9B,KAAK,CAAC,OAAO,CAAC,CAAC,KAAyB,EAAE,GAAW,KAAI;AACxD,YAAA,IAAI,CAAC,GAAG;gBACP;YACD,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;AACxC,QAAA,CAAC,CAAC;IACH;SACK;AACJ,QAAA,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE;AACxB,YAAA,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;YACtB,IAAI,GAAG,KAAK,IAAI;gBACf;AAED,YAAA,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;AACrB,gBAAA,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;;AAE1C,gBAAA,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC;QAC5B;IACD;AAEA,IAAA,OAAO,SAAS;AACjB;AAEA;;;;;;AAMG;AACH,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,KAAmC,KAAI;IACrE,IAAI,KAAK,EAAE;AACV,QAAA,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC;AAEpC,QAAA,IAAI,SAAS,CAAC,IAAI,EAAE;YACnB,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE;gBAC1B,GAAG,IAAI,GAAG;;gBAEV,GAAG,IAAI,GAAG;AAEX,YAAA,GAAG,IAAI,SAAS,CAAC,QAAQ,EAAE;QAC5B;IACD;AAEA,IAAA,OAAO,GAAG;AACX;AAEA;;;;;AAKG;AACH,MAAM,UAAU,GAAG,CAAC,IAAc,KAAI;AACrC,IAAA,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC;AAC/B,IAAA,OAAO,KAAK,CAAC,QAAQ,EAAE;AACxB;;;;;;"}