2025-08-04 18:41:00 -04:00

644 lines
19 KiB
JavaScript

// src/cache/createBrowserLocalStorageCache.ts
function createBrowserLocalStorageCache(options) {
let storage;
const namespaceKey = `algolia-client-js-${options.key}`;
function getStorage() {
if (storage === void 0) {
storage = options.localStorage || window.localStorage;
}
return storage;
}
function getNamespace() {
return JSON.parse(getStorage().getItem(namespaceKey) || "{}");
}
function setNamespace(namespace) {
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
}
function removeOutdatedCacheItems() {
const timeToLive = options.timeToLive ? options.timeToLive * 1e3 : null;
const namespace = getNamespace();
const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries(
Object.entries(namespace).filter(([, cacheItem]) => {
return cacheItem.timestamp !== void 0;
})
);
setNamespace(filteredNamespaceWithoutOldFormattedCacheItems);
if (!timeToLive) {
return;
}
const filteredNamespaceWithoutExpiredItems = Object.fromEntries(
Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => {
const currentTimestamp = (/* @__PURE__ */ new Date()).getTime();
const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;
return !isExpired;
})
);
setNamespace(filteredNamespaceWithoutExpiredItems);
}
return {
get(key, defaultValue, events = {
miss: () => Promise.resolve()
}) {
return Promise.resolve().then(() => {
removeOutdatedCacheItems();
return getNamespace()[JSON.stringify(key)];
}).then((value) => {
return Promise.all([value ? value.value : defaultValue(), value !== void 0]);
}).then(([value, exists]) => {
return Promise.all([value, exists || events.miss(value)]);
}).then(([value]) => value);
},
set(key, value) {
return Promise.resolve().then(() => {
const namespace = getNamespace();
namespace[JSON.stringify(key)] = {
timestamp: (/* @__PURE__ */ new Date()).getTime(),
value
};
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
return value;
});
},
delete(key) {
return Promise.resolve().then(() => {
const namespace = getNamespace();
delete namespace[JSON.stringify(key)];
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
});
},
clear() {
return Promise.resolve().then(() => {
getStorage().removeItem(namespaceKey);
});
}
};
}
// src/cache/createNullCache.ts
function createNullCache() {
return {
get(_key, defaultValue, events = {
miss: () => Promise.resolve()
}) {
const value = defaultValue();
return value.then((result) => Promise.all([result, events.miss(result)])).then(([result]) => result);
},
set(_key, value) {
return Promise.resolve(value);
},
delete(_key) {
return Promise.resolve();
},
clear() {
return Promise.resolve();
}
};
}
// src/cache/createFallbackableCache.ts
function createFallbackableCache(options) {
const caches = [...options.caches];
const current = caches.shift();
if (current === void 0) {
return createNullCache();
}
return {
get(key, defaultValue, events = {
miss: () => Promise.resolve()
}) {
return current.get(key, defaultValue, events).catch(() => {
return createFallbackableCache({ caches }).get(key, defaultValue, events);
});
},
set(key, value) {
return current.set(key, value).catch(() => {
return createFallbackableCache({ caches }).set(key, value);
});
},
delete(key) {
return current.delete(key).catch(() => {
return createFallbackableCache({ caches }).delete(key);
});
},
clear() {
return current.clear().catch(() => {
return createFallbackableCache({ caches }).clear();
});
}
};
}
// src/cache/createMemoryCache.ts
function createMemoryCache(options = { serializable: true }) {
let cache = {};
return {
get(key, defaultValue, events = {
miss: () => Promise.resolve()
}) {
const keyAsString = JSON.stringify(key);
if (keyAsString in cache) {
return Promise.resolve(options.serializable ? JSON.parse(cache[keyAsString]) : cache[keyAsString]);
}
const promise = defaultValue();
return promise.then((value) => events.miss(value)).then(() => promise);
},
set(key, value) {
cache[JSON.stringify(key)] = options.serializable ? JSON.stringify(value) : value;
return Promise.resolve(value);
},
delete(key) {
delete cache[JSON.stringify(key)];
return Promise.resolve();
},
clear() {
cache = {};
return Promise.resolve();
}
};
}
// src/constants.ts
var DEFAULT_CONNECT_TIMEOUT_BROWSER = 1e3;
var DEFAULT_READ_TIMEOUT_BROWSER = 2e3;
var DEFAULT_WRITE_TIMEOUT_BROWSER = 3e4;
var DEFAULT_CONNECT_TIMEOUT_NODE = 2e3;
var DEFAULT_READ_TIMEOUT_NODE = 5e3;
var DEFAULT_WRITE_TIMEOUT_NODE = 3e4;
// src/createAlgoliaAgent.ts
function createAlgoliaAgent(version) {
const algoliaAgent = {
value: `Algolia for JavaScript (${version})`,
add(options) {
const addedAlgoliaAgent = `; ${options.segment}${options.version !== void 0 ? ` (${options.version})` : ""}`;
if (algoliaAgent.value.indexOf(addedAlgoliaAgent) === -1) {
algoliaAgent.value = `${algoliaAgent.value}${addedAlgoliaAgent}`;
}
return algoliaAgent;
}
};
return algoliaAgent;
}
// src/createAuth.ts
function createAuth(appId, apiKey, authMode = "WithinHeaders") {
const credentials = {
"x-algolia-api-key": apiKey,
"x-algolia-application-id": appId
};
return {
headers() {
return authMode === "WithinHeaders" ? credentials : {};
},
queryParameters() {
return authMode === "WithinQueryParameters" ? credentials : {};
}
};
}
// src/createIterablePromise.ts
function createIterablePromise({
func,
validate,
aggregator,
error,
timeout = () => 0
}) {
const retry = (previousResponse) => {
return new Promise((resolve, reject) => {
func(previousResponse).then(async (response) => {
if (aggregator) {
await aggregator(response);
}
if (await validate(response)) {
return resolve(response);
}
if (error && await error.validate(response)) {
return reject(new Error(await error.message(response)));
}
return setTimeout(
() => {
retry(response).then(resolve).catch(reject);
},
await timeout()
);
}).catch((err) => {
reject(err);
});
});
};
return retry();
}
// src/getAlgoliaAgent.ts
function getAlgoliaAgent({ algoliaAgents, client, version }) {
const defaultAlgoliaAgent = createAlgoliaAgent(version).add({
segment: client,
version
});
algoliaAgents.forEach((algoliaAgent) => defaultAlgoliaAgent.add(algoliaAgent));
return defaultAlgoliaAgent;
}
// src/logger/createNullLogger.ts
function createNullLogger() {
return {
debug(_message, _args) {
return Promise.resolve();
},
info(_message, _args) {
return Promise.resolve();
},
error(_message, _args) {
return Promise.resolve();
}
};
}
// src/transporter/createStatefulHost.ts
var EXPIRATION_DELAY = 2 * 60 * 1e3;
function createStatefulHost(host, status = "up") {
const lastUpdate = Date.now();
function isUp() {
return status === "up" || Date.now() - lastUpdate > EXPIRATION_DELAY;
}
function isTimedOut() {
return status === "timed out" && Date.now() - lastUpdate <= EXPIRATION_DELAY;
}
return { ...host, status, lastUpdate, isUp, isTimedOut };
}
// src/transporter/errors.ts
var AlgoliaError = class extends Error {
name = "AlgoliaError";
constructor(message, name) {
super(message);
if (name) {
this.name = name;
}
}
};
var IndexNotFoundError = class extends AlgoliaError {
constructor(indexName) {
super(`${indexName} does not exist`, "IndexNotFoundError");
}
};
var IndicesInSameAppError = class extends AlgoliaError {
constructor() {
super("Indices are in the same application. Use operationIndex instead.", "IndicesInSameAppError");
}
};
var IndexAlreadyExistsError = class extends AlgoliaError {
constructor(indexName) {
super(`${indexName} index already exists.`, "IndexAlreadyExistsError");
}
};
var ErrorWithStackTrace = class extends AlgoliaError {
stackTrace;
constructor(message, stackTrace, name) {
super(message, name);
this.stackTrace = stackTrace;
}
};
var RetryError = class extends ErrorWithStackTrace {
constructor(stackTrace) {
super(
"Unreachable hosts - your application id may be incorrect. If the error persists, please reach out to the Algolia Support team: https://alg.li/support.",
stackTrace,
"RetryError"
);
}
};
var ApiError = class extends ErrorWithStackTrace {
status;
constructor(message, status, stackTrace, name = "ApiError") {
super(message, stackTrace, name);
this.status = status;
}
};
var DeserializationError = class extends AlgoliaError {
response;
constructor(message, response) {
super(message, "DeserializationError");
this.response = response;
}
};
var DetailedApiError = class extends ApiError {
error;
constructor(message, status, error, stackTrace) {
super(message, status, stackTrace, "DetailedApiError");
this.error = error;
}
};
// src/transporter/helpers.ts
function shuffle(array) {
const shuffledArray = array;
for (let c = array.length - 1; c > 0; c--) {
const b = Math.floor(Math.random() * (c + 1));
const a = array[c];
shuffledArray[c] = array[b];
shuffledArray[b] = a;
}
return shuffledArray;
}
function serializeUrl(host, path, queryParameters) {
const queryParametersAsString = serializeQueryParameters(queryParameters);
let url = `${host.protocol}://${host.url}${host.port ? `:${host.port}` : ""}/${path.charAt(0) === "/" ? path.substring(1) : path}`;
if (queryParametersAsString.length) {
url += `?${queryParametersAsString}`;
}
return url;
}
function serializeQueryParameters(parameters) {
return Object.keys(parameters).filter((key) => parameters[key] !== void 0).sort().map(
(key) => `${key}=${encodeURIComponent(
Object.prototype.toString.call(parameters[key]) === "[object Array]" ? parameters[key].join(",") : parameters[key]
).replace(/\+/g, "%20")}`
).join("&");
}
function serializeData(request, requestOptions) {
if (request.method === "GET" || request.data === void 0 && requestOptions.data === void 0) {
return void 0;
}
const data = Array.isArray(request.data) ? request.data : { ...request.data, ...requestOptions.data };
return JSON.stringify(data);
}
function serializeHeaders(baseHeaders, requestHeaders, requestOptionsHeaders) {
const headers = {
Accept: "application/json",
...baseHeaders,
...requestHeaders,
...requestOptionsHeaders
};
const serializedHeaders = {};
Object.keys(headers).forEach((header) => {
const value = headers[header];
serializedHeaders[header.toLowerCase()] = value;
});
return serializedHeaders;
}
function deserializeSuccess(response) {
try {
return JSON.parse(response.content);
} catch (e) {
throw new DeserializationError(e.message, response);
}
}
function deserializeFailure({ content, status }, stackFrame) {
try {
const parsed = JSON.parse(content);
if ("error" in parsed) {
return new DetailedApiError(parsed.message, status, parsed.error, stackFrame);
}
return new ApiError(parsed.message, status, stackFrame);
} catch {
}
return new ApiError(content, status, stackFrame);
}
// src/transporter/responses.ts
function isNetworkError({ isTimedOut, status }) {
return !isTimedOut && ~~status === 0;
}
function isRetryable({ isTimedOut, status }) {
return isTimedOut || isNetworkError({ isTimedOut, status }) || ~~(status / 100) !== 2 && ~~(status / 100) !== 4;
}
function isSuccess({ status }) {
return ~~(status / 100) === 2;
}
// src/transporter/stackTrace.ts
function stackTraceWithoutCredentials(stackTrace) {
return stackTrace.map((stackFrame) => stackFrameWithoutCredentials(stackFrame));
}
function stackFrameWithoutCredentials(stackFrame) {
const modifiedHeaders = stackFrame.request.headers["x-algolia-api-key"] ? { "x-algolia-api-key": "*****" } : {};
return {
...stackFrame,
request: {
...stackFrame.request,
headers: {
...stackFrame.request.headers,
...modifiedHeaders
}
}
};
}
// src/transporter/createTransporter.ts
function createTransporter({
hosts,
hostsCache,
baseHeaders,
logger,
baseQueryParameters,
algoliaAgent,
timeouts,
requester,
requestsCache,
responsesCache
}) {
async function createRetryableOptions(compatibleHosts) {
const statefulHosts = await Promise.all(
compatibleHosts.map((compatibleHost) => {
return hostsCache.get(compatibleHost, () => {
return Promise.resolve(createStatefulHost(compatibleHost));
});
})
);
const hostsUp = statefulHosts.filter((host) => host.isUp());
const hostsTimedOut = statefulHosts.filter((host) => host.isTimedOut());
const hostsAvailable = [...hostsUp, ...hostsTimedOut];
const compatibleHostsAvailable = hostsAvailable.length > 0 ? hostsAvailable : compatibleHosts;
return {
hosts: compatibleHostsAvailable,
getTimeout(timeoutsCount, baseTimeout) {
const timeoutMultiplier = hostsTimedOut.length === 0 && timeoutsCount === 0 ? 1 : hostsTimedOut.length + 3 + timeoutsCount;
return timeoutMultiplier * baseTimeout;
}
};
}
async function retryableRequest(request, requestOptions, isRead = true) {
const stackTrace = [];
const data = serializeData(request, requestOptions);
const headers = serializeHeaders(baseHeaders, request.headers, requestOptions.headers);
const dataQueryParameters = request.method === "GET" ? {
...request.data,
...requestOptions.data
} : {};
const queryParameters = {
...baseQueryParameters,
...request.queryParameters,
...dataQueryParameters
};
if (algoliaAgent.value) {
queryParameters["x-algolia-agent"] = algoliaAgent.value;
}
if (requestOptions && requestOptions.queryParameters) {
for (const key of Object.keys(requestOptions.queryParameters)) {
if (!requestOptions.queryParameters[key] || Object.prototype.toString.call(requestOptions.queryParameters[key]) === "[object Object]") {
queryParameters[key] = requestOptions.queryParameters[key];
} else {
queryParameters[key] = requestOptions.queryParameters[key].toString();
}
}
}
let timeoutsCount = 0;
const retry = async (retryableHosts, getTimeout) => {
const host = retryableHosts.pop();
if (host === void 0) {
throw new RetryError(stackTraceWithoutCredentials(stackTrace));
}
const timeout = { ...timeouts, ...requestOptions.timeouts };
const payload = {
data,
headers,
method: request.method,
url: serializeUrl(host, request.path, queryParameters),
connectTimeout: getTimeout(timeoutsCount, timeout.connect),
responseTimeout: getTimeout(timeoutsCount, isRead ? timeout.read : timeout.write)
};
const pushToStackTrace = (response2) => {
const stackFrame = {
request: payload,
response: response2,
host,
triesLeft: retryableHosts.length
};
stackTrace.push(stackFrame);
return stackFrame;
};
const response = await requester.send(payload);
if (isRetryable(response)) {
const stackFrame = pushToStackTrace(response);
if (response.isTimedOut) {
timeoutsCount++;
}
logger.info("Retryable failure", stackFrameWithoutCredentials(stackFrame));
await hostsCache.set(host, createStatefulHost(host, response.isTimedOut ? "timed out" : "down"));
return retry(retryableHosts, getTimeout);
}
if (isSuccess(response)) {
return deserializeSuccess(response);
}
pushToStackTrace(response);
throw deserializeFailure(response, stackTrace);
};
const compatibleHosts = hosts.filter(
(host) => host.accept === "readWrite" || (isRead ? host.accept === "read" : host.accept === "write")
);
const options = await createRetryableOptions(compatibleHosts);
return retry([...options.hosts].reverse(), options.getTimeout);
}
function createRequest(request, requestOptions = {}) {
const isRead = request.useReadTransporter || request.method === "GET";
if (!isRead) {
return retryableRequest(request, requestOptions, isRead);
}
const createRetryableRequest = () => {
return retryableRequest(request, requestOptions);
};
const cacheable = requestOptions.cacheable || request.cacheable;
if (cacheable !== true) {
return createRetryableRequest();
}
const key = {
request,
requestOptions,
transporter: {
queryParameters: baseQueryParameters,
headers: baseHeaders
}
};
return responsesCache.get(
key,
() => {
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,
timeouts,
logger,
algoliaAgent,
baseHeaders,
baseQueryParameters,
hosts,
request: createRequest,
requestsCache,
responsesCache
};
}
// src/types/logger.ts
var LogLevelEnum = {
Debug: 1,
Info: 2,
Error: 3
};
export {
AlgoliaError,
ApiError,
DEFAULT_CONNECT_TIMEOUT_BROWSER,
DEFAULT_CONNECT_TIMEOUT_NODE,
DEFAULT_READ_TIMEOUT_BROWSER,
DEFAULT_READ_TIMEOUT_NODE,
DEFAULT_WRITE_TIMEOUT_BROWSER,
DEFAULT_WRITE_TIMEOUT_NODE,
DeserializationError,
DetailedApiError,
ErrorWithStackTrace,
IndexAlreadyExistsError,
IndexNotFoundError,
IndicesInSameAppError,
LogLevelEnum,
RetryError,
createAlgoliaAgent,
createAuth,
createBrowserLocalStorageCache,
createFallbackableCache,
createIterablePromise,
createMemoryCache,
createNullCache,
createNullLogger,
createStatefulHost,
createTransporter,
deserializeFailure,
deserializeSuccess,
getAlgoliaAgent,
isNetworkError,
isRetryable,
isSuccess,
serializeData,
serializeHeaders,
serializeQueryParameters,
serializeUrl,
shuffle,
stackFrameWithoutCredentials,
stackTraceWithoutCredentials
};
//# sourceMappingURL=common.js.map