diff --git a/clients/algoliasearch-client-javascript/package.json b/clients/algoliasearch-client-javascript/package.json index ae4291e9b9b..f79dff19799 100644 --- a/clients/algoliasearch-client-javascript/package.json +++ b/clients/algoliasearch-client-javascript/package.json @@ -10,7 +10,7 @@ "build:many": "lerna run build --skip-nx-cache --include-dependencies --scope ${0:-'{@algolia/*,algoliasearch}'}", "clean": "lerna run clean --include-dependencies", "release:bump": "lerna version ${0:-patch} --no-changelog --no-git-tag-version --no-push --exact --force-publish --yes", - "release:publish": "tsc --project tsconfig.script.json && node dist/scripts/publish.js", + "release:publish": "tsc --project scripts/tsconfig.json && node scripts/dist/scripts/publish.js", "test": "lerna run test $*", "test:size": "bundlesize" }, diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/browser-local-storage-cache.test.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/browser-local-storage-cache.test.ts index 4c39edd6723..77fdb521f5b 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/browser-local-storage-cache.test.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/browser-local-storage-cache.test.ts @@ -26,16 +26,12 @@ describe('browser local storage cache', () => { const cache = createBrowserLocalStorageCache({ key: version }); const defaultValue = (): DefaultValue => Promise.resolve({ bar: 1 }); - expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject( - { bar: 1 } - ); + expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 1 }); expect(missMock.mock.calls.length).toBe(1); await cache.set({ key: 'foo' }, { foo: 2 }); - expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject( - { foo: 2 } - ); + expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ foo: 2 }); expect(missMock.mock.calls.length).toBe(1); }); @@ -51,7 +47,7 @@ describe('browser local storage cache', () => { expect( await cache.get({ key: 'foo' }, defaultValue, { miss: () => Promise.resolve(missMock()), - }) + }), ).toMatchObject({ bar: 1 }); expect(missMock.mock.calls.length).toBe(0); @@ -65,9 +61,7 @@ describe('browser local storage cache', () => { const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 }); - expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject( - { bar: 2 } - ); + expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 2 }); expect(missMock.mock.calls.length).toBe(1); }); @@ -83,7 +77,7 @@ describe('browser local storage cache', () => { expect( await cache.get({ key: 'foo' }, defaultValue, { miss: () => Promise.resolve(missMock()), - }) + }), ).toMatchObject({ bar: 2 }); expect(missMock.mock.calls.length).toBe(1); @@ -102,7 +96,7 @@ describe('browser local storage cache', () => { expect( await cache.get({ key: 'foo' }, defaultValue, { miss: () => Promise.resolve(missMock()), - }) + }), ).toMatchObject({ bar: 2 }); expect(missMock.mock.calls.length).toBe(1); @@ -111,8 +105,7 @@ describe('browser local storage cache', () => { }); it('do throws localstorage exceptions on access', async () => { - const message = - "Failed to read the 'localStorage' property from 'Window': Access is denied for this document."; + const message = "Failed to read the 'localStorage' property from 'Window': Access is denied for this document."; const cache = createBrowserLocalStorageCache( new Proxy( { key: 'foo' }, @@ -125,20 +118,16 @@ describe('browser local storage cache', () => { // Simulates a window.localStorage access. throw new DOMException(message); }, - } - ) + }, + ), ); const key = { foo: 'bar' }; const value = 'foo'; const fallback = 'bar'; await expect(cache.delete(key)).rejects.toEqual(new DOMException(message)); - await expect(cache.set(key, value)).rejects.toEqual( - new DOMException(message) - ); - await expect( - cache.get(key, () => Promise.resolve(fallback)) - ).rejects.toEqual(new DOMException(message)); + await expect(cache.set(key, value)).rejects.toEqual(new DOMException(message)); + await expect(cache.get(key, () => Promise.resolve(fallback))).rejects.toEqual(new DOMException(message)); }); it('do throws localstorage exceptions after access', async () => { @@ -153,9 +142,7 @@ describe('browser local storage cache', () => { await expect(cache.delete(key)).rejects.toEqual(new Error(message)); await expect(cache.set(key, value)).rejects.toEqual(new Error(message)); - await expect( - cache.get(key, () => Promise.resolve(fallback)) - ).rejects.toEqual(new Error(message)); + await expect(cache.get(key, () => Promise.resolve(fallback))).rejects.toEqual(new Error(message)); }); it('creates a namespace within local storage', async () => { @@ -175,12 +162,8 @@ describe('browser local storage cache', () => { }, }); - const localStorageValue = localStorage.getItem( - `algolia-client-js-${version}` - ); + const localStorageValue = localStorage.getItem(`algolia-client-js-${version}`); - expect(JSON.parse(localStorageValue ? localStorageValue : '{}')).toEqual( - expectedValue - ); + expect(JSON.parse(localStorageValue ? localStorageValue : '{}')).toEqual(expectedValue); }); }); diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/memory-cache.test.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/memory-cache.test.ts index 80671616bb1..648c7bc7875 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/memory-cache.test.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/memory-cache.test.ts @@ -16,18 +16,14 @@ describe('memory cache', () => { const cache = createMemoryCache(); const defaultValue = (): DefaultValue => Promise.resolve({ bar: 1 }); - expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject( - { - bar: 1, - } - ); + expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ + bar: 1, + }); await cache.set({ key: 'foo' }, { foo: 2 }); expect(missMock.mock.calls.length).toBe(1); - expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject( - { foo: 2 } - ); + expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ foo: 2 }); expect(missMock.mock.calls.length).toBe(1); }); @@ -54,9 +50,7 @@ describe('memory cache', () => { const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 }); - expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject( - { bar: 2 } - ); + expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 2 }); expect(missMock.mock.calls.length).toBe(1); }); @@ -68,9 +62,7 @@ describe('memory cache', () => { const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 }); - expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject( - { bar: 2 } - ); + expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 2 }); expect(missMock.mock.calls.length).toBe(1); }); @@ -82,9 +74,7 @@ describe('memory cache', () => { const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 }); - expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject( - { bar: 2 } - ); + expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 2 }); expect(missMock.mock.calls.length).toBe(1); }); }); diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/null-cache.test.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/null-cache.test.ts index bf862152116..57968e6ce50 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/null-cache.test.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/cache/null-cache.test.ts @@ -18,11 +18,9 @@ describe('null cache', () => { await cache.set({ key: 'key' }, { foo: 10 }); - expect(await cache.get({ key: 'key' }, defaultValue, events)).toMatchObject( - { - bar: 12, - } - ); + expect(await cache.get({ key: 'key' }, defaultValue, events)).toMatchObject({ + bar: 12, + }); expect(missMock.mock.calls.length).toBe(1); }); @@ -30,11 +28,9 @@ describe('null cache', () => { it('returns default value', async () => { const defaultValue = (): DefaultValue => Promise.resolve({ bar: 12 }); - expect(await cache.get({ foo: 'foo' }, defaultValue, events)).toMatchObject( - { - bar: 12, - } - ); + expect(await cache.get({ foo: 'foo' }, defaultValue, events)).toMatchObject({ + bar: 12, + }); expect(missMock.mock.calls.length).toBe(1); }); diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/create-iterable-promise.test.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/create-iterable-promise.test.ts index 80b2430ac35..82b7dcfc1be 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/create-iterable-promise.test.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/__tests__/create-iterable-promise.test.ts @@ -156,9 +156,7 @@ describe('createIterablePromise', () => { validate: () => false, }); - await expect(promise).rejects.toEqual( - expect.objectContaining({ message: 'nope' }) - ); + await expect(promise).rejects.toEqual(expect.objectContaining({ message: 'nope' })); }); it('gets the rejection of the given promise via throw', async () => { @@ -178,9 +176,7 @@ describe('createIterablePromise', () => { validate: () => false, }); - await expect(promise).rejects.toEqual( - expect.objectContaining({ message: 'nope' }) - ); + await expect(promise).rejects.toEqual(expect.objectContaining({ message: 'nope' })); }); it('rejects with the given `message` when `validate` hits', async () => { @@ -204,7 +200,7 @@ describe('createIterablePromise', () => { await expect(promise).rejects.toEqual( expect.objectContaining({ message: 'Error is thrown: 3/3', - }) + }), ); expect(calls).toBe(MAX_RETRIES); }); @@ -230,7 +226,7 @@ describe('createIterablePromise', () => { await expect(promise).rejects.toEqual( expect.objectContaining({ message: 'Error is thrown: 3/3', - }) + }), ); expect(calls).toBe(MAX_RETRIES); }); diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createBrowserLocalStorageCache.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createBrowserLocalStorageCache.ts index 414c5c0fa98..5035ac2a96b 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createBrowserLocalStorageCache.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createBrowserLocalStorageCache.ts @@ -1,13 +1,6 @@ -import type { - BrowserLocalStorageCacheItem, - BrowserLocalStorageOptions, - Cache, - CacheEvents, -} from '../types'; - -export function createBrowserLocalStorageCache( - options: BrowserLocalStorageOptions -): Cache { +import type { BrowserLocalStorageCacheItem, BrowserLocalStorageOptions, Cache, CacheEvents } from '../types'; + +export function createBrowserLocalStorageCache(options: BrowserLocalStorageOptions): Cache { let storage: 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}`; @@ -35,7 +28,7 @@ export function createBrowserLocalStorageCache( const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries( Object.entries(namespace).filter(([, cacheItem]) => { return cacheItem.timestamp !== undefined; - }) + }), ); setNamespace(filteredNamespaceWithoutOldFormattedCacheItems); @@ -45,14 +38,12 @@ export function createBrowserLocalStorageCache( } const filteredNamespaceWithoutExpiredItems = Object.fromEntries( - Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter( - ([, cacheItem]) => { - const currentTimestamp = new Date().getTime(); - const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp; - - return !isExpired; - } - ) + Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => { + const currentTimestamp = new Date().getTime(); + const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp; + + return !isExpired; + }), ); setNamespace(filteredNamespaceWithoutExpiredItems); @@ -64,21 +55,16 @@ export function createBrowserLocalStorageCache( defaultValue: () => Promise, events: CacheEvents = { miss: () => Promise.resolve(), - } + }, ): Promise { return Promise.resolve() .then(() => { removeOutdatedCacheItems(); - return getNamespace>()[ - JSON.stringify(key) - ]; + return getNamespace>()[JSON.stringify(key)]; }) .then((value) => { - return Promise.all([ - value ? value.value : defaultValue(), - value !== undefined, - ]); + return Promise.all([value ? value.value : defaultValue(), value !== undefined]); }) .then(([value, exists]) => { return Promise.all([value, exists || events.miss(value)]); @@ -86,10 +72,7 @@ export function createBrowserLocalStorageCache( .then(([value]) => value); }, - set( - key: Record | string, - value: TValue - ): Promise { + set(key: Record | string, value: TValue): Promise { return Promise.resolve().then(() => { const namespace = getNamespace(); diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createFallbackableCache.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createFallbackableCache.ts index 682eacd41ce..e17a0e763c0 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createFallbackableCache.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createFallbackableCache.ts @@ -2,9 +2,7 @@ import type { FallbackableCacheOptions, Cache, CacheEvents } from '../types'; import { createNullCache } from './createNullCache'; -export function createFallbackableCache( - options: FallbackableCacheOptions -): Cache { +export function createFallbackableCache(options: FallbackableCacheOptions): Cache { const caches = [...options.caches]; const current = caches.shift(); @@ -18,21 +16,14 @@ export function createFallbackableCache( defaultValue: () => Promise, events: CacheEvents = { miss: (): Promise => Promise.resolve(), - } + }, ): Promise { return current.get(key, defaultValue, events).catch(() => { - return createFallbackableCache({ caches }).get( - key, - defaultValue, - events - ); + return createFallbackableCache({ caches }).get(key, defaultValue, events); }); }, - set( - key: Record | string, - value: TValue - ): Promise { + set(key: Record | string, value: TValue): Promise { return current.set(key, value).catch(() => { return createFallbackableCache({ caches }).set(key, value); }); diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createMemoryCache.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createMemoryCache.ts index 30074cb2683..8c0c89f9b6a 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createMemoryCache.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createMemoryCache.ts @@ -1,8 +1,6 @@ import type { Cache, CacheEvents, MemoryCacheOptions } from '../types'; -export function createMemoryCache( - options: MemoryCacheOptions = { serializable: true } -): Cache { +export function createMemoryCache(options: MemoryCacheOptions = { serializable: true }): Cache { let cache: Record = {}; return { @@ -11,32 +9,21 @@ export function createMemoryCache( defaultValue: () => Promise, events: CacheEvents = { miss: (): Promise => Promise.resolve(), - } + }, ): Promise { const keyAsString = JSON.stringify(key); if (keyAsString in cache) { - return Promise.resolve( - options.serializable - ? JSON.parse(cache[keyAsString]) - : cache[keyAsString] - ); + return Promise.resolve(options.serializable ? JSON.parse(cache[keyAsString]) : cache[keyAsString]); } const promise = defaultValue(); - return promise - .then((value: TValue) => events.miss(value)) - .then(() => promise); + return promise.then((value: TValue) => events.miss(value)).then(() => promise); }, - set( - key: Record | string, - value: TValue - ): Promise { - cache[JSON.stringify(key)] = options.serializable - ? JSON.stringify(value) - : value; + set(key: Record | string, value: TValue): Promise { + cache[JSON.stringify(key)] = options.serializable ? JSON.stringify(value) : value; return Promise.resolve(value); }, diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createNullCache.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createNullCache.ts index 3bec1039c47..1a950eb0af0 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createNullCache.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/cache/createNullCache.ts @@ -7,19 +7,14 @@ export function createNullCache(): Cache { defaultValue: () => Promise, events: CacheEvents = { miss: (): Promise => Promise.resolve(), - } + }, ): Promise { const value = defaultValue(); - return value - .then((result) => Promise.all([result, events.miss(result)])) - .then(([result]) => result); + return value.then((result) => Promise.all([result, events.miss(result)])).then(([result]) => result); }, - set( - _key: Record | string, - value: TValue - ): Promise { + set(_key: Record | string, value: TValue): Promise { return Promise.resolve(value); }, diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/createAlgoliaAgent.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/createAlgoliaAgent.ts index 5427ce78c5b..cef551375c9 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/createAlgoliaAgent.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/createAlgoliaAgent.ts @@ -4,9 +4,7 @@ export function createAlgoliaAgent(version: string): AlgoliaAgent { const algoliaAgent = { value: `Algolia for JavaScript (${version})`, add(options: AlgoliaAgentOptions): AlgoliaAgent { - const addedAlgoliaAgent = `; ${options.segment}${ - options.version !== undefined ? ` (${options.version})` : '' - }`; + const addedAlgoliaAgent = `; ${options.segment}${options.version !== undefined ? ` (${options.version})` : ''}`; if (algoliaAgent.value.indexOf(addedAlgoliaAgent) === -1) { algoliaAgent.value = `${algoliaAgent.value}${addedAlgoliaAgent}`; diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/createAuth.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/createAuth.ts index 62ff1d6a378..67c6c3816f1 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/createAuth.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/createAuth.ts @@ -3,7 +3,7 @@ import type { AuthMode, Headers, QueryParameters } from './types'; export function createAuth( appId: string, apiKey: string, - authMode: AuthMode = 'WithinHeaders' + authMode: AuthMode = 'WithinHeaders', ): { readonly headers: () => Headers; readonly queryParameters: () => QueryParameters; diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/createEchoRequester.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/createEchoRequester.ts index a2c30c12146..85bb4f188da 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/createEchoRequester.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/createEchoRequester.ts @@ -39,20 +39,14 @@ function getUrlParams({ return { host, algoliaAgent, - searchParams: - Object.keys(searchParams).length === 0 ? undefined : searchParams, + searchParams: Object.keys(searchParams).length === 0 ? undefined : searchParams, path: pathname, }; } -export function createEchoRequester({ - getURL, - status = 200, -}: EchoRequesterParams): Requester { +export function createEchoRequester({ getURL, status = 200 }: EchoRequesterParams): Requester { function send(request: EndRequest): Promise { - const { host, searchParams, algoliaAgent, path } = getUrlParams( - getURL(request.url) - ); + const { host, searchParams, algoliaAgent, path } = getUrlParams(getURL(request.url)); const content: EchoResponse = { ...request, diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/getAlgoliaAgent.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/getAlgoliaAgent.ts index 9c696660a99..02c1b2118bf 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/getAlgoliaAgent.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/getAlgoliaAgent.ts @@ -7,19 +7,13 @@ export type GetAlgoliaAgent = { version: string; }; -export function getAlgoliaAgent({ - algoliaAgents, - client, - version, -}: GetAlgoliaAgent): AlgoliaAgent { +export function getAlgoliaAgent({ algoliaAgents, client, version }: GetAlgoliaAgent): AlgoliaAgent { const defaultAlgoliaAgent = createAlgoliaAgent(version).add({ segment: client, version, }); - algoliaAgents.forEach((algoliaAgent) => - defaultAlgoliaAgent.add(algoliaAgent) - ); + algoliaAgents.forEach((algoliaAgent) => defaultAlgoliaAgent.add(algoliaAgent)); return defaultAlgoliaAgent; } diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createStatefulHost.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createStatefulHost.ts index ef46f3f15cb..d78c5e00a7c 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createStatefulHost.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createStatefulHost.ts @@ -4,10 +4,7 @@ import type { Host, StatefulHost } from '../types'; // In the JavaScript client, we have 2 mins. const EXPIRATION_DELAY = 2 * 60 * 1000; -export function createStatefulHost( - host: Host, - status: StatefulHost['status'] = 'up' -): StatefulHost { +export function createStatefulHost(host: Host, status: StatefulHost['status'] = 'up'): StatefulHost { const lastUpdate = Date.now(); function isUp(): boolean { @@ -15,9 +12,7 @@ export function createStatefulHost( } function isTimedOut(): boolean { - return ( - status === 'timed out' && Date.now() - lastUpdate <= EXPIRATION_DELAY - ); + return status === 'timed out' && Date.now() - lastUpdate <= EXPIRATION_DELAY; } return { ...host, status, lastUpdate, isUp, isTimedOut }; diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createTransporter.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createTransporter.ts index 708c641f4b8..1c4d6db282c 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createTransporter.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/createTransporter.ts @@ -12,18 +12,9 @@ import type { import { createStatefulHost } from './createStatefulHost'; import { RetryError } from './errors'; -import { - deserializeFailure, - deserializeSuccess, - serializeData, - serializeHeaders, - serializeUrl, -} from './helpers'; +import { deserializeFailure, deserializeSuccess, serializeData, serializeHeaders, serializeUrl } from './helpers'; import { isRetryable, isSuccess } from './responses'; -import { - stackTraceWithoutCredentials, - stackFrameWithoutCredentials, -} from './stackTrace'; +import { stackTraceWithoutCredentials, stackFrameWithoutCredentials } from './stackTrace'; type RetryableOptions = { hosts: Host[]; @@ -41,23 +32,20 @@ export function createTransporter({ requestsCache, responsesCache, }: TransporterOptions): Transporter { - async function createRetryableOptions( - compatibleHosts: Host[] - ): Promise { + async function createRetryableOptions(compatibleHosts: Host[]): Promise { 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. const hostsAvailable = [...hostsUp, ...hostsTimedOut]; - const compatibleHostsAvailable = - hostsAvailable.length > 0 ? hostsAvailable : compatibleHosts; + const compatibleHostsAvailable = hostsAvailable.length > 0 ? hostsAvailable : compatibleHosts; return { hosts: compatibleHostsAvailable, @@ -74,9 +62,7 @@ export function createTransporter({ * current v3 version. */ const timeoutMultiplier = - hostsTimedOut.length === 0 && timeoutsCount === 0 - ? 1 - : hostsTimedOut.length + 3 + timeoutsCount; + hostsTimedOut.length === 0 && timeoutsCount === 0 ? 1 : hostsTimedOut.length + 3 + timeoutsCount; return timeoutMultiplier * baseTimeout; }, @@ -86,7 +72,7 @@ export function createTransporter({ async function retryableRequest( request: Request, requestOptions: RequestOptions, - isRead = true + isRead = true, ): Promise { const stackTrace: StackFrame[] = []; @@ -94,11 +80,7 @@ export function createTransporter({ * First we prepare the payload that do not depend from hosts. */ const data = serializeData(request, requestOptions); - const headers = serializeHeaders( - baseHeaders, - request.headers, - requestOptions.headers - ); + const headers = serializeHeaders(baseHeaders, request.headers, requestOptions.headers); // On `GET`, the data is proxied to query parameters. const dataQueryParameters: QueryParameters = @@ -126,9 +108,7 @@ export function createTransporter({ // handled in the `serializeUrl` step right after. if ( !requestOptions.queryParameters[key] || - Object.prototype.toString.call( - requestOptions.queryParameters[key] - ) === '[object Object]' + Object.prototype.toString.call(requestOptions.queryParameters[key]) === '[object Object]' ) { queryParameters[key] = requestOptions.queryParameters[key]; } else { @@ -141,7 +121,7 @@ export function createTransporter({ const retry = async ( retryableHosts: Host[], - getTimeout: (timeoutsCount: number, timeout: number) => number + getTimeout: (timeoutsCount: number, timeout: number) => number, ): Promise => { /** * We iterate on each host, until there is no host left. @@ -151,15 +131,15 @@ export function createTransporter({ throw new RetryError(stackTraceWithoutCredentials(stackTrace)); } - let responseTimeout = isRead ? requestOptions.timeouts?.read || timeouts.read : requestOptions.timeouts?.write || timeouts.write; + const timeout = { ...timeouts, ...requestOptions.timeouts }; const payload: EndRequest = { data, headers, method: request.method, url: serializeUrl(host, request.path, queryParameters), - connectTimeout: getTimeout(timeoutsCount, requestOptions.timeouts?.connect || timeouts.connect), - responseTimeout: getTimeout(timeoutsCount, responseTimeout), + connectTimeout: getTimeout(timeoutsCount, timeout.connect), + responseTimeout: getTimeout(timeoutsCount, isRead ? timeout.read : timeout.write), }; /** @@ -195,20 +175,14 @@ export function createTransporter({ * when a retry error does not happen. */ // eslint-disable-next-line no-console -- this will be fixed by exposing a `logger` to the transporter - console.log( - 'Retryable failure', - stackFrameWithoutCredentials(stackFrame) - ); + 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') - ); + await hostsCache.set(host, createStatefulHost(host, response.isTimedOut ? 'timed out' : 'down')); return retry(retryableHosts, getTimeout); } @@ -230,19 +204,14 @@ export function createTransporter({ * for the current context. */ const compatibleHosts = hosts.filter( - (host) => - host.accept === 'readWrite' || - (isRead ? host.accept === 'read' : host.accept === 'write') + (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: Request, - requestOptions: RequestOptions = {} - ): Promise { + function createRequest(request: Request, requestOptions: RequestOptions = {}): Promise { /** * A read request is either a `GET` request, or a request that we make * via the `read` transporter (e.g. `search`). @@ -315,10 +284,9 @@ export function createTransporter({ .set(key, createRetryableRequest()) .then( (response) => Promise.all([requestsCache.delete(key), response]), - (err) => - Promise.all([requestsCache.delete(key), Promise.reject(err)]) + (err) => Promise.all([requestsCache.delete(key), Promise.reject(err)]), ) - .then(([_, response]) => response) + .then(([_, response]) => response), ); }, { @@ -328,7 +296,7 @@ export function createTransporter({ * to be used later. */ miss: (response) => responsesCache.set(key, response), - } + }, ); } diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/errors.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/errors.ts index 20e2e728cd9..a76c9f31d26 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/errors.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/errors.ts @@ -27,7 +27,7 @@ export class RetryError extends ErrorWithStackTrace { super( 'Unreachable hosts - your application id may be incorrect. If the error persists, please reach out to the Algolia Support team: https://alg.li/support.', stackTrace, - 'RetryError' + 'RetryError', ); } } @@ -35,12 +35,7 @@ export class RetryError extends ErrorWithStackTrace { export class ApiError extends ErrorWithStackTrace { status: number; - constructor( - message: string, - status: number, - stackTrace: StackFrame[], - name = 'ApiError' - ) { + constructor(message: string, status: number, stackTrace: StackFrame[], name = 'ApiError') { super(message, stackTrace, name); this.status = status; } @@ -75,12 +70,7 @@ export type DetailedError = { export class DetailedApiError extends ApiError { error: DetailedError; - constructor( - message: string, - status: number, - error: DetailedError, - stackTrace: StackFrame[] - ) { + constructor(message: string, status: number, error: DetailedError, stackTrace: StackFrame[]) { super(message, status, stackTrace, 'DetailedApiError'); this.error = error; } diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/helpers.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/helpers.ts index e63f8fd9da3..a84af90d484 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/helpers.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/helpers.ts @@ -1,12 +1,4 @@ -import type { - Headers, - Host, - QueryParameters, - Request, - RequestOptions, - Response, - StackFrame, -} from '../types'; +import type { Headers, Host, QueryParameters, Request, RequestOptions, Response, StackFrame } from '../types'; import { ApiError, DeserializationError, DetailedApiError } from './errors'; @@ -24,11 +16,7 @@ export function shuffle(array: TData[]): TData[] { return shuffledArray; } -export function serializeUrl( - host: Host, - path: string, - queryParameters: QueryParameters -): string { +export function serializeUrl(host: Host, path: string, queryParameters: QueryParameters): string { const queryParametersAsString = serializeQueryParameters(queryParameters); let url = `${host.protocol}://${host.url}${host.port ? `:${host.port}` : ''}/${ path.charAt(0) === '/' ? path.substring(1) : path @@ -50,26 +38,18 @@ export function serializeQueryParameters(parameters: QueryParameters): string { `${key}=${encodeURIComponent( Object.prototype.toString.call(parameters[key]) === '[object Array]' ? parameters[key].join(',') - : parameters[key] - ).replaceAll('+', '%20')}` + : parameters[key], + ).replaceAll('+', '%20')}`, ) .join('&'); } -export function serializeData( - request: Request, - requestOptions: RequestOptions -): string | undefined { - if ( - request.method === 'GET' || - (request.data === undefined && requestOptions.data === undefined) - ) { +export function serializeData(request: Request, requestOptions: RequestOptions): string | undefined { + if (request.method === 'GET' || (request.data === undefined && requestOptions.data === undefined)) { return undefined; } - const data = Array.isArray(request.data) - ? request.data - : { ...request.data, ...requestOptions.data }; + const data = Array.isArray(request.data) ? request.data : { ...request.data, ...requestOptions.data }; return JSON.stringify(data); } @@ -77,7 +57,7 @@ export function serializeData( export function serializeHeaders( baseHeaders: Headers, requestHeaders: Headers, - requestOptionsHeaders?: Headers + requestOptionsHeaders?: Headers, ): Headers { const headers: Headers = { Accept: 'application/json', @@ -103,22 +83,14 @@ export function deserializeSuccess(response: Response): TObject { } } -export function deserializeFailure( - { content, status }: Response, - stackFrame: StackFrame[] -): Error { +export function deserializeFailure({ content, status }: Response, stackFrame: StackFrame[]): Error { try { const parsed = JSON.parse(content); if ('error' in parsed) { - return new DetailedApiError( - parsed.message, - status, - parsed.error, - stackFrame - ); + return new DetailedApiError(parsed.message, status, parsed.error, stackFrame); } return new ApiError(parsed.message, status, stackFrame); - } catch (e) { + } catch { // .. } return new ApiError(content, status, stackFrame); diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/responses.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/responses.ts index 322ecf8093a..9159d6525b8 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/responses.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/responses.ts @@ -1,21 +1,11 @@ import type { Response } from '../types'; -export function isNetworkError({ - isTimedOut, - status, -}: Omit): boolean { +export function isNetworkError({ isTimedOut, status }: Omit): boolean { return !isTimedOut && ~~status === 0; } -export function isRetryable({ - isTimedOut, - status, -}: Omit): boolean { - return ( - isTimedOut || - isNetworkError({ isTimedOut, status }) || - (~~(status / 100) !== 2 && ~~(status / 100) !== 4) - ); +export function isRetryable({ isTimedOut, status }: Omit): boolean { + return isTimedOut || isNetworkError({ isTimedOut, status }) || (~~(status / 100) !== 2 && ~~(status / 100) !== 4); } export function isSuccess({ status }: Pick): boolean { diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/stackTrace.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/stackTrace.ts index 530c42f1d8e..29c933aa2d6 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/stackTrace.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/transporter/stackTrace.ts @@ -1,19 +1,11 @@ import type { Headers, StackFrame } from '../types'; -export function stackTraceWithoutCredentials( - stackTrace: StackFrame[] -): StackFrame[] { - return stackTrace.map((stackFrame) => - stackFrameWithoutCredentials(stackFrame) - ); +export function stackTraceWithoutCredentials(stackTrace: StackFrame[]): StackFrame[] { + return stackTrace.map((stackFrame) => stackFrameWithoutCredentials(stackFrame)); } -export function stackFrameWithoutCredentials( - stackFrame: StackFrame -): StackFrame { - const modifiedHeaders: Headers = stackFrame.request.headers[ - 'x-algolia-api-key' - ] +export function stackFrameWithoutCredentials(stackFrame: StackFrame): StackFrame { + const modifiedHeaders: Headers = stackFrame.request.headers['x-algolia-api-key'] ? { 'x-algolia-api-key': '*****' } : {}; diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/types/cache.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/types/cache.ts index 3432a6e1595..6a7e9fff1a0 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/types/cache.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/types/cache.ts @@ -5,16 +5,13 @@ export type Cache = { get: ( key: Record | string, defaultValue: () => Promise, - events?: CacheEvents + events?: CacheEvents, ) => Promise; /** * Sets the given value with the given `key`. */ - set: ( - key: Record | string, - value: TValue - ) => Promise; + set: (key: Record | string, value: TValue) => Promise; /** * Deletes the given `key`. diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/types/createClient.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/types/createClient.ts index 29535ac8816..1238b9a08a2 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/types/createClient.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/types/createClient.ts @@ -2,15 +2,9 @@ import type { AlgoliaAgentOptions, TransporterOptions } from './transporter'; export type AuthMode = 'WithinHeaders' | 'WithinQueryParameters'; -type OverriddenTransporterOptions = - | 'baseHeaders' - | 'baseQueryParameters' - | 'hosts'; +type OverriddenTransporterOptions = 'baseHeaders' | 'baseQueryParameters' | 'hosts'; -export type CreateClientOptions = Omit< - TransporterOptions, - OverriddenTransporterOptions | 'algoliaAgent' -> & +export type CreateClientOptions = Omit & Partial> & { appId: string; apiKey: string; @@ -18,6 +12,4 @@ export type CreateClientOptions = Omit< algoliaAgents: AlgoliaAgentOptions[]; }; -export type ClientOptions = Partial< - Omit ->; +export type ClientOptions = Partial>; diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/types/transporter.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/types/transporter.ts index 074fca664b4..85fd06ac5bd 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/types/transporter.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/types/transporter.ts @@ -146,8 +146,5 @@ export type Transporter = TransporterOptions & { * Performs a request. * The `baseRequest` and `baseRequestOptions` will be merged accordingly. */ - request: ( - baseRequest: Request, - baseRequestOptions?: RequestOptions - ) => Promise; + request: (baseRequest: Request, baseRequestOptions?: RequestOptions) => Promise; }; diff --git a/clients/algoliasearch-client-javascript/packages/requester-browser-xhr/src/__tests__/browser-xhr-requester.test.ts b/clients/algoliasearch-client-javascript/packages/requester-browser-xhr/src/__tests__/browser-xhr-requester.test.ts index a9bb1067d79..e21609c1cc8 100644 --- a/clients/algoliasearch-client-javascript/packages/requester-browser-xhr/src/__tests__/browser-xhr-requester.test.ts +++ b/clients/algoliasearch-client-javascript/packages/requester-browser-xhr/src/__tests__/browser-xhr-requester.test.ts @@ -210,9 +210,7 @@ describe('error handling', () => { }); it('resolves general network errors', async () => { - mock.post(BASE_URL, () => - Promise.reject(new Error('This is a general error')) - ); + mock.post(BASE_URL, () => Promise.reject(new Error('This is a general error'))); const response = await requester.send(requestStub); diff --git a/clients/algoliasearch-client-javascript/packages/requester-browser-xhr/src/createXhrRequester.ts b/clients/algoliasearch-client-javascript/packages/requester-browser-xhr/src/createXhrRequester.ts index e556c538e66..5f434a9c155 100644 --- a/clients/algoliasearch-client-javascript/packages/requester-browser-xhr/src/createXhrRequester.ts +++ b/clients/algoliasearch-client-javascript/packages/requester-browser-xhr/src/createXhrRequester.ts @@ -8,9 +8,7 @@ export function createXhrRequester(): Requester { const baseRequester = new XMLHttpRequest(); baseRequester.open(request.method, request.url, true); - Object.keys(request.headers).forEach((key) => - baseRequester.setRequestHeader(key, request.headers[key]) - ); + Object.keys(request.headers).forEach((key) => baseRequester.setRequestHeader(key, request.headers[key])); const createTimeout = (timeout: number, content: string): Timeout => { return setTimeout(() => { @@ -24,24 +22,15 @@ export function createXhrRequester(): Requester { }, timeout); }; - const connectTimeout = createTimeout( - request.connectTimeout, - 'Connection timeout' - ); + const connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout'); let responseTimeout: Timeout | undefined; baseRequester.onreadystatechange = (): void => { - if ( - baseRequester.readyState > baseRequester.OPENED && - responseTimeout === undefined - ) { + if (baseRequester.readyState > baseRequester.OPENED && responseTimeout === undefined) { clearTimeout(connectTimeout); - responseTimeout = createTimeout( - request.responseTimeout, - 'Socket timeout' - ); + responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout'); } }; diff --git a/clients/algoliasearch-client-javascript/packages/requester-fetch/src/__tests__/fetch-requester.test.ts b/clients/algoliasearch-client-javascript/packages/requester-fetch/src/__tests__/fetch-requester.test.ts index f7b320419b9..e6ba709a6bd 100644 --- a/clients/algoliasearch-client-javascript/packages/requester-fetch/src/__tests__/fetch-requester.test.ts +++ b/clients/algoliasearch-client-javascript/packages/requester-fetch/src/__tests__/fetch-requester.test.ts @@ -32,10 +32,7 @@ describe('status code handling', () => { it('sends requests', async () => { const body = getStringifiedBody(); - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(200, body); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, body); const response = await requester.send(requestStub); @@ -45,10 +42,7 @@ describe('status code handling', () => { it('resolves status 200', async () => { const body = getStringifiedBody(); - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(200, body); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, body); const response = await requester.send(requestStub); @@ -60,10 +54,7 @@ describe('status code handling', () => { it('resolves status 300', async () => { const reason = 'Multiple Choices'; - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(300, reason); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(300, reason); const response = await requester.send(requestStub); @@ -77,10 +68,7 @@ describe('status code handling', () => { message: 'Invalid Application-Id or API-Key', }); - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(400, body); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(400, body); const response = await requester.send(requestStub); @@ -101,10 +89,7 @@ describe('status code handling', () => { const testStream = Readable.from(generate()); - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(200, testStream); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, testStream); const response = await requester.send(requestStub); @@ -232,7 +217,7 @@ describe('error handling', (): void => { expect(response.status).toBe(0); expect(response.content).toBe( - 'request to https://algolia-dns.net/foo?x-algolia-header=bar failed, reason: This is a general error' + 'request to https://algolia-dns.net/foo?x-algolia-header=bar failed, reason: This is a general error', ); expect(response.isTimedOut).toBe(false); }); diff --git a/clients/algoliasearch-client-javascript/packages/requester-fetch/src/createFetchRequester.ts b/clients/algoliasearch-client-javascript/packages/requester-fetch/src/createFetchRequester.ts index 3db411cbddb..0de00c61be7 100644 --- a/clients/algoliasearch-client-javascript/packages/requester-fetch/src/createFetchRequester.ts +++ b/clients/algoliasearch-client-javascript/packages/requester-fetch/src/createFetchRequester.ts @@ -1,8 +1,4 @@ -import type { - EndRequest, - Requester, - Response as AlgoliaResponse, -} from '@algolia/client-common'; +import type { EndRequest, Requester, Response as AlgoliaResponse } from '@algolia/client-common'; function isAbortError(error: unknown): boolean { return error instanceof Error && error.name === 'AbortError'; @@ -19,9 +15,7 @@ export type FetchRequesterOptions = { readonly requesterOptions?: RequestInit; }; -export function createFetchRequester({ - requesterOptions = {}, -}: FetchRequesterOptions = {}): Requester { +export function createFetchRequester({ requesterOptions = {} }: FetchRequesterOptions = {}): Requester { async function send(request: EndRequest): Promise { const abortController = new AbortController(); const signal = abortController.signal; diff --git a/clients/algoliasearch-client-javascript/packages/requester-node-http/src/__tests__/node-http-requester.test.ts b/clients/algoliasearch-client-javascript/packages/requester-node-http/src/__tests__/node-http-requester.test.ts index d5d827291b4..6ea0381ee1c 100644 --- a/clients/algoliasearch-client-javascript/packages/requester-node-http/src/__tests__/node-http-requester.test.ts +++ b/clients/algoliasearch-client-javascript/packages/requester-node-http/src/__tests__/node-http-requester.test.ts @@ -79,7 +79,7 @@ describe('api', () => { 'my-extra-header': 'algolia', }), }), - expect.any(Function) + expect.any(Function), ); }); }); @@ -88,10 +88,7 @@ describe('status code handling', () => { it('sends requests', async () => { const body = getStringifiedBody(); - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(200, body); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, body); const response = await requester.send(requestStub); @@ -101,10 +98,7 @@ describe('status code handling', () => { it('resolves status 200', async () => { const body = getStringifiedBody(); - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(200, body); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, body); const response = await requester.send(requestStub); @@ -116,10 +110,7 @@ describe('status code handling', () => { it('resolves status 300', async () => { const reason = 'Multiple Choices'; - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(300, reason); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(300, reason); const response = await requester.send(requestStub); @@ -133,10 +124,7 @@ describe('status code handling', () => { message: 'Invalid Application-Id or API-Key', }); - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(400, body); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(400, body); const response = await requester.send(requestStub); @@ -157,10 +145,7 @@ describe('status code handling', () => { const testStream = Readable.from(generate()); - nock(testQueryBaseUrl, { reqheaders: headers }) - .post('/foo') - .query(testQueryHeader) - .reply(200, testStream); + nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, testStream); const response = await requester.send(requestStub); diff --git a/clients/algoliasearch-client-javascript/packages/requester-node-http/src/createHttpRequester.ts b/clients/algoliasearch-client-javascript/packages/requester-node-http/src/createHttpRequester.ts index 687a94c7b1b..d02162d0595 100644 --- a/clients/algoliasearch-client-javascript/packages/requester-node-http/src/createHttpRequester.ts +++ b/clients/algoliasearch-client-javascript/packages/requester-node-http/src/createHttpRequester.ts @@ -34,8 +34,7 @@ export function createHttpRequester({ // eslint-disable-next-line prefer-const -- linter thinks this is not reassigned let connectTimeout: NodeJS.Timeout | undefined; const url = new URL(request.url); - const path = - url.search === null ? url.pathname : `${url.pathname}${url.search}`; + const path = url.search === null ? url.pathname : `${url.pathname}${url.search}`; const options: https.RequestOptions = { agent: url.protocol === 'https:' ? httpsAgent : httpAgent, hostname: url.hostname, @@ -52,32 +51,26 @@ export function createHttpRequester({ options.port = url.port; } - const req = (url.protocol === 'https:' ? https : http).request( - options, - (response) => { - let contentBuffers: Buffer[] = []; + const req = (url.protocol === 'https:' ? https : http).request(options, (response) => { + let contentBuffers: Buffer[] = []; - response.on('data', (chunk) => { - contentBuffers = contentBuffers.concat(chunk); - }); + response.on('data', (chunk) => { + contentBuffers = contentBuffers.concat(chunk); + }); - response.on('end', () => { - clearTimeout(connectTimeout as NodeJS.Timeout); - clearTimeout(responseTimeout as NodeJS.Timeout); + response.on('end', () => { + clearTimeout(connectTimeout as NodeJS.Timeout); + clearTimeout(responseTimeout as NodeJS.Timeout); - resolve({ - status: response.statusCode || 0, - content: Buffer.concat(contentBuffers).toString(), - isTimedOut: false, - }); + resolve({ + status: response.statusCode || 0, + content: Buffer.concat(contentBuffers).toString(), + isTimedOut: false, }); - } - ); + }); + }); - const createTimeout = ( - timeout: number, - content: string - ): NodeJS.Timeout => { + const createTimeout = (timeout: number, content: string): NodeJS.Timeout => { return setTimeout(() => { req.destroy(); @@ -89,10 +82,7 @@ export function createHttpRequester({ }, timeout); }; - connectTimeout = createTimeout( - request.connectTimeout, - 'Connection timeout' - ); + connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout'); req.on('error', (error) => { clearTimeout(connectTimeout as NodeJS.Timeout); @@ -102,10 +92,7 @@ export function createHttpRequester({ req.once('response', () => { clearTimeout(connectTimeout as NodeJS.Timeout); - responseTimeout = createTimeout( - request.responseTimeout, - 'Socket timeout' - ); + responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout'); }); if (request.data !== undefined) { diff --git a/clients/algoliasearch-client-javascript/scripts/publish.ts b/clients/algoliasearch-client-javascript/scripts/publish.ts index 1680417f770..91a0939bd39 100755 --- a/clients/algoliasearch-client-javascript/scripts/publish.ts +++ b/clients/algoliasearch-client-javascript/scripts/publish.ts @@ -1,7 +1,7 @@ import { execaCommand } from 'execa'; import semver from 'semver'; -import packageJSON from "../packages/algoliasearch/package.json" assert { type: "json" }; +import packageJSON from '../packages/algoliasearch/package.json' with { type: 'json' }; async function publish(): Promise { // Get tag like `alpha`, `beta`, ... @@ -13,7 +13,7 @@ async function publish(): Promise { }`, { shell: 'bash', - } + }, ); } diff --git a/clients/algoliasearch-client-javascript/tsconfig.script.json b/clients/algoliasearch-client-javascript/scripts/tsconfig.json similarity index 69% rename from clients/algoliasearch-client-javascript/tsconfig.script.json rename to clients/algoliasearch-client-javascript/scripts/tsconfig.json index a644309ff72..0c3095537e0 100644 --- a/clients/algoliasearch-client-javascript/tsconfig.script.json +++ b/clients/algoliasearch-client-javascript/scripts/tsconfig.json @@ -1,10 +1,10 @@ { - "extends": "./tsconfig.json", + "extends": "../tsconfig.json", "compilerOptions": { "resolveJsonModule": true, "outDir": "dist" }, "include": [ - "scripts" + "*.ts" ] } diff --git a/clients/algoliasearch-client-javascript/tests/utils.ts b/clients/algoliasearch-client-javascript/tests/utils.ts index 96356b014d5..ca65d714875 100644 --- a/clients/algoliasearch-client-javascript/tests/utils.ts +++ b/clients/algoliasearch-client-javascript/tests/utils.ts @@ -44,9 +44,7 @@ export const testQueryBaseUrl = 'https://algolia-dns.net'; /** * Returns a JSON strigified body. */ -export function getStringifiedBody( - body: Record = { foo: 'bar' } -): string { +export function getStringifiedBody(body: Record = { foo: 'bar' }): string { return JSON.stringify(body); } diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index 6e1f2d51c88..b11229800b0 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -136,7 +136,7 @@ public Map postProcessAllModels(Map objs) @Override public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List models) { OperationsMap operations = super.postProcessOperationsWithModels(objs, models); - ModelPruner.removeOrphans(this, operations, models, true); + ModelPruner.removeOrphans(this, operations, models); Helpers.removeHelpers(operations); GenericPropagator.propagateGenericsToOperations(operations, models); return operations; diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java index 9e98e839a74..71123200e3e 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java @@ -124,10 +124,8 @@ private Map traverseParams( isCodegenModel = spec instanceof CodegenModel; } - String finalParamName = getFinalParamName(paramName); - - testOutput.put("key", finalParamName); - testOutput.put("useAnonymousKey", !finalParamName.matches("(.*)_[0-9]$") && depth != 0); + testOutput.put("key", paramName); + testOutput.put("useAnonymousKey", !paramName.matches("(.*)_[0-9]$") && depth != 0); testOutput.put("parent", parent); testOutput.put("isRoot", "".equals(parent)); testOutput.put("objectName", getObjectNameForLanguage(baseType)); @@ -162,11 +160,9 @@ private Map traverseParams( /** Same method but with inference only */ private Map traverseParamsWithoutSpec(String paramName, Object param, String parent, int depth) throws CTSException { - String finalParamName = getFinalParamName(paramName); - Map testOutput = createDefaultOutput(); - testOutput.put("key", finalParamName); - testOutput.put("useAnonymousKey", !finalParamName.matches("(.*)_[0-9]$") && depth != 0); + testOutput.put("key", paramName); + testOutput.put("useAnonymousKey", !paramName.matches("(.*)_[0-9]$") && depth != 0); testOutput.put("parent", parent); testOutput.put("isRoot", "".equals(parent)); // try to infer the type @@ -189,17 +185,6 @@ private Map traverseParamsWithoutSpec(String paramName, Object p return testOutput; } - private String getFinalParamName(String paramName) { - switch (language) { - case "java": - return paramName.startsWith("_") ? paramName.substring(1) : paramName; - case "go": - return paramName.equals("type") ? "type_" : paramName; - } - - return paramName; - } - private Map createDefaultOutput() { Map testOutput = new HashMap<>(); @@ -642,28 +627,16 @@ private IJsonSchemaValidationProperties findMatchingOneOf(Object param, CodegenM } return bestOneOf; } - if (param instanceof List) { - CodegenComposedSchemas composedSchemas = model.getComposedSchemas(); - - if (composedSchemas != null) { - List oneOf = composedSchemas.getOneOf(); - // Somehow this is not yet enough - if (oneOf != null && !oneOf.isEmpty()) { - System.out.println("Choosing the first oneOf by default: " + oneOf.get(0).baseName + " (this won't stay correct forever)"); - return oneOf.get(0); - } + for (CodegenProperty prop : model.getComposedSchemas().getOneOf()) { + // find the correct list + if (param instanceof List && prop.getIsArray()) { + return prop; } - return null; - } - - // find the correct enum - if (param instanceof String) { - for (CodegenProperty prop : model.getComposedSchemas().getOneOf()) { - if (prop.getIsEnumOrRef() && couldMatchEnum(param, prop)) { - return prop; - } + // find the correct enum + if (param instanceof String && prop.getIsEnumOrRef() && couldMatchEnum(param, prop)) { + return prop; } } @@ -693,7 +666,8 @@ private IJsonSchemaValidationProperties findMatchingOneOf(Object param, CodegenM return oneOf; } } - return null; + + return maybeMatch; } // If the model is an enum and contains a valid list of allowed values, diff --git a/generators/src/main/java/com/algolia/codegen/utils/ModelPruner.java b/generators/src/main/java/com/algolia/codegen/utils/ModelPruner.java index 0214f16e066..99b4ebb8839 100644 --- a/generators/src/main/java/com/algolia/codegen/utils/ModelPruner.java +++ b/generators/src/main/java/com/algolia/codegen/utils/ModelPruner.java @@ -72,6 +72,10 @@ private static Map convertToMap(CodegenConfig config, List private void exploreGraph(OperationsMap operations) { for (CodegenModel model : models.values()) { visitModelRecursive(model); + + if ((boolean) model.vendorExtensions.getOrDefault("x-keep-model", false)) { + visitedModels.add(model.name); + } } for (CodegenOperation ope : operations.getOperations().getOperation()) { @@ -95,20 +99,20 @@ private void exploreGraph(OperationsMap operations) { } /** remove all the unused models, most likely the sub models of allOf */ - public static void removeOrphans(CodegenConfig config, OperationsMap operations, List allModels, boolean keepError) { + public static void removeOrphans(CodegenConfig config, OperationsMap operations, List allModels) { // visit all the models that are accessible from: // - the properties of a model (needs recursive search) // - the return type of an operation // - the parameters of an operation + // + // If you really want a model to be generated, you can add x-keep-model: true to the model, and + // add it to the components/schemas in the root spec. ModelPruner modelPruner = new ModelPruner(convertToMap(config, allModels)); modelPruner.exploreGraph(operations); List toRemove = new ArrayList<>(); for (String modelName : modelPruner.models.keySet()) { - if (keepError && modelName.equals("ErrorBase")) { - continue; - } if (!modelPruner.visitedModels.contains(modelName)) { toRemove.add(modelName); } @@ -124,8 +128,4 @@ public static void removeOrphans(CodegenConfig config, OperationsMap operations, } } } - - public static void removeOrphans(CodegenConfig config, OperationsMap operations, List allModels) { - removeOrphans(config, operations, allModels, false); - } } diff --git a/scripts/formatter.ts b/scripts/formatter.ts index 727859caef9..9ea748f4cc2 100644 --- a/scripts/formatter.ts +++ b/scripts/formatter.ts @@ -37,7 +37,7 @@ export async function formatter(language: string, cwd: string): Promise { ); break; case 'javascript': - await run(`yarn eslint --ext=ts,json ${cwd} --fix --no-error-on-unmatched-pattern --debug`); + await run(`yarn eslint --ext=ts,json ${cwd} --fix --no-error-on-unmatched-pattern`); break; case 'kotlin': await run(`./gradle/gradlew -p ${cwd} spotlessApply`, { language }); diff --git a/scripts/generate.ts b/scripts/generate.ts index 3fca0617868..1ae025b9288 100644 --- a/scripts/generate.ts +++ b/scripts/generate.ts @@ -20,13 +20,14 @@ export async function generate(generators: Generator[]): Promise { // We have scoped output folder for JavaScript which allow us to // avoid linting the whole client, only the part that changed if (lang === 'javascript') { + const base = 'clients/algoliasearch-client-javascript/packages'; folder = generators.reduce((folders, gen) => { if (gen.language === 'javascript') { return `${folders} ${gen.output}`; } return folders; - }, ''); + }, `${base}/client-common ${base}/requester-browser-xhr ${base}/requester-node-http ${base}/requester-fetch clients/algoliasearch-client-javascript/scripts clients/algoliasearch-client-javascript/tests`); } await formatter(lang, folder); diff --git a/specs/bundled/search.yml b/specs/bundled/search.yml index 163c64ad35d..ec09350abaa 100644 --- a/specs/bundled/search.yml +++ b/specs/bundled/search.yml @@ -928,14 +928,45 @@ paths: - addObject summary: Add or update attributes x-codegen-request-body-name: attributesToUpdate - description: | + description: > Adds new attributes to a record, or update existing ones. - If a record with the specified object ID doesn't exist, a new record is added to the index **if** `createIfNotExists` is true. - - If the index doesn't exist yet, this method creates a new index. - - You can use any first-level attribute but not nested attributes. + - If the index doesn't exist yet, this method creates a new index. - You + can use any first-level attribute but not nested attributes. If you specify a nested attribute, the engine treats it as a replacement for its first-level ancestor. + + To update an attribute without pushing the entire record, you can use + these built-in operations. These operations can be helpful if you don't + have access to your initial data. + + - Increment: increment a numeric attribute - Decrement: decrement a + numeric attribute - Add: append a number or string element to an array + attribute - Remove: remove all matching number or string elements from + an array attribute made of numbers or strings - AddUnique: add a number + or string element to an array attribute made of numbers or strings only + if it's not already present - IncrementFrom: increment a numeric integer + attribute only if the provided value matches the current value, and + otherwise ignore the whole object update. For example, if you pass an + IncrementFrom value of 2 for the version attribute, but the current + value of the attribute is 1, the engine ignores the update. If the + object doesn't exist, the engine only creates it if you pass an + IncrementFrom value of 0. - IncrementSet: increment a numeric integer + attribute only if the provided value is greater than the current value, + and otherwise ignore the whole object update. For example, if you pass + an IncrementSet value of 2 for the version attribute, and the current + value of the attribute is 1, the engine updates the object. If the + object doesn't exist yet, the engine only creates it if you pass an + IncrementSet value that's greater than 0. + + You can specify an operation by providing an object with the attribute + to update as the key and its value being an object with the following + properties: + + - _operation: the operation to apply on the attribute - value: the + right-hand side argument to the operation, for example, increment or + decrement step, value to add or remove. parameters: - $ref: '#/components/parameters/IndexName' - $ref: '#/components/parameters/ObjectID' @@ -951,10 +982,8 @@ paths: content: application/json: schema: - type: object description: Attributes to update. - additionalProperties: - $ref: '#/components/schemas/attributeToUpdate' + type: object responses: '200': $ref: '#/components/responses/UpdatedAtWithObjectId' @@ -3510,128 +3539,24 @@ components: The required ACL to make a request is listed in each endpoint's reference. - parameters: - PathInPath: - name: path - in: path - description: Path of the endpoint, anything after "/1" must be specified. - required: true - schema: - type: string - example: /keys - Parameters: - name: parameters - in: query - description: Query parameters to apply to the current query. - schema: - type: object - additionalProperties: true - IndexName: - name: indexName - in: path - description: Name of the index on which to perform the operation. - required: true - schema: - type: string - example: YourIndexName - ObjectID: - name: objectID - in: path - description: Unique record identifier. - required: true - schema: - $ref: '#/components/schemas/objectID' - ForwardToReplicas: - in: query - name: forwardToReplicas - required: false - description: Whether changes are applied to replica indices. - schema: - type: boolean - parameters_ObjectID: - name: objectID - in: path - description: Unique identifier of a synonym object. - required: true - schema: - type: string - example: synonymID - ReplaceExistingSynonyms: - in: query - name: replaceExistingSynonyms - schema: - type: boolean - description: >- - Whether to replace all synonyms in the index with the ones sent with - this request. - KeyString: - in: path - name: key - required: true - schema: - type: string - example: YourAPIKey - description: API key. - ObjectIDRule: - in: path - name: objectID - description: Unique identifier of a rule object. - required: true - schema: - $ref: '#/components/schemas/ruleID' - ClearExistingRules: - in: query - name: clearExistingRules - required: false - schema: - type: boolean - description: Whether existing rules should be deleted before adding this batch. - DictionaryName: - in: path - name: dictionaryName - description: Dictionary type in which to search. - required: true - schema: - $ref: '#/components/schemas/dictionaryType' - Page: - in: query - name: page - description: | - Requested page of the API response. - If `null`, the API response is not paginated. - required: false - schema: - oneOf: - - type: integer - minimum: 0 - - type: 'null' - default: null - HitsPerPage: - in: query - name: hitsPerPage - description: Number of hits per page. - required: false - schema: - type: integer - default: 100 - UserIDInHeader: - name: X-Algolia-User-ID - description: Unique identifier of the user who makes the search request. - in: header - required: true - schema: - $ref: '#/components/schemas/userID' - UserIDInPath: - name: userID - description: Unique identifier of the user who makes the search request. - in: path - required: true - schema: - $ref: '#/components/schemas/userID' schemas: + builtInOperation: + type: object + description: Update to perform on the attribute. + x-keep-model: true + additionalProperties: false + properties: + _operation: + $ref: '#/components/schemas/builtInOperationType' + value: + $ref: '#/components/schemas/builtInOperationValue' + required: + - _operation + - value ErrorBase: description: Error. type: object + x-keep-model: true additionalProperties: true properties: message: @@ -6134,44 +6059,6 @@ components: $ref: '#/components/schemas/taskID' updatedAt: $ref: '#/components/schemas/updatedAt' - attribute: - type: string - description: Value of the attribute to update. - builtInOperationType: - type: string - enum: - - Increment - - Decrement - - Add - - Remove - - AddUnique - - IncrementFrom - - IncrementSet - description: How to change the attribute. - builtInOperationValue: - oneOf: - - type: string - description: >- - A string to append or remove for the `Add`, `Remove`, and - `AddUnique` operations. - - type: integer - description: A number to add, remove, or append, depending on the operation. - builtInOperation: - type: object - description: Update to perform on the attribute. - additionalProperties: false - properties: - _operation: - $ref: '#/components/schemas/builtInOperationType' - value: - $ref: '#/components/schemas/builtInOperationValue' - required: - - _operation - - value - attributeToUpdate: - oneOf: - - $ref: '#/components/schemas/attribute' - - $ref: '#/components/schemas/builtInOperation' action: type: string enum: @@ -7723,6 +7610,143 @@ components: - copyOperationResponse - batchResponses - moveOperationResponse + builtInOperationType: + type: string + enum: + - Increment + - Decrement + - Add + - Remove + - AddUnique + - IncrementFrom + - IncrementSet + description: How to change the attribute. + builtInOperationValue: + oneOf: + - type: string + description: >- + A string to append or remove for the `Add`, `Remove`, and + `AddUnique` operations. + - type: integer + description: A number to add, remove, or append, depending on the operation. + parameters: + PathInPath: + name: path + in: path + description: Path of the endpoint, anything after "/1" must be specified. + required: true + schema: + type: string + example: /keys + Parameters: + name: parameters + in: query + description: Query parameters to apply to the current query. + schema: + type: object + additionalProperties: true + IndexName: + name: indexName + in: path + description: Name of the index on which to perform the operation. + required: true + schema: + type: string + example: YourIndexName + ObjectID: + name: objectID + in: path + description: Unique record identifier. + required: true + schema: + $ref: '#/components/schemas/objectID' + ForwardToReplicas: + in: query + name: forwardToReplicas + required: false + description: Whether changes are applied to replica indices. + schema: + type: boolean + parameters_ObjectID: + name: objectID + in: path + description: Unique identifier of a synonym object. + required: true + schema: + type: string + example: synonymID + ReplaceExistingSynonyms: + in: query + name: replaceExistingSynonyms + schema: + type: boolean + description: >- + Whether to replace all synonyms in the index with the ones sent with + this request. + KeyString: + in: path + name: key + required: true + schema: + type: string + example: YourAPIKey + description: API key. + ObjectIDRule: + in: path + name: objectID + description: Unique identifier of a rule object. + required: true + schema: + $ref: '#/components/schemas/ruleID' + ClearExistingRules: + in: query + name: clearExistingRules + required: false + schema: + type: boolean + description: Whether existing rules should be deleted before adding this batch. + DictionaryName: + in: path + name: dictionaryName + description: Dictionary type in which to search. + required: true + schema: + $ref: '#/components/schemas/dictionaryType' + Page: + in: query + name: page + description: | + Requested page of the API response. + If `null`, the API response is not paginated. + required: false + schema: + oneOf: + - type: integer + minimum: 0 + - type: 'null' + default: null + HitsPerPage: + in: query + name: hitsPerPage + description: Number of hits per page. + required: false + schema: + type: integer + default: 100 + UserIDInHeader: + name: X-Algolia-User-ID + description: Unique identifier of the user who makes the search request. + in: header + required: true + schema: + $ref: '#/components/schemas/userID' + UserIDInPath: + name: userID + description: Unique identifier of the user who makes the search request. + in: path + required: true + schema: + $ref: '#/components/schemas/userID' responses: BadRequest: description: Bad request or request arguments. diff --git a/specs/common/schemas/ErrorBase.yml b/specs/common/schemas/ErrorBase.yml index 7fa8e49656e..b11d665181b 100644 --- a/specs/common/schemas/ErrorBase.yml +++ b/specs/common/schemas/ErrorBase.yml @@ -1,5 +1,6 @@ description: Error. type: object +x-keep-model: true additionalProperties: true properties: message: diff --git a/specs/search/paths/objects/common/schemas.yml b/specs/search/paths/objects/common/schemas.yml index f6a85137694..8b856bbbb03 100644 --- a/specs/search/paths/objects/common/schemas.yml +++ b/specs/search/paths/objects/common/schemas.yml @@ -17,10 +17,6 @@ builtInOperationValue: - type: integer description: A number to add, remove, or append, depending on the operation. -attribute: - type: string - description: Value of the attribute to update. - builtInOperation: type: object description: Update to perform on the attribute. @@ -35,8 +31,10 @@ builtInOperation: - value attributeToUpdate: + x-keep-model: true + deprecated: true oneOf: - - $ref: '#/attribute' + - type: string - $ref: '#/builtInOperation' batchResponse: diff --git a/specs/search/paths/objects/partialUpdate.yml b/specs/search/paths/objects/partialUpdate.yml index b079d4f4f29..b465d4e609e 100644 --- a/specs/search/paths/objects/partialUpdate.yml +++ b/specs/search/paths/objects/partialUpdate.yml @@ -14,6 +14,21 @@ post: - If the index doesn't exist yet, this method creates a new index. - You can use any first-level attribute but not nested attributes. If you specify a nested attribute, the engine treats it as a replacement for its first-level ancestor. + + To update an attribute without pushing the entire record, you can use these built-in operations. These operations can be helpful if you don't have access to your initial data. + + - Increment: increment a numeric attribute + - Decrement: decrement a numeric attribute + - Add: append a number or string element to an array attribute + - Remove: remove all matching number or string elements from an array attribute made of numbers or strings + - AddUnique: add a number or string element to an array attribute made of numbers or strings only if it's not already present + - IncrementFrom: increment a numeric integer attribute only if the provided value matches the current value, and otherwise ignore the whole object update. For example, if you pass an IncrementFrom value of 2 for the version attribute, but the current value of the attribute is 1, the engine ignores the update. If the object doesn't exist, the engine only creates it if you pass an IncrementFrom value of 0. + - IncrementSet: increment a numeric integer attribute only if the provided value is greater than the current value, and otherwise ignore the whole object update. For example, if you pass an IncrementSet value of 2 for the version attribute, and the current value of the attribute is 1, the engine updates the object. If the object doesn't exist yet, the engine only creates it if you pass an IncrementSet value that's greater than 0. + + You can specify an operation by providing an object with the attribute to update as the key and its value being an object with the following properties: + + - _operation: the operation to apply on the attribute + - value: the right-hand side argument to the operation, for example, increment or decrement step, value to add or remove. parameters: - $ref: '../../../common/parameters.yml#/IndexName' - $ref: '../../../common/parameters.yml#/ObjectID' @@ -29,10 +44,8 @@ post: content: application/json: schema: - type: object description: Attributes to update. - additionalProperties: - $ref: 'common/schemas.yml#/attributeToUpdate' + type: object responses: '200': $ref: '../../../common/responses/UpdatedAtWithObjectId.yml' diff --git a/specs/search/spec.yml b/specs/search/spec.yml index cd028cf9e32..801722c8f5d 100644 --- a/specs/search/spec.yml +++ b/specs/search/spec.yml @@ -82,6 +82,9 @@ components: $ref: '../common/securitySchemes.yml#/appId' apiKey: $ref: '../common/securitySchemes.yml#/apiKey' + schemas: + attributeToUpdate: + $ref: 'paths/objects/common/schemas.yml#/attributeToUpdate' servers: - url: https://{appId}.algolia.net variables: diff --git a/templates/csharp/tests/generateInnerParams.mustache b/templates/csharp/tests/generateInnerParams.mustache index 5e51358aeef..368e70763b3 100644 --- a/templates/csharp/tests/generateInnerParams.mustache +++ b/templates/csharp/tests/generateInnerParams.mustache @@ -31,7 +31,7 @@ {{/isObject}} {{#isFreeFormObject}} {{#isAnyType}} - new Dictionary { {{#value}}{ {{#entrySet}}"{{{key}}}", "{{{value}}}" }{{^-last}},{{/-last}}{{/entrySet}}{{/value}} } + new Dictionary { {{#value}}{ {{#entrySet}}"{{{key}}}", "{{{value}}}" }{{^-last}},{{/-last}}{{/entrySet}}{{/value}} } {{/isAnyType}} {{^isAnyType}} new Dictionary{{> tests/forceMapGenerics}}{ {{#value}}{"{{{key}}}", {{> tests/generateParams}} }{{^-last}},{{/-last}}{{/value}} } diff --git a/templates/kotlin/tests/param_json_element.mustache b/templates/kotlin/tests/param_json_element.mustache index 4f2f0d39dbd..b74f2a29e1c 100644 --- a/templates/kotlin/tests/param_json_element.mustache +++ b/templates/kotlin/tests/param_json_element.mustache @@ -20,7 +20,7 @@ JsonPrimitive({{{value}}}), JsonPrimitive("{{{value}}}"), {{/isEnum}} {{#isArray}} -{{> tests/param_json_element}}, +JsonArray(listOf({{#value}}{{> tests/param_json_element}}{{/value}})), {{/isArray}} {{#isFreeFormObject}} buildJsonObject { diff --git a/templates/scala/tests/param_json_element.mustache b/templates/scala/tests/param_json_element.mustache index a2500ec718b..fb26d451880 100644 --- a/templates/scala/tests/param_json_element.mustache +++ b/templates/scala/tests/param_json_element.mustache @@ -20,7 +20,7 @@ JBool({{{value}}}) JString("{{{value}}}") {{/isEnum}} {{#isArray}} -JArray({{#value}}{{> tests/param_json_element}}{{^-last}},{{/-last}}{{/value}}) +JArray(List({{#value}}{{> tests/param_json_element}}{{^-last}},{{/-last}}{{/value}})) {{/isArray}} {{#isFreeFormObject}} JObject(List({{#value}}JField("{{{key}}}", {{> tests/param_json_element}}){{^-last}},{{/-last}}{{/value}})) diff --git a/tests/CTS/requests/search/partialUpdateObject.json b/tests/CTS/requests/search/partialUpdateObject.json index 7ec765361c7..589298e30a9 100644 --- a/tests/CTS/requests/search/partialUpdateObject.json +++ b/tests/CTS/requests/search/partialUpdateObject.json @@ -1,42 +1,88 @@ [ { - "testName": "Partial update with string value", + "testName": "Partial update with a new value for a string attribute", "parameters": { "indexName": "theIndexName", "objectID": "uniqueID", "attributesToUpdate": { - "id1": "test", - "id2": { - "_operation": "AddUnique", - "value": "test2" - } - }, - "createIfNotExists": true + "attributeId": "new value" + } }, "request": { "path": "/1/indexes/theIndexName/uniqueID/partial", "method": "POST", "body": { - "id1": "test", - "id2": { - "_operation": "AddUnique", - "value": "test2" - } - }, - "queryParameters": { - "createIfNotExists": "true" + "attributeId": "new value" + } + } + }, + { + "testName": "Partial update with a new value for an integer attribute", + "parameters": { + "indexName": "theIndexName", + "objectID": "uniqueID", + "attributesToUpdate": { + "attributeId": 1 + } + }, + "request": { + "path": "/1/indexes/theIndexName/uniqueID/partial", + "method": "POST", + "body": { + "attributeId": 1 + } + } + }, + { + "testName": "Partial update with a new value for a boolean attribute", + "parameters": { + "indexName": "theIndexName", + "objectID": "uniqueID", + "attributesToUpdate": { + "attributeId": true + } + }, + "request": { + "path": "/1/indexes/theIndexName/uniqueID/partial", + "method": "POST", + "body": { + "attributeId": true + } + } + }, + { + "testName": "Partial update with a new value for an array attribute", + "parameters": { + "indexName": "theIndexName", + "objectID": "uniqueID", + "attributesToUpdate": { + "attributeId": [ + "one", + "two", + "three" + ] + } + }, + "request": { + "path": "/1/indexes/theIndexName/uniqueID/partial", + "method": "POST", + "body": { + "attributeId": [ + "one", + "two", + "three" + ] } } }, { - "testName": "Partial update with integer value", + "testName": "Partial update with a new value for an object attribute", "parameters": { "indexName": "theIndexName", "objectID": "uniqueID", "attributesToUpdate": { "attributeId": { - "_operation": "Increment", - "value": 2 + "nested": "value" } } }, @@ -45,8 +91,7 @@ "method": "POST", "body": { "attributeId": { - "_operation": "Increment", - "value": 2 + "nested": "value" } } }