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 0d2b248f98..60b93cd2ab 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 @@ -12,6 +12,24 @@ export class AlgoliaError extends Error { } } +export class IndexNotFoundError extends AlgoliaError { + constructor(indexName: string) { + super(`${indexName} does not exist`, 'IndexNotFoundError'); + } +} + +export class IndicesInSameAppError extends AlgoliaError { + constructor() { + super('Indices are in the same application. Use operationIndex instead.', 'IndicesInSameAppError'); + } +} + +export class IndexAlreadyExistsError extends AlgoliaError { + constructor(indexName: string) { + super(`${indexName} index already exists.`, 'IndexAlreadyExistsError'); + } +} + export class ErrorWithStackTrace extends AlgoliaError { stackTrace: StackFrame[]; diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java index fb17d9e4fe..59422e098e 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java @@ -67,6 +67,13 @@ private Map loadSnippets(Map operat if (ope == null || !(boolean) ope.vendorExtensions.getOrDefault("x-helper", false)) { continue; } + + List availableLanguages = (List) ope.vendorExtensions.getOrDefault("x-available-languages", new ArrayList<>()); + + if (availableLanguages.size() > 0 && !availableLanguages.contains(language)) { + continue; + } + Snippet newSnippet = new Snippet(step.method, test.testName, step.parameters, step.requestOptions); Snippet[] existing = snippets.get(step.method); if (existing == null) { diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java index b73f354c25..c18dd14cef 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java @@ -114,6 +114,20 @@ public void run(Map models, Map throw new CTSException("Cannot find operation for method: " + step.method, test.testName); } + + boolean isHelper = (boolean) ope.vendorExtensions.getOrDefault("x-helper", false); + + if (isHelper) { + List availableLanguages = (List) ope.vendorExtensions.getOrDefault( + "x-available-languages", + new ArrayList<>() + ); + + if (availableLanguages.size() > 0 && !availableLanguages.contains(language)) { + continue skipTest; + } + } + stepOut.put("stepTemplate", "tests/client/method.mustache"); stepOut.put("isMethod", true); // TODO: remove once kotlin is converted stepOut.put("hasParams", ope.hasParams); @@ -123,7 +137,6 @@ public void run(Map models, Map stepOut.put("returnsBoolean", ope.returnType.equals("Boolean")); // ruby requires a ? for boolean functions. } - boolean isHelper = (boolean) ope.vendorExtensions.getOrDefault("x-helper", false); stepOut.put("isHelper", isHelper); // default to true because most api calls are asynchronous stepOut.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); diff --git a/playground/javascript/node/realtimePersonalization.ts b/playground/javascript/node/realtimePersonalization.ts deleted file mode 100644 index ec39d2dce3..0000000000 --- a/playground/javascript/node/realtimePersonalization.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ApiError } from '@algolia/client-common'; -import { realtimePersonalizationClient } from '@algolia/client-realtime-personalization'; - -const appId = process.env.METIS_APPLICATION_ID || '**** APP_ID *****'; -const apiKey = process.env.METIS_API_KEY || '**** ADMIN_KEY *****'; - -// Init client with appId and apiKey -const client = realtimePersonalizationClient(appId, apiKey, 'us'); - -async function testRealtimePersonalization() { - try { - console.log(appId, apiKey); - - const resp = await client.getUser({userToken: "foo"}); - - console.log(resp); - - } catch (e) { - if (e instanceof ApiError) { - return console.log(`[${e.status}] ${e.message}`, e.stackTrace, e); - } - - console.log('[ERROR]', e); - } -} - -testRealtimePersonalization(); diff --git a/scripts/cts/runCts.ts b/scripts/cts/runCts.ts index f9325bb11b..0ebfa12882 100644 --- a/scripts/cts/runCts.ts +++ b/scripts/cts/runCts.ts @@ -5,6 +5,7 @@ import { getTestOutputFolder } from '../config.ts'; import { createSpinner } from '../spinners.ts'; import type { Language } from '../types.ts'; +import { assertValidAccountCopyIndex } from './testServer/accountCopyIndex.ts'; import { printBenchmarkReport } from './testServer/benchmark.ts'; import { assertChunkWrapperValid } from './testServer/chunkWrapper.ts'; import { startTestServer } from './testServer/index.ts'; @@ -147,10 +148,12 @@ export async function runCts( if (withClientServer && (clients.includes('search') || clients.includes('all') || process.platform === 'darwin')) { // the macos swift CI also runs the clients tests const skip = (lang: Language): number => (languages.includes(lang) ? 1 : 0); + const only = skip; assertValidTimeouts(languages.length); assertChunkWrapperValid(languages.length - skip('dart')); assertValidReplaceAllObjects(languages.length - skip('dart')); + assertValidAccountCopyIndex(only('javascript')); assertValidReplaceAllObjectsFailed(languages.length - skip('dart')); assertValidReplaceAllObjectsScopes(languages.length - skip('dart')); assertValidWaitForApiKey(languages.length - skip('dart')); diff --git a/scripts/cts/testServer/accountCopyIndex.ts b/scripts/cts/testServer/accountCopyIndex.ts new file mode 100644 index 0000000000..e8dbc6ff5c --- /dev/null +++ b/scripts/cts/testServer/accountCopyIndex.ts @@ -0,0 +1,207 @@ +import type { Server } from 'http'; + +import { expect } from 'chai'; +import type { Express } from 'express'; +import express from 'express'; + +import { setupServer } from './index.ts'; + +const aciState: Record< + string, + { + setSettingsCount: number; + getSettingsCount: number; + saveRulesCount: number; + browseRulesCount: number; + saveSynonymsCount: number; + browseSynonymsCount: number; + saveObjectsCount: number; + browseObjectsCount: number; + waitTaskCount: number; + successful: boolean; + } +> = {}; + +export function assertValidAccountCopyIndex(expectedCount: number): void { + expect(Object.keys(aciState)).to.have.length(expectedCount); + for (const lang in aciState) { + expect(aciState[lang].waitTaskCount).to.equal(4); + } +} + +function addRoutes(app: Express): void { + app.use(express.urlencoded({ extended: true })); + app.use( + express.json({ + type: ['application/json', 'text/plain'], // the js client sends the body as text/plain + }), + ); + + app.get('/1/indexes/:indexName/settings', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_(source|destination)_(.*)$/)?.[2] as string; + + if (!aciState[lang] || aciState[lang].successful) { + aciState[lang] = { + setSettingsCount: 0, + getSettingsCount: 0, + saveRulesCount: 0, + browseRulesCount: 0, + saveSynonymsCount: 0, + browseSynonymsCount: 0, + saveObjectsCount: 0, + browseObjectsCount: 0, + waitTaskCount: 0, + successful: false, + }; + } else { + expect(aciState).to.include.keys(lang); + } + + aciState[lang].getSettingsCount++; + + if (req.params.indexName.includes('destination')) { + res.status(404).json({ message: 'Index not found' }); + } else { + res.status(200).json({ + minWordSizefor1Typo: 4, + minWordSizefor2Typos: 8, + hitsPerPage: 100, + maxValuesPerFacet: 100, + paginationLimitedTo: 10, + exactOnSingleWordQuery: 'attribute', + ranking: ['typo', 'geo', 'words', 'filters', 'proximity', 'attribute', 'exact', 'custom'], + separatorsToIndex: '', + removeWordsIfNoResults: 'none', + queryType: 'prefixLast', + highlightPreTag: '', + highlightPostTag: '', + alternativesAsExact: ['ignorePlurals', 'singleWordSynonym'], + }); + } + }); + + app.put('/1/indexes/:indexName/settings', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string; + expect(aciState).to.include.keys(lang); + + aciState[lang].setSettingsCount++; + + res.json({ taskID: 123 + aciState[lang].setSettingsCount, updatedAt: '2021-01-01T00:00:00.000Z' }); + }); + + app.post('/1/indexes/:indexName/rules/search', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_source_(.*)$/)?.[1] as string; + expect(aciState).to.include.keys(lang); + + aciState[lang].browseRulesCount++; + + res.json({ + hits: [ + { + conditions: [ + { + alternatives: true, + anchoring: 'contains', + pattern: 'zorro', + }, + ], + consequence: { + params: { + ignorePlurals: 'true', + }, + filterPromotes: true, + promote: [ + { + objectIDs: ['\u00C6on Flux'], + position: 0, + }, + ], + }, + description: 'test_rule', + enabled: true, + objectID: 'qr-1725004648916', + }, + ], + nbHits: 1, + nbPages: 1, + page: 0, + }); + }); + + app.post('/1/indexes/:indexName/rules/batch', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string; + expect(aciState).to.include.keys(lang); + + aciState[lang].saveRulesCount++; + + res.json({ taskID: 456 + aciState[lang].saveRulesCount, updatedAt: '2021-01-01T00:00:00.000Z' }); + }); + + app.post('/1/indexes/:indexName/synonyms/search', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_source_(.*)$/)?.[1] as string; + expect(aciState).to.include.keys(lang); + + aciState[lang].browseSynonymsCount++; + + res.json({ + hits: [ + { + objectID: 'foo', + }, + ], + nbHits: 1, + nbPages: 1, + page: 0, + }); + }); + + app.post('/1/indexes/:indexName/synonyms/batch', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string; + expect(aciState).to.include.keys(lang); + + aciState[lang].saveSynonymsCount++; + + res.json({ taskID: 789 + aciState[lang].saveSynonymsCount, updatedAt: '2021-01-01T00:00:00.000Z' }); + }); + + app.post('/1/indexes/:indexName/browse', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_source_(.*)$/)?.[1] as string; + expect(aciState).to.include.keys(lang); + + aciState[lang].browseObjectsCount++; + + res.json({ + page: 0, + nbHits: 1, + nbPages: 1, + hitsPerPage: 1000, + query: '', + params: '', + hits: [{ objectID: 'bar' }], + }); + }); + + app.post('/1/indexes/:indexName/batch', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string; + expect(aciState).to.include.keys(lang); + + aciState[lang].saveObjectsCount++; + + res.json({ taskID: 10 + aciState[lang].saveObjectsCount, updatedAt: '2021-01-01T00:00:00.000Z' }); + }); + + app.get('/1/indexes/:indexName/task/:taskID', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string; + expect(aciState).to.include.keys(lang); + + aciState[lang].waitTaskCount++; + + res.json({ status: 'published', updatedAt: '2021-01-01T00:00:00.000Z' }); + }); +} + +export function accountCopyIndexServer(): Promise { + // this server is used to simulate the responses for the accountCopyIndex method, + // and uses a state machine to determine if the logic is correct. + return setupServer('accountCopyIndex', 6687, addRoutes); +} diff --git a/scripts/cts/testServer/index.ts b/scripts/cts/testServer/index.ts index 6f4ec3d1f2..4549ed225f 100644 --- a/scripts/cts/testServer/index.ts +++ b/scripts/cts/testServer/index.ts @@ -7,6 +7,7 @@ import { createSpinner } from '../../spinners.ts'; import type { CTSType } from '../runCts.ts'; import { expect } from 'chai'; +import { accountCopyIndexServer } from './accountCopyIndex.ts'; import { algoliaMockServer } from './algoliaMock.ts'; import { apiKeyServer } from './apiKey.ts'; import { benchmarkServer } from './benchmark.ts'; @@ -26,6 +27,7 @@ export async function startTestServer(suites: Record): Promise timeoutServer(), gzipServer(), timeoutServerBis(), + accountCopyIndexServer(), replaceAllObjectsServer(), replaceAllObjectsServerFailed(), replaceAllObjectsScopesServer(), diff --git a/specs/common/responses/IndexAlreadyExists.yml b/specs/common/responses/IndexAlreadyExists.yml new file mode 100644 index 0000000000..b39121fbaf --- /dev/null +++ b/specs/common/responses/IndexAlreadyExists.yml @@ -0,0 +1,5 @@ +description: Destination index already exists. +content: + application/json: + schema: + $ref: '../schemas/ErrorBase.yml' diff --git a/specs/common/responses/IndexInSameApp.yml b/specs/common/responses/IndexInSameApp.yml new file mode 100644 index 0000000000..98edb0e5d1 --- /dev/null +++ b/specs/common/responses/IndexInSameApp.yml @@ -0,0 +1,5 @@ +description: Indices are in the same application. Use operationIndex instead. +content: + application/json: + schema: + $ref: '../schemas/ErrorBase.yml' diff --git a/specs/search/helpers/accountCopyIndex.yml b/specs/search/helpers/accountCopyIndex.yml new file mode 100644 index 0000000000..c652b12756 --- /dev/null +++ b/specs/search/helpers/accountCopyIndex.yml @@ -0,0 +1,49 @@ +method: + get: + x-helper: true + x-available-languages: + - javascript + tags: + - Account + x-acl: + - browse + - editSettings + - addObject + - settings + - search + operationId: accountCopyIndex + summary: Copies the given `sourceIndexName` records, rules and synonyms to an other Algolia application for the given `destinationIndexName` + description: | + Copies the given `sourceIndexName` records, rules and synonyms to an other Algolia application for the given `destinationIndexName`. + parameters: + - in: query + name: sourceIndexName + description: The name of the index to copy. + required: true + schema: + type: string + - in: query + name: destinationAppID + description: The application ID to write the index to. + required: true + schema: + type: string + - in: query + name: destinationApiKey + description: The API Key of the `destinationAppID` to write the index to, must have write ACLs. + required: true + schema: + type: string + - in: query + name: destinationIndexName + description: The name of the index to write the copied index to. + required: true + schema: + type: string + responses: + '400': + $ref: '../../common/responses/IndexInSameApp.yml' + '403': + $ref: '../../common/responses/IndexAlreadyExists.yml' + '404': + $ref: '../../common/responses/IndexNotFound.yml' diff --git a/specs/search/spec.yml b/specs/search/spec.yml index a3f61e1ac8..224d6ab047 100644 --- a/specs/search/spec.yml +++ b/specs/search/spec.yml @@ -372,6 +372,9 @@ paths: /generateSecuredApiKey: $ref: 'helpers/generateSecuredApiKey.yml#/method' + /accountCopyIndex: + $ref: 'helpers/accountCopyIndex.yml#/method' + /replaceAllObjects: $ref: 'helpers/replaceAllObjects.yml#/method' diff --git a/templates/javascript/clients/client/api/nodeHelpers.mustache b/templates/javascript/clients/client/api/nodeHelpers.mustache index be4a9acd09..083fd1f403 100644 --- a/templates/javascript/clients/client/api/nodeHelpers.mustache +++ b/templates/javascript/clients/client/api/nodeHelpers.mustache @@ -33,4 +33,102 @@ generateSecuredApiKey: ({ const queryParameters = serializeQueryParameters(mergedRestrictions); return Buffer.from(createHmac('sha256', parentApiKey).update(queryParameters).digest('hex') + queryParameters,).toString('base64'); +}, + +/** + * Helper: Copies the given `sourceIndexName` records, rules and synonyms to an other Algolia application for the given `destinationIndexName`. + * See https://api-clients-automation.netlify.app/docs/add-new-api-client#5-helpers for implementation details. + * + * @summary Helper: Copies the given `sourceIndexName` records, rules and synonyms to an other Algolia application for the given `destinationIndexName`. + * @param accountCopyIndex - The `accountCopyIndex` object. + * @param accountCopyIndex.sourceIndexName - The name of the index to copy. + * @param accountCopyIndex.destinationAppID - The application ID to write the index to. + * @param accountCopyIndex.destinationApiKey - The API Key of the `destinationAppID` to write the index to, must have write ACLs. + * @param accountCopyIndex.destinationIndexName - The name of the index to write the copied index to. + * @param requestOptions - The requestOptions to send along with the query, they will be forwarded to the `setSettings`, `saveRules`, `saveSynonyms` and `saveObjects` method and merged with the transporter requestOptions. + */ +async accountCopyIndex( + { sourceIndexName, destinationAppID, destinationApiKey, destinationIndexName }: AccountCopyIndexOptions, + requestOptions?: RequestOptions, +): Promise { + const responses: Array<{ taskID: UpdatedAtResponse['taskID'] }> = []; + + if (this.appId === destinationAppID) { + throw new IndicesInSameAppError(); + } + + if (!(await this.indexExists({ indexName: sourceIndexName }))) { + throw new IndexNotFoundError(sourceIndexName); + } + + const destinationClient = createSearchClient({ + appId: destinationAppID, + apiKey: destinationApiKey, + timeouts: { + connect: {{x-timeouts.server.connect}}, + read: {{x-timeouts.server.read}}, + write: {{x-timeouts.server.write}}, + }, + logger: createNullLogger(), + requester: createHttpRequester(), + algoliaAgents: [{ segment: 'accountCopyIndex', version: process.versions.node }], + responsesCache: createNullCache(), + requestsCache: createNullCache(), + hostsCache: createMemoryCache(), + ...options, + }); + + if (await destinationClient.indexExists({ indexName: destinationIndexName })) { + throw new IndexAlreadyExistsError(destinationIndexName); + } + + responses.push( + await destinationClient.setSettings( + { + indexName: destinationIndexName, + indexSettings: await this.getSettings({ indexName: sourceIndexName }), + }, + requestOptions, + ), + ); + + await this.browseRules({ + indexName: sourceIndexName, + async aggregator(response: SearchRulesResponse) { + responses.push( + await destinationClient.saveRules( + { indexName: destinationIndexName, rules: response.hits }, + requestOptions, + ), + ); + }, + }); + + await this.browseSynonyms({ + indexName: sourceIndexName, + async aggregator(response: SearchSynonymsResponse) { + responses.push( + await destinationClient.saveSynonyms( + { indexName: destinationIndexName, synonymHit: response.hits }, + requestOptions, + ), + ); + }, + }); + + await this.browseObjects({ + indexName: sourceIndexName, + async aggregator(response: BrowseResponse) { + responses.push( + ...(await destinationClient.saveObjects( + { indexName: destinationIndexName, objects: response.hits }, + requestOptions, + )), + ); + }, + }); + + for (const response of responses) { + await destinationClient.waitForTask({ indexName: destinationIndexName, taskID: response.taskID }); + } }, \ No newline at end of file diff --git a/templates/javascript/clients/client/builds/definition.mustache b/templates/javascript/clients/client/builds/definition.mustache index 3412ffeb9c..b3e41bd74a 100644 --- a/templates/javascript/clients/client/builds/definition.mustache +++ b/templates/javascript/clients/client/builds/definition.mustache @@ -1,9 +1,19 @@ import { createXhrRequester } from '@algolia/requester-browser-xhr'; import { createHttpRequester } from '@algolia/requester-node-http'; import { createFetchRequester } from '@algolia/requester-fetch'; -import { createNullLogger, createMemoryCache, createFallbackableCache, createBrowserLocalStorageCache, createNullCache, serializeQueryParameters } from '@algolia/client-common'; +import { + IndexNotFoundError, + IndicesInSameAppError, + IndexAlreadyExistsError, + createNullLogger, + createMemoryCache, + createFallbackableCache, + createBrowserLocalStorageCache, + createNullCache, + serializeQueryParameters +} from '@algolia/client-common'; -import type { ClientOptions } from '@algolia/client-common'; +import type { RequestOptions, ClientOptions } from '@algolia/client-common'; import { create{{#lambda.titlecase}}{{clientName}}{{/lambda.titlecase}}, apiClientVersion } from '../src/{{#isCompositionFullClient}}compositionFullClient{{/isCompositionFullClient}}{{^isCompositionFullClient}}{{clientName}}{{/isCompositionFullClient}}'; @@ -28,7 +38,16 @@ export * from '../model'; {{#isSearchClient}} {{^isAlgoliasearchClient}} -import type { GenerateSecuredApiKeyOptions, GetSecuredApiKeyRemainingValidityOptions, SearchClientNodeHelpers } from '../model'; +import type { + AccountCopyIndexOptions, + BrowseResponse, + GenerateSecuredApiKeyOptions, + GetSecuredApiKeyRemainingValidityOptions, + SearchClientNodeHelpers, + SearchRulesResponse, + SearchSynonymsResponse, + UpdatedAtResponse, +} from '../model'; {{/isAlgoliasearchClient}} {{/isSearchClient}} diff --git a/templates/javascript/clients/client/model/clientMethodProps.mustache b/templates/javascript/clients/client/model/clientMethodProps.mustache index 4ec998600f..0f83641996 100644 --- a/templates/javascript/clients/client/model/clientMethodProps.mustache +++ b/templates/javascript/clients/client/model/clientMethodProps.mustache @@ -14,6 +14,7 @@ import type { {{classname}} } from '{{filename}}'; {{! Imports for the helpers method of the search client }} {{#isSearchClient}} +import type { createSearchClient } from '../src/searchClient'; import type { CreateIterablePromise } from '@algolia/client-common'; {{/isSearchClient}} @@ -127,9 +128,10 @@ export type GetSecuredApiKeyRemainingValidityOptions = { } export type SearchClientNodeHelpers = { + accountCopyIndex: (opts: AccountCopyIndexOptions) => Promise; generateSecuredApiKey: (opts: GenerateSecuredApiKeyOptions) => string; getSecuredApiKeyRemainingValidity: (opts: GetSecuredApiKeyRemainingValidityOptions) => number; -} +}; {{/isSearchClient}} export type DeleteObjectsOptions = Pick & { @@ -187,6 +189,28 @@ export type ReplaceAllObjectsOptions = { */ scopes?: Array; } + +export type AccountCopyIndexOptions = { + /** + * The name of the index to copy to the `destinationClient`. + */ + sourceIndexName: string; + + /** + * The application ID to write the index to. + */ + destinationAppID: string; + + /** + * The API Key of the `destinationAppID` to write the index to, must have write ACLs. + */ + destinationApiKey: string; + + /** + * The name of the index to write the copy in. + */ + destinationIndexName: string; +}; {{/isAlgoliasearchClient}} {{/isSearchClient}} {{#isCompositionFullClient}} diff --git a/tests/CTS/client/search/accountCopyIndex.json b/tests/CTS/client/search/accountCopyIndex.json new file mode 100644 index 0000000000..4838250538 --- /dev/null +++ b/tests/CTS/client/search/accountCopyIndex.json @@ -0,0 +1,30 @@ +[ + { + "testName": "call accountCopyIndex without error", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "test-app-id", + "apiKey": "test-api-key", + "customHosts": [ + { + "port": 6687 + } + ] + } + }, + { + "type": "method", + "method": "accountCopyIndex", + "parameters": { + "sourceIndexName": "cts_e2e_account_copy_index_source_${{language}}", + "destinationAppID": "test-app-id-destination", + "destinationApiKey": "test-api-key-destination", + "destinationIndexName": "cts_e2e_account_copy_index_destination_${{language}}" + } + } + ] + } +] diff --git a/tests/output/javascript/yarn.lock b/tests/output/javascript/yarn.lock index 45ded5626e..f0c7fce9a8 100644 --- a/tests/output/javascript/yarn.lock +++ b/tests/output/javascript/yarn.lock @@ -404,32 +404,32 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:22.14.0": - version: 22.14.0 - resolution: "@types/node@npm:22.14.0" +"@types/node@npm:22.15.3": + version: 22.15.3 + resolution: "@types/node@npm:22.15.3" dependencies: undici-types: "npm:~6.21.0" - checksum: 10/d0669a8a37a18532c886ccfa51eb3fe1e46088deb4d3d27ebcd5d7d68bd6343ad1c7a3fcb85164780a57629359c33a6c917ecff748ea232bceac7692acc96537 + checksum: 10/6b4ff03c36598432b419980f828281aa16383e2de6eb61f73275495ef8d2cbf8cb5607659b4cae5ff8b2b2ff69913ea07ffcc0be029e4280b6e8bc138dc6629b languageName: node linkType: hard -"@vitest/expect@npm:3.1.1": - version: 3.1.1 - resolution: "@vitest/expect@npm:3.1.1" +"@vitest/expect@npm:3.1.2": + version: 3.1.2 + resolution: "@vitest/expect@npm:3.1.2" dependencies: - "@vitest/spy": "npm:3.1.1" - "@vitest/utils": "npm:3.1.1" + "@vitest/spy": "npm:3.1.2" + "@vitest/utils": "npm:3.1.2" chai: "npm:^5.2.0" tinyrainbow: "npm:^2.0.0" - checksum: 10/79754e35fb505f6ee9636e49f78961299b99b12cebf6fd7ea6455a05d9a9589a65fa5d4537a7b4f6b837a41988ab629a6ff76252a4a5accf77f9462dbf3570c8 + checksum: 10/3c414e376154c8095f40efe409bb5f2c9380ba05a15b20552ee2e29f73197ab73068177e3da298ac135ef72673d1ea92090c466c78443ee69a7438bc8ab65f4f languageName: node linkType: hard -"@vitest/mocker@npm:3.1.1": - version: 3.1.1 - resolution: "@vitest/mocker@npm:3.1.1" +"@vitest/mocker@npm:3.1.2": + version: 3.1.2 + resolution: "@vitest/mocker@npm:3.1.2" dependencies: - "@vitest/spy": "npm:3.1.1" + "@vitest/spy": "npm:3.1.2" estree-walker: "npm:^3.0.3" magic-string: "npm:^0.30.17" peerDependencies: @@ -440,57 +440,57 @@ __metadata: optional: true vite: optional: true - checksum: 10/18b72593071b4416b096c2c09899e9213804ec5e0fc90f6c5464ebd9645f34de9c2d0ecb09cbc7f3d44fbcf897fca2921ba114b185fc86a70050ccee1c6d6cc8 + checksum: 10/e6d730400daa7a97fb277159733df1366a932b5b06ac83d72e094e5383191c2597b4a5ae3538b28de6112b9e5d314cb50b44e031e79522f43f3dfc8ab022a584 languageName: node linkType: hard -"@vitest/pretty-format@npm:3.1.1, @vitest/pretty-format@npm:^3.1.1": - version: 3.1.1 - resolution: "@vitest/pretty-format@npm:3.1.1" +"@vitest/pretty-format@npm:3.1.2, @vitest/pretty-format@npm:^3.1.2": + version: 3.1.2 + resolution: "@vitest/pretty-format@npm:3.1.2" dependencies: tinyrainbow: "npm:^2.0.0" - checksum: 10/d1bc6a6c687d686194ef19ebc748894c543bc520d79db5e86d53ac97f004d13d5b364592a21e151031bf76bf8865ce25e60fc71cc02ca0d513d20bc0c600a63f + checksum: 10/454d0a8c250dbe52f7ec9dab4968e7c769fa10c8318eb5c54cb4b6d5b524772c04856e1990279f2c6e76705ffa107fddcbc1973560ed3b88167c231ccfeada16 languageName: node linkType: hard -"@vitest/runner@npm:3.1.1": - version: 3.1.1 - resolution: "@vitest/runner@npm:3.1.1" +"@vitest/runner@npm:3.1.2": + version: 3.1.2 + resolution: "@vitest/runner@npm:3.1.2" dependencies: - "@vitest/utils": "npm:3.1.1" + "@vitest/utils": "npm:3.1.2" pathe: "npm:^2.0.3" - checksum: 10/652a351e59d2f615f3b3de0bb47bfb7875117dd3e57a62c031d8f385614513dbe77847a39037aedb79818a3fde1d957bb82e2f3a5c74651d27721553ed8b4668 + checksum: 10/b09c1ff3a556f318585307e6bb8954d219d0d35d1e17708fdd5d5ae1a230e6f29eba4f37a86faa71192f72406bb96b576c1b620d49d686def87bc5dcb8bf5737 languageName: node linkType: hard -"@vitest/snapshot@npm:3.1.1": - version: 3.1.1 - resolution: "@vitest/snapshot@npm:3.1.1" +"@vitest/snapshot@npm:3.1.2": + version: 3.1.2 + resolution: "@vitest/snapshot@npm:3.1.2" dependencies: - "@vitest/pretty-format": "npm:3.1.1" + "@vitest/pretty-format": "npm:3.1.2" magic-string: "npm:^0.30.17" pathe: "npm:^2.0.3" - checksum: 10/517ad76c35bd55d2908349a9a5749f874927971c2d6f4ffb3a6f6345acb7a393fac4cc9778119a215caf9ee0c96c75dc27c9f9227a51bd3907c36da946652d43 + checksum: 10/dda969b697bdcd8616f17e98c74ad5e95a5f3c2284140aa72390ce668db34e70936ee0b8ebe89adb2e0dea332500689d54c8ff03f8adf1e00be70639ec9032bf languageName: node linkType: hard -"@vitest/spy@npm:3.1.1": - version: 3.1.1 - resolution: "@vitest/spy@npm:3.1.1" +"@vitest/spy@npm:3.1.2": + version: 3.1.2 + resolution: "@vitest/spy@npm:3.1.2" dependencies: tinyspy: "npm:^3.0.2" - checksum: 10/2b2c8cb2f13fe4ea48779b91c69140c596c8fe3a3ef283ef8b19c025cfdb59a60db7c65a320c84fa28b26655877570860bb44bc2d0e99639d097a7e042e3c604 + checksum: 10/c2c638368fa4130f903901fdf4e86da6f90d5d6a8cf7ce880cdd24768a1f8e6b726ea3428501c97e00c34ac2e8e39ac09b3a03606dffd8081559e0a35c892ddc languageName: node linkType: hard -"@vitest/utils@npm:3.1.1": - version: 3.1.1 - resolution: "@vitest/utils@npm:3.1.1" +"@vitest/utils@npm:3.1.2": + version: 3.1.2 + resolution: "@vitest/utils@npm:3.1.2" dependencies: - "@vitest/pretty-format": "npm:3.1.1" + "@vitest/pretty-format": "npm:3.1.2" loupe: "npm:^3.1.3" tinyrainbow: "npm:^2.0.0" - checksum: 10/05f28d3e6636966803d60d4867200d6a3a5b7bc2ebaf1583bc3d5a2fc024e024c4958237eca73e420349faf326a8f62c3ae00b2cc2edb42d601b79a759f21b8f + checksum: 10/221faaaf6c69ef24eacdcf68581c833cb99bf3e5125945b5dec928af7ef1af4359aa520b90c42413a128b308037bf3217d8c41a41f44ca4aee3ac44e3f0d56b5 languageName: node linkType: hard @@ -667,10 +667,10 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:16.4.7": - version: 16.4.7 - resolution: "dotenv@npm:16.4.7" - checksum: 10/f13bfe97db88f0df4ec505eeffb8925ec51f2d56a3d0b6d916964d8b4af494e6fb1633ba5d09089b552e77ab2a25de58d70259b2c5ed45ec148221835fc99a0c +"dotenv@npm:16.5.0": + version: 16.5.0 + resolution: "dotenv@npm:16.5.0" + checksum: 10/e68a16834f1a41cc2dfb01563bc150668ad675e6cd09191211467b5c0806b6ecd6ec438e021aa8e01cd0e72d2b70ef4302bec7cc0fe15b6955f85230b62dc8a9 languageName: node linkType: hard @@ -820,7 +820,7 @@ __metadata: languageName: node linkType: hard -"expect-type@npm:^1.2.0": +"expect-type@npm:^1.2.1": version: 1.2.1 resolution: "expect-type@npm:1.2.1" checksum: 10/d121d90f4f3f705ca0b656e36f28c0ba91483d0cddf2876e64e23c3dea2f2d5853e9c0c9a4e90eb4b3e4663bf09c2c02e9729c339dcd308c70b2107188e6b286 @@ -846,6 +846,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.4.4": + version: 6.4.4 + resolution: "fdir@npm:6.4.4" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10/d0000d6b790059b35f4ed19acc8847a66452e0bc68b28766c929ffd523e5ec2083811fc8a545e4a1d4945ce70e887b3a610c145c681073b506143ae3076342ed + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" @@ -1001,11 +1013,11 @@ __metadata: "@algolia/client-composition": "link:../../../clients/algoliasearch-client-javascript/packages/client-composition" "@algolia/composition": "link:../../../clients/algoliasearch-client-javascript/packages/composition" "@algolia/requester-testing": "link:../../../clients/algoliasearch-client-javascript/packages/requester-testing" - "@types/node": "npm:22.14.0" + "@types/node": "npm:22.15.3" algoliasearch: "link:../../../clients/algoliasearch-client-javascript/packages/algoliasearch" - dotenv: "npm:16.4.7" + dotenv: "npm:16.5.0" typescript: "npm:5.8.3" - vitest: "npm:3.1.1" + vitest: "npm:3.1.2" languageName: unknown linkType: soft @@ -1479,7 +1491,7 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.8.1": +"std-env@npm:^3.9.0": version: 3.9.0 resolution: "std-env@npm:3.9.0" checksum: 10/3044b2c54a74be4f460db56725571241ab3ac89a91f39c7709519bc90fa37148784bc4cd7d3a301aa735f43bd174496f263563f76703ce3e81370466ab7c235b @@ -1564,6 +1576,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.13": + version: 0.2.13 + resolution: "tinyglobby@npm:0.2.13" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10/b04557ee58ad2be5f2d2cbb4b441476436c92bb45ba2e1fc464d686b793392b305ed0bcb8b877429e9b5036bdd46770c161a08384c0720b6682b7cd6ac80e403 + languageName: node + linkType: hard + "tinypool@npm:^1.0.2": version: 1.0.2 resolution: "tinypool@npm:1.0.2" @@ -1630,9 +1652,9 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:3.1.1": - version: 3.1.1 - resolution: "vite-node@npm:3.1.1" +"vite-node@npm:3.1.2": + version: 3.1.2 + resolution: "vite-node@npm:3.1.2" dependencies: cac: "npm:^6.7.14" debug: "npm:^4.4.0" @@ -1641,7 +1663,7 @@ __metadata: vite: "npm:^5.0.0 || ^6.0.0" bin: vite-node: vite-node.mjs - checksum: 10/8243cbc2d83f7862d7882c982e85f3e45a654908de380591edd419338c4c75a7991bd22d12b290ad892b9ea102419e81fde92c87296ec7554f89d2ff2034d5e3 + checksum: 10/8af0465810c6f27200dc899792002320995f3d85c432aaa411bf7ff15580c0b93c4a5153d8a93c7af89b496a6e1a7979a7777984e37ebd7311851ea7572eaac7 languageName: node linkType: hard @@ -1697,36 +1719,37 @@ __metadata: languageName: node linkType: hard -"vitest@npm:3.1.1": - version: 3.1.1 - resolution: "vitest@npm:3.1.1" - dependencies: - "@vitest/expect": "npm:3.1.1" - "@vitest/mocker": "npm:3.1.1" - "@vitest/pretty-format": "npm:^3.1.1" - "@vitest/runner": "npm:3.1.1" - "@vitest/snapshot": "npm:3.1.1" - "@vitest/spy": "npm:3.1.1" - "@vitest/utils": "npm:3.1.1" +"vitest@npm:3.1.2": + version: 3.1.2 + resolution: "vitest@npm:3.1.2" + dependencies: + "@vitest/expect": "npm:3.1.2" + "@vitest/mocker": "npm:3.1.2" + "@vitest/pretty-format": "npm:^3.1.2" + "@vitest/runner": "npm:3.1.2" + "@vitest/snapshot": "npm:3.1.2" + "@vitest/spy": "npm:3.1.2" + "@vitest/utils": "npm:3.1.2" chai: "npm:^5.2.0" debug: "npm:^4.4.0" - expect-type: "npm:^1.2.0" + expect-type: "npm:^1.2.1" magic-string: "npm:^0.30.17" pathe: "npm:^2.0.3" - std-env: "npm:^3.8.1" + std-env: "npm:^3.9.0" tinybench: "npm:^2.9.0" tinyexec: "npm:^0.3.2" + tinyglobby: "npm:^0.2.13" tinypool: "npm:^1.0.2" tinyrainbow: "npm:^2.0.0" vite: "npm:^5.0.0 || ^6.0.0" - vite-node: "npm:3.1.1" + vite-node: "npm:3.1.2" why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" "@types/debug": ^4.1.12 "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 - "@vitest/browser": 3.1.1 - "@vitest/ui": 3.1.1 + "@vitest/browser": 3.1.2 + "@vitest/ui": 3.1.2 happy-dom: "*" jsdom: "*" peerDependenciesMeta: @@ -1746,7 +1769,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10/9dc54ef6854f877ad524667a0f3798b6c97c8138bee15a3dbad76557a45e3a3e42d438140df7a9eeaa10e5da7b5eb74ba854f06ffd233fa3c9e5936f6ae42e97 + checksum: 10/aa5638bf37b2811b01ad8ff0563cdec09202f7a28d9dbcb8eabb2e51cadefc57309cba4f5ff2bac4a72edda44055a844236fc4a09eb72127ef1bd34eb25d0808 languageName: node linkType: hard