@clairejs/client 3.4.9 → 3.5.1
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 +2 -1
- package/dist/api/AbstractHttpClient.d.ts +2 -7
- package/dist/api/AbstractHttpClient.js +0 -25
- package/dist/api/CrudApi.d.ts +2 -2
- package/dist/api/CrudApi.js +2 -7
- package/dist/api/DefaultHttpClient.d.ts +4 -2
- package/dist/api/DefaultHttpClient.js +28 -7
- package/dist/api/RefreshHttpClient.js +2 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/api/CrudApi.d.ts
CHANGED
|
@@ -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>,
|
|
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>;
|
package/dist/api/CrudApi.js
CHANGED
|
@@ -81,13 +81,8 @@ export class CrudApi {
|
|
|
81
81
|
getEndpointBaseUrl() {
|
|
82
82
|
return `/${this.model.name.toLowerCase()}`;
|
|
83
83
|
}
|
|
84
|
-
async getMany(queries,
|
|
85
|
-
const
|
|
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
|
|
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
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
|
-
|
|
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
|
-
|
|
10
|
+
axiosClient = axios.create();
|
|
11
|
+
issuedGetRequests = {};
|
|
11
12
|
constructor(apiServerUrl, logger, maxRetryCount = 2, delayMsBetweenRetry = 200, storage) {
|
|
12
|
-
super(
|
|
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.
|
|
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.
|
|
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,6 +51,16 @@ 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
|
}
|
|
@@ -74,13 +85,14 @@ export class DefaultHttpClient extends AbstractHttpClient {
|
|
|
74
85
|
const finalUrl = await this.resolveUrl(data.url);
|
|
75
86
|
const operation = async () => {
|
|
76
87
|
const authHeader = !data.options?.noAuthorization ? await this.getAuthorizationHeader() : {};
|
|
77
|
-
const result = await this.
|
|
88
|
+
const result = await this.axiosClient({
|
|
78
89
|
method: data.method,
|
|
79
90
|
url: finalUrl,
|
|
80
91
|
data: data.body,
|
|
81
92
|
headers: {
|
|
82
93
|
...data.headers,
|
|
83
94
|
...authHeader,
|
|
95
|
+
...(data.options?.noCache && { "Cache-Control": "no-cache" }),
|
|
84
96
|
},
|
|
85
97
|
withCredentials: data.options?.withCredentials,
|
|
86
98
|
});
|
|
@@ -96,8 +108,17 @@ export class DefaultHttpClient extends AbstractHttpClient {
|
|
|
96
108
|
return await this.errorHandler(operation, err.response?.data || Errors.HTTP_REQUEST_ERROR());
|
|
97
109
|
}
|
|
98
110
|
}
|
|
99
|
-
async
|
|
100
|
-
|
|
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;
|
|
101
122
|
}
|
|
102
123
|
async post(url, body, headers, options) {
|
|
103
124
|
return await this.performRequest({
|
|
@@ -33,11 +33,7 @@ export class RefreshHttpClient extends DefaultHttpClient {
|
|
|
33
33
|
}
|
|
34
34
|
async errorHandler(operation, err) {
|
|
35
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
|
-
});
|
|
36
|
+
return await this.refreshToken().then(operation);
|
|
41
37
|
}
|
|
42
38
|
throw err;
|
|
43
39
|
}
|
|
@@ -58,7 +54,7 @@ export class RefreshHttpClient extends DefaultHttpClient {
|
|
|
58
54
|
}
|
|
59
55
|
try {
|
|
60
56
|
this.refreshing = true;
|
|
61
|
-
token = await this.refreshSession(token.refreshToken);
|
|
57
|
+
token = await this.refreshSession(token.refreshToken).catch(() => undefined);
|
|
62
58
|
if (!token) {
|
|
63
59
|
throw Errors.SESSION_EXPIRED();
|
|
64
60
|
}
|