@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 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) => 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,mBAA2D;;;ACA3D,iBAAyB;AAElB,IAAM,EAAE,IAAI,QAAI,qBAGlB,wBAAyB;;;ADI9B,IAAM,cAAc;AACpB,IAAM,gBAAgB;AAKtB,IAAM,oBAAoB,oBAAI,IAAK,CAAE,OAAO,QAAQ,WAAW,OAAO,QAAS,CAAE;AAEjF,IAAI;AAEG,IAAM,cAAc,MAAM;AAChC,MAAK,CAAE,UAAW;AACjB,eAAW,aAAAC,QAAM,OAAQ;AAAA,MACxB,SAAS,IAAI;AAAA,MACb,SAAS;AAAA,MACT,SAAS;AAAA,QACR,gBAAgB;AAAA,QAChB,GAAG,IAAI;AAAA,MACR;AAAA,IACD,CAAE;AAEF,aAAS,aAAa,SAAS;AAAA,MAC9B,CAAE,aAAc;AAAA,MAChB,OAAQ,UAAuB;AAC9B,cAAM,SAAS,MAAM;AAKrB,YAAK,CAAE,UAAU,CAAE,YAAa,KAAM,GAAI;AACzC,iBAAO,QAAQ,OAAQ,KAAM;AAAA,QAC9B;AAEA,cAAM,aAAa,OAAO,gBAAgB;AAE1C,YAAK,cAAc,aAAc;AAChC,iBAAO,QAAQ,OAAQ,KAAM;AAAA,QAC9B;AAEA,cAAM,YAAY,gBAAgB,KAAK,IAAK,GAAG,UAAW;AAC1D,cAAM,SAAS,KAAK,OAAO,IAAI,gBAAgB;AAC/C,cAAM,MAAO,YAAY,MAAO;AAEhC,cAAM,cAAc,OAAO,iBAAiB,OAAO,WAAW;AAE9D,eAAO,SAAU;AAAA,UAChB,GAAG;AAAA,UACH,cAAc,aAAa;AAAA,UAC3B,eAAe;AAAA,UACf,SAAS,eAAgB,aAAa;AAAA,QACvC,CAAmB;AAAA,MACpB;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,YAAa,OAA6B;AAClD,QAAM,SAAS,MAAM,QAAQ,QAAQ,YAAY;AACjD,MAAK,UAAU,CAAE,kBAAkB,IAAK,MAAO,GAAI;AAClD,WAAO;AAAA,EACR;AAEA,MAAK,CAAE,MAAM,UAAW;AACvB,WAAO;AAAA,EACR;AAEA,MAAK,MAAM,SAAS,WAAW,KAAM;AACpC,WAAO;AAAA,EACR;AACA,SAAO,MAAM,SAAS,UAAU;AACjC;AAEA,SAAS,MAAO,IAA8B;AAC7C,SAAO,IAAI,QAAS,CAAE,YAAa,WAAY,SAAS,EAAG,CAAE;AAC9D;","names":["import_axios","axios"]}
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) => 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
@@ -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,WAAoD;;;ACA3D,SAAS,gBAAgB;AAElB,IAAM,EAAE,IAAI,IAAI,SAGlB,wBAAyB;;;ADI9B,IAAM,cAAc;AACpB,IAAM,gBAAgB;AAKtB,IAAM,oBAAoB,oBAAI,IAAK,CAAE,OAAO,QAAQ,WAAW,OAAO,QAAS,CAAE;AAEjF,IAAI;AAEG,IAAM,cAAc,MAAM;AAChC,MAAK,CAAE,UAAW;AACjB,eAAW,MAAM,OAAQ;AAAA,MACxB,SAAS,IAAI;AAAA,MACb,SAAS;AAAA,MACT,SAAS;AAAA,QACR,gBAAgB;AAAA,QAChB,GAAG,IAAI;AAAA,MACR;AAAA,IACD,CAAE;AAEF,aAAS,aAAa,SAAS;AAAA,MAC9B,CAAE,aAAc;AAAA,MAChB,OAAQ,UAAuB;AAC9B,cAAM,SAAS,MAAM;AAKrB,YAAK,CAAE,UAAU,CAAE,YAAa,KAAM,GAAI;AACzC,iBAAO,QAAQ,OAAQ,KAAM;AAAA,QAC9B;AAEA,cAAM,aAAa,OAAO,gBAAgB;AAE1C,YAAK,cAAc,aAAc;AAChC,iBAAO,QAAQ,OAAQ,KAAM;AAAA,QAC9B;AAEA,cAAM,YAAY,gBAAgB,KAAK,IAAK,GAAG,UAAW;AAC1D,cAAM,SAAS,KAAK,OAAO,IAAI,gBAAgB;AAC/C,cAAM,MAAO,YAAY,MAAO;AAEhC,cAAM,cAAc,OAAO,iBAAiB,OAAO,WAAW;AAE9D,eAAO,SAAU;AAAA,UAChB,GAAG;AAAA,UACH,cAAc,aAAa;AAAA,UAC3B,eAAe;AAAA,UACf,SAAS,eAAgB,aAAa;AAAA,QACvC,CAAmB;AAAA,MACpB;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,YAAa,OAA6B;AAClD,QAAM,SAAS,MAAM,QAAQ,QAAQ,YAAY;AACjD,MAAK,UAAU,CAAE,kBAAkB,IAAK,MAAO,GAAI;AAClD,WAAO;AAAA,EACR;AAEA,MAAK,CAAE,MAAM,UAAW;AACvB,WAAO;AAAA,EACR;AAEA,MAAK,MAAM,SAAS,WAAW,KAAM;AACpC,WAAO;AAAA,EACR;AACA,SAAO,MAAM,SAAS,UAAU;AACjC;AAEA,SAAS,MAAO,IAA8B;AAC7C,SAAO,IAAI,QAAS,CAAE,YAAa,WAAY,SAAS,EAAG,CAAE;AAC9D;","names":[]}
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.1.0-manual",
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.1.0-manual",
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 ) => 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';