Skip to content

Commit 272ebd3

Browse files
authored
feat(javascript): use responses and requests cache (#281)
1 parent 2e5e1ad commit 272ebd3

File tree

13 files changed

+162
-40
lines changed

13 files changed

+162
-40
lines changed

.github/.cache_version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4
1+
5

.github/actions/setup/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ runs:
9595
id: js-matrix
9696
shell: bash
9797
run: |
98-
base_changed=${{ steps.diff.outputs.GITHUB_ACTIONS_CHANGED > 0 || steps.diff.outputs.COMMON_SPECS_CHANGED > 0 || steps.diff.outputs.SCRIPTS_CHANGED > 0 || steps.diff.outputs.JS_TEMPLATE_CHANGED > 0 }}
98+
base_changed=${{ steps.diff.outputs.GITHUB_ACTIONS_CHANGED > 0 || steps.diff.outputs.COMMON_SPECS_CHANGED > 0 || steps.diff.outputs.SCRIPTS_CHANGED > 0 || steps.diff.outputs.JS_TEMPLATE_CHANGED > 0 || steps.diff.outputs.JS_COMMON_CHANGED > 0 }}
9999
algoliasearch_changed=${{ steps.diff.outputs.JS_ALGOLIASEARCH_CHANGED > 0 }}
100100
101101
matrix=$(./scripts/ci/create-client-matrix.sh javascript $base_changed ${{ steps.diff.outputs.ORIGIN_BRANCH }})

clients/algoliasearch-client-javascript/bundlesize.config.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,43 @@
22
"files": [
33
{
44
"path": "packages/algoliasearch/dist/algoliasearch.umd.browser.js",
5-
"maxSize": "6.90KB"
5+
"maxSize": "6.95KB"
66
},
77
{
88
"path": "packages/client-abtesting/dist/client-abtesting.umd.browser.js",
9-
"maxSize": "3.65KB"
9+
"maxSize": "3.75KB"
1010
},
1111
{
1212
"path": "packages/client-analytics/dist/client-analytics.umd.browser.js",
13-
"maxSize": "4.20KB"
13+
"maxSize": "4.35KB"
1414
},
1515
{
1616
"path": "packages/client-insights/dist/client-insights.umd.browser.js",
17-
"maxSize": "3.45KB"
17+
"maxSize": "3.60KB"
1818
},
1919
{
2020
"path": "packages/client-personalization/dist/client-personalization.umd.browser.js",
21-
"maxSize": "3.60KB"
21+
"maxSize": "3.75KB"
2222
},
2323
{
2424
"path": "packages/client-query-suggestions/dist/client-query-suggestions.umd.browser.js",
25-
"maxSize": "3.65KB"
25+
"maxSize": "3.80KB"
2626
},
2727
{
2828
"path": "packages/client-search/dist/client-search.umd.browser.js",
29-
"maxSize": "5.65KB"
29+
"maxSize": "5.80KB"
3030
},
3131
{
3232
"path": "packages/client-sources/dist/client-sources.umd.browser.js",
33-
"maxSize": "3.50KB"
33+
"maxSize": "3.60KB"
3434
},
3535
{
3636
"path": "packages/recommend/dist/recommend.umd.browser.js",
37-
"maxSize": "3.55KB"
37+
"maxSize": "3.70KB"
3838
},
3939
{
4040
"path": "packages/client-common/dist/client-common.esm.node.js",
41-
"maxSize": "3.45KB"
41+
"maxSize": "3.65KB"
4242
},
4343
{
4444
"path": "packages/requester-browser-xhr/dist/requester-browser-xhr.esm.node.js",
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
export * from './src/createAuth';
22
export * from './src/createEchoRequester';
33
export * from './src/cache';
4-
export * from './src/createStatefulHost';
5-
export * from './src/createTransporter';
4+
export * from './src/transporter';
65
export * from './src/createUserAgent';
7-
export * from './src/errors';
86
export * from './src/getUserAgent';
9-
export * from './src/helpers';
10-
export * from './src/Response';
11-
export * from './src/stackTrace';
127
export * from './src/types';

clients/algoliasearch-client-javascript/packages/client-common/src/createStatefulHost.ts renamed to clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createStatefulHost.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Host, StatefulHost } from './types';
1+
import type { Host, StatefulHost } from '../types';
22

33
// By default, API Clients at Algolia have expiration delay of 5 mins.
44
// In the JavaScript client, we have 2 mins.

clients/algoliasearch-client-javascript/packages/client-common/src/createTransporter.ts renamed to clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createTransporter.ts

Lines changed: 105 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import { isRetryable, isSuccess } from './Response';
1+
import type {
2+
EndRequest,
3+
Host,
4+
Request,
5+
RequestOptions,
6+
Response,
7+
StackFrame,
8+
TransporterOptions,
9+
Transporter,
10+
} from '../types';
11+
212
import { createStatefulHost } from './createStatefulHost';
313
import { RetryError } from './errors';
414
import {
@@ -8,20 +18,11 @@ import {
818
serializeHeaders,
919
serializeUrl,
1020
} from './helpers';
21+
import { isRetryable, isSuccess } from './responses';
1122
import {
1223
stackTraceWithoutCredentials,
1324
stackFrameWithoutCredentials,
1425
} from './stackTrace';
15-
import type {
16-
EndRequest,
17-
Host,
18-
Request,
19-
RequestOptions,
20-
Response,
21-
StackFrame,
22-
TransporterOptions,
23-
Transporter,
24-
} from './types';
2526

2627
type RetryableOptions = {
2728
hosts: Host[];
@@ -36,6 +37,8 @@ export function createTransporter({
3637
userAgent,
3738
timeouts,
3839
requester,
40+
requestsCache,
41+
responsesCache,
3942
}: TransporterOptions): Transporter {
4043
async function createRetryableOptions(
4144
compatibleHosts: Host[]
@@ -213,6 +216,94 @@ export function createTransporter({
213216
return retry([...options.hosts].reverse(), options.getTimeout);
214217
}
215218

219+
function createRequest<TResponse>(
220+
request: Request,
221+
requestOptions: RequestOptions
222+
): Promise<TResponse> {
223+
if (request.method !== 'GET') {
224+
/**
225+
* On write requests, no cache mechanisms are applied, and we
226+
* proxy the request immediately to the requester.
227+
*/
228+
return retryableRequest<TResponse>(request, requestOptions);
229+
}
230+
231+
const createRetryableRequest = (): Promise<TResponse> => {
232+
/**
233+
* Then, we prepare a function factory that contains the construction of
234+
* the retryable request. At this point, we may *not* perform the actual
235+
* request. But we want to have the function factory ready.
236+
*/
237+
return retryableRequest<TResponse>(request, requestOptions);
238+
};
239+
240+
/**
241+
* Once we have the function factory ready, we need to determine of the
242+
* request is "cacheable" - should be cached. Note that, once again,
243+
* the user can force this option.
244+
*/
245+
const cacheable = Boolean(requestOptions.cacheable || request.cacheable);
246+
247+
/**
248+
* If is not "cacheable", we immediatly trigger the retryable request, no
249+
* need to check cache implementations.
250+
*/
251+
if (cacheable !== true) {
252+
return createRetryableRequest();
253+
}
254+
255+
/**
256+
* If the request is "cacheable", we need to first compute the key to ask
257+
* the cache implementations if this request is on progress or if the
258+
* response already exists on the cache.
259+
*/
260+
const key = {
261+
request,
262+
requestOptions,
263+
transporter: {
264+
queryParameters: requestOptions.queryParameters,
265+
headers: requestOptions.headers,
266+
},
267+
};
268+
269+
/**
270+
* With the computed key, we first ask the responses cache
271+
* implemention if this request was been resolved before.
272+
*/
273+
return responsesCache.get(
274+
key,
275+
() => {
276+
/**
277+
* If the request has never resolved before, we actually ask if there
278+
* is a current request with the same key on progress.
279+
*/
280+
return requestsCache.get(key, () =>
281+
/**
282+
* Finally, if there is no request in progress with the same key,
283+
* this `createRetryableRequest()` will actually trigger the
284+
* retryable request.
285+
*/
286+
requestsCache
287+
.set(key, createRetryableRequest())
288+
.then(
289+
(response) => Promise.all([requestsCache.delete(key), response]),
290+
(err) =>
291+
Promise.all([requestsCache.delete(key), Promise.reject(err)])
292+
)
293+
.then(([_, response]) => response)
294+
);
295+
},
296+
{
297+
/**
298+
* Of course, once we get this response back from the server, we
299+
* tell response cache to actually store the received response
300+
* to be used later.
301+
*/
302+
miss: (response) => responsesCache.set(key, response),
303+
}
304+
);
305+
}
306+
216307
return {
217308
hostsCache,
218309
requester,
@@ -221,6 +312,8 @@ export function createTransporter({
221312
baseHeaders,
222313
baseQueryParameters,
223314
hosts,
224-
request: retryableRequest,
315+
request: createRequest,
316+
requestsCache,
317+
responsesCache,
225318
};
226319
}

clients/algoliasearch-client-javascript/packages/client-common/src/errors.ts renamed to clients/algoliasearch-client-javascript/packages/client-common/src/transporter/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Response, StackFrame } from './types';
1+
import type { Response, StackFrame } from '../types';
22

33
class ErrorWithStackTrace extends Error {
44
stackTrace: StackFrame[];

clients/algoliasearch-client-javascript/packages/client-common/src/helpers.ts renamed to clients/algoliasearch-client-javascript/packages/client-common/src/transporter/helpers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { ApiError, DeserializationError } from './errors';
21
import type {
32
Headers,
43
Host,
54
Request,
65
RequestOptions,
76
Response,
87
StackFrame,
9-
} from './types';
8+
} from '../types';
9+
10+
import { ApiError, DeserializationError } from './errors';
1011

1112
export function shuffle<TData>(array: TData[]): TData[] {
1213
const shuffledArray = array;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './createTransporter';
2+
export * from './createStatefulHost';
3+
export * from './errors';
4+
export * from './helpers';
5+
export * from './responses';
6+
export * from './stackTrace';

clients/algoliasearch-client-javascript/packages/client-common/src/Response.ts renamed to clients/algoliasearch-client-javascript/packages/client-common/src/transporter/responses.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Response } from './types';
1+
import type { Response } from '../types';
22

33
export function isNetworkError({
44
isTimedOut,

clients/algoliasearch-client-javascript/packages/client-common/src/stackTrace.ts renamed to clients/algoliasearch-client-javascript/packages/client-common/src/transporter/stackTrace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { StackFrame } from './types';
1+
import type { StackFrame } from '../types';
22

33
export function stackTraceWithoutCredentials(
44
stackTrace: StackFrame[]

clients/algoliasearch-client-javascript/packages/client-common/src/types/Requester.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type Request = {
66
method: Method;
77
path: string;
88
data?: Record<string, any>;
9+
cacheable?: boolean;
910
};
1011

1112
export type EndRequest = {

clients/algoliasearch-client-javascript/packages/client-common/src/types/Transporter.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,19 @@ export type RequestOptions = {
2424
* Custom query parameters for the request. This query parameters are
2525
* going to be merged the transporter query parameters.
2626
*/
27-
queryParameters: QueryParameters;
27+
queryParameters?: QueryParameters;
28+
29+
/**
30+
* Custom data for the request. This data are
31+
* going to be merged the transporter data.
32+
*/
2833
data?: Record<string, any>;
34+
35+
/**
36+
* If the given request should persist on the cache. Keep in mind,
37+
* that some methods may have this option enabled by default.
38+
*/
39+
cacheable?: boolean;
2940
};
3041

3142
export type StackFrame = {
@@ -39,12 +50,12 @@ export type UserAgentOptions = {
3950
/**
4051
* The segment. Usually the integration name.
4152
*/
42-
readonly segment: string;
53+
segment: string;
4354

4455
/**
4556
* The version. Usually the integration version.
4657
*/
47-
readonly version?: string;
58+
version?: string;
4859
};
4960

5061
export type UserAgent = {
@@ -56,7 +67,7 @@ export type UserAgent = {
5667
/**
5768
* Mutates the current user agent ading the given user agent options.
5869
*/
59-
readonly add: (options: UserAgentOptions) => UserAgent;
70+
add: (options: UserAgentOptions) => UserAgent;
6071
};
6172

6273
export type Timeouts = {
@@ -140,6 +151,21 @@ export type Transporter = {
140151
*/
141152
requester: Requester;
142153

154+
/**
155+
* The cache of the requests. When requests are
156+
* `cacheable`, the returned promised persists
157+
* in this cache to shared in similar resquests
158+
* before being resolved.
159+
*/
160+
requestsCache: Cache;
161+
162+
/**
163+
* The cache of the responses. When requests are
164+
* `cacheable`, the returned responses persists
165+
* in this cache to shared in similar resquests.
166+
*/
167+
responsesCache: Cache;
168+
143169
/**
144170
* The timeouts used by the requester. The transporter
145171
* layer may increase this timeouts as defined on the

0 commit comments

Comments
 (0)