fetchbasequery 0.0.1 → 0.1.0

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
@@ -2,8 +2,21 @@
2
2
 
3
3
  Library for easily make HTTP queries without mass of boilerplate code.
4
4
 
5
- Usage
6
- -----
5
+ ## Installation
6
+
7
+ NPM:
8
+
9
+ ```sh
10
+ npm install fetchbasequery
11
+ ```
12
+
13
+ PNPM:
14
+
15
+ ```sh
16
+ pnpm add fetchbasequery
17
+ ```
18
+
19
+ ## Usage
7
20
 
8
21
  Build base query function:
9
22
 
@@ -11,15 +24,22 @@ Build base query function:
11
24
  import fetchBaseQuery, { baseQuery, encodeBody, accessToken, jsonReply, commonReply } from 'fetchbasequery'
12
25
 
13
26
  const query = fetchBaseQuery({
27
+ // main options ...
14
28
  baseUrl: 'https://api.example.org/v1'
15
29
  baseQuery,
16
30
  logErrors: true,
31
+ // add some defaults for `fetch()` ...
32
+ mode: 'cors',
33
+ credentials: 'include',
34
+ cache: 'reload',
35
+ // middlewares
17
36
  middleware: [
18
37
  encodeBody(),
19
38
  accessToken({
20
39
  getAccessToken: () => localStorage.getItem('access_token'),
21
40
  setAccessToken: (token) => localStorage.setItem('access_token', token),
22
- getUpdatedAccessToken: (response) => response.headers.get('X-Access-Token')
41
+ removeAccessToken: (token) => localStorage.removeItem('access_token'),
42
+ getUpdatedAccessToken: async (response) => response.headers.get('X-Access-Token')
23
43
  }),
24
44
  jsonReply(),
25
45
  commonReply()
@@ -41,6 +61,20 @@ if (success) {
41
61
  }
42
62
  ```
43
63
 
64
+ Using instance of `Request`:
65
+
66
+ ```js
67
+ // for example inside ServiceWorker
68
+ addEventListener('fetch', evt => {
69
+ const { request } = evt
70
+ return evt.respondWith(query({ request }))
71
+ })
72
+ ```
73
+
44
74
  It works fine both on frontend and backend sides. For backend side usage Node.js v18+ is required,
45
75
  because the middleware `baseQuery` based on `Fetch API`. But it can be replaced with your own
46
76
  implementation based on another API (e.g. based on `axios` etc).
77
+
78
+ ## License
79
+
80
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fetchbasequery",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Library for easily make HTTP queries without mass of boilerplate code.",
5
5
  "type": "module",
6
6
  "main": "src/fetchBaseQuery.js",
@@ -8,6 +8,7 @@
8
8
  "scripts": {
9
9
  },
10
10
  "keywords": [
11
+ "fetch", "fetchBaseQuery"
11
12
  ],
12
13
  "author": "h41ku <h41ku.c0de@ya.ru>",
13
14
  "license": "MIT",
package/src/baseQuery.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import responseFromError from './helpers/responseFromError.js'
2
2
  import purge from './helpers/purge.js'
3
3
 
4
- export default async ({ url: endpoint, ...options } = {}) => {
4
+ const moduleName = 'fetchBaseQuery/baseQuery'
5
+
6
+ export default async ({ url: endpoint, request: subject, ...options } = {}) => {
5
7
  const {
6
8
  baseUrl,
7
9
  logErrors,
10
+ debug,
8
11
  ...restOptions
9
12
  } = {
10
13
  logErrors: false,
@@ -13,12 +16,6 @@ export default async ({ url: endpoint, ...options } = {}) => {
13
16
  : undefined,
14
17
  ...options
15
18
  }
16
- // default options for `fetch()`
17
- const defaultFetchOptions = {
18
- method: 'GET',
19
- cache: 'reload',
20
- credentials: 'include',
21
- }
22
19
  // extract options which are known by `fetch()`
23
20
  const { method, headers, body, mode, credentials, cache, redirect,
24
21
  referrer, referrerPolicy, integrity, keepalive, signal } = restOptions
@@ -26,33 +23,41 @@ export default async ({ url: endpoint, ...options } = {}) => {
26
23
  const fetchOptions = purge({ method, headers, body, mode, credentials, cache, redirect,
27
24
  referrer, referrerPolicy, integrity, keepalive, signal })
28
25
  // do request
26
+ let request
29
27
  let response
30
28
  try {
31
- let url, origin, prefix = '/'
32
- if (baseUrl) {
33
- const url = new URL(baseUrl)
34
- origin = url.origin
35
- prefix = url.pathname
29
+ if (endpoint !== undefined) {
30
+ let url, origin, prefix = '/'
31
+ if (baseUrl) {
32
+ const url = new URL(baseUrl)
33
+ origin = url.origin
34
+ prefix = url.pathname
35
+ }
36
+ try {
37
+ url = new URL(endpoint)
38
+ } catch (error) {
39
+ url = new URL(
40
+ /\/$/.test(prefix) && /^\//.test(endpoint)
41
+ ? prefix + endpoint.substring(1)
42
+ : prefix + endpoint,
43
+ origin
44
+ )
45
+ }
46
+ subject = url
36
47
  }
37
- try {
38
- url = new URL(endpoint)
39
- } catch (error) {
40
- url = new URL(
41
- /\/$/.test(prefix) && /^\//.test(endpoint)
42
- ? prefix + endpoint.substring(1)
43
- : prefix + endpoint,
44
- origin
45
- )
48
+ request = new Request(subject, fetchOptions)
49
+ if (debug?.baseQuery) {
50
+ console.log(moduleName, { request })
46
51
  }
47
- response = await fetch(url, {
48
- ...defaultFetchOptions,
49
- ...fetchOptions
50
- })
52
+ response = await fetch(request)
51
53
  } catch (error) {
52
54
  if (logErrors) {
53
- console.error('fetchBaseQuery/query', error)
55
+ console.error(moduleName, error)
54
56
  }
55
- return responseFromError(error)
57
+ response = responseFromError(error)
58
+ }
59
+ if (debug?.baseQuery) {
60
+ console.log(moduleName, { response })
56
61
  }
57
62
  return response
58
63
  }
@@ -1,7 +1,5 @@
1
- export default (error, status = 0) => ({
2
- status,
3
- error: typeof error === 'string'
4
- ? { message: error }
5
- : error,
6
- headers: new Headers()
7
- })
1
+ export default (error) => {
2
+ const response = Response.error()
3
+ response.error = error
4
+ return response
5
+ }
@@ -1,44 +1,61 @@
1
1
  import responseFromError from '../helpers/responseFromError.js'
2
2
  import purge from '../helpers/purge.js'
3
3
 
4
+ const moduleName = 'fetchBaseQuery/middlewares/accessToken'
5
+
4
6
  export default (settings = {}) => query => async ({ payload, headers, ...options } = {}) => {
5
7
  const {
6
8
  getAccessToken,
7
9
  setAccessToken,
10
+ removeAccessToken,
8
11
  isUnauthorized,
9
- getUpdatedAccessToken
12
+ getUpdatedAccessToken,
13
+ getAuthorizationHeaders
10
14
  } = {
11
15
  getAccessToken: (payload) => undefined,
12
16
  setAccessToken: (token) => {},
17
+ removeAccessToken: () => {},
13
18
  isUnauthorized: (response) => response.status === 401,
14
- getUpdatedAccessToken: (response) => undefined,
19
+ getUpdatedAccessToken: async (response) => undefined,
20
+ getAuthorizationHeaders: (accessToken) => (
21
+ accessToken
22
+ ? { Authorization: `bearer ${accessToken}` }
23
+ : {}
24
+ ),
15
25
  ...settings
16
26
  }
17
27
  let response
18
- let status = 0
19
28
  try {
20
- const accessToken = getAccessToken(payload)
21
29
  response = await query({
22
30
  ...options,
23
31
  headers: purge({
24
- ...(accessToken ? { Authorization: `bearer ${accessToken}` } : {}),
32
+ ...(getAuthorizationHeaders(getAccessToken(payload))),
25
33
  ...(headers || {})
26
34
  })
27
35
  })
28
- status = response.status
36
+ } catch (error) {
37
+ if (options.logErrors) {
38
+ console.error(moduleName, error)
39
+ }
40
+ response = responseFromError(error)
41
+ }
42
+ try {
29
43
  if (isUnauthorized(response)) {
30
- setAccessToken(null) // special case to remove access token from storage
44
+ removeAccessToken()
31
45
  } else {
32
- const updatedAccessToken = getUpdatedAccessToken(response)
46
+ const updatedAccessToken = await getUpdatedAccessToken(response)
33
47
  if (updatedAccessToken) {
34
48
  setAccessToken(updatedAccessToken)
35
49
  }
36
50
  }
37
51
  } catch (error) {
38
52
  if (options.logErrors) {
39
- console.error('fetchBaseQuery/middlewares/accessToken', error)
53
+ console.error(moduleName, error)
40
54
  }
41
- return responseFromError(error, status)
55
+ response = responseFromError(error)
56
+ }
57
+ if (options.debug?.accessToken) {
58
+ console.log(moduleName, { response })
42
59
  }
43
60
  return response
44
61
  }
@@ -1,14 +1,23 @@
1
+ const moduleName = 'fetchBaseQuery/middlewares/commonReply'
2
+
1
3
  export default () => query => async (options) => {
2
4
  const { status, data, error, headers } = await query(options)
5
+ let reply
3
6
  if (error) {
4
- return { status, error: true, message: error.message, headers }
7
+ reply = { status, error: true, message: error.message || error, headers }
5
8
  } else if (data) {
6
9
  if (data.success) {
7
- return { ...data, status, headers }
10
+ reply = { ...data, status, headers }
8
11
  } else if (data.error) {
9
- return { status, error: true, message: data.message, headers }
12
+ reply = { status, error: true, message: data.message, headers }
13
+ } else {
14
+ reply = { status, error: true, message: 'Unexpected format of common response body.', headers }
10
15
  }
11
- return { status, error: true, message: 'Unexpected format of common response body.', headers }
16
+ } else {
17
+ reply = { status, headers }
12
18
  }
13
- return { status, headers }
19
+ if (options.debug?.commonReply) {
20
+ console.log(moduleName, { reply })
21
+ }
22
+ return reply
14
23
  }
@@ -2,6 +2,8 @@ import purge from '../helpers/purge.js'
2
2
  import encodeJson from '../helpers/encodeJson.js'
3
3
  import responseFromError from '../helpers/responseFromError.js'
4
4
 
5
+ const moduleName = 'fetchBaseQuery/middlewares/accessToken'
6
+
5
7
  const getEncodedBody = body => (
6
8
  typeof blob === 'string'
7
9
  || body instanceof String
@@ -56,7 +58,6 @@ export default (settings = {}) => (query) => async ({ body, headers, ...options
56
58
  ...settings
57
59
  }
58
60
  let response
59
- let status = 0
60
61
  try {
61
62
  response = await query({
62
63
  ...options,
@@ -70,9 +71,12 @@ export default (settings = {}) => (query) => async ({ body, headers, ...options
70
71
  })
71
72
  } catch (error) {
72
73
  if (options.logErrors) {
73
- console.error('fetchBaseQuery/middlewares/accessToken', error)
74
+ console.error(moduleName, error)
74
75
  }
75
- return responseFromError(error, status)
76
+ response = responseFromError(error)
77
+ }
78
+ if (options.debug?.encodeBody) {
79
+ console.log(moduleName, { response })
76
80
  }
77
81
  return response
78
82
  }
@@ -1,3 +1,5 @@
1
+ const moduleName = 'fetchBaseQuery/middlewares/jsonReply'
2
+
1
3
  export default (settings = {}) => query => async (options = {}) => {
2
4
  const {
3
5
  isJsonBody
@@ -7,19 +9,27 @@ export default (settings = {}) => query => async (options = {}) => {
7
9
  }
8
10
  const response = await query(options)
9
11
  const { status, error, headers } = response
12
+ let reply
10
13
  if (error) {
11
- return { status, error, headers }
12
- }
13
- let data
14
- try {
15
- if (isJsonBody(response) && response.json) {
16
- data = await response.json()
17
- }
18
- } catch (error) {
19
- if (options.logErrors) {
20
- console.error('fetchBaseQuery/middlewares/jsonReply', error)
14
+ reply = { status, error, headers }
15
+ } else {
16
+ let data
17
+ try {
18
+ if (isJsonBody(response)) {
19
+ data = await response.json()
20
+ reply = { status, data, headers }
21
+ } else {
22
+ reply = { status, headers }
23
+ }
24
+ } catch (error) {
25
+ if (options.logErrors) {
26
+ console.error(moduleName, error)
27
+ }
28
+ reply = { status, error, headers }
21
29
  }
22
- return { status, error, headers }
23
30
  }
24
- return { status, data, headers }
31
+ if (options.debug?.jsonReply) {
32
+ console.log(moduleName, { reply })
33
+ }
34
+ return reply
25
35
  }
package/types/index.d.ts CHANGED
@@ -1,5 +1,15 @@
1
- export type fetchInit = {
1
+ export type DebuggingFlags = {
2
+ baseQuery: boolean | undefined,
3
+ encodeBody: boolean | undefined,
4
+ accessToken: boolean | undefined,
5
+ jsonReply: boolean | undefined,
6
+ commonReply: boolean | undefined
7
+ };
8
+
9
+ export type FetchOptions = {
2
10
  logErrors: boolean | undefined,
11
+ url: URL | string | undefined,
12
+ request: Request | undefined,
3
13
  method: string | undefined,
4
14
  headers: object | undefined,
5
15
  body: string
@@ -28,14 +38,27 @@ export type fetchInit = {
28
38
  referrerPolicy: string | undefined,
29
39
  integrity: string | undefined,
30
40
  keepalive: boolean | undefined,
31
- signal: AbortSignal | undefined
41
+ signal: AbortSignal | undefined,
42
+ debug: DebuggingFlags | undefined
32
43
  };
33
44
 
34
- export type queryFn = (options: fetchInit | any | undefined) => Promise<any>;
35
- export type Middleware = (settings: any) => (query: queryFn) => queryFn;
45
+ export type baseQueryFn = (options: FetchOptions | undefined) => Promise<Response>;
46
+ export type queryFn = (options: FetchOptions | undefined) => Promise<any>;
47
+ export type middlewareFn = (settings: any) => (query: queryFn) => queryFn;
36
48
 
37
- export type fetchBaseQueryInit = {
49
+ export type FetchBaseQueryOptions = {
38
50
  baseUrl: URL | string | undefined,
39
- baseQuery: queryFn,
40
- middlewares: Array<Middleware>
51
+ baseQuery: baseQueryFn | undefined,
52
+ middlewares: Array<middlewareFn>
41
53
  };
54
+
55
+ export const baseQuery: baseQueryFn;
56
+
57
+ export const encodeBody: middlewareFn;
58
+ export const accessToken: middlewareFn;
59
+ export const jsonReply: middlewareFn;
60
+ export const commonReply: middlewareFn;
61
+
62
+ const fetchBaseQuery: (options: FetchBaseQueryOptions & FetchOptions) => queryFn;
63
+
64
+ export default fetchBaseQuery;