piral-fetch 1.4.0-beta.6355 → 1.4.1-beta.6391

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
@@ -83,6 +83,48 @@ const instance = createInstance({
83
83
 
84
84
  **Note**: `piral-fetch` plays nicely together with authentication providers such as `piral-adal`. As such authentication tokens are automatically inserted on requests to the base URL.
85
85
 
86
+ ### Middlewares
87
+
88
+ `piral-fetch` allows you to configure middleware functions which are executed on each `fetch` call. Middleware functions receive the same parameters as `fetch`, plus a `next` function which calls either the next middleware or the actual `fetch` function. The following code shows an exemplary middleware which logs when requests start and finish:
89
+
90
+ ```ts
91
+ const logRequests: FetchMiddleware = async (
92
+ path: string,
93
+ options: FetchOptions,
94
+ next: PiletFetchApiFetch,
95
+ ): Promise<FetchResponse<any>> => {
96
+ try {
97
+ console.log(`Making request to ${path}...`);
98
+ const response = await next(path, options);
99
+ console.log(`Request to ${path} returned status code ${response.code}.`);
100
+ return response;
101
+ } catch (e) {
102
+ console.error(`Request to ${path} threw an error: `, e);
103
+ throw e;
104
+ }
105
+ };
106
+ ```
107
+
108
+ Middlewares must be configured in the Piral instance:
109
+
110
+ ```ts
111
+ const instance = createInstance({
112
+ plugins: [createFetchApi({
113
+ // important part
114
+ middlewares: [
115
+ firstMiddleware,
116
+ secondMiddleware,
117
+ thirdMiddleware,
118
+ logRequests,
119
+ ],
120
+ // ...other options...
121
+ })],
122
+ // ...
123
+ });
124
+ ```
125
+
126
+ Middlewares are invoked in a top-down order. In the above example, this means that `firstMiddleware` is invoked first, then `secondMiddleware`, then `thirdMiddleware`, then `logRequests` and finally the actual `fetch` function.
127
+
86
128
  :::
87
129
 
88
130
  ## License
package/esm/config.d.ts CHANGED
@@ -1,3 +1,14 @@
1
+ import type { FetchOptions, FetchResponse, PiletFetchApiFetch } from './types';
2
+ export interface FetchMiddleware {
3
+ /**
4
+ * A middleware function for the fetch API.
5
+ * @param path The target of the fetch.
6
+ * @param options The options to be used.
7
+ * @param next A function that invokes the next middleware or the final `fetch`.
8
+ * @returns The promise waiting for the response to arrive.
9
+ */
10
+ (path: string, options: FetchOptions, next: PiletFetchApiFetch): Promise<FetchResponse<any>>;
11
+ }
1
12
  export interface FetchConfig {
2
13
  /**
3
14
  * Sets the default request init settings.
@@ -9,4 +20,10 @@ export interface FetchConfig {
9
20
  * @default location.origin
10
21
  */
11
22
  base?: string;
23
+ /**
24
+ * An ordered list of middleware functions which can intercept and transform any request made via `piral-fetch`.
25
+ * Middleware functions are executed in a top-down order for each fetch request.
26
+ * @default []
27
+ */
28
+ middlewares?: Array<FetchMiddleware>;
12
29
  }
package/esm/fetch.js CHANGED
@@ -2,39 +2,56 @@ const headerAccept = 'accept';
2
2
  const headerContentType = 'content-type';
3
3
  const mimeApplicationJson = 'application/json';
4
4
  export function httpFetch(config, path, options = {}) {
5
- const baseInit = config.default || {};
6
- const baseHeaders = baseInit.headers || {};
7
- const baseUrl = config.base || location.origin;
8
- const { method = 'get', body, headers = {}, cache = baseInit.cache, mode = baseInit.mode, result = 'auto', signal } = options;
9
- const json = Array.isArray(body) ||
10
- typeof body === 'number' ||
11
- (typeof body === 'object' && body instanceof FormData === false && body instanceof Blob === false);
12
- const url = new URL(path, baseUrl);
13
- const init = {
14
- ...baseInit,
15
- method,
16
- body: json ? JSON.stringify(body) : body,
17
- headers: {
18
- ...baseHeaders,
19
- ...headers,
20
- },
21
- cache,
22
- mode,
23
- signal,
5
+ // fetcher makes the actual HTTP request.
6
+ // It is used as the last step in the upcoming middleware chain and does *not* call/require next
7
+ // (which is undefined in this case).
8
+ const fetcher = (path, options) => {
9
+ const baseInit = config.default || {};
10
+ const baseHeaders = baseInit.headers || {};
11
+ const baseUrl = config.base || location.origin;
12
+ const { method = 'get', body, headers = {}, cache = baseInit.cache, mode = baseInit.mode, result = 'auto', signal, } = options;
13
+ const json = Array.isArray(body) ||
14
+ typeof body === 'number' ||
15
+ (typeof body === 'object' && body instanceof FormData === false && body instanceof Blob === false);
16
+ const url = new URL(path, baseUrl);
17
+ const init = {
18
+ ...baseInit,
19
+ method,
20
+ body: json ? JSON.stringify(body) : body,
21
+ headers: {
22
+ ...baseHeaders,
23
+ ...headers,
24
+ },
25
+ cache,
26
+ mode,
27
+ signal,
28
+ };
29
+ if (json) {
30
+ init.headers[headerContentType] = 'application/json';
31
+ init.headers[headerAccept] = mimeApplicationJson;
32
+ }
33
+ return fetch(url.href, init).then((res) => {
34
+ const contentType = res.headers.get(headerContentType);
35
+ const json = result === 'json' || (result === 'auto' && !!contentType && contentType.indexOf('json') !== -1);
36
+ const promise = json ? res.json() : res.text();
37
+ return promise.then((body) => ({
38
+ body,
39
+ code: res.status,
40
+ text: res.statusText,
41
+ }));
42
+ });
24
43
  };
25
- if (json) {
26
- init.headers[headerContentType] = 'application/json';
27
- init.headers[headerAccept] = mimeApplicationJson;
28
- }
29
- return fetch(url.href, init).then((res) => {
30
- const contentType = res.headers.get(headerContentType);
31
- const json = result === 'json' || (result === 'auto' && !!contentType && contentType.indexOf('json') !== -1);
32
- const promise = json ? res.json() : res.text();
33
- return promise.then((body) => ({
34
- body,
35
- code: res.status,
36
- text: res.statusText,
37
- }));
44
+ // Prepare the middleware chain. Middlewares are called in a top-down order.
45
+ // Every configured middleware function must receive a `next` function with the same shape
46
+ // as a `fetch` function. `next` invokes the next function in the chain.
47
+ // `fetcher` from above is the last function in the chain and always terminates it.
48
+ const middlewareFns = [...(config.middlewares || []), fetcher];
49
+ let middlewareChain;
50
+ middlewareChain = middlewareFns.map((middleware, i) => {
51
+ const next = (path, options) => middlewareChain[i + 1](path, options);
52
+ const invoke = (path, options) => middleware(path, options, next);
53
+ return invoke;
38
54
  });
55
+ return middlewareChain[0](path, options);
39
56
  }
40
57
  //# sourceMappingURL=fetch.js.map
package/esm/fetch.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAGA,MAAM,YAAY,GAAG,QAAQ,CAAC;AAC9B,MAAM,iBAAiB,GAAG,cAAc,CAAC;AACzC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAE/C,MAAM,UAAU,SAAS,CAAI,MAAmB,EAAE,IAAY,EAAE,UAAwB,EAAE;IACxF,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;IACtC,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC;IAC/C,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC9H,MAAM,IAAI,GACR,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QACnB,OAAO,IAAI,KAAK,QAAQ;QACxB,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,YAAY,QAAQ,KAAK,KAAK,IAAI,IAAI,YAAY,IAAI,KAAK,KAAK,CAAC,CAAC;IACrG,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,IAAI,GAAgB;QACxB,GAAG,QAAQ;QACX,MAAM;QACN,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAiB;QACtD,OAAO,EAAE;YACP,GAAG,WAAW;YACd,GAAG,OAAO;SACX;QACD,KAAK;QACL,IAAI;QACJ,MAAM;KACP,CAAC;IAEF,IAAI,IAAI,EAAE;QACR,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,kBAAkB,CAAC;QACrD,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,mBAAmB,CAAC;KAClD;IAED,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACxC,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,MAAM,KAAK,MAAM,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,WAAW,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7G,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAE/C,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC7B,IAAI;YACJ,IAAI,EAAE,GAAG,CAAC,MAAM;YAChB,IAAI,EAAE,GAAG,CAAC,UAAU;SACrB,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"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"}
package/lib/config.d.ts CHANGED
@@ -1,3 +1,14 @@
1
+ import type { FetchOptions, FetchResponse, PiletFetchApiFetch } from './types';
2
+ export interface FetchMiddleware {
3
+ /**
4
+ * A middleware function for the fetch API.
5
+ * @param path The target of the fetch.
6
+ * @param options The options to be used.
7
+ * @param next A function that invokes the next middleware or the final `fetch`.
8
+ * @returns The promise waiting for the response to arrive.
9
+ */
10
+ (path: string, options: FetchOptions, next: PiletFetchApiFetch): Promise<FetchResponse<any>>;
11
+ }
1
12
  export interface FetchConfig {
2
13
  /**
3
14
  * Sets the default request init settings.
@@ -9,4 +20,10 @@ export interface FetchConfig {
9
20
  * @default location.origin
10
21
  */
11
22
  base?: string;
23
+ /**
24
+ * An ordered list of middleware functions which can intercept and transform any request made via `piral-fetch`.
25
+ * Middleware functions are executed in a top-down order for each fetch request.
26
+ * @default []
27
+ */
28
+ middlewares?: Array<FetchMiddleware>;
12
29
  }
package/lib/fetch.js CHANGED
@@ -5,40 +5,57 @@ const headerAccept = 'accept';
5
5
  const headerContentType = 'content-type';
6
6
  const mimeApplicationJson = 'application/json';
7
7
  function httpFetch(config, path, options = {}) {
8
- const baseInit = config.default || {};
9
- const baseHeaders = baseInit.headers || {};
10
- const baseUrl = config.base || location.origin;
11
- const { method = 'get', body, headers = {}, cache = baseInit.cache, mode = baseInit.mode, result = 'auto', signal } = options;
12
- const json = Array.isArray(body) ||
13
- typeof body === 'number' ||
14
- (typeof body === 'object' && body instanceof FormData === false && body instanceof Blob === false);
15
- const url = new URL(path, baseUrl);
16
- const init = {
17
- ...baseInit,
18
- method,
19
- body: json ? JSON.stringify(body) : body,
20
- headers: {
21
- ...baseHeaders,
22
- ...headers,
23
- },
24
- cache,
25
- mode,
26
- signal,
8
+ // fetcher makes the actual HTTP request.
9
+ // It is used as the last step in the upcoming middleware chain and does *not* call/require next
10
+ // (which is undefined in this case).
11
+ const fetcher = (path, options) => {
12
+ const baseInit = config.default || {};
13
+ const baseHeaders = baseInit.headers || {};
14
+ const baseUrl = config.base || location.origin;
15
+ const { method = 'get', body, headers = {}, cache = baseInit.cache, mode = baseInit.mode, result = 'auto', signal, } = options;
16
+ const json = Array.isArray(body) ||
17
+ typeof body === 'number' ||
18
+ (typeof body === 'object' && body instanceof FormData === false && body instanceof Blob === false);
19
+ const url = new URL(path, baseUrl);
20
+ const init = {
21
+ ...baseInit,
22
+ method,
23
+ body: json ? JSON.stringify(body) : body,
24
+ headers: {
25
+ ...baseHeaders,
26
+ ...headers,
27
+ },
28
+ cache,
29
+ mode,
30
+ signal,
31
+ };
32
+ if (json) {
33
+ init.headers[headerContentType] = 'application/json';
34
+ init.headers[headerAccept] = mimeApplicationJson;
35
+ }
36
+ return fetch(url.href, init).then((res) => {
37
+ const contentType = res.headers.get(headerContentType);
38
+ const json = result === 'json' || (result === 'auto' && !!contentType && contentType.indexOf('json') !== -1);
39
+ const promise = json ? res.json() : res.text();
40
+ return promise.then((body) => ({
41
+ body,
42
+ code: res.status,
43
+ text: res.statusText,
44
+ }));
45
+ });
27
46
  };
28
- if (json) {
29
- init.headers[headerContentType] = 'application/json';
30
- init.headers[headerAccept] = mimeApplicationJson;
31
- }
32
- return fetch(url.href, init).then((res) => {
33
- const contentType = res.headers.get(headerContentType);
34
- const json = result === 'json' || (result === 'auto' && !!contentType && contentType.indexOf('json') !== -1);
35
- const promise = json ? res.json() : res.text();
36
- return promise.then((body) => ({
37
- body,
38
- code: res.status,
39
- text: res.statusText,
40
- }));
47
+ // Prepare the middleware chain. Middlewares are called in a top-down order.
48
+ // Every configured middleware function must receive a `next` function with the same shape
49
+ // as a `fetch` function. `next` invokes the next function in the chain.
50
+ // `fetcher` from above is the last function in the chain and always terminates it.
51
+ const middlewareFns = [...(config.middlewares || []), fetcher];
52
+ let middlewareChain;
53
+ middlewareChain = middlewareFns.map((middleware, i) => {
54
+ const next = (path, options) => middlewareChain[i + 1](path, options);
55
+ const invoke = (path, options) => middleware(path, options, next);
56
+ return invoke;
41
57
  });
58
+ return middlewareChain[0](path, options);
42
59
  }
43
60
  exports.httpFetch = httpFetch;
44
61
  //# sourceMappingURL=fetch.js.map
package/lib/fetch.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":";;;AAGA,MAAM,YAAY,GAAG,QAAQ,CAAC;AAC9B,MAAM,iBAAiB,GAAG,cAAc,CAAC;AACzC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAE/C,SAAgB,SAAS,CAAI,MAAmB,EAAE,IAAY,EAAE,UAAwB,EAAE;IACxF,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;IACtC,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC;IAC/C,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC9H,MAAM,IAAI,GACR,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QACnB,OAAO,IAAI,KAAK,QAAQ;QACxB,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,YAAY,QAAQ,KAAK,KAAK,IAAI,IAAI,YAAY,IAAI,KAAK,KAAK,CAAC,CAAC;IACrG,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,IAAI,GAAgB;QACxB,GAAG,QAAQ;QACX,MAAM;QACN,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAiB;QACtD,OAAO,EAAE;YACP,GAAG,WAAW;YACd,GAAG,OAAO;SACX;QACD,KAAK;QACL,IAAI;QACJ,MAAM;KACP,CAAC;IAEF,IAAI,IAAI,EAAE;QACR,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,kBAAkB,CAAC;QACrD,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,mBAAmB,CAAC;KAClD;IAED,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACxC,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,MAAM,KAAK,MAAM,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,WAAW,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7G,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAE/C,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC7B,IAAI;YACJ,IAAI,EAAE,GAAG,CAAC,MAAM;YAChB,IAAI,EAAE,GAAG,CAAC,UAAU;SACrB,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;AACL,CAAC;AAvCD,8BAuCC"}
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "piral-fetch",
3
- "version": "1.4.0-beta.6355",
3
+ "version": "1.4.1-beta.6391",
4
4
  "description": "Plugin for standardizing fetch in Piral.",
5
5
  "keywords": [
6
6
  "piral",
@@ -65,8 +65,8 @@
65
65
  "@types/express": "^4.11.1",
66
66
  "cors": "^2.8.5",
67
67
  "express": "^4.17.1",
68
- "piral-core": "1.4.0-beta.6355",
68
+ "piral-core": "1.4.1-beta.6391",
69
69
  "whatwg-fetch": "^3.0.0"
70
70
  },
71
- "gitHead": "4ee547e3b136e8f82ab4a4e90b1fee23d47c05e8"
71
+ "gitHead": "a486c7752bb63d5f00f41d6660d60846b3f66f33"
72
72
  }
@@ -1 +1 @@
1
- var piralFetch=(()=>{var y=Object.defineProperty;var j=e=>y(e,"__esModule",{value:!0});var A=(e,s)=>{j(e);for(var r in s)y(e,r,{get:s[r],enumerable:!0})};var R={};A(R,{createFetchApi:()=>O,httpFetch:()=>m});var T="accept",g="content-type",C="application/json";function m(e,s,r={}){let o=e.default||{},a=o.headers||{},h=e.base||location.origin,{method:n="get",body:t,headers:p={},cache:c=o.cache,mode:F=o.mode,result:d="auto",signal:P}=r,u=Array.isArray(t)||typeof t=="number"||typeof t=="object"&&!(t instanceof FormData)&&!(t instanceof Blob),b=new URL(s,h),f={...o,method:n,body:u?JSON.stringify(t):t,headers:{...a,...p},cache:c,mode:F,signal:P};return u&&(f.headers[g]="application/json",f.headers[T]=C),fetch(b.href,f).then(i=>{let l=i.headers.get(g);return(d==="json"||d==="auto"&&!!l&&l.indexOf("json")!==-1?i.json():i.text()).then(x=>({body:x,code:i.status,text:i.statusText}))})}function O(e={}){return s=>({fetch(r,o={}){let a=o.headers||{},h=[];return s.emit("before-fetch",{headers:a,method:o.method||"GET",target:r,setHeaders(n){n&&h.push(n)}}),Promise.all(h).then(n=>{let t=n.reduce((p,c)=>typeof c=="object"&&c?{...p,...c}:p,a);return{...o,headers:t}}).then(n=>m(e,r,n))}})}return R;})();
1
+ var piralFetch=(()=>{var P=Object.defineProperty;var O=e=>P(e,"__esModule",{value:!0});var R=(e,c)=>{O(e);for(var n in c)P(e,n,{get:c[n],enumerable:!0})};var v={};R(v,{createFetchApi:()=>k,httpFetch:()=>u});var H="accept",A="content-type",I="application/json";function u(e,c,n={}){let a=(t,h)=>{let o=e.default||{},s=o.headers||{},p=e.base||location.origin,{method:f="get",body:r,headers:x={},cache:b=o.cache,mode:j=o.mode,result:F="auto",signal:T}=h,y=Array.isArray(r)||typeof r=="number"||typeof r=="object"&&!(r instanceof FormData)&&!(r instanceof Blob),w=new URL(t,p),l={...o,method:f,body:y?JSON.stringify(r):r,headers:{...s,...x},cache:b,mode:j,signal:T};return y&&(l.headers[A]="application/json",l.headers[H]=I),fetch(w.href,l).then(m=>{let g=m.headers.get(A);return(F==="json"||F==="auto"&&!!g&&g.indexOf("json")!==-1?m.json():m.text()).then(C=>({body:C,code:m.status,text:m.statusText}))})},d=[...e.middlewares||[],a],i;return i=d.map((t,h)=>{let o=(p,f)=>i[h+1](p,f);return(p,f)=>t(p,f,o)}),i[0](c,n)}function k(e={}){return c=>({fetch(n,a={}){let d=a.headers||{},i=[];return c.emit("before-fetch",{headers:d,method:a.method||"GET",target:n,setHeaders(t){t&&i.push(t)}}),Promise.all(i).then(t=>{let h=t.reduce((o,s)=>typeof s=="object"&&s?{...o,...s}:o,d);return{...a,headers:h}}).then(t=>u(e,n,t))}})}return v;})();
package/src/config.ts CHANGED
@@ -1,3 +1,16 @@
1
+ import type { FetchOptions, FetchResponse, PiletFetchApiFetch } from './types';
2
+
3
+ export interface FetchMiddleware {
4
+ /**
5
+ * A middleware function for the fetch API.
6
+ * @param path The target of the fetch.
7
+ * @param options The options to be used.
8
+ * @param next A function that invokes the next middleware or the final `fetch`.
9
+ * @returns The promise waiting for the response to arrive.
10
+ */
11
+ (path: string, options: FetchOptions, next: PiletFetchApiFetch): Promise<FetchResponse<any>>;
12
+ }
13
+
1
14
  export interface FetchConfig {
2
15
  /**
3
16
  * Sets the default request init settings.
@@ -9,4 +22,10 @@ export interface FetchConfig {
9
22
  * @default location.origin
10
23
  */
11
24
  base?: string;
25
+ /**
26
+ * An ordered list of middleware functions which can intercept and transform any request made via `piral-fetch`.
27
+ * Middleware functions are executed in a top-down order for each fetch request.
28
+ * @default []
29
+ */
30
+ middlewares?: Array<FetchMiddleware>;
12
31
  }
@@ -83,4 +83,48 @@ describe('Create fetch API Module', () => {
83
83
  const result = response.body;
84
84
  expect(result.substr(0, 5)).toBe(`<?xml`);
85
85
  });
86
+
87
+ it('invokes configured middleware function and calls API', async () => {
88
+ const context = { emit: vitest.fn() } as any;
89
+ const middleware = vitest.fn((path, options, next) => next(path, options));
90
+ const { fetch } = createFetchApi({
91
+ base: `http://localhost:${port}`,
92
+ middlewares: [middleware],
93
+ })(context) as any;
94
+ const response = await fetch('json');
95
+ const result = response.body;
96
+ expect(Array.isArray(result)).toBeTruthy();
97
+ expect(result.length).toBe(10);
98
+ expect(result[0]).toBe(1);
99
+ expect(middleware).toHaveBeenCalledOnce();
100
+ });
101
+
102
+ it('invokes middleware functions in top-down order', async () => {
103
+ const context = { emit: vitest.fn() } as any;
104
+ const invocationOrder: Array<number> = [];
105
+ const createMiddleware = (myPosition: number) => (path, options, next) => {
106
+ invocationOrder.push(myPosition);
107
+ return next(path, options);
108
+ };
109
+ const { fetch } = createFetchApi({
110
+ base: `http://localhost:${port}`,
111
+ middlewares: [createMiddleware(1), createMiddleware(2), createMiddleware(3)],
112
+ })(context) as any;
113
+ await fetch('json');
114
+ expect(invocationOrder).toEqual([1, 2, 3]);
115
+ });
116
+
117
+ it('allows middleware functions to terminate middleware chain', async () => {
118
+ const context = { emit: vitest.fn() } as any;
119
+ const expectedResponse = { code: 200, body: 'Terminated by middleware', text: 'Terminated by middleware' };
120
+ const middleware = () => Promise.resolve(expectedResponse);
121
+ const { fetch } = createFetchApi({
122
+ base: `http://localhost:${port}`,
123
+ middlewares: [middleware],
124
+ })(context) as any;
125
+ const globalFetch = vitest.spyOn(global, 'fetch');
126
+ const response = await fetch('json');
127
+ expect(response).toBe(expectedResponse);
128
+ expect(globalFetch).not.toHaveBeenCalled();
129
+ });
86
130
  });
package/src/fetch.ts CHANGED
@@ -1,47 +1,74 @@
1
- import { FetchConfig } from './config';
2
- import { FetchOptions, FetchResponse } from './types';
1
+ import { FetchConfig, FetchMiddleware } from './config';
2
+ import { FetchOptions, FetchResponse, PiletFetchApiFetch } from './types';
3
3
 
4
4
  const headerAccept = 'accept';
5
5
  const headerContentType = 'content-type';
6
6
  const mimeApplicationJson = 'application/json';
7
7
 
8
8
  export function httpFetch<T>(config: FetchConfig, path: string, options: FetchOptions = {}): Promise<FetchResponse<T>> {
9
- const baseInit = config.default || {};
10
- const baseHeaders = baseInit.headers || {};
11
- const baseUrl = config.base || location.origin;
12
- const { method = 'get', body, headers = {}, cache = baseInit.cache, mode = baseInit.mode, result = 'auto', signal } = options;
13
- const json =
14
- Array.isArray(body) ||
15
- typeof body === 'number' ||
16
- (typeof body === 'object' && body instanceof FormData === false && body instanceof Blob === false);
17
- const url = new URL(path, baseUrl);
18
- const init: RequestInit = {
19
- ...baseInit,
20
- method,
21
- body: json ? JSON.stringify(body) : (body as BodyInit),
22
- headers: {
23
- ...baseHeaders,
24
- ...headers,
25
- },
26
- cache,
27
- mode,
28
- signal,
29
- };
9
+ // fetcher makes the actual HTTP request.
10
+ // It is used as the last step in the upcoming middleware chain and does *not* call/require next
11
+ // (which is undefined in this case).
12
+ const fetcher: FetchMiddleware = (path, options) => {
13
+ const baseInit = config.default || {};
14
+ const baseHeaders = baseInit.headers || {};
15
+ const baseUrl = config.base || location.origin;
16
+ const {
17
+ method = 'get',
18
+ body,
19
+ headers = {},
20
+ cache = baseInit.cache,
21
+ mode = baseInit.mode,
22
+ result = 'auto',
23
+ signal,
24
+ } = options;
25
+ const json =
26
+ Array.isArray(body) ||
27
+ typeof body === 'number' ||
28
+ (typeof body === 'object' && body instanceof FormData === false && body instanceof Blob === false);
29
+ const url = new URL(path, baseUrl);
30
+ const init: RequestInit = {
31
+ ...baseInit,
32
+ method,
33
+ body: json ? JSON.stringify(body) : (body as BodyInit),
34
+ headers: {
35
+ ...baseHeaders,
36
+ ...headers,
37
+ },
38
+ cache,
39
+ mode,
40
+ signal,
41
+ };
30
42
 
31
- if (json) {
32
- init.headers[headerContentType] = 'application/json';
33
- init.headers[headerAccept] = mimeApplicationJson;
34
- }
43
+ if (json) {
44
+ init.headers[headerContentType] = 'application/json';
45
+ init.headers[headerAccept] = mimeApplicationJson;
46
+ }
35
47
 
36
- return fetch(url.href, init).then((res) => {
37
- const contentType = res.headers.get(headerContentType);
38
- const json = result === 'json' || (result === 'auto' && !!contentType && contentType.indexOf('json') !== -1);
39
- const promise = json ? res.json() : res.text();
48
+ return fetch(url.href, init).then((res) => {
49
+ const contentType = res.headers.get(headerContentType);
50
+ const json = result === 'json' || (result === 'auto' && !!contentType && contentType.indexOf('json') !== -1);
51
+ const promise = json ? res.json() : res.text();
40
52
 
41
- return promise.then((body) => ({
42
- body,
43
- code: res.status,
44
- text: res.statusText,
45
- }));
53
+ return promise.then((body) => ({
54
+ body,
55
+ code: res.status,
56
+ text: res.statusText,
57
+ }));
58
+ });
59
+ };
60
+
61
+ // Prepare the middleware chain. Middlewares are called in a top-down order.
62
+ // Every configured middleware function must receive a `next` function with the same shape
63
+ // as a `fetch` function. `next` invokes the next function in the chain.
64
+ // `fetcher` from above is the last function in the chain and always terminates it.
65
+ const middlewareFns = [...(config.middlewares || []), fetcher];
66
+ let middlewareChain: Array<PiletFetchApiFetch>;
67
+ middlewareChain = middlewareFns.map((middleware, i) => {
68
+ const next: PiletFetchApiFetch = (path, options) => middlewareChain[i + 1](path, options);
69
+ const invoke: PiletFetchApiFetch = (path, options) => middleware(path, options, next);
70
+ return invoke;
46
71
  });
72
+
73
+ return middlewareChain[0](path, options);
47
74
  }