Skip to content

Commit 2a00209

Browse files
feat(javascript): add replaceAllObjectsWithTransformation (generated)
algolia/api-clients-automation#5008 Co-authored-by: algolia-bot <[email protected]> Co-authored-by: Clément Vannicatte <[email protected]>
1 parent b2a41b6 commit 2a00209

File tree

7 files changed

+753
-8
lines changed

7 files changed

+753
-8
lines changed

packages/algoliasearch/builds/browser.ts

Lines changed: 184 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Code generated by OpenAPI Generator (https://openapi-generator.tech), manual changes will be lost - read more on https://github.com/algolia/api-clients-automation. DO NOT EDIT.
22

3-
import type { ClientOptions, RequestOptions } from '@algolia/client-common';
3+
import type { ApiError, ClientOptions, RequestOptions } from '@algolia/client-common';
4+
import { createIterablePromise } from '@algolia/client-common';
45

56
import type { AbtestingClient } from '@algolia/client-abtesting';
67
import { abtestingClient } from '@algolia/client-abtesting';
@@ -21,7 +22,13 @@ import { monitoringClient } from '@algolia/monitoring';
2122
import type { RecommendClient } from '@algolia/recommend';
2223
import { recommendClient } from '@algolia/recommend';
2324

24-
import type { PartialUpdateObjectsOptions, SaveObjectsOptions } from '@algolia/client-search';
25+
import type {
26+
ChunkedBatchOptions,
27+
PartialUpdateObjectsOptions,
28+
ReplaceAllObjectsOptions,
29+
ReplaceAllObjectsWithTransformationResponse,
30+
SaveObjectsOptions,
31+
} from '@algolia/client-search';
2532
import type { PushTaskRecords, WatchResponse } from '@algolia/ingestion';
2633

2734
import type {
@@ -81,6 +88,36 @@ export type Algoliasearch = SearchClient & {
8188
options: PartialUpdateObjectsOptions,
8289
requestOptions?: RequestOptions | undefined,
8390
) => Promise<WatchResponse>;
91+
92+
/**
93+
* Helper: Similar to the `replaceAllObjects` method but requires a Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) to be created first, in order to transform records before indexing them to Algolia. The `region` must have been passed to the client instantiation method.
94+
*
95+
* @summary Helper: Replaces all objects (records) in the given `index_name` by leveraging the Transformation pipeline setup in the Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) with the given `objects`. A temporary index is created during this process in order to backup your data.
96+
* @param replaceAllObjects - The `replaceAllObjects` object.
97+
* @param replaceAllObjects.indexName - The `indexName` to replace `objects` in.
98+
* @param replaceAllObjects.objects - The array of `objects` to store in the given Algolia `indexName`.
99+
* @param replaceAllObjects.batchSize - The size of the chunk of `objects`. The number of `batch` calls will be equal to `objects.length / batchSize`. Defaults to 1000.
100+
* @param replaceAllObjects.scopes - The `scopes` to keep from the index. Defaults to ['settings', 'rules', 'synonyms'].
101+
* @param requestOptions - The requestOptions to send along with the query, they will be forwarded to the `push`, `operationIndex` and `getEvent` method and merged with the transporter requestOptions.
102+
*/
103+
replaceAllObjectsWithTransformation: (
104+
options: ReplaceAllObjectsOptions,
105+
requestOptions?: RequestOptions | undefined,
106+
) => Promise<ReplaceAllObjectsWithTransformationResponse>;
107+
108+
/**
109+
* Helper: Chunks the given `objects` list in subset of 1000 elements max in order to make it fit in `push` requests by leveraging the Transformation pipeline setup in the Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/).
110+
*
111+
* @summary Helper: Chunks the given `objects` list in subset of 1000 elements max in order to make it fit in `batch` requests.
112+
* @param chunkedPush - The `chunkedPush` object.
113+
* @param chunkedPush.indexName - The `indexName` to replace `objects` in.
114+
* @param chunkedPush.objects - The array of `objects` to store in the given Algolia `indexName`.
115+
* @param chunkedPush.action - The `batch` `action` to perform on the given array of `objects`, defaults to `addObject`.
116+
* @param chunkedPush.waitForTasks - Whether or not we should wait until every `batch` tasks has been processed, this operation may slow the total execution time of this method but is more reliable.
117+
* @param chunkedPush.batchSize - The size of the chunk of `objects`. The number of `batch` calls will be equal to `length(objects) / batchSize`. Defaults to 1000.
118+
* @param requestOptions - The requestOptions to send along with the query, they will be forwarded to the `getEvent` method and merged with the transporter requestOptions.
119+
*/
120+
chunkedPush: (options: ChunkedBatchOptions, requestOptions?: RequestOptions) => Promise<Array<WatchResponse>>;
84121
};
85122

86123
export type TransformationOptions = {
@@ -168,6 +205,151 @@ export function algoliasearch(
168205
);
169206
},
170207

208+
async chunkedPush(
209+
{ indexName, objects, action = 'addObject', waitForTasks, batchSize = 1000 }: ChunkedBatchOptions,
210+
requestOptions?: RequestOptions,
211+
): Promise<Array<WatchResponse>> {
212+
if (!ingestionTransporter) {
213+
throw new Error('`transformation.region` must be provided at client instantiation before calling this method.');
214+
}
215+
216+
if (!options?.transformation?.region) {
217+
throw new Error('`region` must be provided when leveraging the transformation pipeline');
218+
}
219+
220+
let records: Array<PushTaskRecords> = [];
221+
const responses: Array<WatchResponse> = [];
222+
223+
const objectEntries = objects.entries();
224+
for (const [i, obj] of objectEntries) {
225+
records.push(obj as PushTaskRecords);
226+
if (records.length === batchSize || i === objects.length - 1) {
227+
responses.push(
228+
await ingestionTransporter.push(
229+
{ indexName, pushTaskPayload: { action, records }, watch: waitForTasks },
230+
requestOptions,
231+
),
232+
);
233+
records = [];
234+
}
235+
}
236+
237+
let retryCount = 0;
238+
239+
if (waitForTasks) {
240+
for (const resp of responses) {
241+
if (!resp.eventID) {
242+
throw new Error('received unexpected response from the push endpoint, eventID must not be undefined');
243+
}
244+
245+
await createIterablePromise({
246+
func: async () => {
247+
if (resp.eventID === undefined || !resp.eventID) {
248+
throw new Error('received unexpected response from the push endpoint, eventID must not be undefined');
249+
}
250+
251+
return ingestionTransporter
252+
.getEvent({ runID: resp.runID, eventID: resp.eventID })
253+
.catch((error: ApiError) => {
254+
if (error.status === 404) {
255+
return undefined;
256+
}
257+
258+
throw error;
259+
});
260+
},
261+
validate: (response) => response !== undefined,
262+
aggregator: () => (retryCount += 1),
263+
error: {
264+
validate: () => retryCount >= 50,
265+
message: () => `The maximum number of retries exceeded. (${retryCount}/${50})`,
266+
},
267+
timeout: (): number => Math.min(retryCount * 500, 5000),
268+
});
269+
}
270+
}
271+
272+
return responses;
273+
},
274+
275+
async replaceAllObjectsWithTransformation(
276+
{ indexName, objects, batchSize, scopes }: ReplaceAllObjectsOptions,
277+
requestOptions?: RequestOptions | undefined,
278+
): Promise<ReplaceAllObjectsWithTransformationResponse> {
279+
if (!ingestionTransporter) {
280+
throw new Error('`transformation.region` must be provided at client instantiation before calling this method.');
281+
}
282+
283+
if (!options?.transformation?.region) {
284+
throw new Error('`region` must be provided when leveraging the transformation pipeline');
285+
}
286+
287+
const randomSuffix = Math.floor(Math.random() * 1000000) + 100000;
288+
const tmpIndexName = `${indexName}_tmp_${randomSuffix}`;
289+
290+
if (scopes === undefined) {
291+
scopes = ['settings', 'rules', 'synonyms'];
292+
}
293+
294+
try {
295+
let copyOperationResponse = await this.operationIndex(
296+
{
297+
indexName,
298+
operationIndexParams: {
299+
operation: 'copy',
300+
destination: tmpIndexName,
301+
scope: scopes,
302+
},
303+
},
304+
requestOptions,
305+
);
306+
307+
const watchResponses = await this.chunkedPush(
308+
{ indexName: tmpIndexName, objects, waitForTasks: true, batchSize },
309+
requestOptions,
310+
);
311+
312+
await this.waitForTask({
313+
indexName: tmpIndexName,
314+
taskID: copyOperationResponse.taskID,
315+
});
316+
317+
copyOperationResponse = await this.operationIndex(
318+
{
319+
indexName,
320+
operationIndexParams: {
321+
operation: 'copy',
322+
destination: tmpIndexName,
323+
scope: scopes,
324+
},
325+
},
326+
requestOptions,
327+
);
328+
await this.waitForTask({
329+
indexName: tmpIndexName,
330+
taskID: copyOperationResponse.taskID,
331+
});
332+
333+
const moveOperationResponse = await this.operationIndex(
334+
{
335+
indexName: tmpIndexName,
336+
operationIndexParams: { operation: 'move', destination: indexName },
337+
},
338+
requestOptions,
339+
);
340+
await this.waitForTask({
341+
indexName: tmpIndexName,
342+
taskID: moveOperationResponse.taskID,
343+
});
344+
345+
return { copyOperationResponse, watchResponses, moveOperationResponse };
346+
} catch (error) {
347+
await this.deleteIndex({ indexName: tmpIndexName });
348+
349+
throw error;
350+
}
351+
},
352+
171353
/**
172354
* Get the value of the `algoliaAgent`, used by our libraries internally and telemetry system.
173355
*/

0 commit comments

Comments
 (0)