Skip to content

Commit 702791a

Browse files
feat(javascript): add bridge to transformation on algoliasearch (generated)
algolia/api-clients-automation#4852 Co-authored-by: algolia-bot <[email protected]> Co-authored-by: Clément Vannicatte <[email protected]>
1 parent 42905a0 commit 702791a

File tree

6 files changed

+521
-110
lines changed

6 files changed

+521
-110
lines changed

packages/algoliasearch/__tests__/algoliasearch.common.test.ts

Lines changed: 78 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -143,131 +143,110 @@ describe('api', () => {
143143
});
144144
});
145145

146-
describe('init clients', () => {
147-
test('provides an init method for the analytics client', () => {
148-
expect(client.initAnalytics).not.toBeUndefined();
149-
});
150-
151-
test('provides an init method for the abtesting client', () => {
152-
expect(client.initAbtesting).not.toBeUndefined();
146+
describe('bridge methods', () => {
147+
test('throws when missing transformation.region', () => {
148+
//@ts-expect-error
149+
expect(() => algoliasearch('APP_ID', 'API_KEY', { transformation: {} })).toThrow(
150+
'`region` must be provided when leveraging the transformation pipeline',
151+
);
153152
});
154153

155-
test('provides an init method for the personalization client', () => {
156-
expect(client.initPersonalization).not.toBeUndefined();
157-
});
154+
test('throws when calling the transformation methods without init parameters', async () => {
155+
await expect(
156+
client.saveObjectsWithTransformation({
157+
indexName: 'foo',
158+
objects: [{ objectID: 'bar', baz: 42 }],
159+
waitForTasks: true,
160+
}),
161+
).rejects.toThrow('`transformation.region` must be provided at client instantiation before calling this method.');
158162

159-
test('provides an init method for the recommend client', () => {
160-
expect(client.initRecommend).not.toBeUndefined();
163+
await expect(
164+
client.partialUpdateObjectsWithTransformation({
165+
indexName: 'foo',
166+
objects: [{ objectID: 'bar', baz: 42 }],
167+
waitForTasks: true,
168+
}),
169+
).rejects.toThrow('`transformation.region` must be provided at client instantiation before calling this method.');
161170
});
162171

163-
test('default `init` clients to the root `algoliasearch` credentials', async () => {
164-
const abtestingClient = client.initAbtesting({ options: { requester: browserEchoRequester() } });
165-
const analyticsClient = client.initAnalytics({ options: { requester: browserEchoRequester() } });
166-
const recommendClient = client.initRecommend({ options: { requester: browserEchoRequester() } });
167-
const personalizationClient = client.initPersonalization({
168-
region: 'eu',
169-
options: { requester: browserEchoRequester() },
172+
test('exposes the transformation methods at the root of the client', async () => {
173+
const ingestionClient = algoliasearch('APP_ID', 'API_KEY', {
174+
requester: browserEchoRequester(),
175+
transformation: { region: 'us' },
170176
});
171177

172-
const res1 = (await abtestingClient.customGet({
173-
path: 'abtestingClient',
174-
})) as unknown as EchoResponse;
175-
const res2 = (await analyticsClient.customGet({
176-
path: 'analyticsClient',
177-
})) as unknown as EchoResponse;
178-
const res3 = (await personalizationClient.customGet({
179-
path: 'personalizationClient',
180-
})) as unknown as EchoResponse;
181-
const res4 = (await recommendClient.customGet({
182-
path: 'recommendClient',
178+
expect(ingestionClient.saveObjectsWithTransformation).not.toBeUndefined();
179+
180+
let res = (await ingestionClient.saveObjectsWithTransformation({
181+
indexName: 'foo',
182+
objects: [{ objectID: 'bar', baz: 42 }],
183+
waitForTasks: true,
183184
})) as unknown as EchoResponse;
184185

185-
expect(res1.headers).toEqual(
186-
expect.objectContaining({
187-
'x-algolia-application-id': 'APP_ID',
188-
'x-algolia-api-key': 'API_KEY',
189-
}),
190-
);
191-
expect(res2.headers).toEqual(
186+
expect(res.headers).toEqual(
192187
expect.objectContaining({
193188
'x-algolia-application-id': 'APP_ID',
194189
'x-algolia-api-key': 'API_KEY',
195190
}),
196191
);
197-
expect(res3.headers).toEqual(
198-
expect.objectContaining({
199-
'x-algolia-application-id': 'APP_ID',
200-
'x-algolia-api-key': 'API_KEY',
201-
}),
202-
);
203-
expect(res4.headers).toEqual(
192+
expect(res.url.startsWith('https://data.us.algolia.com/1/push/foo?watch=true')).toBeTruthy();
193+
expect(res.data).toEqual({
194+
action: 'addObject',
195+
records: [
196+
{
197+
baz: 42,
198+
objectID: 'bar',
199+
},
200+
],
201+
});
202+
expect(ingestionClient.partialUpdateObjectsWithTransformation).not.toBeUndefined();
203+
204+
res = (await ingestionClient.partialUpdateObjectsWithTransformation({
205+
indexName: 'foo',
206+
objects: [{ objectID: 'bar', baz: 42 }],
207+
waitForTasks: true,
208+
createIfNotExists: true,
209+
})) as unknown as EchoResponse;
210+
211+
expect(res.headers).toEqual(
204212
expect.objectContaining({
205213
'x-algolia-application-id': 'APP_ID',
206214
'x-algolia-api-key': 'API_KEY',
207215
}),
208216
);
209-
});
210-
211-
test('`init` clients accept different credentials', async () => {
212-
const abtestingClient = client.initAbtesting({
213-
appId: 'appId1',
214-
apiKey: 'apiKey1',
215-
options: { requester: browserEchoRequester() },
216-
});
217-
const analyticsClient = client.initAnalytics({
218-
appId: 'appId2',
219-
apiKey: 'apiKey2',
220-
options: { requester: browserEchoRequester() },
221-
});
222-
const personalizationClient = client.initPersonalization({
223-
appId: 'appId3',
224-
apiKey: 'apiKey3',
225-
region: 'eu',
226-
options: { requester: browserEchoRequester() },
227-
});
228-
const recommendClient = client.initRecommend({
229-
appId: 'appId4',
230-
apiKey: 'apiKey4',
231-
options: { requester: browserEchoRequester() },
217+
expect(res.url.startsWith('https://data.us.algolia.com/1/push/foo?watch=true')).toBeTruthy();
218+
expect(res.data).toEqual({
219+
action: 'partialUpdateObject',
220+
records: [
221+
{
222+
baz: 42,
223+
objectID: 'bar',
224+
},
225+
],
232226
});
233227

234-
const res1 = (await abtestingClient.customGet({
235-
path: 'abtestingClient',
236-
})) as unknown as EchoResponse;
237-
const res2 = (await analyticsClient.customGet({
238-
path: 'analyticsClient',
239-
})) as unknown as EchoResponse;
240-
const res3 = (await personalizationClient.customGet({
241-
path: 'personalizationClient',
242-
})) as unknown as EchoResponse;
243-
const res4 = (await recommendClient.customGet({
244-
path: 'recommendClient',
228+
res = (await ingestionClient.partialUpdateObjectsWithTransformation({
229+
indexName: 'foo',
230+
objects: [{ objectID: 'bar', baz: 42 }],
231+
waitForTasks: true,
245232
})) as unknown as EchoResponse;
246233

247-
expect(res1.headers).toEqual(
248-
expect.objectContaining({
249-
'x-algolia-application-id': 'appId1',
250-
'x-algolia-api-key': 'apiKey1',
251-
}),
252-
);
253-
expect(res2.headers).toEqual(
254-
expect.objectContaining({
255-
'x-algolia-application-id': 'appId2',
256-
'x-algolia-api-key': 'apiKey2',
257-
}),
258-
);
259-
expect(res3.headers).toEqual(
234+
expect(res.headers).toEqual(
260235
expect.objectContaining({
261-
'x-algolia-application-id': 'appId3',
262-
'x-algolia-api-key': 'apiKey3',
263-
}),
264-
);
265-
expect(res4.headers).toEqual(
266-
expect.objectContaining({
267-
'x-algolia-application-id': 'appId4',
268-
'x-algolia-api-key': 'apiKey4',
236+
'x-algolia-application-id': 'APP_ID',
237+
'x-algolia-api-key': 'API_KEY',
269238
}),
270239
);
240+
expect(res.url.startsWith('https://data.us.algolia.com/1/push/foo?watch=true')).toBeTruthy();
241+
expect(res.data).toEqual({
242+
action: 'partialUpdateObjectNoCreate',
243+
records: [
244+
{
245+
baz: 42,
246+
objectID: 'bar',
247+
},
248+
],
249+
});
271250
});
272251
});
273252
});

packages/algoliasearch/builds/browser.ts

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
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 } from '@algolia/client-common';
3+
import type { ClientOptions, RequestOptions } from '@algolia/client-common';
44

55
import type { AbtestingClient } from '@algolia/client-abtesting';
66
import { abtestingClient } from '@algolia/client-abtesting';
@@ -21,9 +21,13 @@ import { monitoringClient } from '@algolia/monitoring';
2121
import type { RecommendClient } from '@algolia/recommend';
2222
import { recommendClient } from '@algolia/recommend';
2323

24+
import type { PartialUpdateObjectsOptions, SaveObjectsOptions } from '@algolia/client-search';
25+
import type { PushTaskRecords, WatchResponse } from '@algolia/ingestion';
26+
2427
import type {
2528
AbtestingRegionOptions,
2629
AnalyticsRegionOptions,
30+
IngestionRegion,
2731
IngestionRegionOptions,
2832
InitClientOptions,
2933
InsightsRegionOptions,
@@ -42,9 +46,56 @@ export type Algoliasearch = SearchClient & {
4246
initPersonalization: (initOptions: InitClientOptions & PersonalizationRegionOptions) => PersonalizationClient;
4347
initQuerySuggestions: (initOptions: InitClientOptions & QuerySuggestionsRegionOptions) => QuerySuggestionsClient;
4448
initRecommend: (initOptions?: InitClientOptions) => RecommendClient;
49+
50+
// Bridge helpers to expose along with the search endpoints at the root of the API client
51+
52+
/**
53+
* Helper: Similar to the `saveObjects` 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've been passed to the client instantiation method.
54+
*
55+
* @summary Save objects to an Algolia index 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/).
56+
* @param saveObjects - The `saveObjects` object.
57+
* @param saveObjects.indexName - The `indexName` to save `objects` in.
58+
* @param saveObjects.objects - The array of `objects` to store in the given Algolia `indexName`.
59+
* @param saveObjects.batchSize - The size of the chunk of `objects`. The number of `batch` calls will be equal to `length(objects) / batchSize`. Defaults to 1000.
60+
* @param saveObjects.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.
61+
* @param requestOptions - The requestOptions to send along with the query, they will be forwarded to the `batch` method and merged with the transporter requestOptions.
62+
*/
63+
saveObjectsWithTransformation: (
64+
options: SaveObjectsOptions,
65+
requestOptions?: RequestOptions,
66+
) => Promise<WatchResponse>;
67+
68+
/**
69+
* Helper: Similar to the `partialUpdateObjects` 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've been passed to the client instantiation method.
70+
*
71+
* @summary Save objects to an Algolia index 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/).
72+
* @param partialUpdateObjects - The `partialUpdateObjects` object.
73+
* @param partialUpdateObjects.indexName - The `indexName` to update `objects` in.
74+
* @param partialUpdateObjects.objects - The array of `objects` to update in the given Algolia `indexName`.
75+
* @param partialUpdateObjects.createIfNotExists - To be provided if non-existing objects are passed, otherwise, the call will fail..
76+
* @param partialUpdateObjects.batchSize - The size of the chunk of `objects`. The number of `batch` calls will be equal to `length(objects) / batchSize`. Defaults to 1000.
77+
* @param partialUpdateObjects.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.
78+
* @param requestOptions - The requestOptions to send along with the query, they will be forwarded to the `getTask` method and merged with the transporter requestOptions.
79+
*/
80+
partialUpdateObjectsWithTransformation: (
81+
options: PartialUpdateObjectsOptions,
82+
requestOptions?: RequestOptions,
83+
) => Promise<WatchResponse>;
84+
};
85+
86+
export type TransformationOptions = {
87+
// When provided, a second transporter will be created in order to leverage the `*WithTransformation` methods exposed by the Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/).
88+
transformation?: {
89+
// The region of your Algolia application ID, used to target the correct hosts of the transformation service.
90+
region: IngestionRegion;
91+
};
4592
};
4693

47-
export function algoliasearch(appId: string, apiKey: string, options?: ClientOptions): Algoliasearch {
94+
export function algoliasearch(
95+
appId: string,
96+
apiKey: string,
97+
options?: ClientOptions & TransformationOptions,
98+
): Algoliasearch {
4899
if (!appId || typeof appId !== 'string') {
49100
throw new Error('`appId` is missing.');
50101
}
@@ -55,9 +106,66 @@ export function algoliasearch(appId: string, apiKey: string, options?: ClientOpt
55106

56107
const client = searchClient(appId, apiKey, options);
57108

109+
let ingestionTransporter: IngestionClient | undefined;
110+
111+
if (options?.transformation) {
112+
if (!options.transformation.region) {
113+
throw new Error('`region` must be provided when leveraging the transformation pipeline');
114+
}
115+
116+
ingestionTransporter = ingestionClient(appId, apiKey, options.transformation.region, options);
117+
}
118+
58119
return {
59120
...client,
60121

122+
async saveObjectsWithTransformation({ indexName, objects, waitForTasks }, requestOptions): Promise<WatchResponse> {
123+
if (!ingestionTransporter) {
124+
throw new Error('`transformation.region` must be provided at client instantiation before calling this method.');
125+
}
126+
127+
if (!options?.transformation?.region) {
128+
throw new Error('`region` must be provided when leveraging the transformation pipeline');
129+
}
130+
131+
return ingestionTransporter?.push(
132+
{
133+
indexName,
134+
watch: waitForTasks,
135+
pushTaskPayload: {
136+
action: 'addObject',
137+
records: objects as PushTaskRecords[],
138+
},
139+
},
140+
requestOptions,
141+
);
142+
},
143+
144+
async partialUpdateObjectsWithTransformation(
145+
{ indexName, objects, createIfNotExists, waitForTasks },
146+
requestOptions,
147+
): Promise<WatchResponse> {
148+
if (!ingestionTransporter) {
149+
throw new Error('`transformation.region` must be provided at client instantiation before calling this method.');
150+
}
151+
152+
if (!options?.transformation?.region) {
153+
throw new Error('`region` must be provided when leveraging the transformation pipeline');
154+
}
155+
156+
return ingestionTransporter?.push(
157+
{
158+
indexName,
159+
watch: waitForTasks,
160+
pushTaskPayload: {
161+
action: createIfNotExists ? 'partialUpdateObject' : 'partialUpdateObjectNoCreate',
162+
records: objects as PushTaskRecords[],
163+
},
164+
},
165+
requestOptions,
166+
);
167+
},
168+
61169
/**
62170
* Get the value of the `algoliaAgent`, used by our libraries internally and telemetry system.
63171
*/

0 commit comments

Comments
 (0)