@algolia/recommend
Advanced tools
Comparing version 4.14.1 to 5.0.0-alpha.1
'use strict'; | ||
var cacheCommon = require('@algolia/cache-common'); | ||
var cacheInMemory = require('@algolia/cache-in-memory'); | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var clientCommon = require('@algolia/client-common'); | ||
var loggerCommon = require('@algolia/logger-common'); | ||
var requesterNodeHttp = require('@algolia/requester-node-http'); | ||
var transporter = require('@algolia/transporter'); | ||
var requesterCommon = require('@algolia/requester-common'); | ||
const createRecommendClient = options => { | ||
const appId = options.appId; | ||
const auth = clientCommon.createAuth(options.authMode !== undefined ? options.authMode : clientCommon.AuthMode.WithinHeaders, appId, options.apiKey); | ||
const transporter$1 = transporter.createTransporter({ | ||
hosts: [ | ||
{ url: `${appId}-dsn.algolia.net`, accept: transporter.CallEnum.Read }, | ||
{ url: `${appId}.algolia.net`, accept: transporter.CallEnum.Write }, | ||
].concat(clientCommon.shuffle([ | ||
{ url: `${appId}-1.algolianet.com` }, | ||
{ url: `${appId}-2.algolianet.com` }, | ||
{ url: `${appId}-3.algolianet.com` }, | ||
])), | ||
// This file is generated, manual changes will be lost - read more on https://github.com/algolia/api-clients-automation. | ||
const apiClientVersion = '5.0.0-alpha.1'; | ||
function getDefaultHosts(appId) { | ||
return [ | ||
{ | ||
url: `${appId}-dsn.algolia.net`, | ||
accept: 'read', | ||
protocol: 'https', | ||
}, | ||
{ | ||
url: `${appId}.algolia.net`, | ||
accept: 'write', | ||
protocol: 'https', | ||
}, | ||
].concat(clientCommon.shuffle([ | ||
{ | ||
url: `${appId}-1.algolianet.com`, | ||
accept: 'readWrite', | ||
protocol: 'https', | ||
}, | ||
{ | ||
url: `${appId}-2.algolianet.com`, | ||
accept: 'readWrite', | ||
protocol: 'https', | ||
}, | ||
{ | ||
url: `${appId}-3.algolianet.com`, | ||
accept: 'readWrite', | ||
protocol: 'https', | ||
}, | ||
])); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
function createRecommendClient({ appId: appIdOption, apiKey: apiKeyOption, authMode, algoliaAgents, ...options }) { | ||
const auth = clientCommon.createAuth(appIdOption, apiKeyOption, authMode); | ||
const transporter = clientCommon.createTransporter({ | ||
hosts: getDefaultHosts(appIdOption), | ||
...options, | ||
headers: { | ||
algoliaAgent: clientCommon.getAlgoliaAgent({ | ||
algoliaAgents, | ||
client: 'Recommend', | ||
version: apiClientVersion, | ||
}), | ||
baseHeaders: { | ||
'content-type': 'text/plain', | ||
...auth.headers(), | ||
...{ 'content-type': 'application/x-www-form-urlencoded' }, | ||
...options.headers, | ||
...options.baseHeaders, | ||
}, | ||
queryParameters: { | ||
baseQueryParameters: { | ||
...auth.queryParameters(), | ||
...options.queryParameters, | ||
...options.baseQueryParameters, | ||
}, | ||
}); | ||
const base = { | ||
transporter: transporter$1, | ||
appId, | ||
return { | ||
transporter, | ||
/** | ||
* Get the value of the `algoliaAgent`, used by our libraries internally and telemetry system. | ||
*/ | ||
get _ua() { | ||
return transporter.algoliaAgent.value; | ||
}, | ||
/** | ||
* Adds a `segment` to the `x-algolia-agent` sent with every requests. | ||
* | ||
* @param segment - The algolia agent (user-agent) segment to add. | ||
* @param version - The version of the agent. | ||
*/ | ||
addAlgoliaAgent(segment, version) { | ||
transporter$1.userAgent.add({ segment, version }); | ||
transporter.algoliaAgent.add({ segment, version }); | ||
}, | ||
clearCache() { | ||
return Promise.all([ | ||
transporter$1.requestsCache.clear(), | ||
transporter$1.responsesCache.clear(), | ||
]).then(() => undefined); | ||
/** | ||
* This method allow you to send requests to the Algolia REST API. | ||
* | ||
* @summary Send requests to the Algolia REST API. | ||
* @param del - The del object. | ||
* @param del.path - The path of the API endpoint to target, anything after the /1 needs to be specified. | ||
* @param del.parameters - Query parameters to be applied to the current query. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
del({ path, parameters }, requestOptions) { | ||
if (!path) { | ||
throw new Error('Parameter `path` is required when calling `del`.'); | ||
} | ||
const requestPath = '/1{path}'.replace('{path}', path); | ||
const headers = {}; | ||
const queryParameters = parameters ? parameters : {}; | ||
const request = { | ||
method: 'DELETE', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
/** | ||
* This method allow you to send requests to the Algolia REST API. | ||
* | ||
* @summary Send requests to the Algolia REST API. | ||
* @param get - The get object. | ||
* @param get.path - The path of the API endpoint to target, anything after the /1 needs to be specified. | ||
* @param get.parameters - Query parameters to be applied to the current query. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
get({ path, parameters }, requestOptions) { | ||
if (!path) { | ||
throw new Error('Parameter `path` is required when calling `get`.'); | ||
} | ||
const requestPath = '/1{path}'.replace('{path}', path); | ||
const headers = {}; | ||
const queryParameters = parameters ? parameters : {}; | ||
const request = { | ||
method: 'GET', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
/** | ||
* Returns recommendations or trending results, for a specific model and `objectID`. | ||
* | ||
* @summary Get results. | ||
* @param getRecommendationsParams - The getRecommendationsParams object. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
getRecommendations(getRecommendationsParams, requestOptions) { | ||
if (!getRecommendationsParams) { | ||
throw new Error('Parameter `getRecommendationsParams` is required when calling `getRecommendations`.'); | ||
} | ||
if (!getRecommendationsParams.requests) { | ||
throw new Error('Parameter `getRecommendationsParams.requests` is required when calling `getRecommendations`.'); | ||
} | ||
const requestPath = '/1/indexes/*/recommendations'; | ||
const headers = {}; | ||
const queryParameters = {}; | ||
const request = { | ||
method: 'POST', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
data: getRecommendationsParams, | ||
useReadTransporter: true, | ||
cacheable: true, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
/** | ||
* This method allow you to send requests to the Algolia REST API. | ||
* | ||
* @summary Send requests to the Algolia REST API. | ||
* @param post - The post object. | ||
* @param post.path - The path of the API endpoint to target, anything after the /1 needs to be specified. | ||
* @param post.parameters - Query parameters to be applied to the current query. | ||
* @param post.body - The parameters to send with the custom request. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
post({ path, parameters, body }, requestOptions) { | ||
if (!path) { | ||
throw new Error('Parameter `path` is required when calling `post`.'); | ||
} | ||
const requestPath = '/1{path}'.replace('{path}', path); | ||
const headers = {}; | ||
const queryParameters = parameters ? parameters : {}; | ||
const request = { | ||
method: 'POST', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
data: body ? body : {}, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
/** | ||
* This method allow you to send requests to the Algolia REST API. | ||
* | ||
* @summary Send requests to the Algolia REST API. | ||
* @param put - The put object. | ||
* @param put.path - The path of the API endpoint to target, anything after the /1 needs to be specified. | ||
* @param put.parameters - Query parameters to be applied to the current query. | ||
* @param put.body - The parameters to send with the custom request. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
put({ path, parameters, body }, requestOptions) { | ||
if (!path) { | ||
throw new Error('Parameter `path` is required when calling `put`.'); | ||
} | ||
const requestPath = '/1{path}'.replace('{path}', path); | ||
const headers = {}; | ||
const queryParameters = parameters ? parameters : {}; | ||
const request = { | ||
method: 'PUT', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
data: body ? body : {}, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
}; | ||
return clientCommon.addMethods(base, options.methods); | ||
}; | ||
} | ||
const getRecommendations = base => { | ||
return (queries, requestOptions) => { | ||
const requests = queries.map(query => ({ | ||
...query, | ||
// The `threshold` param is required by the endpoint to make it easier | ||
// to provide a default value later, so we default it in the client | ||
// so that users don't have to provide a value. | ||
threshold: query.threshold || 0, | ||
})); | ||
return base.transporter.read({ | ||
method: requesterCommon.MethodEnum.Post, | ||
path: '1/indexes/*/recommendations', | ||
data: { | ||
requests, | ||
}, | ||
cacheable: true, | ||
}, requestOptions); | ||
}; | ||
}; | ||
const getFrequentlyBoughtTogether = base => { | ||
return (queries, requestOptions) => { | ||
return getRecommendations(base)(queries.map(query => ({ | ||
...query, | ||
fallbackParameters: {}, | ||
model: 'bought-together', | ||
})), requestOptions); | ||
}; | ||
}; | ||
const getRelatedProducts = base => { | ||
return (queries, requestOptions) => { | ||
return getRecommendations(base)(queries.map(query => ({ | ||
...query, | ||
model: 'related-products', | ||
})), requestOptions); | ||
}; | ||
}; | ||
const getTrendingFacets = base => { | ||
return (queries, requestOptions) => { | ||
const requests = queries.map(query => ({ | ||
...query, | ||
model: 'trending-facets', | ||
// The `threshold` param is required by the endpoint to make it easier | ||
// to provide a default value later, so we default it in the client | ||
// so that users don't have to provide a value. | ||
threshold: query.threshold || 0, | ||
})); | ||
return base.transporter.read({ | ||
method: requesterCommon.MethodEnum.Post, | ||
path: '1/indexes/*/recommendations', | ||
data: { | ||
requests, | ||
}, | ||
cacheable: true, | ||
}, requestOptions); | ||
}; | ||
}; | ||
const getTrendingItems = base => { | ||
return (queries, requestOptions) => { | ||
const requests = queries.map(query => ({ | ||
...query, | ||
model: 'trending-items', | ||
// The `threshold` param is required by the endpoint to make it easier | ||
// to provide a default value later, so we default it in the client | ||
// so that users don't have to provide a value. | ||
threshold: query.threshold || 0, | ||
})); | ||
return base.transporter.read({ | ||
method: requesterCommon.MethodEnum.Post, | ||
path: '1/indexes/*/recommendations', | ||
data: { | ||
requests, | ||
}, | ||
cacheable: true, | ||
}, requestOptions); | ||
}; | ||
}; | ||
function recommend(appId, apiKey, options) { | ||
const commonOptions = { | ||
// This file is generated, manual changes will be lost - read more on https://github.com/algolia/api-clients-automation. | ||
function recommendClient(appId, apiKey, options) { | ||
if (!appId || typeof appId !== 'string') { | ||
throw new Error('`appId` is missing.'); | ||
} | ||
if (!apiKey || typeof apiKey !== 'string') { | ||
throw new Error('`apiKey` is missing.'); | ||
} | ||
return createRecommendClient({ | ||
appId, | ||
apiKey, | ||
timeouts: { | ||
connect: 2, | ||
read: 5, | ||
write: 30, | ||
connect: clientCommon.DEFAULT_CONNECT_TIMEOUT_NODE, | ||
read: clientCommon.DEFAULT_READ_TIMEOUT_NODE, | ||
write: clientCommon.DEFAULT_WRITE_TIMEOUT_NODE, | ||
}, | ||
requester: requesterNodeHttp.createNodeHttpRequester(), | ||
logger: loggerCommon.createNullLogger(), | ||
responsesCache: cacheCommon.createNullCache(), | ||
requestsCache: cacheCommon.createNullCache(), | ||
hostsCache: cacheInMemory.createInMemoryCache(), | ||
userAgent: transporter.createUserAgent(clientCommon.version) | ||
.add({ segment: 'Recommend', version: clientCommon.version }) | ||
.add({ segment: 'Node.js', version: process.versions.node }), | ||
}; | ||
return createRecommendClient({ | ||
...commonOptions, | ||
requester: requesterNodeHttp.createHttpRequester(), | ||
algoliaAgents: [{ segment: 'Node.js', version: process.versions.node }], | ||
responsesCache: clientCommon.createNullCache(), | ||
requestsCache: clientCommon.createNullCache(), | ||
hostsCache: clientCommon.createMemoryCache(), | ||
...options, | ||
methods: { | ||
destroy: clientCommon.destroy, | ||
getFrequentlyBoughtTogether, | ||
getRecommendations, | ||
getRelatedProducts, | ||
getTrendingFacets, | ||
getTrendingItems, | ||
}, | ||
}); | ||
} | ||
// eslint-disable-next-line functional/immutable-data | ||
recommend.version = clientCommon.version; | ||
module.exports = recommend; | ||
exports.apiClientVersion = apiClientVersion; | ||
exports.recommendClient = recommendClient; |
@@ -0,506 +1,527 @@ | ||
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 : {}; | ||
} | ||
}; | ||
} | ||
function createBrowserLocalStorageCache(options) { | ||
const namespaceKey = `algoliasearch-client-js-${options.key}`; | ||
// eslint-disable-next-line functional/no-let | ||
let storage; | ||
const getStorage = () => { | ||
if (storage === undefined) { | ||
storage = options.localStorage || window.localStorage; | ||
} | ||
return storage; | ||
}; | ||
const getNamespace = () => { | ||
return JSON.parse(getStorage().getItem(namespaceKey) || '{}'); | ||
}; | ||
return { | ||
get(key, defaultValue, events = { | ||
miss: () => Promise.resolve(), | ||
}) { | ||
return Promise.resolve() | ||
.then(() => { | ||
const keyAsString = JSON.stringify(key); | ||
const value = getNamespace()[keyAsString]; | ||
return Promise.all([value || defaultValue(), value !== undefined]); | ||
}) | ||
.then(([value, exists]) => { | ||
return Promise.all([value, exists || events.miss(value)]); | ||
}) | ||
.then(([value]) => value); | ||
}, | ||
set(key, value) { | ||
return Promise.resolve().then(() => { | ||
const namespace = getNamespace(); | ||
// eslint-disable-next-line functional/immutable-data | ||
namespace[JSON.stringify(key)] = value; | ||
getStorage().setItem(namespaceKey, JSON.stringify(namespace)); | ||
return value; | ||
}); | ||
}, | ||
delete(key) { | ||
return Promise.resolve().then(() => { | ||
const namespace = getNamespace(); | ||
// eslint-disable-next-line functional/immutable-data | ||
delete namespace[JSON.stringify(key)]; | ||
getStorage().setItem(namespaceKey, JSON.stringify(namespace)); | ||
}); | ||
}, | ||
clear() { | ||
return Promise.resolve().then(() => { | ||
getStorage().removeItem(namespaceKey); | ||
}); | ||
}, | ||
}; | ||
let storage; // We've changed the namespace to avoid conflicts with v4, as this version is a huge breaking change | ||
const namespaceKey = `algolia-client-js-${options.key}`; | ||
function getStorage() { | ||
if (storage === undefined) { | ||
storage = options.localStorage || window.localStorage; | ||
} | ||
return storage; | ||
} | ||
function getNamespace() { | ||
return JSON.parse(getStorage().getItem(namespaceKey) || '{}'); | ||
} | ||
return { | ||
get(key, defaultValue, events = { | ||
miss: () => Promise.resolve() | ||
}) { | ||
return Promise.resolve().then(() => { | ||
const keyAsString = JSON.stringify(key); | ||
const value = getNamespace()[keyAsString]; | ||
return Promise.all([value || defaultValue(), value !== undefined]); | ||
}).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)] = 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); | ||
}); | ||
} | ||
}; | ||
} | ||
// @todo Add logger on options to debug when caches go wrong. | ||
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(); | ||
} | ||
}; | ||
} | ||
function createFallbackableCache(options) { | ||
const caches = [...options.caches]; | ||
const current = caches.shift(); // eslint-disable-line functional/immutable-data | ||
if (current === undefined) { | ||
return createNullCache(); | ||
const caches = [...options.caches]; | ||
const current = caches.shift(); | ||
if (current === undefined) { | ||
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(); | ||
}); | ||
} | ||
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(); | ||
}); | ||
}, | ||
}; | ||
}; | ||
} | ||
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(); | ||
}, | ||
}; | ||
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(); | ||
} | ||
}; | ||
} | ||
function createInMemoryCache(options = { serializable: true }) { | ||
// eslint-disable-next-line functional/no-let | ||
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(); | ||
const miss = (events && events.miss) || (() => Promise.resolve()); | ||
return promise.then((value) => miss(value)).then(() => promise); | ||
}, | ||
set(key, value) { | ||
// eslint-disable-next-line functional/immutable-data | ||
cache[JSON.stringify(key)] = options.serializable ? JSON.stringify(value) : value; | ||
return Promise.resolve(value); | ||
}, | ||
delete(key) { | ||
// eslint-disable-next-line functional/immutable-data | ||
delete cache[JSON.stringify(key)]; | ||
return Promise.resolve(); | ||
}, | ||
clear() { | ||
cache = {}; | ||
return Promise.resolve(); | ||
}, | ||
}; | ||
// By default, API Clients at Algolia have expiration delay of 5 mins. | ||
// In the JavaScript client, we have 2 mins. | ||
const EXPIRATION_DELAY = 2 * 60 * 1000; | ||
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 | ||
}; | ||
} | ||
function createAuth(authMode, appId, apiKey) { | ||
const credentials = { | ||
'x-algolia-api-key': apiKey, | ||
'x-algolia-application-id': appId, | ||
}; | ||
return { | ||
headers() { | ||
return authMode === AuthMode.WithinHeaders ? credentials : {}; | ||
}, | ||
queryParameters() { | ||
return authMode === AuthMode.WithinQueryParameters ? credentials : {}; | ||
}, | ||
}; | ||
function _defineProperty(obj, key, value) { | ||
if (key in obj) { | ||
Object.defineProperty(obj, key, { | ||
value: value, | ||
enumerable: true, | ||
configurable: true, | ||
writable: true | ||
}); | ||
} else { | ||
obj[key] = value; | ||
} | ||
return obj; | ||
} | ||
// eslint-disable-next-line functional/prefer-readonly-type | ||
function shuffle(array) { | ||
let c = array.length - 1; // eslint-disable-line functional/no-let | ||
// eslint-disable-next-line functional/no-loop-statement | ||
for (c; c > 0; c--) { | ||
const b = Math.floor(Math.random() * (c + 1)); | ||
const a = array[c]; | ||
array[c] = array[b]; // eslint-disable-line functional/immutable-data, no-param-reassign | ||
array[b] = a; // eslint-disable-line functional/immutable-data, no-param-reassign | ||
class AlgoliaError extends Error { | ||
constructor(message, name) { | ||
super(message); | ||
_defineProperty(this, "name", 'AlgoliaError'); | ||
if (name) { | ||
this.name = name; | ||
} | ||
return array; | ||
} | ||
} | ||
function addMethods(base, methods) { | ||
if (!methods) { | ||
return base; | ||
} | ||
Object.keys(methods).forEach(key => { | ||
// eslint-disable-next-line functional/immutable-data, no-param-reassign | ||
base[key] = methods[key](base); | ||
}); | ||
return base; | ||
class ErrorWithStackTrace extends AlgoliaError { | ||
constructor(message, stackTrace, name) { | ||
super(message, name); // the array and object should be frozen to reflect the stackTrace at the time of the error | ||
_defineProperty(this, "stackTrace", void 0); | ||
this.stackTrace = stackTrace; | ||
} | ||
} | ||
function encode(format, ...args) { | ||
// eslint-disable-next-line functional/no-let | ||
let i = 0; | ||
return format.replace(/%s/g, () => encodeURIComponent(args[i++])); | ||
class RetryError extends ErrorWithStackTrace { | ||
constructor(stackTrace) { | ||
super('Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.', stackTrace, 'RetryError'); | ||
} | ||
} | ||
class ApiError extends ErrorWithStackTrace { | ||
constructor(message, status, stackTrace) { | ||
super(message, stackTrace, 'ApiError'); | ||
const version = '4.14.1'; | ||
_defineProperty(this, "status", void 0); | ||
const AuthMode = { | ||
/** | ||
* If auth credentials should be in query parameters. | ||
*/ | ||
WithinQueryParameters: 0, | ||
/** | ||
* If auth credentials should be in headers. | ||
*/ | ||
WithinHeaders: 1, | ||
}; | ||
this.status = status; | ||
} | ||
const LogLevelEnum = { | ||
Debug: 1, | ||
Info: 2, | ||
Error: 3, | ||
}; | ||
} | ||
class DeserializationError extends AlgoliaError { | ||
constructor(message, response) { | ||
super(message, 'DeserializationError'); | ||
/* eslint no-console: 0 */ | ||
function createConsoleLogger(logLevel) { | ||
return { | ||
debug(message, args) { | ||
if (LogLevelEnum.Debug >= logLevel) { | ||
console.debug(message, args); | ||
} | ||
return Promise.resolve(); | ||
}, | ||
info(message, args) { | ||
if (LogLevelEnum.Info >= logLevel) { | ||
console.info(message, args); | ||
} | ||
return Promise.resolve(); | ||
}, | ||
error(message, args) { | ||
console.error(message, args); | ||
return Promise.resolve(); | ||
}, | ||
}; | ||
_defineProperty(this, "response", void 0); | ||
this.response = response; | ||
} | ||
} | ||
function createBrowserXhrRequester() { | ||
return { | ||
send(request) { | ||
return new Promise((resolve) => { | ||
const baseRequester = new XMLHttpRequest(); | ||
baseRequester.open(request.method, request.url, true); | ||
Object.keys(request.headers).forEach(key => baseRequester.setRequestHeader(key, request.headers[key])); | ||
const createTimeout = (timeout, content) => { | ||
return setTimeout(() => { | ||
baseRequester.abort(); | ||
resolve({ | ||
status: 0, | ||
content, | ||
isTimedOut: true, | ||
}); | ||
}, timeout * 1000); | ||
}; | ||
const connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout'); | ||
// eslint-disable-next-line functional/no-let | ||
let responseTimeout; | ||
// eslint-disable-next-line functional/immutable-data | ||
baseRequester.onreadystatechange = () => { | ||
if (baseRequester.readyState > baseRequester.OPENED && responseTimeout === undefined) { | ||
clearTimeout(connectTimeout); | ||
responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout'); | ||
} | ||
}; | ||
// eslint-disable-next-line functional/immutable-data | ||
baseRequester.onerror = () => { | ||
// istanbul ignore next | ||
if (baseRequester.status === 0) { | ||
clearTimeout(connectTimeout); | ||
clearTimeout(responseTimeout); | ||
resolve({ | ||
content: baseRequester.responseText || 'Network request failed', | ||
status: baseRequester.status, | ||
isTimedOut: false, | ||
}); | ||
} | ||
}; | ||
// eslint-disable-next-line functional/immutable-data | ||
baseRequester.onload = () => { | ||
clearTimeout(connectTimeout); | ||
clearTimeout(responseTimeout); | ||
resolve({ | ||
content: baseRequester.responseText, | ||
status: baseRequester.status, | ||
isTimedOut: false, | ||
}); | ||
}; | ||
baseRequester.send(request.data); | ||
}); | ||
}, | ||
}; | ||
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}/${path.charAt(0) === '/' ? path.substr(1) : path}`; | ||
function createMappedRequestOptions(requestOptions, timeout) { | ||
const options = requestOptions || {}; | ||
const data = options.data || {}; | ||
Object.keys(options).forEach(key => { | ||
if (['timeout', 'headers', 'queryParameters', 'data', 'cacheable'].indexOf(key) === -1) { | ||
data[key] = options[key]; // eslint-disable-line functional/immutable-data | ||
} | ||
}); | ||
return { | ||
data: Object.entries(data).length > 0 ? data : undefined, | ||
timeout: options.timeout || timeout, | ||
headers: options.headers || {}, | ||
queryParameters: options.queryParameters || {}, | ||
cacheable: options.cacheable, | ||
}; | ||
if (queryParametersAsString.length) { | ||
url += `?${queryParametersAsString}`; | ||
} | ||
return url; | ||
} | ||
function serializeQueryParameters(parameters) { | ||
const isObjectOrArray = value => Object.prototype.toString.call(value) === '[object Object]' || Object.prototype.toString.call(value) === '[object Array]'; | ||
const CallEnum = { | ||
/** | ||
* If the host is read only. | ||
*/ | ||
Read: 1, | ||
/** | ||
* If the host is write only. | ||
*/ | ||
Write: 2, | ||
/** | ||
* If the host is both read and write. | ||
*/ | ||
Any: 3, | ||
}; | ||
return Object.keys(parameters).map(key => `${key}=${isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key]}`).join('&'); | ||
} | ||
function serializeData(request, requestOptions) { | ||
if (request.method === 'GET' || request.data === undefined && requestOptions.data === undefined) { | ||
return undefined; | ||
} | ||
const HostStatusEnum = { | ||
Up: 1, | ||
Down: 2, | ||
Timeouted: 3, | ||
}; | ||
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) { | ||
let message = content; | ||
// By default, API Clients at Algolia have expiration delay | ||
// of 5 mins. In the JavaScript client, we have 2 mins. | ||
const EXPIRATION_DELAY = 2 * 60 * 1000; | ||
function createStatefulHost(host, status = HostStatusEnum.Up) { | ||
return { | ||
...host, | ||
status, | ||
lastUpdate: Date.now(), | ||
}; | ||
try { | ||
message = JSON.parse(content).message; | ||
} catch (e) {// .. | ||
} | ||
return new ApiError(message, status, stackFrame); | ||
} | ||
function isStatefulHostUp(host) { | ||
return host.status === HostStatusEnum.Up || Date.now() - host.lastUpdate > EXPIRATION_DELAY; | ||
function isNetworkError({ | ||
isTimedOut, | ||
status | ||
}) { | ||
return !isTimedOut && ~~status === 0; | ||
} | ||
function isStatefulHostTimeouted(host) { | ||
return (host.status === HostStatusEnum.Timeouted && Date.now() - host.lastUpdate <= EXPIRATION_DELAY); | ||
function isRetryable({ | ||
isTimedOut, | ||
status | ||
}) { | ||
return isTimedOut || isNetworkError({ | ||
isTimedOut, | ||
status | ||
}) || ~~(status / 100) !== 2 && ~~(status / 100) !== 4; | ||
} | ||
function isSuccess({ | ||
status | ||
}) { | ||
return ~~(status / 100) === 2; | ||
} | ||
function createStatelessHost(options) { | ||
if (typeof options === 'string') { | ||
return { | ||
protocol: 'https', | ||
url: options, | ||
accept: CallEnum.Any, | ||
}; | ||
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 | ||
} | ||
} | ||
return { | ||
protocol: options.protocol || 'https', | ||
url: options.url, | ||
accept: options.accept || CallEnum.Any, | ||
}; | ||
}; | ||
} | ||
const MethodEnum = { | ||
Delete: 'DELETE', | ||
Get: 'GET', | ||
Post: 'POST', | ||
Put: 'PUT', | ||
}; | ||
function createTransporter({ | ||
hosts, | ||
hostsCache, | ||
baseHeaders, | ||
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()); // Note, we put the hosts that previously timed out on the end of the list. | ||
function createRetryableOptions(hostsCache, statelessHosts) { | ||
return Promise.all(statelessHosts.map(statelessHost => { | ||
return hostsCache.get(statelessHost, () => { | ||
return Promise.resolve(createStatefulHost(statelessHost)); | ||
}); | ||
})).then(statefulHosts => { | ||
const hostsUp = statefulHosts.filter(host => isStatefulHostUp(host)); | ||
const hostsTimeouted = statefulHosts.filter(host => isStatefulHostTimeouted(host)); | ||
const hostsAvailable = [...hostsUp, ...hostsTimedOut]; | ||
const compatibleHostsAvailable = hostsAvailable.length > 0 ? hostsAvailable : compatibleHosts; | ||
return { | ||
hosts: compatibleHostsAvailable, | ||
getTimeout(timeoutsCount, baseTimeout) { | ||
/** | ||
* Note, we put the hosts that previously timeouted on the end of the list. | ||
* Imagine that you have 4 hosts, if timeouts will increase | ||
* on the following way: 1 (timed out) > 4 (timed out) > 5 (200). | ||
* | ||
* Note that, the very next request, we start from the previous timeout. | ||
* | ||
* 5 (timed out) > 6 (timed out) > 7 ... | ||
* | ||
* This strategy may need to be reviewed, but is the strategy on the our | ||
* current v3 version. | ||
*/ | ||
const hostsAvailable = [...hostsUp, ...hostsTimeouted]; | ||
const statelessHostsAvailable = hostsAvailable.length > 0 | ||
? hostsAvailable.map(host => createStatelessHost(host)) | ||
: statelessHosts; | ||
return { | ||
getTimeout(timeoutsCount, baseTimeout) { | ||
/** | ||
* Imagine that you have 4 hosts, if timeouts will increase | ||
* on the following way: 1 (timeouted) > 4 (timeouted) > 5 (200) | ||
* | ||
* Note that, the very next request, we start from the previous timeout | ||
* | ||
* 5 (timeouted) > 6 (timeouted) > 7 ... | ||
* | ||
* This strategy may need to be reviewed, but is the strategy on the our | ||
* current v3 version. | ||
*/ | ||
const timeoutMultiplier = hostsTimeouted.length === 0 && timeoutsCount === 0 | ||
? 1 | ||
: hostsTimeouted.length + 3 + timeoutsCount; | ||
return timeoutMultiplier * baseTimeout; | ||
}, | ||
statelessHosts: statelessHostsAvailable, | ||
}; | ||
}); | ||
} | ||
const timeoutMultiplier = hostsTimedOut.length === 0 && timeoutsCount === 0 ? 1 : hostsTimedOut.length + 3 + timeoutsCount; | ||
return timeoutMultiplier * baseTimeout; | ||
} | ||
const isNetworkError = ({ isTimedOut, status }) => { | ||
return !isTimedOut && ~~status === 0; | ||
}; | ||
const isRetryable = (response) => { | ||
const status = response.status; | ||
const isTimedOut = response.isTimedOut; | ||
return (isTimedOut || isNetworkError(response) || (~~(status / 100) !== 2 && ~~(status / 100) !== 4)); | ||
}; | ||
const isSuccess = ({ status }) => { | ||
return ~~(status / 100) === 2; | ||
}; | ||
const retryDecision = (response, outcomes) => { | ||
if (isRetryable(response)) { | ||
return outcomes.onRetry(response); | ||
} | ||
if (isSuccess(response)) { | ||
return outcomes.onSuccess(response); | ||
} | ||
return outcomes.onFail(response); | ||
}; | ||
}; | ||
} | ||
function retryableRequest(transporter, statelessHosts, request, requestOptions) { | ||
const stackTrace = []; // eslint-disable-line functional/prefer-readonly-type | ||
async function retryableRequest(request, requestOptions, isRead = true) { | ||
const stackTrace = []; | ||
/** | ||
* First we prepare the payload that do not depend from hosts. | ||
*/ | ||
const data = serializeData(request, requestOptions); | ||
const headers = serializeHeaders(transporter, requestOptions); | ||
const method = request.method; | ||
// On `GET`, the data is proxied to query parameters. | ||
const dataQueryParameters = request.method !== MethodEnum.Get | ||
? {} | ||
: { | ||
...request.data, | ||
...requestOptions.data, | ||
}; | ||
const headers = serializeHeaders(baseHeaders, request.headers, requestOptions.headers); // On `GET`, the data is proxied to query parameters. | ||
const dataQueryParameters = request.method === 'GET' ? { ...request.data, | ||
...requestOptions.data | ||
} : {}; | ||
const queryParameters = { | ||
'x-algolia-agent': transporter.userAgent.value, | ||
...transporter.queryParameters, | ||
...dataQueryParameters, | ||
...requestOptions.queryParameters, | ||
'x-algolia-agent': algoliaAgent.value, | ||
...baseQueryParameters, | ||
...request.queryParameters, | ||
...dataQueryParameters | ||
}; | ||
let timeoutsCount = 0; // eslint-disable-line functional/no-let | ||
const retry = (hosts, // eslint-disable-line functional/prefer-readonly-type | ||
getTimeout) => { | ||
/** | ||
* We iterate on each host, until there is no host left. | ||
*/ | ||
const host = hosts.pop(); // eslint-disable-line functional/immutable-data | ||
if (host === undefined) { | ||
throw createRetryError(stackTraceWithoutCredentials(stackTrace)); | ||
if (requestOptions && requestOptions.queryParameters) { | ||
for (const key of Object.keys(requestOptions.queryParameters)) { | ||
// We want to keep `undefined` and `null` values, | ||
// but also avoid stringifying `object`s, as they are | ||
// handled in the `serializeUrl` step right after. | ||
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(); | ||
} | ||
const payload = { | ||
data, | ||
headers, | ||
method, | ||
url: serializeUrl(host, request.path, queryParameters), | ||
connectTimeout: getTimeout(timeoutsCount, transporter.timeouts.connect), | ||
responseTimeout: getTimeout(timeoutsCount, requestOptions.timeout), | ||
} | ||
} | ||
let timeoutsCount = 0; | ||
const retry = async (retryableHosts, getTimeout) => { | ||
/** | ||
* We iterate on each host, until there is no host left. | ||
*/ | ||
const host = retryableHosts.pop(); | ||
if (host === undefined) { | ||
throw new RetryError(stackTraceWithoutCredentials(stackTrace)); | ||
} | ||
let responseTimeout = requestOptions.timeout; | ||
if (responseTimeout === undefined) { | ||
responseTimeout = isRead ? timeouts.read : timeouts.write; | ||
} | ||
const payload = { | ||
data, | ||
headers, | ||
method: request.method, | ||
url: serializeUrl(host, request.path, queryParameters), | ||
connectTimeout: getTimeout(timeoutsCount, timeouts.connect), | ||
responseTimeout: getTimeout(timeoutsCount, responseTimeout) | ||
}; | ||
/** | ||
* The stackFrame is pushed to the stackTrace so we | ||
* can have information about onRetry and onFailure | ||
* decisions. | ||
*/ | ||
const pushToStackTrace = response => { | ||
const stackFrame = { | ||
request: payload, | ||
response, | ||
host, | ||
triesLeft: retryableHosts.length | ||
}; | ||
stackTrace.push(stackFrame); | ||
return stackFrame; | ||
}; | ||
const response = await requester.send(payload); | ||
if (isRetryable(response)) { | ||
const stackFrame = pushToStackTrace(response); // If response is a timeout, we increase the number of timeouts so we can increase the timeout later. | ||
if (response.isTimedOut) { | ||
timeoutsCount++; | ||
} | ||
/** | ||
* The stackFrame is pushed to the stackTrace so we | ||
* can have information about onRetry and onFailure | ||
* decisions. | ||
* Failures are individually sent to the logger, allowing | ||
* the end user to debug / store stack frames even | ||
* when a retry error does not happen. | ||
*/ | ||
const pushToStackTrace = (response) => { | ||
const stackFrame = { | ||
request: payload, | ||
response, | ||
host, | ||
triesLeft: hosts.length, | ||
}; | ||
// eslint-disable-next-line functional/immutable-data | ||
stackTrace.push(stackFrame); | ||
return stackFrame; | ||
}; | ||
const decisions = { | ||
onSuccess: response => deserializeSuccess(response), | ||
onRetry(response) { | ||
const stackFrame = pushToStackTrace(response); | ||
/** | ||
* If response is a timeout, we increaset the number of | ||
* timeouts so we can increase the timeout later. | ||
*/ | ||
if (response.isTimedOut) { | ||
timeoutsCount++; | ||
} | ||
return Promise.all([ | ||
/** | ||
* Failures are individually send the logger, allowing | ||
* the end user to debug / store stack frames even | ||
* when a retry error does not happen. | ||
*/ | ||
transporter.logger.info('Retryable failure', stackFrameWithoutCredentials(stackFrame)), | ||
/** | ||
* We also store the state of the host in failure cases. If the host, is | ||
* down it will remain down for the next 2 minutes. In a timeout situation, | ||
* this host will be added end of the list of hosts on the next request. | ||
*/ | ||
transporter.hostsCache.set(host, createStatefulHost(host, response.isTimedOut ? HostStatusEnum.Timeouted : HostStatusEnum.Down)), | ||
]).then(() => retry(hosts, getTimeout)); | ||
}, | ||
onFail(response) { | ||
pushToStackTrace(response); | ||
throw deserializeFailure(response, stackTraceWithoutCredentials(stackTrace)); | ||
}, | ||
}; | ||
return transporter.requester.send(payload).then(response => { | ||
return retryDecision(response, decisions); | ||
}); | ||
// eslint-disable-next-line no-console -- this will be fixed by exposing a `logger` to the transporter | ||
console.log('Retryable failure', stackFrameWithoutCredentials(stackFrame)); | ||
/** | ||
* We also store the state of the host in failure cases. If the host, is | ||
* down it will remain down for the next 2 minutes. In a timeout situation, | ||
* this host will be added end of the list of hosts on the next request. | ||
*/ | ||
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); | ||
}; | ||
@@ -515,384 +536,423 @@ /** | ||
*/ | ||
return createRetryableOptions(transporter.hostsCache, statelessHosts).then(options => { | ||
return retry([...options.statelessHosts].reverse(), options.getTimeout); | ||
}); | ||
} | ||
function createTransporter(options) { | ||
const { hostsCache, logger, requester, requestsCache, responsesCache, timeouts, userAgent, hosts, queryParameters, headers, } = options; | ||
const transporter = { | ||
hostsCache, | ||
logger, | ||
requester, | ||
requestsCache, | ||
responsesCache, | ||
timeouts, | ||
userAgent, | ||
headers, | ||
queryParameters, | ||
hosts: hosts.map(host => createStatelessHost(host)), | ||
read(request, requestOptions) { | ||
/** | ||
* First, we compute the user request options. Now, keep in mind, | ||
* that using request options the user is able to modified the intire | ||
* payload of the request. Such as headers, query parameters, and others. | ||
*/ | ||
const mappedRequestOptions = createMappedRequestOptions(requestOptions, transporter.timeouts.read); | ||
const createRetryableRequest = () => { | ||
/** | ||
* 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(transporter, transporter.hosts.filter(host => (host.accept & CallEnum.Read) !== 0), request, mappedRequestOptions); | ||
}; | ||
/** | ||
* 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 = mappedRequestOptions.cacheable !== undefined | ||
? mappedRequestOptions.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, | ||
mappedRequestOptions, | ||
transporter: { | ||
queryParameters: transporter.queryParameters, | ||
headers: transporter.headers, | ||
}, | ||
}; | ||
/** | ||
* With the computed key, we first ask the responses cache | ||
* implemention if this request was been resolved before. | ||
*/ | ||
return transporter.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 transporter.requestsCache.get(key, () => { | ||
return (transporter.requestsCache | ||
/** | ||
* Finally, if there is no request in progress with the same key, | ||
* this `createRetryableRequest()` will actually trigger the | ||
* retryable request. | ||
*/ | ||
.set(key, createRetryableRequest()) | ||
.then(response => Promise.all([transporter.requestsCache.delete(key), response]), err => Promise.all([transporter.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 => transporter.responsesCache.set(key, response), | ||
}); | ||
}, | ||
write(request, requestOptions) { | ||
/** | ||
* On write requests, no cache mechanisms are applied, and we | ||
* proxy the request immediately to the requester. | ||
*/ | ||
return retryableRequest(transporter, transporter.hosts.filter(host => (host.accept & CallEnum.Write) !== 0), request, createMappedRequestOptions(requestOptions, transporter.timeouts.write)); | ||
}, | ||
}; | ||
return transporter; | ||
} | ||
function createUserAgent(version) { | ||
const userAgent = { | ||
value: `Algolia for JavaScript (${version})`, | ||
add(options) { | ||
const addedUserAgent = `; ${options.segment}${options.version !== undefined ? ` (${options.version})` : ''}`; | ||
if (userAgent.value.indexOf(addedUserAgent) === -1) { | ||
// eslint-disable-next-line functional/immutable-data | ||
userAgent.value = `${userAgent.value}${addedUserAgent}`; | ||
} | ||
return userAgent; | ||
}, | ||
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 = {}) { | ||
/** | ||
* A read request is either a `GET` request, or a request that we make | ||
* via the `read` transporter (e.g. `search`). | ||
*/ | ||
const isRead = request.useReadTransporter || request.method === 'GET'; | ||
if (!isRead) { | ||
/** | ||
* On write requests, no cache mechanisms are applied, and we | ||
* proxy the request immediately to the requester. | ||
*/ | ||
return retryableRequest(request, requestOptions, isRead); | ||
} | ||
const createRetryableRequest = () => { | ||
/** | ||
* 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(request, requestOptions); | ||
}; | ||
return userAgent; | ||
} | ||
/** | ||
* 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. | ||
*/ | ||
function deserializeSuccess(response) { | ||
// eslint-disable-next-line functional/no-try-statement | ||
try { | ||
return JSON.parse(response.content); | ||
const cacheable = requestOptions.cacheable || request.cacheable; | ||
/** | ||
* If is not "cacheable", we immediately trigger the retryable request, no | ||
* need to check cache implementations. | ||
*/ | ||
if (cacheable !== true) { | ||
return createRetryableRequest(); | ||
} | ||
catch (e) { | ||
throw createDeserializationError(e.message, response); | ||
} | ||
} | ||
function deserializeFailure({ content, status }, stackFrame) { | ||
// eslint-disable-next-line functional/no-let | ||
let message = content; | ||
// eslint-disable-next-line functional/no-try-statement | ||
try { | ||
message = JSON.parse(content).message; | ||
} | ||
catch (e) { | ||
// .. | ||
} | ||
return createApiError(message, status, stackFrame); | ||
} | ||
/** | ||
* 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. | ||
*/ | ||
function serializeUrl(host, path, queryParameters) { | ||
const queryParametersAsString = serializeQueryParameters(queryParameters); | ||
// eslint-disable-next-line functional/no-let | ||
let url = `${host.protocol}://${host.url}/${path.charAt(0) === '/' ? path.substr(1) : path}`; | ||
if (queryParametersAsString.length) { | ||
url += `?${queryParametersAsString}`; | ||
} | ||
return url; | ||
} | ||
function serializeQueryParameters(parameters) { | ||
const isObjectOrArray = (value) => Object.prototype.toString.call(value) === '[object Object]' || | ||
Object.prototype.toString.call(value) === '[object Array]'; | ||
return Object.keys(parameters) | ||
.map(key => encode('%s=%s', key, isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key])) | ||
.join('&'); | ||
} | ||
function serializeData(request, requestOptions) { | ||
if (request.method === MethodEnum.Get || | ||
(request.data === undefined && requestOptions.data === undefined)) { | ||
return undefined; | ||
} | ||
const data = Array.isArray(request.data) | ||
? request.data | ||
: { ...request.data, ...requestOptions.data }; | ||
return JSON.stringify(data); | ||
} | ||
function serializeHeaders(transporter, requestOptions) { | ||
const headers = { | ||
...transporter.headers, | ||
...requestOptions.headers, | ||
const key = { | ||
request, | ||
requestOptions, | ||
transporter: { | ||
queryParameters: baseQueryParameters, | ||
headers: baseHeaders | ||
} | ||
}; | ||
const serializedHeaders = {}; | ||
Object.keys(headers).forEach(header => { | ||
const value = headers[header]; | ||
// @ts-ignore | ||
// eslint-disable-next-line functional/immutable-data | ||
serializedHeaders[header.toLowerCase()] = value; | ||
/** | ||
* With the computed key, we first ask the responses cache | ||
* implementation 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 serializedHeaders; | ||
} | ||
return { | ||
hostsCache, | ||
requester, | ||
timeouts, | ||
algoliaAgent, | ||
baseHeaders, | ||
baseQueryParameters, | ||
hosts, | ||
request: createRequest, | ||
requestsCache, | ||
responsesCache | ||
}; | ||
} | ||
function stackTraceWithoutCredentials(stackTrace) { | ||
return stackTrace.map(stackFrame => stackFrameWithoutCredentials(stackFrame)); | ||
function createAlgoliaAgent(version) { | ||
const algoliaAgent = { | ||
value: `Algolia for JavaScript (${version})`, | ||
add(options) { | ||
const addedAlgoliaAgent = `; ${options.segment}${options.version !== undefined ? ` (${options.version})` : ''}`; | ||
if (algoliaAgent.value.indexOf(addedAlgoliaAgent) === -1) { | ||
algoliaAgent.value = `${algoliaAgent.value}${addedAlgoliaAgent}`; | ||
} | ||
return algoliaAgent; | ||
} | ||
}; | ||
return algoliaAgent; | ||
} | ||
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, | ||
}, | ||
}, | ||
}; | ||
} | ||
function createApiError(message, status, transporterStackTrace) { | ||
return { | ||
name: 'ApiError', | ||
message, | ||
status, | ||
transporterStackTrace, | ||
}; | ||
function getAlgoliaAgent({ | ||
algoliaAgents, | ||
client, | ||
version | ||
}) { | ||
const defaultAlgoliaAgent = createAlgoliaAgent(version).add({ | ||
segment: client, | ||
version | ||
}); | ||
algoliaAgents.forEach(algoliaAgent => defaultAlgoliaAgent.add(algoliaAgent)); | ||
return defaultAlgoliaAgent; | ||
} | ||
function createDeserializationError(message, response) { | ||
return { | ||
name: 'DeserializationError', | ||
message, | ||
response, | ||
}; | ||
const DEFAULT_CONNECT_TIMEOUT_BROWSER = 1000; | ||
const DEFAULT_READ_TIMEOUT_BROWSER = 2000; | ||
const DEFAULT_WRITE_TIMEOUT_BROWSER = 30000; | ||
function createXhrRequester() { | ||
function send(request) { | ||
return new Promise((resolve) => { | ||
const baseRequester = new XMLHttpRequest(); | ||
baseRequester.open(request.method, request.url, true); | ||
Object.keys(request.headers).forEach((key) => baseRequester.setRequestHeader(key, request.headers[key])); | ||
const createTimeout = (timeout, content) => { | ||
return setTimeout(() => { | ||
baseRequester.abort(); | ||
resolve({ | ||
status: 0, | ||
content, | ||
isTimedOut: true, | ||
}); | ||
}, timeout); | ||
}; | ||
const connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout'); | ||
let responseTimeout; | ||
baseRequester.onreadystatechange = () => { | ||
if (baseRequester.readyState > baseRequester.OPENED && | ||
responseTimeout === undefined) { | ||
clearTimeout(connectTimeout); | ||
responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout'); | ||
} | ||
}; | ||
baseRequester.onerror = () => { | ||
// istanbul ignore next | ||
if (baseRequester.status === 0) { | ||
clearTimeout(connectTimeout); | ||
clearTimeout(responseTimeout); | ||
resolve({ | ||
content: baseRequester.responseText || 'Network request failed', | ||
status: baseRequester.status, | ||
isTimedOut: false, | ||
}); | ||
} | ||
}; | ||
baseRequester.onload = () => { | ||
clearTimeout(connectTimeout); | ||
clearTimeout(responseTimeout); | ||
resolve({ | ||
content: baseRequester.responseText, | ||
status: baseRequester.status, | ||
isTimedOut: false, | ||
}); | ||
}; | ||
baseRequester.send(request.data); | ||
}); | ||
} | ||
return { send }; | ||
} | ||
function createRetryError(transporterStackTrace) { | ||
return { | ||
name: 'RetryError', | ||
message: 'Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.', | ||
transporterStackTrace, | ||
}; | ||
// This file is generated, manual changes will be lost - read more on https://github.com/algolia/api-clients-automation. | ||
const apiClientVersion = '5.0.0-alpha.1'; | ||
function getDefaultHosts(appId) { | ||
return [ | ||
{ | ||
url: `${appId}-dsn.algolia.net`, | ||
accept: 'read', | ||
protocol: 'https', | ||
}, | ||
{ | ||
url: `${appId}.algolia.net`, | ||
accept: 'write', | ||
protocol: 'https', | ||
}, | ||
].concat(shuffle([ | ||
{ | ||
url: `${appId}-1.algolianet.com`, | ||
accept: 'readWrite', | ||
protocol: 'https', | ||
}, | ||
{ | ||
url: `${appId}-2.algolianet.com`, | ||
accept: 'readWrite', | ||
protocol: 'https', | ||
}, | ||
{ | ||
url: `${appId}-3.algolianet.com`, | ||
accept: 'readWrite', | ||
protocol: 'https', | ||
}, | ||
])); | ||
} | ||
const createRecommendClient = options => { | ||
const appId = options.appId; | ||
const auth = createAuth(options.authMode !== undefined ? options.authMode : AuthMode.WithinHeaders, appId, options.apiKey); | ||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
function createRecommendClient({ appId: appIdOption, apiKey: apiKeyOption, authMode, algoliaAgents, ...options }) { | ||
const auth = createAuth(appIdOption, apiKeyOption, authMode); | ||
const transporter = createTransporter({ | ||
hosts: [ | ||
{ url: `${appId}-dsn.algolia.net`, accept: CallEnum.Read }, | ||
{ url: `${appId}.algolia.net`, accept: CallEnum.Write }, | ||
].concat(shuffle([ | ||
{ url: `${appId}-1.algolianet.com` }, | ||
{ url: `${appId}-2.algolianet.com` }, | ||
{ url: `${appId}-3.algolianet.com` }, | ||
])), | ||
hosts: getDefaultHosts(appIdOption), | ||
...options, | ||
headers: { | ||
algoliaAgent: getAlgoliaAgent({ | ||
algoliaAgents, | ||
client: 'Recommend', | ||
version: apiClientVersion, | ||
}), | ||
baseHeaders: { | ||
'content-type': 'text/plain', | ||
...auth.headers(), | ||
...{ 'content-type': 'application/x-www-form-urlencoded' }, | ||
...options.headers, | ||
...options.baseHeaders, | ||
}, | ||
queryParameters: { | ||
baseQueryParameters: { | ||
...auth.queryParameters(), | ||
...options.queryParameters, | ||
...options.baseQueryParameters, | ||
}, | ||
}); | ||
const base = { | ||
return { | ||
transporter, | ||
appId, | ||
/** | ||
* Get the value of the `algoliaAgent`, used by our libraries internally and telemetry system. | ||
*/ | ||
get _ua() { | ||
return transporter.algoliaAgent.value; | ||
}, | ||
/** | ||
* Adds a `segment` to the `x-algolia-agent` sent with every requests. | ||
* | ||
* @param segment - The algolia agent (user-agent) segment to add. | ||
* @param version - The version of the agent. | ||
*/ | ||
addAlgoliaAgent(segment, version) { | ||
transporter.userAgent.add({ segment, version }); | ||
transporter.algoliaAgent.add({ segment, version }); | ||
}, | ||
clearCache() { | ||
return Promise.all([ | ||
transporter.requestsCache.clear(), | ||
transporter.responsesCache.clear(), | ||
]).then(() => undefined); | ||
/** | ||
* This method allow you to send requests to the Algolia REST API. | ||
* | ||
* @summary Send requests to the Algolia REST API. | ||
* @param del - The del object. | ||
* @param del.path - The path of the API endpoint to target, anything after the /1 needs to be specified. | ||
* @param del.parameters - Query parameters to be applied to the current query. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
del({ path, parameters }, requestOptions) { | ||
if (!path) { | ||
throw new Error('Parameter `path` is required when calling `del`.'); | ||
} | ||
const requestPath = '/1{path}'.replace('{path}', path); | ||
const headers = {}; | ||
const queryParameters = parameters ? parameters : {}; | ||
const request = { | ||
method: 'DELETE', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
/** | ||
* This method allow you to send requests to the Algolia REST API. | ||
* | ||
* @summary Send requests to the Algolia REST API. | ||
* @param get - The get object. | ||
* @param get.path - The path of the API endpoint to target, anything after the /1 needs to be specified. | ||
* @param get.parameters - Query parameters to be applied to the current query. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
get({ path, parameters }, requestOptions) { | ||
if (!path) { | ||
throw new Error('Parameter `path` is required when calling `get`.'); | ||
} | ||
const requestPath = '/1{path}'.replace('{path}', path); | ||
const headers = {}; | ||
const queryParameters = parameters ? parameters : {}; | ||
const request = { | ||
method: 'GET', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
/** | ||
* Returns recommendations or trending results, for a specific model and `objectID`. | ||
* | ||
* @summary Get results. | ||
* @param getRecommendationsParams - The getRecommendationsParams object. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
getRecommendations(getRecommendationsParams, requestOptions) { | ||
if (!getRecommendationsParams) { | ||
throw new Error('Parameter `getRecommendationsParams` is required when calling `getRecommendations`.'); | ||
} | ||
if (!getRecommendationsParams.requests) { | ||
throw new Error('Parameter `getRecommendationsParams.requests` is required when calling `getRecommendations`.'); | ||
} | ||
const requestPath = '/1/indexes/*/recommendations'; | ||
const headers = {}; | ||
const queryParameters = {}; | ||
const request = { | ||
method: 'POST', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
data: getRecommendationsParams, | ||
useReadTransporter: true, | ||
cacheable: true, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
/** | ||
* This method allow you to send requests to the Algolia REST API. | ||
* | ||
* @summary Send requests to the Algolia REST API. | ||
* @param post - The post object. | ||
* @param post.path - The path of the API endpoint to target, anything after the /1 needs to be specified. | ||
* @param post.parameters - Query parameters to be applied to the current query. | ||
* @param post.body - The parameters to send with the custom request. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
post({ path, parameters, body }, requestOptions) { | ||
if (!path) { | ||
throw new Error('Parameter `path` is required when calling `post`.'); | ||
} | ||
const requestPath = '/1{path}'.replace('{path}', path); | ||
const headers = {}; | ||
const queryParameters = parameters ? parameters : {}; | ||
const request = { | ||
method: 'POST', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
data: body ? body : {}, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
/** | ||
* This method allow you to send requests to the Algolia REST API. | ||
* | ||
* @summary Send requests to the Algolia REST API. | ||
* @param put - The put object. | ||
* @param put.path - The path of the API endpoint to target, anything after the /1 needs to be specified. | ||
* @param put.parameters - Query parameters to be applied to the current query. | ||
* @param put.body - The parameters to send with the custom request. | ||
* @param requestOptions - The requestOptions to send along with the query, they will be merged with the transporter requestOptions. | ||
*/ | ||
put({ path, parameters, body }, requestOptions) { | ||
if (!path) { | ||
throw new Error('Parameter `path` is required when calling `put`.'); | ||
} | ||
const requestPath = '/1{path}'.replace('{path}', path); | ||
const headers = {}; | ||
const queryParameters = parameters ? parameters : {}; | ||
const request = { | ||
method: 'PUT', | ||
path: requestPath, | ||
queryParameters, | ||
headers, | ||
data: body ? body : {}, | ||
}; | ||
return transporter.request(request, requestOptions); | ||
}, | ||
}; | ||
return addMethods(base, options.methods); | ||
}; | ||
} | ||
const getRecommendations = base => { | ||
return (queries, requestOptions) => { | ||
const requests = queries.map(query => ({ | ||
...query, | ||
// The `threshold` param is required by the endpoint to make it easier | ||
// to provide a default value later, so we default it in the client | ||
// so that users don't have to provide a value. | ||
threshold: query.threshold || 0, | ||
})); | ||
return base.transporter.read({ | ||
method: MethodEnum.Post, | ||
path: '1/indexes/*/recommendations', | ||
data: { | ||
requests, | ||
}, | ||
cacheable: true, | ||
}, requestOptions); | ||
}; | ||
}; | ||
const getFrequentlyBoughtTogether = base => { | ||
return (queries, requestOptions) => { | ||
return getRecommendations(base)(queries.map(query => ({ | ||
...query, | ||
fallbackParameters: {}, | ||
model: 'bought-together', | ||
})), requestOptions); | ||
}; | ||
}; | ||
const getRelatedProducts = base => { | ||
return (queries, requestOptions) => { | ||
return getRecommendations(base)(queries.map(query => ({ | ||
...query, | ||
model: 'related-products', | ||
})), requestOptions); | ||
}; | ||
}; | ||
const getTrendingFacets = base => { | ||
return (queries, requestOptions) => { | ||
const requests = queries.map(query => ({ | ||
...query, | ||
model: 'trending-facets', | ||
// The `threshold` param is required by the endpoint to make it easier | ||
// to provide a default value later, so we default it in the client | ||
// so that users don't have to provide a value. | ||
threshold: query.threshold || 0, | ||
})); | ||
return base.transporter.read({ | ||
method: MethodEnum.Post, | ||
path: '1/indexes/*/recommendations', | ||
data: { | ||
requests, | ||
}, | ||
cacheable: true, | ||
}, requestOptions); | ||
}; | ||
}; | ||
const getTrendingItems = base => { | ||
return (queries, requestOptions) => { | ||
const requests = queries.map(query => ({ | ||
...query, | ||
model: 'trending-items', | ||
// The `threshold` param is required by the endpoint to make it easier | ||
// to provide a default value later, so we default it in the client | ||
// so that users don't have to provide a value. | ||
threshold: query.threshold || 0, | ||
})); | ||
return base.transporter.read({ | ||
method: MethodEnum.Post, | ||
path: '1/indexes/*/recommendations', | ||
data: { | ||
requests, | ||
}, | ||
cacheable: true, | ||
}, requestOptions); | ||
}; | ||
}; | ||
function recommend(appId, apiKey, options) { | ||
const commonOptions = { | ||
// This file is generated, manual changes will be lost - read more on https://github.com/algolia/api-clients-automation. | ||
function recommendClient(appId, apiKey, options) { | ||
if (!appId || typeof appId !== 'string') { | ||
throw new Error('`appId` is missing.'); | ||
} | ||
if (!apiKey || typeof apiKey !== 'string') { | ||
throw new Error('`apiKey` is missing.'); | ||
} | ||
return createRecommendClient({ | ||
appId, | ||
apiKey, | ||
timeouts: { | ||
connect: 1, | ||
read: 2, | ||
write: 30, | ||
connect: DEFAULT_CONNECT_TIMEOUT_BROWSER, | ||
read: DEFAULT_READ_TIMEOUT_BROWSER, | ||
write: DEFAULT_WRITE_TIMEOUT_BROWSER, | ||
}, | ||
requester: createBrowserXhrRequester(), | ||
logger: createConsoleLogger(LogLevelEnum.Error), | ||
responsesCache: createInMemoryCache(), | ||
requestsCache: createInMemoryCache({ serializable: false }), | ||
requester: createXhrRequester(), | ||
algoliaAgents: [{ segment: 'Browser' }], | ||
authMode: 'WithinQueryParameters', | ||
responsesCache: createMemoryCache(), | ||
requestsCache: createMemoryCache({ serializable: false }), | ||
hostsCache: createFallbackableCache({ | ||
caches: [ | ||
createBrowserLocalStorageCache({ key: `${version}-${appId}` }), | ||
createInMemoryCache(), | ||
createBrowserLocalStorageCache({ key: `${apiClientVersion}-${appId}` }), | ||
createMemoryCache(), | ||
], | ||
}), | ||
userAgent: createUserAgent(version) | ||
.add({ segment: 'Recommend', version }) | ||
.add({ segment: 'Browser' }), | ||
authMode: AuthMode.WithinQueryParameters, | ||
}; | ||
return createRecommendClient({ | ||
...commonOptions, | ||
...options, | ||
methods: { | ||
getFrequentlyBoughtTogether, | ||
getRecommendations, | ||
getRelatedProducts, | ||
getTrendingFacets, | ||
getTrendingItems, | ||
}, | ||
}); | ||
} | ||
// eslint-disable-next-line functional/immutable-data | ||
recommend.version = version; | ||
export default recommend; | ||
export { apiClientVersion, recommendClient }; |
@@ -1,2 +0,2 @@ | ||
/*! recommend.umd.js | 4.14.1 | © Algolia, inc. | https://github.com/algolia/algoliasearch-client-javascript */ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self)["@algolia/recommend"]=t()}(this,(function(){"use strict";function e(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function t(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function r(r){for(var n=1;n<arguments.length;n++){var o=null!=arguments[n]?arguments[n]:{};n%2?t(Object(o),!0).forEach((function(t){e(r,t,o[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(o)):t(Object(o)).forEach((function(e){Object.defineProperty(r,e,Object.getOwnPropertyDescriptor(o,e))}))}return r}function n(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if(!(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e)))return;var r=[],n=!0,o=!1,a=void 0;try{for(var u,i=e[Symbol.iterator]();!(n=(u=i.next()).done)&&(r.push(u.value),!t||r.length!==t);n=!0);}catch(e){o=!0,a=e}finally{try{n||null==i.return||i.return()}finally{if(o)throw a}}return r}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function o(e){return function(e){if(Array.isArray(e)){for(var t=0,r=new Array(e.length);t<e.length;t++)r[t]=e[t];return r}}(e)||function(e){if(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e))return Array.from(e)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}function a(e){var t,r="algoliasearch-client-js-".concat(e.key),o=function(){return void 0===t&&(t=e.localStorage||window.localStorage),t},a=function(){return JSON.parse(o().getItem(r)||"{}")};return{get:function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}};return Promise.resolve().then((function(){var r=JSON.stringify(e),n=a()[r];return Promise.all([n||t(),void 0!==n])})).then((function(e){var t=n(e,2),o=t[0],a=t[1];return Promise.all([o,a||r.miss(o)])})).then((function(e){return n(e,1)[0]}))},set:function(e,t){return Promise.resolve().then((function(){var n=a();return n[JSON.stringify(e)]=t,o().setItem(r,JSON.stringify(n)),t}))},delete:function(e){return Promise.resolve().then((function(){var t=a();delete t[JSON.stringify(e)],o().setItem(r,JSON.stringify(t))}))},clear:function(){return Promise.resolve().then((function(){o().removeItem(r)}))}}}function u(e){var t=o(e.caches),r=t.shift();return void 0===r?{get:function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}},o=t();return o.then((function(e){return Promise.all([e,r.miss(e)])})).then((function(e){return n(e,1)[0]}))},set:function(e,t){return Promise.resolve(t)},delete:function(e){return Promise.resolve()},clear:function(){return Promise.resolve()}}:{get:function(e,n){var o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}};return r.get(e,n,o).catch((function(){return u({caches:t}).get(e,n,o)}))},set:function(e,n){return r.set(e,n).catch((function(){return u({caches:t}).set(e,n)}))},delete:function(e){return r.delete(e).catch((function(){return u({caches:t}).delete(e)}))},clear:function(){return r.clear().catch((function(){return u({caches:t}).clear()}))}}}function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{serializable:!0},t={};return{get:function(r,n){var o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}},a=JSON.stringify(r);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);var u=n(),i=o&&o.miss||function(){return Promise.resolve()};return u.then((function(e){return i(e)})).then((function(){return u}))},set:function(r,n){return t[JSON.stringify(r)]=e.serializable?JSON.stringify(n):n,Promise.resolve(n)},delete:function(e){return delete t[JSON.stringify(e)],Promise.resolve()},clear:function(){return t={},Promise.resolve()}}}function s(e){for(var t=e.length-1;t>0;t--){var r=Math.floor(Math.random()*(t+1)),n=e[t];e[t]=e[r],e[r]=n}return e}var c={WithinQueryParameters:0,WithinHeaders:1},l=1,f=2,d=3;function h(e,t){var r=e||{},n=r.data||{};return Object.keys(r).forEach((function(e){-1===["timeout","headers","queryParameters","data","cacheable"].indexOf(e)&&(n[e]=r[e])})),{data:Object.entries(n).length>0?n:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var m={Read:1,Write:2,Any:3},p=1,g=2,v=3;function y(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:p;return r(r({},e),{},{status:t,lastUpdate:Date.now()})}function b(e){return"string"==typeof e?{protocol:"https",url:e,accept:m.Any}:{protocol:e.protocol||"https",url:e.url,accept:e.accept||m.Any}}var P="GET",O="POST";function q(e,t){return Promise.all(t.map((function(t){return e.get(t,(function(){return Promise.resolve(y(t))}))}))).then((function(e){var r=e.filter((function(e){return function(e){return e.status===p||Date.now()-e.lastUpdate>12e4}(e)})),n=e.filter((function(e){return function(e){return e.status===v&&Date.now()-e.lastUpdate<=12e4}(e)})),a=[].concat(o(r),o(n));return{getTimeout:function(e,t){return(0===n.length&&0===e?1:n.length+3+e)*t},statelessHosts:a.length>0?a.map((function(e){return b(e)})):t}}))}function S(e,t,n,a){var u=[],i=function(e,t){if(e.method===P||void 0===e.data&&void 0===t.data)return;var n=Array.isArray(e.data)?e.data:r(r({},e.data),t.data);return JSON.stringify(n)}(n,a),s=function(e,t){var n=r(r({},e.headers),t.headers),o={};return Object.keys(n).forEach((function(e){var t=n[e];o[e.toLowerCase()]=t})),o}(e,a),c=n.method,l=n.method!==P?{}:r(r({},n.data),a.data),f=r(r(r({"x-algolia-agent":e.userAgent.value},e.queryParameters),l),a.queryParameters),d=0,h=function t(r,o){var l=r.pop();if(void 0===l)throw{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:T(u)};var h={data:i,headers:s,method:c,url:w(l,n.path,f),connectTimeout:o(d,e.timeouts.connect),responseTimeout:o(d,a.timeout)},m=function(e){var t={request:h,response:e,host:l,triesLeft:r.length};return u.push(t),t},p={onSuccess:function(e){return function(e){try{return JSON.parse(e.content)}catch(t){throw function(e,t){return{name:"DeserializationError",message:e,response:t}}(t.message,e)}}(e)},onRetry:function(n){var a=m(n);return n.isTimedOut&&d++,Promise.all([e.logger.info("Retryable failure",A(a)),e.hostsCache.set(l,y(l,n.isTimedOut?v:g))]).then((function(){return t(r,o)}))},onFail:function(e){throw m(e),function(e,t){var r=e.content,n=e.status,o=r;try{o=JSON.parse(r).message}catch(e){}return function(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}(o,n,t)}(e,T(u))}};return e.requester.send(h).then((function(e){return function(e,t){return function(e){var t=e.status;return e.isTimedOut||function(e){var t=e.isTimedOut,r=e.status;return!t&&0==~~r}(e)||2!=~~(t/100)&&4!=~~(t/100)}(e)?t.onRetry(e):2==~~(e.status/100)?t.onSuccess(e):t.onFail(e)}(e,p)}))};return q(e.hostsCache,t).then((function(e){return h(o(e.statelessHosts).reverse(),e.getTimeout)}))}function j(e){var t={value:"Algolia for JavaScript (".concat(e,")"),add:function(e){var r="; ".concat(e.segment).concat(void 0!==e.version?" (".concat(e.version,")"):"");return-1===t.value.indexOf(r)&&(t.value="".concat(t.value).concat(r)),t}};return t}function w(e,t,r){var n,o=(n=r,Object.keys(n).map((function(e){return function(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),n=1;n<t;n++)r[n-1]=arguments[n];var o=0;return e.replace(/%s/g,(function(){return encodeURIComponent(r[o++])}))}("%s=%s",e,(t=n[e],"[object Object]"===Object.prototype.toString.call(t)||"[object Array]"===Object.prototype.toString.call(t)?JSON.stringify(n[e]):n[e]));var t})).join("&")),a="".concat(e.protocol,"://").concat(e.url,"/").concat("/"===t.charAt(0)?t.substr(1):t);return o.length&&(a+="?".concat(o)),a}function T(e){return e.map((function(e){return A(e)}))}function A(e){var t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return r(r({},e),{},{request:r(r({},e.request),{},{headers:r(r({},e.request.headers),t)})})}var C=function(e){var t=e.appId,o=function(e,t,r){var n={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers:function(){return e===c.WithinHeaders?n:{}},queryParameters:function(){return e===c.WithinQueryParameters?n:{}}}}(void 0!==e.authMode?e.authMode:c.WithinHeaders,t,e.apiKey),a=function(e){var t=e.hostsCache,r=e.logger,o=e.requester,a=e.requestsCache,u=e.responsesCache,i=e.timeouts,s=e.userAgent,c=e.hosts,l=e.queryParameters,f={hostsCache:t,logger:r,requester:o,requestsCache:a,responsesCache:u,timeouts:i,userAgent:s,headers:e.headers,queryParameters:l,hosts:c.map((function(e){return b(e)})),read:function(e,t){var r=h(t,f.timeouts.read),o=function(){return S(f,f.hosts.filter((function(e){return 0!=(e.accept&m.Read)})),e,r)};if(!0!==(void 0!==r.cacheable?r.cacheable:e.cacheable))return o();var a={request:e,mappedRequestOptions:r,transporter:{queryParameters:f.queryParameters,headers:f.headers}};return f.responsesCache.get(a,(function(){return f.requestsCache.get(a,(function(){return f.requestsCache.set(a,o()).then((function(e){return Promise.all([f.requestsCache.delete(a),e])}),(function(e){return Promise.all([f.requestsCache.delete(a),Promise.reject(e)])})).then((function(e){var t=n(e,2);t[0];return t[1]}))}))}),{miss:function(e){return f.responsesCache.set(a,e)}})},write:function(e,t){return S(f,f.hosts.filter((function(e){return 0!=(e.accept&m.Write)})),e,h(t,f.timeouts.write))}};return f}(r(r({hosts:[{url:"".concat(t,"-dsn.algolia.net"),accept:m.Read},{url:"".concat(t,".algolia.net"),accept:m.Write}].concat(s([{url:"".concat(t,"-1.algolianet.com")},{url:"".concat(t,"-2.algolianet.com")},{url:"".concat(t,"-3.algolianet.com")}]))},e),{},{headers:r(r(r({},o.headers()),{"content-type":"application/x-www-form-urlencoded"}),e.headers),queryParameters:r(r({},o.queryParameters()),e.queryParameters)}));return function(e,t){return t?(Object.keys(t).forEach((function(r){e[r]=t[r](e)})),e):e}({transporter:a,appId:t,addAlgoliaAgent:function(e,t){a.userAgent.add({segment:e,version:t})},clearCache:function(){return Promise.all([a.requestsCache.clear(),a.responsesCache.clear()]).then((function(){}))}},e.methods)},N=function(e){return function(t,n){var o=t.map((function(e){return r(r({},e),{},{threshold:e.threshold||0})}));return e.transporter.read({method:O,path:"1/indexes/*/recommendations",data:{requests:o},cacheable:!0},n)}},k=function(e){return function(t,n){return N(e)(t.map((function(e){return r(r({},e),{},{fallbackParameters:{},model:"bought-together"})})),n)}},x=function(e){return function(t,n){return N(e)(t.map((function(e){return r(r({},e),{},{model:"related-products"})})),n)}},J=function(e){return function(t,n){var o=t.map((function(e){return r(r({},e),{},{model:"trending-facets",threshold:e.threshold||0})}));return e.transporter.read({method:O,path:"1/indexes/*/recommendations",data:{requests:o},cacheable:!0},n)}},E=function(e){return function(t,n){var o=t.map((function(e){return r(r({},e),{},{model:"trending-items",threshold:e.threshold||0})}));return e.transporter.read({method:O,path:"1/indexes/*/recommendations",data:{requests:o},cacheable:!0},n)}};function R(e,t,n){var o,s={appId:e,apiKey:t,timeouts:{connect:1,read:2,write:30},requester:{send:function(e){return new Promise((function(t){var r=new XMLHttpRequest;r.open(e.method,e.url,!0),Object.keys(e.headers).forEach((function(t){return r.setRequestHeader(t,e.headers[t])}));var n,o=function(e,n){return setTimeout((function(){r.abort(),t({status:0,content:n,isTimedOut:!0})}),1e3*e)},a=o(e.connectTimeout,"Connection timeout");r.onreadystatechange=function(){r.readyState>r.OPENED&&void 0===n&&(clearTimeout(a),n=o(e.responseTimeout,"Socket timeout"))},r.onerror=function(){0===r.status&&(clearTimeout(a),clearTimeout(n),t({content:r.responseText||"Network request failed",status:r.status,isTimedOut:!1}))},r.onload=function(){clearTimeout(a),clearTimeout(n),t({content:r.responseText,status:r.status,isTimedOut:!1})},r.send(e.data)}))}},logger:(o=d,{debug:function(e,t){return l>=o&&console.debug(e,t),Promise.resolve()},info:function(e,t){return f>=o&&console.info(e,t),Promise.resolve()},error:function(e,t){return console.error(e,t),Promise.resolve()}}),responsesCache:i(),requestsCache:i({serializable:!1}),hostsCache:u({caches:[a({key:"".concat("4.14.1","-").concat(e)}),i()]}),userAgent:j("4.14.1").add({segment:"Recommend",version:"4.14.1"}).add({segment:"Browser"}),authMode:c.WithinQueryParameters};return C(r(r(r({},s),n),{},{methods:{getFrequentlyBoughtTogether:k,getRecommendations:N,getRelatedProducts:x,getTrendingFacets:J,getTrendingItems:E}}))}return R.version="4.14.1",R})); | ||
/*! recommend.umd.js | 5.0.0-alpha.1 | © Algolia, inc. | https://github.com/algolia/algoliasearch-client-javascript */ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@algolia/recommend"]={})}(this,(function(e){"use strict";function t(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function r(e){let t;const r=`algolia-client-js-${e.key}`;function a(){return void 0===t&&(t=e.localStorage||window.localStorage),t}function s(){return JSON.parse(a().getItem(r)||"{}")}return{get:(e,t,r={miss:()=>Promise.resolve()})=>Promise.resolve().then((()=>{const r=JSON.stringify(e),a=s()[r];return Promise.all([a||t(),void 0!==a])})).then((([e,t])=>Promise.all([e,t||r.miss(e)]))).then((([e])=>e)),set:(e,t)=>Promise.resolve().then((()=>{const n=s();return n[JSON.stringify(e)]=t,a().setItem(r,JSON.stringify(n)),t})),delete:e=>Promise.resolve().then((()=>{const t=s();delete t[JSON.stringify(e)],a().setItem(r,JSON.stringify(t))})),clear:()=>Promise.resolve().then((()=>{a().removeItem(r)}))}}function a(e){const t=[...e.caches],r=t.shift();return void 0===r?{get:(e,t,r={miss:()=>Promise.resolve()})=>t().then((e=>Promise.all([e,r.miss(e)]))).then((([e])=>e)),set:(e,t)=>Promise.resolve(t),delete:e=>Promise.resolve(),clear:()=>Promise.resolve()}:{get:(e,s,n={miss:()=>Promise.resolve()})=>r.get(e,s,n).catch((()=>a({caches:t}).get(e,s,n))),set:(e,s)=>r.set(e,s).catch((()=>a({caches:t}).set(e,s))),delete:e=>r.delete(e).catch((()=>a({caches:t}).delete(e))),clear:()=>r.clear().catch((()=>a({caches:t}).clear()))}}function s(e={serializable:!0}){let t={};return{get(r,a,s={miss:()=>Promise.resolve()}){const n=JSON.stringify(r);if(n in t)return Promise.resolve(e.serializable?JSON.parse(t[n]):t[n]);const o=a();return o.then((e=>s.miss(e))).then((()=>o))},set:(r,a)=>(t[JSON.stringify(r)]=e.serializable?JSON.stringify(a):a,Promise.resolve(a)),delete:e=>(delete t[JSON.stringify(e)],Promise.resolve()),clear:()=>(t={},Promise.resolve())}}const n=12e4;function o(e,t="up"){const r=Date.now();return{...e,status:t,lastUpdate:r,isUp:function(){return"up"===t||Date.now()-r>n},isTimedOut:function(){return"timed out"===t&&Date.now()-r<=n}}}function i(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}class c extends Error{constructor(e,t){super(e),i(this,"name","AlgoliaError"),t&&(this.name=t)}}class u extends c{constructor(e,t,r){super(e,r),i(this,"stackTrace",void 0),this.stackTrace=t}}class l extends u{constructor(e){super("Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",e,"RetryError")}}class p extends u{constructor(e,t,r){super(e,r,"ApiError"),i(this,"status",void 0),this.status=t}}class d extends c{constructor(e,t){super(e,"DeserializationError"),i(this,"response",void 0),this.response=t}}function h(e,t,r){const a=function(e){const t=e=>"[object Object]"===Object.prototype.toString.call(e)||"[object Array]"===Object.prototype.toString.call(e);return Object.keys(e).map((r=>`${r}=${t(e[r])?JSON.stringify(e[r]):e[r]}`)).join("&")}(r);let s=`${e.protocol}://${e.url}/${"/"===t.charAt(0)?t.substr(1):t}`;return a.length&&(s+=`?${a}`),s}function m(e){const t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return{...e,request:{...e.request,headers:{...e.request.headers,...t}}}}function f({hosts:e,hostsCache:t,baseHeaders:r,baseQueryParameters:a,algoliaAgent:s,timeouts:n,requester:i,requestsCache:c,responsesCache:u}){async function f(c,u,f=!0){const g=[],y=function(e,t){if("GET"===e.method||void 0===e.data&&void 0===t.data)return;const r=Array.isArray(e.data)?e.data:{...e.data,...t.data};return JSON.stringify(r)}(c,u),O=function(e,t,r){const a={Accept:"application/json",...e,...t,...r},s={};return Object.keys(a).forEach((e=>{const t=a[e];s[e.toLowerCase()]=t})),s}(r,c.headers,u.headers),b="GET"===c.method?{...c.data,...u.data}:{},P={"x-algolia-agent":s.value,...a,...c.queryParameters,...b};if(u&&u.queryParameters)for(const e of Object.keys(u.queryParameters))u.queryParameters[e]&&"[object Object]"!==Object.prototype.toString.call(u.queryParameters[e])?P[e]=u.queryParameters[e].toString():P[e]=u.queryParameters[e];let v=0;const w=async(e,r)=>{const a=e.pop();if(void 0===a)throw new l(function(e){return e.map((e=>m(e)))}(g));let s=u.timeout;void 0===s&&(s=f?n.read:n.write);const b={data:y,headers:O,method:c.method,url:h(a,c.path,P),connectTimeout:r(v,n.connect),responseTimeout:r(v,s)},j=t=>{const r={request:b,response:t,host:a,triesLeft:e.length};return g.push(r),r},q=await i.send(b);if(function({isTimedOut:e,status:t}){return e||function({isTimedOut:e,status:t}){return!e&&0==~~t}({isTimedOut:e,status:t})||2!=~~(t/100)&&4!=~~(t/100)}(q)){const s=j(q);return q.isTimedOut&&v++,console.log("Retryable failure",m(s)),await t.set(a,o(a,q.isTimedOut?"timed out":"down")),w(e,r)}if(function({status:e}){return 2==~~(e/100)}(q))return function(e){try{return JSON.parse(e.content)}catch(t){throw new d(t.message,e)}}(q);throw j(q),function({content:e,status:t},r){let a=e;try{a=JSON.parse(e).message}catch(e){}return new p(a,t,r)}(q,g)},j=e.filter((e=>"readWrite"===e.accept||(f?"read"===e.accept:"write"===e.accept))),q=await async function(e){const r=await Promise.all(e.map((e=>t.get(e,(()=>Promise.resolve(o(e))))))),a=r.filter((e=>e.isUp())),s=r.filter((e=>e.isTimedOut())),n=[...a,...s];return{hosts:n.length>0?n:e,getTimeout:(e,t)=>(0===s.length&&0===e?1:s.length+3+e)*t}}(j);return w([...q.hosts].reverse(),q.getTimeout)}return{hostsCache:t,requester:i,timeouts:n,algoliaAgent:s,baseHeaders:r,baseQueryParameters:a,hosts:e,request:function(e,t={}){const s=e.useReadTransporter||"GET"===e.method;if(!s)return f(e,t,s);const n=()=>f(e,t);if(!0!==(t.cacheable||e.cacheable))return n();const o={request:e,requestOptions:t,transporter:{queryParameters:a,headers:r}};return u.get(o,(()=>c.get(o,(()=>c.set(o,n()).then((e=>Promise.all([c.delete(o),e])),(e=>Promise.all([c.delete(o),Promise.reject(e)]))).then((([e,t])=>t))))),{miss:e=>u.set(o,e)})},requestsCache:c,responsesCache:u}}function g({algoliaAgents:e,client:t,version:r}){const a=function(e){const t={value:`Algolia for JavaScript (${e})`,add(e){const r=`; ${e.segment}${void 0!==e.version?` (${e.version})`:""}`;return-1===t.value.indexOf(r)&&(t.value=`${t.value}${r}`),t}};return t}(r).add({segment:t,version:r});return e.forEach((e=>a.add(e))),a}function y(e,t){if(null==e)return{};var r,a,s=function(e,t){if(null==e)return{};var r,a,s={},n=Object.keys(e);for(a=0;a<n.length;a++)r=n[a],t.indexOf(r)>=0||(s[r]=e[r]);return s}(e,t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);for(a=0;a<n.length;a++)r=n[a],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(s[r]=e[r])}return s}var O=["appId","apiKey","authMode","algoliaAgents"];function b(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,a)}return r}function P(e){for(var r=1;r<arguments.length;r++){var a=null!=arguments[r]?arguments[r]:{};r%2?b(Object(a),!0).forEach((function(r){t(e,r,a[r])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(a)):b(Object(a)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(a,t))}))}return e}var v="5.0.0-alpha.1";function w(e){return[{url:"".concat(e,"-dsn.algolia.net"),accept:"read",protocol:"https"},{url:"".concat(e,".algolia.net"),accept:"write",protocol:"https"}].concat(function(e){const t=e;for(let r=e.length-1;r>0;r--){const a=Math.floor(Math.random()*(r+1)),s=e[r];t[r]=e[a],t[a]=s}return t}([{url:"".concat(e,"-1.algolianet.com"),accept:"readWrite",protocol:"https"},{url:"".concat(e,"-2.algolianet.com"),accept:"readWrite",protocol:"https"},{url:"".concat(e,"-3.algolianet.com"),accept:"readWrite",protocol:"https"}]))}function j(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,a)}return r}e.apiClientVersion=v,e.recommendClient=function(e,n,o){if(!e||"string"!=typeof e)throw new Error("`appId` is missing.");if(!n||"string"!=typeof n)throw new Error("`apiKey` is missing.");return function(e){var t=e.appId,r=e.apiKey,a=e.authMode,s=e.algoliaAgents,n=y(e,O),o=function(e,t,r="WithinHeaders"){const a={"x-algolia-api-key":t,"x-algolia-application-id":e};return{headers:()=>"WithinHeaders"===r?a:{},queryParameters:()=>"WithinQueryParameters"===r?a:{}}}(t,r,a),i=f(P(P({hosts:w(t)},n),{},{algoliaAgent:g({algoliaAgents:s,client:"Recommend",version:v}),baseHeaders:P(P({"content-type":"text/plain"},o.headers()),n.baseHeaders),baseQueryParameters:P(P({},o.queryParameters()),n.baseQueryParameters)}));return{transporter:i,get _ua(){return i.algoliaAgent.value},addAlgoliaAgent:function(e,t){i.algoliaAgent.add({segment:e,version:t})},del:function(e,t){var r=e.path,a=e.parameters;if(!r)throw new Error("Parameter `path` is required when calling `del`.");var s={method:"DELETE",path:"/1{path}".replace("{path}",r),queryParameters:a||{},headers:{}};return i.request(s,t)},get:function(e,t){var r=e.path,a=e.parameters;if(!r)throw new Error("Parameter `path` is required when calling `get`.");var s={method:"GET",path:"/1{path}".replace("{path}",r),queryParameters:a||{},headers:{}};return i.request(s,t)},getRecommendations:function(e,t){if(!e)throw new Error("Parameter `getRecommendationsParams` is required when calling `getRecommendations`.");if(!e.requests)throw new Error("Parameter `getRecommendationsParams.requests` is required when calling `getRecommendations`.");var r={method:"POST",path:"/1/indexes/*/recommendations",queryParameters:{},headers:{},data:e,useReadTransporter:!0,cacheable:!0};return i.request(r,t)},post:function(e,t){var r=e.path,a=e.parameters,s=e.body;if(!r)throw new Error("Parameter `path` is required when calling `post`.");var n={method:"POST",path:"/1{path}".replace("{path}",r),queryParameters:a||{},headers:{},data:s||{}};return i.request(n,t)},put:function(e,t){var r=e.path,a=e.parameters,s=e.body;if(!r)throw new Error("Parameter `path` is required when calling `put`.");var n={method:"PUT",path:"/1{path}".replace("{path}",r),queryParameters:a||{},headers:{},data:s||{}};return i.request(n,t)}}}(function(e){for(var r=1;r<arguments.length;r++){var a=null!=arguments[r]?arguments[r]:{};r%2?j(Object(a),!0).forEach((function(r){t(e,r,a[r])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(a)):j(Object(a)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(a,t))}))}return e}({appId:e,apiKey:n,timeouts:{connect:1e3,read:2e3,write:3e4},requester:{send:function(e){return new Promise((t=>{const r=new XMLHttpRequest;r.open(e.method,e.url,!0),Object.keys(e.headers).forEach((t=>r.setRequestHeader(t,e.headers[t])));const a=(e,a)=>setTimeout((()=>{r.abort(),t({status:0,content:a,isTimedOut:!0})}),e),s=a(e.connectTimeout,"Connection timeout");let n;r.onreadystatechange=()=>{r.readyState>r.OPENED&&void 0===n&&(clearTimeout(s),n=a(e.responseTimeout,"Socket timeout"))},r.onerror=()=>{0===r.status&&(clearTimeout(s),clearTimeout(n),t({content:r.responseText||"Network request failed",status:r.status,isTimedOut:!1}))},r.onload=()=>{clearTimeout(s),clearTimeout(n),t({content:r.responseText,status:r.status,isTimedOut:!1})},r.send(e.data)}))}},algoliaAgents:[{segment:"Browser"}],authMode:"WithinQueryParameters",responsesCache:s(),requestsCache:s({serializable:!1}),hostsCache:a({caches:[r({key:"".concat(v,"-").concat(e)}),s()]})},o))}})); |
@@ -1,3 +0,2 @@ | ||
/* eslint-disable import/no-unresolved*/ | ||
export * from './dist/recommend'; | ||
export { default } from './dist/recommend'; | ||
// eslint-disable-next-line import/no-unresolved | ||
export * from './dist/builds/node'; |
17
index.js
@@ -1,15 +0,2 @@ | ||
/* eslint-disable functional/immutable-data, import/no-commonjs */ | ||
const recommend = require('./dist/recommend.cjs.js'); | ||
/** | ||
* The Common JS build is the default entry point for the Node environment. Keep in | ||
* in mind, that for the browser environment, we hint the bundler to use the UMD | ||
* build instead as specified on the key `browser` of our `package.json` file. | ||
*/ | ||
module.exports = recommend; | ||
/** | ||
* In addition, we also set explicitly the default export below making | ||
* this Common JS module in compliance with es6 modules specification. | ||
*/ | ||
module.exports.default = recommend; | ||
// eslint-disable-next-line import/no-commonjs,import/extensions | ||
module.exports = require('./dist/recommend.cjs.js'); |
{ | ||
"name": "@algolia/recommend", | ||
"version": "4.14.1", | ||
"private": false, | ||
"description": "The perfect starting point to integrate Algolia Recommend within your JavaScript project.", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/algolia/algoliasearch-client-javascript.git" | ||
}, | ||
"version": "5.0.0-alpha.1", | ||
"description": "JavaScript client for recommend", | ||
"repository": "algolia/algoliasearch-client-javascript", | ||
"license": "MIT", | ||
"sideEffects": false, | ||
"author": "Algolia", | ||
"main": "index.js", | ||
"jsdelivr": "./dist/recommend.umd.js", | ||
"unpkg": "./dist/recommend.umd.js", | ||
"browser": { | ||
"./index.js": "./dist/recommend.umd.js" | ||
}, | ||
"jsdelivr": "dist/recommend.umd.js", | ||
"unpkg": "dist/recommend.umd.js", | ||
"module": "dist/recommend.esm.node.js", | ||
"browser": "dist/recommend.umd.js", | ||
"types": "index.d.ts", | ||
"files": [ | ||
"dist", | ||
"model", | ||
"index.js", | ||
"index.d.ts" | ||
], | ||
"scripts": { | ||
"clean": "rm -rf ./dist" | ||
}, | ||
"dependencies": { | ||
"@algolia/cache-browser-local-storage": "4.14.1", | ||
"@algolia/cache-common": "4.14.1", | ||
"@algolia/cache-in-memory": "4.14.1", | ||
"@algolia/client-common": "4.14.1", | ||
"@algolia/client-search": "4.14.1", | ||
"@algolia/logger-common": "4.14.1", | ||
"@algolia/logger-console": "4.14.1", | ||
"@algolia/requester-browser-xhr": "4.14.1", | ||
"@algolia/requester-common": "4.14.1", | ||
"@algolia/requester-node-http": "4.14.1", | ||
"@algolia/transporter": "4.14.1" | ||
"@algolia/client-common": "5.0.0-alpha.1", | ||
"@algolia/requester-browser-xhr": "5.0.0-alpha.1", | ||
"@algolia/requester-node-http": "5.0.0-alpha.1" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "16.11.45", | ||
"typescript": "4.7.4" | ||
}, | ||
"engines": { | ||
"node": ">= 14.0.0" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
158760
3
172
3197
1
2
2
1
0
1
2
+ Added@algolia/client-common@5.0.0-alpha.1(transitive)
+ Added@algolia/requester-browser-xhr@5.0.0-alpha.1(transitive)
+ Added@algolia/requester-node-http@5.0.0-alpha.1(transitive)
- Removed@algolia/cache-common@4.14.1
- Removed@algolia/cache-in-memory@4.14.1
- Removed@algolia/client-search@4.14.1
- Removed@algolia/logger-common@4.14.1
- Removed@algolia/logger-console@4.14.1
- Removed@algolia/requester-common@4.14.1
- Removed@algolia/transporter@4.14.1
- Removed@algolia/cache-browser-local-storage@4.14.1(transitive)
- Removed@algolia/cache-common@4.14.1(transitive)
- Removed@algolia/cache-in-memory@4.14.1(transitive)
- Removed@algolia/client-common@4.14.1(transitive)
- Removed@algolia/client-search@4.14.1(transitive)
- Removed@algolia/logger-common@4.14.1(transitive)
- Removed@algolia/logger-console@4.14.1(transitive)
- Removed@algolia/requester-browser-xhr@4.14.1(transitive)
- Removed@algolia/requester-common@4.14.1(transitive)
- Removed@algolia/requester-node-http@4.14.1(transitive)
- Removed@algolia/transporter@4.14.1(transitive)