@elementor/http-client 4.1.0-manual → 4.2.0-839
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/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +74 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +72 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/http.ts +93 -2
- package/src/index.ts +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -5,6 +5,7 @@ type HttpResponse<TData, TMeta = Record<string, unknown>> = {
|
|
|
5
5
|
data: TData;
|
|
6
6
|
meta: TMeta;
|
|
7
7
|
};
|
|
8
|
+
declare function registerUrlForCache(partialUrl: string, ttlMs?: number): void;
|
|
8
9
|
declare const httpService: () => AxiosInstance;
|
|
9
10
|
|
|
10
|
-
export { type HttpResponse, httpService };
|
|
11
|
+
export { type HttpResponse, httpService, registerUrlForCache };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ type HttpResponse<TData, TMeta = Record<string, unknown>> = {
|
|
|
5
5
|
data: TData;
|
|
6
6
|
meta: TMeta;
|
|
7
7
|
};
|
|
8
|
+
declare function registerUrlForCache(partialUrl: string, ttlMs?: number): void;
|
|
8
9
|
declare const httpService: () => AxiosInstance;
|
|
9
10
|
|
|
10
|
-
export { type HttpResponse, httpService };
|
|
11
|
+
export { type HttpResponse, httpService, registerUrlForCache };
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AxiosError: () => import_axios2.AxiosError,
|
|
34
|
-
httpService: () => httpService
|
|
34
|
+
httpService: () => httpService,
|
|
35
|
+
registerUrlForCache: () => registerUrlForCache
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(index_exports);
|
|
37
38
|
var import_axios2 = require("axios");
|
|
@@ -46,6 +47,56 @@ var { env } = (0, import_env.parseEnv)("@elementor/http-client");
|
|
|
46
47
|
// src/http.ts
|
|
47
48
|
var MAX_RETRIES = 3;
|
|
48
49
|
var BASE_DELAY_MS = 1e3;
|
|
50
|
+
var CACHE_TTL_MS = 2e4;
|
|
51
|
+
var cache = /* @__PURE__ */ new Map();
|
|
52
|
+
var cacheableUrls = /* @__PURE__ */ new Map();
|
|
53
|
+
function registerUrlForCache(partialUrl, ttlMs = CACHE_TTL_MS) {
|
|
54
|
+
cacheableUrls.set(partialUrl, ttlMs);
|
|
55
|
+
}
|
|
56
|
+
function getUrlCacheTtl(url) {
|
|
57
|
+
for (const [pattern, ttl] of cacheableUrls) {
|
|
58
|
+
if (url.includes(pattern)) {
|
|
59
|
+
return ttl;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function getCacheKey(config) {
|
|
65
|
+
const url = config.url ?? "";
|
|
66
|
+
const params = config.params ? JSON.stringify(config.params) : "";
|
|
67
|
+
return `${config.baseURL ?? ""}${url}${params}`;
|
|
68
|
+
}
|
|
69
|
+
function getCachedResponse(config) {
|
|
70
|
+
if (config.method?.toLowerCase() !== "get") {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const url = `${config.baseURL ?? ""}${config.url ?? ""}`;
|
|
74
|
+
const ttl = getUrlCacheTtl(url);
|
|
75
|
+
if (ttl === null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const key = getCacheKey(config);
|
|
79
|
+
const entry = cache.get(key);
|
|
80
|
+
if (!entry) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (Date.now() - entry.timestamp > ttl) {
|
|
84
|
+
cache.delete(key);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return entry.response;
|
|
88
|
+
}
|
|
89
|
+
function setCachedResponse(config, response) {
|
|
90
|
+
if (!config || config.method?.toLowerCase() !== "get") {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const url = `${config.baseURL ?? ""}${config.url ?? ""}`;
|
|
94
|
+
if (getUrlCacheTtl(url) === null) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const key = getCacheKey(config);
|
|
98
|
+
cache.set(key, { response, timestamp: Date.now() });
|
|
99
|
+
}
|
|
49
100
|
var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["get", "head", "options", "put", "delete"]);
|
|
50
101
|
var instance;
|
|
51
102
|
var httpService = () => {
|
|
@@ -58,10 +109,29 @@ var httpService = () => {
|
|
|
58
109
|
...env.headers
|
|
59
110
|
}
|
|
60
111
|
});
|
|
112
|
+
instance.interceptors.request.use((config) => {
|
|
113
|
+
const cachedResponse = getCachedResponse(config);
|
|
114
|
+
if (cachedResponse) {
|
|
115
|
+
const controller = new AbortController();
|
|
116
|
+
controller.abort();
|
|
117
|
+
return {
|
|
118
|
+
...config,
|
|
119
|
+
signal: controller.signal,
|
|
120
|
+
__cachedResponse: cachedResponse
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return config;
|
|
124
|
+
});
|
|
61
125
|
instance.interceptors.response.use(
|
|
62
|
-
(response) =>
|
|
126
|
+
(response) => {
|
|
127
|
+
setCachedResponse(response.config, response);
|
|
128
|
+
return response;
|
|
129
|
+
},
|
|
63
130
|
async (error) => {
|
|
64
131
|
const config = error.config;
|
|
132
|
+
if (config?.__cachedResponse) {
|
|
133
|
+
return config.__cachedResponse;
|
|
134
|
+
}
|
|
65
135
|
if (!config || !shouldRetry(error)) {
|
|
66
136
|
return Promise.reject(error);
|
|
67
137
|
}
|
|
@@ -103,6 +173,7 @@ function sleep(ms) {
|
|
|
103
173
|
// Annotate the CommonJS export names for ESM import in node:
|
|
104
174
|
0 && (module.exports = {
|
|
105
175
|
AxiosError,
|
|
106
|
-
httpService
|
|
176
|
+
httpService,
|
|
177
|
+
registerUrlForCache
|
|
107
178
|
});
|
|
108
179
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/http.ts","../src/env.ts"],"sourcesContent":["export { type AxiosResponse, AxiosError } from 'axios';\nexport { type HttpResponse, httpService } from './http';\n","import axios, { type AxiosError, type AxiosInstance } from 'axios';\n\nimport { env } from './env';\n\nexport type HttpResponse< TData, TMeta = Record< string, unknown > > = {\n\tdata: TData;\n\tmeta: TMeta;\n};\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\n\n// Only idempotent / safe methods are retried. POST and PATCH are excluded because\n// a 500 may arrive after the server partially completed the operation, and retrying\n// could produce duplicates (e.g. create, lock, archive endpoints).\nconst RETRYABLE_METHODS = new Set( [ 'get', 'head', 'options', 'put', 'delete' ] );\n\nlet instance: AxiosInstance;\n\nexport const httpService = () => {\n\tif ( ! instance ) {\n\t\tinstance = axios.create( {\n\t\t\tbaseURL: env.base_url,\n\t\t\ttimeout: 10000,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t...env.headers,\n\t\t\t},\n\t\t} );\n\n\t\tinstance.interceptors.response.use(\n\t\t\t( response ) => response,\n\t\t\tasync ( error: AxiosError ) => {\n\t\t\t\tconst config = error.config as typeof error.config & {\n\t\t\t\t\t__retryCount?: number;\n\t\t\t\t\t__baseTimeout?: number;\n\t\t\t\t};\n\n\t\t\t\tif ( ! config || ! shouldRetry( error ) ) {\n\t\t\t\t\treturn Promise.reject( error );\n\t\t\t\t}\n\n\t\t\t\tconst retryCount = config.__retryCount ?? 0;\n\n\t\t\t\tif ( retryCount >= MAX_RETRIES ) {\n\t\t\t\t\treturn Promise.reject( error );\n\t\t\t\t}\n\n\t\t\t\tconst baseDelay = BASE_DELAY_MS * Math.pow( 2, retryCount );\n\t\t\t\tconst jitter = Math.random() * BASE_DELAY_MS * 0.1;\n\t\t\t\tawait sleep( baseDelay + jitter );\n\n\t\t\t\tconst baseTimeout = config.__baseTimeout ?? config.timeout ?? 10000;\n\n\t\t\t\treturn instance( {\n\t\t\t\t\t...config,\n\t\t\t\t\t__retryCount: retryCount + 1,\n\t\t\t\t\t__baseTimeout: baseTimeout,\n\t\t\t\t\ttimeout: baseTimeout * ( retryCount + 2 ),\n\t\t\t\t} as typeof config );\n\t\t\t}\n\t\t);\n\t}\n\n\treturn instance;\n};\n\nfunction shouldRetry( error: AxiosError ): boolean {\n\tconst method = error.config?.method?.toLowerCase();\n\tif ( method && ! RETRYABLE_METHODS.has( method ) ) {\n\t\treturn false;\n\t}\n\n\tif ( ! error.response ) {\n\t\treturn true;\n\t}\n\t// 429 Too Many Requests: transient rate-limiting, backoff helps\n\tif ( error.response.status === 429 ) {\n\t\treturn true;\n\t}\n\treturn error.response.status >= 500;\n}\n\nfunction sleep( ms: number ): Promise< void > {\n\treturn new Promise( ( resolve ) => setTimeout( resolve, ms ) );\n}\n","import { parseEnv } from '@elementor/env';\n\nexport const { env } = parseEnv< {\n\tbase_url: string;\n\theaders: Record< string, string >;\n} >( '@elementor/http-client' );\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,gBAA+C;;;ACA/C,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/http.ts","../src/env.ts"],"sourcesContent":["export { type AxiosResponse, AxiosError } from 'axios';\nexport { type HttpResponse, httpService, registerUrlForCache } from './http';\n","import axios, { type AxiosError, type AxiosInstance, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';\n\nimport { env } from './env';\n\nexport type HttpResponse< TData, TMeta = Record< string, unknown > > = {\n\tdata: TData;\n\tmeta: TMeta;\n};\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\nconst CACHE_TTL_MS = 20_000;\n\ntype CacheEntry = {\n\tresponse: AxiosResponse;\n\ttimestamp: number;\n};\n\nconst cache = new Map< string, CacheEntry >();\nconst cacheableUrls = new Map< string, number >();\n\nexport function registerUrlForCache( partialUrl: string, ttlMs: number = CACHE_TTL_MS ): void {\n\tcacheableUrls.set( partialUrl, ttlMs );\n}\n\nfunction getUrlCacheTtl( url: string ): number | null {\n\tfor ( const [ pattern, ttl ] of cacheableUrls ) {\n\t\tif ( url.includes( pattern ) ) {\n\t\t\treturn ttl;\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction getCacheKey( config: InternalAxiosRequestConfig ): string {\n\tconst url = config.url ?? '';\n\tconst params = config.params ? JSON.stringify( config.params ) : '';\n\treturn `${ config.baseURL ?? '' }${ url }${ params }`;\n}\n\nfunction getCachedResponse( config: InternalAxiosRequestConfig ): AxiosResponse | null {\n\tif ( config.method?.toLowerCase() !== 'get' ) {\n\t\treturn null;\n\t}\n\n\tconst url = `${ config.baseURL ?? '' }${ config.url ?? '' }`;\n\tconst ttl = getUrlCacheTtl( url );\n\tif ( ttl === null ) {\n\t\treturn null;\n\t}\n\n\tconst key = getCacheKey( config );\n\tconst entry = cache.get( key );\n\n\tif ( ! entry ) {\n\t\treturn null;\n\t}\n\n\tif ( Date.now() - entry.timestamp > ttl ) {\n\t\tcache.delete( key );\n\t\treturn null;\n\t}\n\n\treturn entry.response;\n}\n\nfunction setCachedResponse( config: InternalAxiosRequestConfig | undefined, response: AxiosResponse ): void {\n\tif ( ! config || config.method?.toLowerCase() !== 'get' ) {\n\t\treturn;\n\t}\n\n\tconst url = `${ config.baseURL ?? '' }${ config.url ?? '' }`;\n\tif ( getUrlCacheTtl( url ) === null ) {\n\t\treturn;\n\t}\n\n\tconst key = getCacheKey( config );\n\tcache.set( key, { response, timestamp: Date.now() } );\n}\n\n// Only idempotent / safe methods are retried. POST and PATCH are excluded because\n// a 500 may arrive after the server partially completed the operation, and retrying\n// could produce duplicates (e.g. create, lock, archive endpoints).\nconst RETRYABLE_METHODS = new Set( [ 'get', 'head', 'options', 'put', 'delete' ] );\n\nlet instance: AxiosInstance;\n\nexport const httpService = () => {\n\tif ( ! instance ) {\n\t\tinstance = axios.create( {\n\t\t\tbaseURL: env.base_url,\n\t\t\ttimeout: 10000,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t...env.headers,\n\t\t\t},\n\t\t} );\n\n\t\tinstance.interceptors.request.use( ( config ) => {\n\t\t\tconst cachedResponse = getCachedResponse( config );\n\t\t\tif ( cachedResponse ) {\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tcontroller.abort();\n\t\t\t\treturn {\n\t\t\t\t\t...config,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t__cachedResponse: cachedResponse,\n\t\t\t\t} as typeof config;\n\t\t\t}\n\t\t\treturn config;\n\t\t} );\n\n\t\tinstance.interceptors.response.use(\n\t\t\t( response ) => {\n\t\t\t\tsetCachedResponse( response.config, response );\n\t\t\t\treturn response;\n\t\t\t},\n\t\t\tasync ( error: AxiosError ) => {\n\t\t\t\tconst config = error.config as typeof error.config & {\n\t\t\t\t\t__cachedResponse?: AxiosResponse;\n\t\t\t\t\t__retryCount?: number;\n\t\t\t\t\t__baseTimeout?: number;\n\t\t\t\t};\n\n\t\t\t\tif ( config?.__cachedResponse ) {\n\t\t\t\t\treturn config.__cachedResponse;\n\t\t\t\t}\n\n\t\t\t\tif ( ! config || ! shouldRetry( error ) ) {\n\t\t\t\t\treturn Promise.reject( error );\n\t\t\t\t}\n\n\t\t\t\tconst retryCount = config.__retryCount ?? 0;\n\n\t\t\t\tif ( retryCount >= MAX_RETRIES ) {\n\t\t\t\t\treturn Promise.reject( error );\n\t\t\t\t}\n\n\t\t\t\tconst baseDelay = BASE_DELAY_MS * Math.pow( 2, retryCount );\n\t\t\t\tconst jitter = Math.random() * BASE_DELAY_MS * 0.1;\n\t\t\t\tawait sleep( baseDelay + jitter );\n\n\t\t\t\tconst baseTimeout = config.__baseTimeout ?? config.timeout ?? 10000;\n\n\t\t\t\treturn instance( {\n\t\t\t\t\t...config,\n\t\t\t\t\t__retryCount: retryCount + 1,\n\t\t\t\t\t__baseTimeout: baseTimeout,\n\t\t\t\t\ttimeout: baseTimeout * ( retryCount + 2 ),\n\t\t\t\t} as typeof config );\n\t\t\t}\n\t\t);\n\t}\n\n\treturn instance;\n};\n\nfunction shouldRetry( error: AxiosError ): boolean {\n\tconst method = error.config?.method?.toLowerCase();\n\tif ( method && ! RETRYABLE_METHODS.has( method ) ) {\n\t\treturn false;\n\t}\n\n\tif ( ! error.response ) {\n\t\treturn true;\n\t}\n\t// 429 Too Many Requests: transient rate-limiting, backoff helps\n\tif ( error.response.status === 429 ) {\n\t\treturn true;\n\t}\n\treturn error.response.status >= 500;\n}\n\nfunction sleep( ms: number ): Promise< void > {\n\treturn new Promise( ( resolve ) => setTimeout( resolve, ms ) );\n}\n","import { parseEnv } from '@elementor/env';\n\nexport const { env } = parseEnv< {\n\tbase_url: string;\n\theaders: Record< string, string >;\n} >( '@elementor/http-client' );\n"],"mappings":"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","names":["import_axios","axios"]}
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,56 @@ var { env } = parseEnv("@elementor/http-client");
|
|
|
11
11
|
// src/http.ts
|
|
12
12
|
var MAX_RETRIES = 3;
|
|
13
13
|
var BASE_DELAY_MS = 1e3;
|
|
14
|
+
var CACHE_TTL_MS = 2e4;
|
|
15
|
+
var cache = /* @__PURE__ */ new Map();
|
|
16
|
+
var cacheableUrls = /* @__PURE__ */ new Map();
|
|
17
|
+
function registerUrlForCache(partialUrl, ttlMs = CACHE_TTL_MS) {
|
|
18
|
+
cacheableUrls.set(partialUrl, ttlMs);
|
|
19
|
+
}
|
|
20
|
+
function getUrlCacheTtl(url) {
|
|
21
|
+
for (const [pattern, ttl] of cacheableUrls) {
|
|
22
|
+
if (url.includes(pattern)) {
|
|
23
|
+
return ttl;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function getCacheKey(config) {
|
|
29
|
+
const url = config.url ?? "";
|
|
30
|
+
const params = config.params ? JSON.stringify(config.params) : "";
|
|
31
|
+
return `${config.baseURL ?? ""}${url}${params}`;
|
|
32
|
+
}
|
|
33
|
+
function getCachedResponse(config) {
|
|
34
|
+
if (config.method?.toLowerCase() !== "get") {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const url = `${config.baseURL ?? ""}${config.url ?? ""}`;
|
|
38
|
+
const ttl = getUrlCacheTtl(url);
|
|
39
|
+
if (ttl === null) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const key = getCacheKey(config);
|
|
43
|
+
const entry = cache.get(key);
|
|
44
|
+
if (!entry) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (Date.now() - entry.timestamp > ttl) {
|
|
48
|
+
cache.delete(key);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return entry.response;
|
|
52
|
+
}
|
|
53
|
+
function setCachedResponse(config, response) {
|
|
54
|
+
if (!config || config.method?.toLowerCase() !== "get") {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const url = `${config.baseURL ?? ""}${config.url ?? ""}`;
|
|
58
|
+
if (getUrlCacheTtl(url) === null) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const key = getCacheKey(config);
|
|
62
|
+
cache.set(key, { response, timestamp: Date.now() });
|
|
63
|
+
}
|
|
14
64
|
var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["get", "head", "options", "put", "delete"]);
|
|
15
65
|
var instance;
|
|
16
66
|
var httpService = () => {
|
|
@@ -23,10 +73,29 @@ var httpService = () => {
|
|
|
23
73
|
...env.headers
|
|
24
74
|
}
|
|
25
75
|
});
|
|
76
|
+
instance.interceptors.request.use((config) => {
|
|
77
|
+
const cachedResponse = getCachedResponse(config);
|
|
78
|
+
if (cachedResponse) {
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
controller.abort();
|
|
81
|
+
return {
|
|
82
|
+
...config,
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
__cachedResponse: cachedResponse
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return config;
|
|
88
|
+
});
|
|
26
89
|
instance.interceptors.response.use(
|
|
27
|
-
(response) =>
|
|
90
|
+
(response) => {
|
|
91
|
+
setCachedResponse(response.config, response);
|
|
92
|
+
return response;
|
|
93
|
+
},
|
|
28
94
|
async (error) => {
|
|
29
95
|
const config = error.config;
|
|
96
|
+
if (config?.__cachedResponse) {
|
|
97
|
+
return config.__cachedResponse;
|
|
98
|
+
}
|
|
30
99
|
if (!config || !shouldRetry(error)) {
|
|
31
100
|
return Promise.reject(error);
|
|
32
101
|
}
|
|
@@ -67,6 +136,7 @@ function sleep(ms) {
|
|
|
67
136
|
}
|
|
68
137
|
export {
|
|
69
138
|
AxiosError,
|
|
70
|
-
httpService
|
|
139
|
+
httpService,
|
|
140
|
+
registerUrlForCache
|
|
71
141
|
};
|
|
72
142
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/http.ts","../src/env.ts"],"sourcesContent":["export { type AxiosResponse, AxiosError } from 'axios';\nexport { type HttpResponse, httpService } from './http';\n","import axios, { type AxiosError, type AxiosInstance } from 'axios';\n\nimport { env } from './env';\n\nexport type HttpResponse< TData, TMeta = Record< string, unknown > > = {\n\tdata: TData;\n\tmeta: TMeta;\n};\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\n\n// Only idempotent / safe methods are retried. POST and PATCH are excluded because\n// a 500 may arrive after the server partially completed the operation, and retrying\n// could produce duplicates (e.g. create, lock, archive endpoints).\nconst RETRYABLE_METHODS = new Set( [ 'get', 'head', 'options', 'put', 'delete' ] );\n\nlet instance: AxiosInstance;\n\nexport const httpService = () => {\n\tif ( ! instance ) {\n\t\tinstance = axios.create( {\n\t\t\tbaseURL: env.base_url,\n\t\t\ttimeout: 10000,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t...env.headers,\n\t\t\t},\n\t\t} );\n\n\t\tinstance.interceptors.response.use(\n\t\t\t( response ) => response,\n\t\t\tasync ( error: AxiosError ) => {\n\t\t\t\tconst config = error.config as typeof error.config & {\n\t\t\t\t\t__retryCount?: number;\n\t\t\t\t\t__baseTimeout?: number;\n\t\t\t\t};\n\n\t\t\t\tif ( ! config || ! shouldRetry( error ) ) {\n\t\t\t\t\treturn Promise.reject( error );\n\t\t\t\t}\n\n\t\t\t\tconst retryCount = config.__retryCount ?? 0;\n\n\t\t\t\tif ( retryCount >= MAX_RETRIES ) {\n\t\t\t\t\treturn Promise.reject( error );\n\t\t\t\t}\n\n\t\t\t\tconst baseDelay = BASE_DELAY_MS * Math.pow( 2, retryCount );\n\t\t\t\tconst jitter = Math.random() * BASE_DELAY_MS * 0.1;\n\t\t\t\tawait sleep( baseDelay + jitter );\n\n\t\t\t\tconst baseTimeout = config.__baseTimeout ?? config.timeout ?? 10000;\n\n\t\t\t\treturn instance( {\n\t\t\t\t\t...config,\n\t\t\t\t\t__retryCount: retryCount + 1,\n\t\t\t\t\t__baseTimeout: baseTimeout,\n\t\t\t\t\ttimeout: baseTimeout * ( retryCount + 2 ),\n\t\t\t\t} as typeof config );\n\t\t\t}\n\t\t);\n\t}\n\n\treturn instance;\n};\n\nfunction shouldRetry( error: AxiosError ): boolean {\n\tconst method = error.config?.method?.toLowerCase();\n\tif ( method && ! RETRYABLE_METHODS.has( method ) ) {\n\t\treturn false;\n\t}\n\n\tif ( ! error.response ) {\n\t\treturn true;\n\t}\n\t// 429 Too Many Requests: transient rate-limiting, backoff helps\n\tif ( error.response.status === 429 ) {\n\t\treturn true;\n\t}\n\treturn error.response.status >= 500;\n}\n\nfunction sleep( ms: number ): Promise< void > {\n\treturn new Promise( ( resolve ) => setTimeout( resolve, ms ) );\n}\n","import { parseEnv } from '@elementor/env';\n\nexport const { env } = parseEnv< {\n\tbase_url: string;\n\theaders: Record< string, string >;\n} >( '@elementor/http-client' );\n"],"mappings":";AAAA,SAA6B,kBAAkB;;;ACA/C,OAAO,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/http.ts","../src/env.ts"],"sourcesContent":["export { type AxiosResponse, AxiosError } from 'axios';\nexport { type HttpResponse, httpService, registerUrlForCache } from './http';\n","import axios, { type AxiosError, type AxiosInstance, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';\n\nimport { env } from './env';\n\nexport type HttpResponse< TData, TMeta = Record< string, unknown > > = {\n\tdata: TData;\n\tmeta: TMeta;\n};\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\nconst CACHE_TTL_MS = 20_000;\n\ntype CacheEntry = {\n\tresponse: AxiosResponse;\n\ttimestamp: number;\n};\n\nconst cache = new Map< string, CacheEntry >();\nconst cacheableUrls = new Map< string, number >();\n\nexport function registerUrlForCache( partialUrl: string, ttlMs: number = CACHE_TTL_MS ): void {\n\tcacheableUrls.set( partialUrl, ttlMs );\n}\n\nfunction getUrlCacheTtl( url: string ): number | null {\n\tfor ( const [ pattern, ttl ] of cacheableUrls ) {\n\t\tif ( url.includes( pattern ) ) {\n\t\t\treturn ttl;\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction getCacheKey( config: InternalAxiosRequestConfig ): string {\n\tconst url = config.url ?? '';\n\tconst params = config.params ? JSON.stringify( config.params ) : '';\n\treturn `${ config.baseURL ?? '' }${ url }${ params }`;\n}\n\nfunction getCachedResponse( config: InternalAxiosRequestConfig ): AxiosResponse | null {\n\tif ( config.method?.toLowerCase() !== 'get' ) {\n\t\treturn null;\n\t}\n\n\tconst url = `${ config.baseURL ?? '' }${ config.url ?? '' }`;\n\tconst ttl = getUrlCacheTtl( url );\n\tif ( ttl === null ) {\n\t\treturn null;\n\t}\n\n\tconst key = getCacheKey( config );\n\tconst entry = cache.get( key );\n\n\tif ( ! entry ) {\n\t\treturn null;\n\t}\n\n\tif ( Date.now() - entry.timestamp > ttl ) {\n\t\tcache.delete( key );\n\t\treturn null;\n\t}\n\n\treturn entry.response;\n}\n\nfunction setCachedResponse( config: InternalAxiosRequestConfig | undefined, response: AxiosResponse ): void {\n\tif ( ! config || config.method?.toLowerCase() !== 'get' ) {\n\t\treturn;\n\t}\n\n\tconst url = `${ config.baseURL ?? '' }${ config.url ?? '' }`;\n\tif ( getUrlCacheTtl( url ) === null ) {\n\t\treturn;\n\t}\n\n\tconst key = getCacheKey( config );\n\tcache.set( key, { response, timestamp: Date.now() } );\n}\n\n// Only idempotent / safe methods are retried. POST and PATCH are excluded because\n// a 500 may arrive after the server partially completed the operation, and retrying\n// could produce duplicates (e.g. create, lock, archive endpoints).\nconst RETRYABLE_METHODS = new Set( [ 'get', 'head', 'options', 'put', 'delete' ] );\n\nlet instance: AxiosInstance;\n\nexport const httpService = () => {\n\tif ( ! instance ) {\n\t\tinstance = axios.create( {\n\t\t\tbaseURL: env.base_url,\n\t\t\ttimeout: 10000,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t...env.headers,\n\t\t\t},\n\t\t} );\n\n\t\tinstance.interceptors.request.use( ( config ) => {\n\t\t\tconst cachedResponse = getCachedResponse( config );\n\t\t\tif ( cachedResponse ) {\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tcontroller.abort();\n\t\t\t\treturn {\n\t\t\t\t\t...config,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t__cachedResponse: cachedResponse,\n\t\t\t\t} as typeof config;\n\t\t\t}\n\t\t\treturn config;\n\t\t} );\n\n\t\tinstance.interceptors.response.use(\n\t\t\t( response ) => {\n\t\t\t\tsetCachedResponse( response.config, response );\n\t\t\t\treturn response;\n\t\t\t},\n\t\t\tasync ( error: AxiosError ) => {\n\t\t\t\tconst config = error.config as typeof error.config & {\n\t\t\t\t\t__cachedResponse?: AxiosResponse;\n\t\t\t\t\t__retryCount?: number;\n\t\t\t\t\t__baseTimeout?: number;\n\t\t\t\t};\n\n\t\t\t\tif ( config?.__cachedResponse ) {\n\t\t\t\t\treturn config.__cachedResponse;\n\t\t\t\t}\n\n\t\t\t\tif ( ! config || ! shouldRetry( error ) ) {\n\t\t\t\t\treturn Promise.reject( error );\n\t\t\t\t}\n\n\t\t\t\tconst retryCount = config.__retryCount ?? 0;\n\n\t\t\t\tif ( retryCount >= MAX_RETRIES ) {\n\t\t\t\t\treturn Promise.reject( error );\n\t\t\t\t}\n\n\t\t\t\tconst baseDelay = BASE_DELAY_MS * Math.pow( 2, retryCount );\n\t\t\t\tconst jitter = Math.random() * BASE_DELAY_MS * 0.1;\n\t\t\t\tawait sleep( baseDelay + jitter );\n\n\t\t\t\tconst baseTimeout = config.__baseTimeout ?? config.timeout ?? 10000;\n\n\t\t\t\treturn instance( {\n\t\t\t\t\t...config,\n\t\t\t\t\t__retryCount: retryCount + 1,\n\t\t\t\t\t__baseTimeout: baseTimeout,\n\t\t\t\t\ttimeout: baseTimeout * ( retryCount + 2 ),\n\t\t\t\t} as typeof config );\n\t\t\t}\n\t\t);\n\t}\n\n\treturn instance;\n};\n\nfunction shouldRetry( error: AxiosError ): boolean {\n\tconst method = error.config?.method?.toLowerCase();\n\tif ( method && ! RETRYABLE_METHODS.has( method ) ) {\n\t\treturn false;\n\t}\n\n\tif ( ! error.response ) {\n\t\treturn true;\n\t}\n\t// 429 Too Many Requests: transient rate-limiting, backoff helps\n\tif ( error.response.status === 429 ) {\n\t\treturn true;\n\t}\n\treturn error.response.status >= 500;\n}\n\nfunction sleep( ms: number ): Promise< void > {\n\treturn new Promise( ( resolve ) => setTimeout( resolve, ms ) );\n}\n","import { parseEnv } from '@elementor/env';\n\nexport const { env } = parseEnv< {\n\tbase_url: string;\n\theaders: Record< string, string >;\n} >( '@elementor/http-client' );\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/http-client",
|
|
3
3
|
"description": "Provides a simple way to make HTTP requests",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.2.0-839",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/env": "4.
|
|
43
|
+
"@elementor/env": "4.2.0-839",
|
|
44
44
|
"axios": "^1.9.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/http.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import axios, { type AxiosError, type AxiosInstance } from 'axios';
|
|
1
|
+
import axios, { type AxiosError, type AxiosInstance, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
|
|
2
2
|
|
|
3
3
|
import { env } from './env';
|
|
4
4
|
|
|
@@ -9,6 +9,75 @@ export type HttpResponse< TData, TMeta = Record< string, unknown > > = {
|
|
|
9
9
|
|
|
10
10
|
const MAX_RETRIES = 3;
|
|
11
11
|
const BASE_DELAY_MS = 1000;
|
|
12
|
+
const CACHE_TTL_MS = 20_000;
|
|
13
|
+
|
|
14
|
+
type CacheEntry = {
|
|
15
|
+
response: AxiosResponse;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const cache = new Map< string, CacheEntry >();
|
|
20
|
+
const cacheableUrls = new Map< string, number >();
|
|
21
|
+
|
|
22
|
+
export function registerUrlForCache( partialUrl: string, ttlMs: number = CACHE_TTL_MS ): void {
|
|
23
|
+
cacheableUrls.set( partialUrl, ttlMs );
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getUrlCacheTtl( url: string ): number | null {
|
|
27
|
+
for ( const [ pattern, ttl ] of cacheableUrls ) {
|
|
28
|
+
if ( url.includes( pattern ) ) {
|
|
29
|
+
return ttl;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getCacheKey( config: InternalAxiosRequestConfig ): string {
|
|
37
|
+
const url = config.url ?? '';
|
|
38
|
+
const params = config.params ? JSON.stringify( config.params ) : '';
|
|
39
|
+
return `${ config.baseURL ?? '' }${ url }${ params }`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getCachedResponse( config: InternalAxiosRequestConfig ): AxiosResponse | null {
|
|
43
|
+
if ( config.method?.toLowerCase() !== 'get' ) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const url = `${ config.baseURL ?? '' }${ config.url ?? '' }`;
|
|
48
|
+
const ttl = getUrlCacheTtl( url );
|
|
49
|
+
if ( ttl === null ) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const key = getCacheKey( config );
|
|
54
|
+
const entry = cache.get( key );
|
|
55
|
+
|
|
56
|
+
if ( ! entry ) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if ( Date.now() - entry.timestamp > ttl ) {
|
|
61
|
+
cache.delete( key );
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return entry.response;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function setCachedResponse( config: InternalAxiosRequestConfig | undefined, response: AxiosResponse ): void {
|
|
69
|
+
if ( ! config || config.method?.toLowerCase() !== 'get' ) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const url = `${ config.baseURL ?? '' }${ config.url ?? '' }`;
|
|
74
|
+
if ( getUrlCacheTtl( url ) === null ) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const key = getCacheKey( config );
|
|
79
|
+
cache.set( key, { response, timestamp: Date.now() } );
|
|
80
|
+
}
|
|
12
81
|
|
|
13
82
|
// Only idempotent / safe methods are retried. POST and PATCH are excluded because
|
|
14
83
|
// a 500 may arrive after the server partially completed the operation, and retrying
|
|
@@ -28,14 +97,36 @@ export const httpService = () => {
|
|
|
28
97
|
},
|
|
29
98
|
} );
|
|
30
99
|
|
|
100
|
+
instance.interceptors.request.use( ( config ) => {
|
|
101
|
+
const cachedResponse = getCachedResponse( config );
|
|
102
|
+
if ( cachedResponse ) {
|
|
103
|
+
const controller = new AbortController();
|
|
104
|
+
controller.abort();
|
|
105
|
+
return {
|
|
106
|
+
...config,
|
|
107
|
+
signal: controller.signal,
|
|
108
|
+
__cachedResponse: cachedResponse,
|
|
109
|
+
} as typeof config;
|
|
110
|
+
}
|
|
111
|
+
return config;
|
|
112
|
+
} );
|
|
113
|
+
|
|
31
114
|
instance.interceptors.response.use(
|
|
32
|
-
( response ) =>
|
|
115
|
+
( response ) => {
|
|
116
|
+
setCachedResponse( response.config, response );
|
|
117
|
+
return response;
|
|
118
|
+
},
|
|
33
119
|
async ( error: AxiosError ) => {
|
|
34
120
|
const config = error.config as typeof error.config & {
|
|
121
|
+
__cachedResponse?: AxiosResponse;
|
|
35
122
|
__retryCount?: number;
|
|
36
123
|
__baseTimeout?: number;
|
|
37
124
|
};
|
|
38
125
|
|
|
126
|
+
if ( config?.__cachedResponse ) {
|
|
127
|
+
return config.__cachedResponse;
|
|
128
|
+
}
|
|
129
|
+
|
|
39
130
|
if ( ! config || ! shouldRetry( error ) ) {
|
|
40
131
|
return Promise.reject( error );
|
|
41
132
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { type AxiosResponse, AxiosError } from 'axios';
|
|
2
|
-
export { type HttpResponse, httpService } from './http';
|
|
2
|
+
export { type HttpResponse, httpService, registerUrlForCache } from './http';
|