@clairejs/client 3.4.8 → 3.5.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
@@ -1,6 +1,6 @@
1
1
  ## Change Log
2
2
 
3
- #### 3.4.8:
3
+ #### 3.5.0:
4
4
 
5
5
  - refactor http client
6
6
 
@@ -1,16 +1,11 @@
1
- import { LogHandler } from "@clairejs/core";
2
1
  export interface RequestOptions {
3
2
  noAuthorization?: boolean;
4
3
  withCredentials?: boolean;
5
4
  noErrorHandling?: boolean;
5
+ noCache?: boolean;
6
6
  }
7
7
  export declare abstract class AbstractHttpClient {
8
- protected readonly logger?: LogHandler | undefined;
9
- constructor(logger?: LogHandler | undefined);
10
- protected issuedGetRequests: Record<string, boolean>;
11
- resetCache(pattern: string): void;
12
- protected abstract doGet<T = any>(url: string, headers?: object, options?: RequestOptions): Promise<T | undefined>;
13
- get<T = any>(url: string, headers?: object, options?: RequestOptions): Promise<T | undefined>;
8
+ abstract get<T = any>(url: string, headers?: object, options?: RequestOptions): Promise<T | undefined>;
14
9
  abstract post<T = any, R = any>(url: string, body: R, headers?: object, options?: RequestOptions): Promise<T | undefined>;
15
10
  abstract put<T = any, R = any>(url: string, body: R, headers?: object, options?: RequestOptions): Promise<T | undefined>;
16
11
  abstract delete<T = any>(url: string, headers?: object, options?: RequestOptions): Promise<T | undefined>;
@@ -1,27 +1,2 @@
1
- import { LogLevel } from "@clairejs/core";
2
1
  export class AbstractHttpClient {
3
- logger;
4
- constructor(logger) {
5
- this.logger = logger;
6
- }
7
- issuedGetRequests = {};
8
- /*
9
- Keep track off all get request had been sent and allow decache (using regex pattern) those requests
10
- so next request will not get from cache
11
- */
12
- resetCache(pattern) {
13
- const regex = new RegExp(pattern);
14
- Object.keys(this.issuedGetRequests)
15
- .filter((key) => key.match(regex))
16
- .forEach((key) => (this.issuedGetRequests[key] = false));
17
- }
18
- //-- normally T will be returned or error will throw, but in case undefined is returned, that is because error handler
19
- //-- has intercepted and decided to return nothing
20
- async get(url, headers, options) {
21
- const shouldUseCache = this.issuedGetRequests[url];
22
- this.logger?.log(LogLevel.DEBUG, `[cache:${!!shouldUseCache}] Request: ${url}`);
23
- const result = await this.doGet(url, { ...(!shouldUseCache && { "cache-control": "no-cache" }), ...headers }, options);
24
- this.issuedGetRequests[url] = true;
25
- return result;
26
- }
27
2
  }
@@ -1,5 +1,5 @@
1
1
  import { AbstractModel, Constructor, CreateManyRequestBody, CreateManyResponseBody, DeepPartial, GetManyQueries, GetManyResponseBody, Identifiable, ReturningQueries, UpdateManyBody, UpdateManyQueries, UpdateManyResponse, UpdateRecordsBody } from "@clairejs/core";
2
- import { AbstractHttpClient } from "./AbstractHttpClient";
2
+ import { AbstractHttpClient, RequestOptions } from "./AbstractHttpClient";
3
3
  export declare const stringifyQueries: (queries: Record<string, any>) => string;
4
4
  export declare const removeInstances: <T extends Identifiable>(target: T[], source: T[]) => T[];
5
5
  export declare const mergeInstances: <T extends Identifiable>(model: Constructor<T>, target: readonly T[], source: readonly DeepPartial<T>[] | undefined, syncTarget?: boolean) => T[];
@@ -9,7 +9,7 @@ export declare class CrudApi<T extends AbstractModel> {
9
9
  private dirty;
10
10
  constructor(model: Constructor<T>, httpClient: AbstractHttpClient);
11
11
  protected getEndpointBaseUrl(): string;
12
- getMany(queries?: GetManyQueries<T>, useCache?: boolean): Promise<GetManyResponseBody<T> | undefined>;
12
+ getMany(queries?: GetManyQueries<T>, options?: RequestOptions): Promise<GetManyResponseBody<T> | undefined>;
13
13
  updateMany(body: UpdateManyBody<T>, queries?: UpdateManyQueries<T>): Promise<UpdateManyResponse<T> | undefined>;
14
14
  deleteMany(queries?: UpdateManyQueries<T>): Promise<UpdateManyResponse<T> | undefined>;
15
15
  createMany(body: CreateManyRequestBody<T>): Promise<CreateManyResponseBody<T> | undefined>;
@@ -81,13 +81,8 @@ export class CrudApi {
81
81
  getEndpointBaseUrl() {
82
82
  return `/${this.model.name.toLowerCase()}`;
83
83
  }
84
- async getMany(queries, useCache = true) {
85
- const noCache = !useCache || this.dirty;
86
- const result = await this.httpClient.get(`${this.getEndpointBaseUrl()}?${stringifyQueries(queries || {})}`, noCache
87
- ? {
88
- "cache-control": "no-cache",
89
- }
90
- : undefined);
84
+ async getMany(queries, options) {
85
+ const result = await this.httpClient.get(`${this.getEndpointBaseUrl()}?${stringifyQueries(queries || {})}`, undefined, { ...options, noCache: options?.noCache || this.dirty });
91
86
  this.dirty = false;
92
87
  return result;
93
88
  }
@@ -15,14 +15,16 @@ export declare abstract class DefaultHttpClient extends AbstractHttpClient {
15
15
  protected readonly maxRetryCount: number;
16
16
  protected readonly delayMsBetweenRetry: number;
17
17
  protected readonly storage?: AbstractStorage | undefined;
18
- private readonly api;
18
+ private readonly axiosClient;
19
+ protected issuedGetRequests: Record<string, boolean>;
19
20
  constructor(apiServerUrl: string, logger?: LogHandler | undefined, maxRetryCount?: number, delayMsBetweenRetry?: number, storage?: AbstractStorage | undefined);
21
+ resetCache(pattern: string): void;
20
22
  protected resolveUrl(url: string): Promise<string>;
21
23
  protected abstract getAuthorizationHeader(): Promise<Record<string, string>>;
22
- protected errorHandler<T = any>(_operation: () => Promise<T>, err: any): Promise<T | undefined>;
24
+ protected abstract errorHandler<T = any>(_operation: () => Promise<T>, err: any): Promise<T | undefined>;
23
25
  protected retry<T = any>(apiCall: () => Promise<T>, retryCount?: number): Promise<T | undefined>;
24
26
  protected performRequest<T = any>(data: RequestData): Promise<T | undefined>;
25
- protected doGet<T = any>(url: string, headers?: object, options?: RequestOptions): Promise<T | undefined>;
27
+ get<T = any>(url: string, headers?: object, options?: RequestOptions): Promise<T | undefined>;
26
28
  post<T = any, R = any>(url: string, body: R, headers?: object, options?: RequestOptions): Promise<T | undefined>;
27
29
  put<T = any, R = any>(url: string, body: R, headers?: object, options?: RequestOptions): Promise<T | undefined>;
28
30
  delete<T = any>(url: string, headers?: object, options?: RequestOptions): Promise<T | undefined>;
@@ -7,9 +7,10 @@ export class DefaultHttpClient extends AbstractHttpClient {
7
7
  maxRetryCount;
8
8
  delayMsBetweenRetry;
9
9
  storage;
10
- api = axios.create();
10
+ axiosClient = axios.create();
11
+ issuedGetRequests = {};
11
12
  constructor(apiServerUrl, logger, maxRetryCount = 2, delayMsBetweenRetry = 200, storage) {
12
- super(logger);
13
+ super();
13
14
  this.apiServerUrl = apiServerUrl;
14
15
  this.logger = logger;
15
16
  this.maxRetryCount = maxRetryCount;
@@ -17,7 +18,7 @@ export class DefaultHttpClient extends AbstractHttpClient {
17
18
  this.storage = storage;
18
19
  if (this.storage) {
19
20
  //-- setup axios cache
20
- this.api.interceptors.request.use(async (config) => {
21
+ this.axiosClient.interceptors.request.use(async (config) => {
21
22
  if (config.method === "get") {
22
23
  const cacheKey = config.url || "";
23
24
  const cachedData = await this.storage.getItem(cacheKey);
@@ -33,7 +34,7 @@ export class DefaultHttpClient extends AbstractHttpClient {
33
34
  return config;
34
35
  });
35
36
  // add a response interceptor to cache response data
36
- this.api.interceptors.response.use(async (response) => {
37
+ this.axiosClient.interceptors.response.use(async (response) => {
37
38
  if (response.config.method === "get") {
38
39
  const cacheKey = response.config.url || "";
39
40
  const maxAgeMatch = response.headers["Cache-Control"]?.toString()?.match(/max-age=(\d+)/);
@@ -50,12 +51,19 @@ export class DefaultHttpClient extends AbstractHttpClient {
50
51
  });
51
52
  }
52
53
  }
54
+ /*
55
+ Keep track off all get request had been sent and allow decache (using regex pattern) those requests
56
+ so next request will not get from cache
57
+ */
58
+ resetCache(pattern) {
59
+ const regex = new RegExp(pattern);
60
+ Object.keys(this.issuedGetRequests)
61
+ .filter((key) => key.match(regex))
62
+ .forEach((key) => (this.issuedGetRequests[key] = false));
63
+ }
53
64
  async resolveUrl(url) {
54
65
  return this.apiServerUrl + url;
55
66
  }
56
- async errorHandler(_operation, err) {
57
- throw err;
58
- }
59
67
  async retry(apiCall, retryCount = 0) {
60
68
  try {
61
69
  return await apiCall();
@@ -77,13 +85,14 @@ export class DefaultHttpClient extends AbstractHttpClient {
77
85
  const finalUrl = await this.resolveUrl(data.url);
78
86
  const operation = async () => {
79
87
  const authHeader = !data.options?.noAuthorization ? await this.getAuthorizationHeader() : {};
80
- const result = await this.api({
88
+ const result = await this.axiosClient({
81
89
  method: data.method,
82
90
  url: finalUrl,
83
91
  data: data.body,
84
92
  headers: {
85
93
  ...data.headers,
86
94
  ...authHeader,
95
+ ...(data.options?.noCache && { "Cache-Control": "no-cache" }),
87
96
  },
88
97
  withCredentials: data.options?.withCredentials,
89
98
  });
@@ -99,8 +108,17 @@ export class DefaultHttpClient extends AbstractHttpClient {
99
108
  return await this.errorHandler(operation, err.response?.data || Errors.HTTP_REQUEST_ERROR());
100
109
  }
101
110
  }
102
- async doGet(url, headers, options) {
103
- return await this.performRequest({ method: "GET", url, headers, options });
111
+ async get(url, headers, options) {
112
+ const noCache = !!options?.noCache || !this.issuedGetRequests[url];
113
+ this.logger?.log(LogLevel.DEBUG, `[cache:${!noCache}] Request: ${url}`);
114
+ const result = await this.performRequest({
115
+ method: "GET",
116
+ url,
117
+ headers,
118
+ options: { ...options, noCache },
119
+ });
120
+ this.issuedGetRequests[url] = true;
121
+ return result;
104
122
  }
105
123
  async post(url, body, headers, options) {
106
124
  return await this.performRequest({
@@ -14,7 +14,8 @@ export declare abstract class RefreshHttpClient extends DefaultHttpClient {
14
14
  private tokenQueue;
15
15
  constructor(apiServerUrl: string, tokenManager: AbstractTokenManager, logger?: LogHandler | undefined, maxRetryCount?: number, delayMsBetweenRetry?: number, storage?: AbstractStorage | undefined);
16
16
  protected getAuthorization(): Promise<string>;
17
- protected abstract getRefreshedAccessToken(): Promise<AccessToken>;
17
+ protected abstract refreshSession(refreshToken: string): Promise<AccessToken | undefined>;
18
+ protected errorHandler<T = any>(operation: () => Promise<T>, err: any): Promise<T | undefined>;
18
19
  protected getAuthorizationHeader(): Promise<Record<string, string>>;
19
20
  protected refreshToken(token?: AccessToken): Promise<void>;
20
21
  }
@@ -31,6 +31,16 @@ export class RefreshHttpClient extends DefaultHttpClient {
31
31
  return accessToken?.token || "";
32
32
  }
33
33
  }
34
+ async errorHandler(operation, err) {
35
+ if (err.name === Errors.TOKEN_EXPIRED().name) {
36
+ return await this.refreshToken()
37
+ .then(operation)
38
+ .catch((err) => {
39
+ throw err.response.data || err;
40
+ });
41
+ }
42
+ throw err;
43
+ }
34
44
  async getAuthorizationHeader() {
35
45
  return { authorization: await this.getAuthorization() };
36
46
  }
@@ -42,14 +52,16 @@ export class RefreshHttpClient extends DefaultHttpClient {
42
52
  });
43
53
  }
44
54
  token = token || (await this.tokenManager.getAccessToken());
45
- //-- call to api server to refresh token
46
55
  if (!token || !token.refreshToken) {
47
56
  //-- there is no refresh token to refresh
48
57
  throw Errors.SESSION_EXPIRED();
49
58
  }
50
59
  try {
51
60
  this.refreshing = true;
52
- token = await this.getRefreshedAccessToken();
61
+ token = await this.refreshSession(token.refreshToken);
62
+ if (!token) {
63
+ throw Errors.SESSION_EXPIRED();
64
+ }
53
65
  await this.tokenManager.setAccessToken(token);
54
66
  this.logger?.log(LogLevel.DEBUG, "Access token was refreshed");
55
67
  }
@@ -59,7 +71,7 @@ export class RefreshHttpClient extends DefaultHttpClient {
59
71
  resolver();
60
72
  }
61
73
  for (const resolver of this.tokenQueue) {
62
- resolver(token.token);
74
+ resolver(token?.token || "");
63
75
  }
64
76
  //-- clear queue
65
77
  this.refreshQueue = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clairejs/client",
3
- "version": "3.4.8",
3
+ "version": "3.5.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",