Skip to content

Commit

Permalink
feat(javascript): use responses and requests cache (#281)
Browse files Browse the repository at this point in the history
  • Loading branch information
shortcuts authored Mar 24, 2022
1 parent 2e5e1ad commit 272ebd3
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .github/.cache_version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4
5
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ runs:
id: js-matrix
shell: bash
run: |
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 }}
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 }}
algoliasearch_changed=${{ steps.diff.outputs.JS_ALGOLIASEARCH_CHANGED > 0 }}
matrix=$(./scripts/ci/create-client-matrix.sh javascript $base_changed ${{ steps.diff.outputs.ORIGIN_BRANCH }})
Expand Down
20 changes: 10 additions & 10 deletions clients/algoliasearch-client-javascript/bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,43 @@
"files": [
{
"path": "packages/algoliasearch/dist/algoliasearch.umd.browser.js",
"maxSize": "6.90KB"
"maxSize": "6.95KB"
},
{
"path": "packages/client-abtesting/dist/client-abtesting.umd.browser.js",
"maxSize": "3.65KB"
"maxSize": "3.75KB"
},
{
"path": "packages/client-analytics/dist/client-analytics.umd.browser.js",
"maxSize": "4.20KB"
"maxSize": "4.35KB"
},
{
"path": "packages/client-insights/dist/client-insights.umd.browser.js",
"maxSize": "3.45KB"
"maxSize": "3.60KB"
},
{
"path": "packages/client-personalization/dist/client-personalization.umd.browser.js",
"maxSize": "3.60KB"
"maxSize": "3.75KB"
},
{
"path": "packages/client-query-suggestions/dist/client-query-suggestions.umd.browser.js",
"maxSize": "3.65KB"
"maxSize": "3.80KB"
},
{
"path": "packages/client-search/dist/client-search.umd.browser.js",
"maxSize": "5.65KB"
"maxSize": "5.80KB"
},
{
"path": "packages/client-sources/dist/client-sources.umd.browser.js",
"maxSize": "3.50KB"
"maxSize": "3.60KB"
},
{
"path": "packages/recommend/dist/recommend.umd.browser.js",
"maxSize": "3.55KB"
"maxSize": "3.70KB"
},
{
"path": "packages/client-common/dist/client-common.esm.node.js",
"maxSize": "3.45KB"
"maxSize": "3.65KB"
},
{
"path": "packages/requester-browser-xhr/dist/requester-browser-xhr.esm.node.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
export * from './src/createAuth';
export * from './src/createEchoRequester';
export * from './src/cache';
export * from './src/createStatefulHost';
export * from './src/createTransporter';
export * from './src/transporter';
export * from './src/createUserAgent';
export * from './src/errors';
export * from './src/getUserAgent';
export * from './src/helpers';
export * from './src/Response';
export * from './src/stackTrace';
export * from './src/types';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Host, StatefulHost } from './types';
import type { Host, StatefulHost } from '../types';

// By default, API Clients at Algolia have expiration delay of 5 mins.
// In the JavaScript client, we have 2 mins.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { isRetryable, isSuccess } from './Response';
import type {
EndRequest,
Host,
Request,
RequestOptions,
Response,
StackFrame,
TransporterOptions,
Transporter,
} from '../types';

import { createStatefulHost } from './createStatefulHost';
import { RetryError } from './errors';
import {
Expand All @@ -8,20 +18,11 @@ import {
serializeHeaders,
serializeUrl,
} from './helpers';
import { isRetryable, isSuccess } from './responses';
import {
stackTraceWithoutCredentials,
stackFrameWithoutCredentials,
} from './stackTrace';
import type {
EndRequest,
Host,
Request,
RequestOptions,
Response,
StackFrame,
TransporterOptions,
Transporter,
} from './types';

type RetryableOptions = {
hosts: Host[];
Expand All @@ -36,6 +37,8 @@ export function createTransporter({
userAgent,
timeouts,
requester,
requestsCache,
responsesCache,
}: TransporterOptions): Transporter {
async function createRetryableOptions(
compatibleHosts: Host[]
Expand Down Expand Up @@ -213,6 +216,94 @@ export function createTransporter({
return retry([...options.hosts].reverse(), options.getTimeout);
}

function createRequest<TResponse>(
request: Request,
requestOptions: RequestOptions
): Promise<TResponse> {
if (request.method !== 'GET') {
/**
* On write requests, no cache mechanisms are applied, and we
* proxy the request immediately to the requester.
*/
return retryableRequest<TResponse>(request, requestOptions);
}

const createRetryableRequest = (): Promise<TResponse> => {
/**
* Then, we prepare a function factory that contains the construction of
* the retryable request. At this point, we may *not* perform the actual
* request. But we want to have the function factory ready.
*/
return retryableRequest<TResponse>(request, requestOptions);
};

/**
* Once we have the function factory ready, we need to determine of the
* request is "cacheable" - should be cached. Note that, once again,
* the user can force this option.
*/
const cacheable = Boolean(requestOptions.cacheable || request.cacheable);

/**
* If is not "cacheable", we immediatly trigger the retryable request, no
* need to check cache implementations.
*/
if (cacheable !== true) {
return createRetryableRequest();
}

/**
* If the request is "cacheable", we need to first compute the key to ask
* the cache implementations if this request is on progress or if the
* response already exists on the cache.
*/
const key = {
request,
requestOptions,
transporter: {
queryParameters: requestOptions.queryParameters,
headers: requestOptions.headers,
},
};

/**
* With the computed key, we first ask the responses cache
* implemention if this request was been resolved before.
*/
return responsesCache.get(
key,
() => {
/**
* If the request has never resolved before, we actually ask if there
* is a current request with the same key on progress.
*/
return requestsCache.get(key, () =>
/**
* Finally, if there is no request in progress with the same key,
* this `createRetryableRequest()` will actually trigger the
* retryable request.
*/
requestsCache
.set(key, createRetryableRequest())
.then(
(response) => Promise.all([requestsCache.delete(key), response]),
(err) =>
Promise.all([requestsCache.delete(key), Promise.reject(err)])
)
.then(([_, response]) => response)
);
},
{
/**
* Of course, once we get this response back from the server, we
* tell response cache to actually store the received response
* to be used later.
*/
miss: (response) => responsesCache.set(key, response),
}
);
}

return {
hostsCache,
requester,
Expand All @@ -221,6 +312,8 @@ export function createTransporter({
baseHeaders,
baseQueryParameters,
hosts,
request: retryableRequest,
request: createRequest,
requestsCache,
responsesCache,
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Response, StackFrame } from './types';
import type { Response, StackFrame } from '../types';

class ErrorWithStackTrace extends Error {
stackTrace: StackFrame[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ApiError, DeserializationError } from './errors';
import type {
Headers,
Host,
Request,
RequestOptions,
Response,
StackFrame,
} from './types';
} from '../types';

import { ApiError, DeserializationError } from './errors';

export function shuffle<TData>(array: TData[]): TData[] {
const shuffledArray = array;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './createTransporter';
export * from './createStatefulHost';
export * from './errors';
export * from './helpers';
export * from './responses';
export * from './stackTrace';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Response } from './types';
import type { Response } from '../types';

export function isNetworkError({
isTimedOut,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { StackFrame } from './types';
import type { StackFrame } from '../types';

export function stackTraceWithoutCredentials(
stackTrace: StackFrame[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type Request = {
method: Method;
path: string;
data?: Record<string, any>;
cacheable?: boolean;
};

export type EndRequest = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,19 @@ export type RequestOptions = {
* Custom query parameters for the request. This query parameters are
* going to be merged the transporter query parameters.
*/
queryParameters: QueryParameters;
queryParameters?: QueryParameters;

/**
* Custom data for the request. This data are
* going to be merged the transporter data.
*/
data?: Record<string, any>;

/**
* If the given request should persist on the cache. Keep in mind,
* that some methods may have this option enabled by default.
*/
cacheable?: boolean;
};

export type StackFrame = {
Expand All @@ -39,12 +50,12 @@ export type UserAgentOptions = {
/**
* The segment. Usually the integration name.
*/
readonly segment: string;
segment: string;

/**
* The version. Usually the integration version.
*/
readonly version?: string;
version?: string;
};

export type UserAgent = {
Expand All @@ -56,7 +67,7 @@ export type UserAgent = {
/**
* Mutates the current user agent ading the given user agent options.
*/
readonly add: (options: UserAgentOptions) => UserAgent;
add: (options: UserAgentOptions) => UserAgent;
};

export type Timeouts = {
Expand Down Expand Up @@ -140,6 +151,21 @@ export type Transporter = {
*/
requester: Requester;

/**
* The cache of the requests. When requests are
* `cacheable`, the returned promised persists
* in this cache to shared in similar resquests
* before being resolved.
*/
requestsCache: Cache;

/**
* The cache of the responses. When requests are
* `cacheable`, the returned responses persists
* in this cache to shared in similar resquests.
*/
responsesCache: Cache;

/**
* The timeouts used by the requester. The transporter
* layer may increase this timeouts as defined on the
Expand Down

0 comments on commit 272ebd3

Please sign in to comment.