@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 +1 -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 +5 -3
- package/dist/api/DefaultHttpClient.js +28 -10
- package/dist/api/RefreshHttpClient.d.ts +2 -1
- package/dist/api/RefreshHttpClient.js +15 -3
- 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
|
-
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
|
-
|
|
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,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.
|
|
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
|
|
103
|
-
|
|
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
|
|
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.
|
|
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
|
|
74
|
+
resolver(token?.token || "");
|
|
63
75
|
}
|
|
64
76
|
//-- clear queue
|
|
65
77
|
this.refreshQueue = [];
|