Skip to content

Commit eb50a2f

Browse files
algolia-api-clients-automation-bot[bot]eric-zahariaxroche
committed
feat(javascript): Request-ID and Correlation-ID support (generated)
algolia/api-clients-automation#6747 Co-authored-by: algolia-api-clients-automation-bot[bot] <288895823+algolia-api-clients-automation-bot[bot]@users.noreply.github.com> Co-authored-by: Eric Zaharia <94015633+eric-zaharia@users.noreply.github.com> Co-authored-by: Xavier Roche <xavier.roche@algolia.com>
1 parent cb3174b commit eb50a2f

34 files changed

Lines changed: 1168 additions & 191 deletions

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@
2727
"files": [
2828
{
2929
"path": "packages/algoliasearch/dist/algoliasearch.umd.js",
30-
"maxSize": "21KB"
30+
"maxSize": "21.25KB"
3131
},
3232
{
3333
"path": "packages/algoliasearch/dist/lite/builds/browser.umd.js",
3434
"maxSize": "6KB"
3535
},
3636
{
3737
"path": "packages/abtesting/dist/builds/browser.umd.js",
38-
"maxSize": "6KB"
38+
"maxSize": "6.25KB"
3939
},
4040
{
4141
"path": "packages/client-abtesting/dist/builds/browser.umd.js",
42-
"maxSize": "6KB"
42+
"maxSize": "6.25KB"
4343
},
4444
{
4545
"path": "packages/client-analytics/dist/builds/browser.umd.js",
@@ -75,7 +75,7 @@
7575
},
7676
{
7777
"path": "packages/recommend/dist/builds/browser.umd.js",
78-
"maxSize": "6KB"
78+
"maxSize": "6.25KB"
7979
},
8080
{
8181
"path": "packages/agent-studio/dist/builds/browser.umd.js",

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ describe('api', () => {
6969
test('exposes the search client transporter for the algoliasearch client', () => {
7070
expect(client.transporter).not.toBeUndefined();
7171
expect(client.transporter).toEqual({
72+
requestIdChannel: 'queryParameters',
7273
algoliaAgent: {
7374
add: expect.any(Function),
7475
value: expect.stringContaining(
@@ -214,6 +215,7 @@ describe('search with legacy signature', () => {
214215
expect(req.searchParams).toStrictEqual({
215216
'x-algolia-api-key': 'API_KEY',
216217
'x-algolia-application-id': 'APP_ID',
218+
'x-algolia-request-id': expect.stringMatching(/^[0-9A-Za-z]{11}$/),
217219
});
218220
});
219221

@@ -234,6 +236,7 @@ describe('search with legacy signature', () => {
234236
expect(req.searchParams).toStrictEqual({
235237
'x-algolia-api-key': 'API_KEY',
236238
'x-algolia-application-id': 'APP_ID',
239+
'x-algolia-request-id': expect.stringMatching(/^[0-9A-Za-z]{11}$/),
237240
});
238241
});
239242

@@ -255,6 +258,7 @@ describe('search with legacy signature', () => {
255258
expect(req.searchParams).toStrictEqual({
256259
'x-algolia-api-key': 'API_KEY',
257260
'x-algolia-application-id': 'APP_ID',
261+
'x-algolia-request-id': expect.stringMatching(/^[0-9A-Za-z]{11}$/),
258262
});
259263
});
260264
});
@@ -276,6 +280,7 @@ describe('init', () => {
276280
expect(qpResult.searchParams).toEqual({
277281
'x-algolia-api-key': 'bar',
278282
'x-algolia-application-id': 'foo',
283+
'x-algolia-request-id': expect.stringMatching(/^[0-9A-Za-z]{11}$/),
279284
});
280285

281286
const headerResult = (await headerClient.customGet({
@@ -296,6 +301,7 @@ describe('init', () => {
296301
expect(res.searchParams).toEqual({
297302
'x-algolia-api-key': 'API_KEY',
298303
'x-algolia-application-id': 'APP_ID',
304+
'x-algolia-request-id': expect.stringMatching(/^[0-9A-Za-z]{11}$/),
299305
});
300306
});
301307
});

packages/algoliasearch/__tests__/algoliasearch.node.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect, test, vi } from 'vitest';
22

3+
import type { EndRequest, Requester } from '../../client-common/src/types';
34
import { LogLevelEnum } from '../../client-common/src/types';
45
import { createConsoleLogger } from '../../logger-console/src/logger';
56
import { algoliasearch, apiClientVersion } from '../builds/node';
@@ -24,6 +25,111 @@ test('forwards node search helpers', () => {
2425
}).not.toThrow();
2526
});
2627

28+
function createRecordingRequester(): { requester: Requester; requests: EndRequest[] } {
29+
const requests: EndRequest[] = [];
30+
31+
return {
32+
requests,
33+
requester: {
34+
send(request: EndRequest) {
35+
requests.push(request);
36+
let content = JSON.stringify({ taskID: 42, objectIDs: [], updatedAt: '2026-01-01T00:00:00Z' });
37+
if (request.url.includes('/task/')) {
38+
content = JSON.stringify({ status: 'published', updatedAt: '2026-01-01T00:00:00Z' });
39+
} else if (request.url.includes('/push/')) {
40+
content = JSON.stringify({ runID: 'run-1', eventID: 'event-1', message: 'pushed' });
41+
} else if (request.url.includes('/events/')) {
42+
content = JSON.stringify({
43+
eventID: 'event-1',
44+
runID: 'run-1',
45+
status: 'succeeded',
46+
type: 'record',
47+
batchSize: 1,
48+
publishedAt: '2026-01-01T00:00:00Z',
49+
});
50+
}
51+
52+
return Promise.resolve({ content, isTimedOut: false, status: 200 });
53+
},
54+
},
55+
};
56+
}
57+
58+
test('chunkedBatch shares one request-id across its batch calls and task polls', async () => {
59+
const { requester, requests } = createRecordingRequester();
60+
const client = algoliasearch('APP_ID', 'API_KEY', { requester });
61+
const objects = Array.from({ length: 1500 }, (_, i) => ({ objectID: `${i}` }));
62+
63+
await client.chunkedBatch({ indexName: 'foo', objects, waitForTasks: true });
64+
65+
expect(requests).toHaveLength(4);
66+
const ids = requests.map((request) => request.headers['request-id']);
67+
expect(ids[0]).toMatch(/^[0-9A-Za-z]{11}$/);
68+
expect(new Set(ids).size).toBe(1);
69+
});
70+
71+
test('replaceAllObjects shares one request-id across copy, batch, wait and move', async () => {
72+
const { requester, requests } = createRecordingRequester();
73+
const client = algoliasearch('APP_ID', 'API_KEY', { requester });
74+
75+
await client.replaceAllObjects({ indexName: 'foo', objects: [{ objectID: '1' }] });
76+
77+
expect(requests).toHaveLength(8);
78+
const ids = requests.map((request) => request.headers['request-id']);
79+
expect(ids[0]).toMatch(/^[0-9A-Za-z]{11}$/);
80+
expect(new Set(ids).size).toBe(1);
81+
});
82+
83+
test('replaceAllObjectsWithTransformation shares one request-id across its search calls and sends none to ingestion', async () => {
84+
const { requester, requests } = createRecordingRequester();
85+
const client = algoliasearch('APP_ID', 'API_KEY', {
86+
requester,
87+
transformationOptions: { region: 'eu', requester },
88+
});
89+
90+
await client.replaceAllObjectsWithTransformation({ indexName: 'foo', objects: [{ objectID: '1' }] });
91+
92+
const searchRequests = requests.filter((request) => request.url.includes('algolia.net'));
93+
const ingestionRequests = requests.filter((request) => request.url.includes('data.eu.algolia.com'));
94+
95+
expect(requests).toHaveLength(8);
96+
expect(searchRequests).toHaveLength(6);
97+
expect(ingestionRequests).toHaveLength(2);
98+
99+
const searchIds = searchRequests.map((request) => request.headers['request-id']);
100+
expect(searchIds[0]).toMatch(/^[0-9A-Za-z]{11}$/);
101+
expect(new Set(searchIds).size).toBe(1);
102+
103+
for (const request of ingestionRequests) {
104+
expect(request.headers['request-id']).toBeUndefined();
105+
expect(new URL(request.url).searchParams.get('x-algolia-request-id')).toBeNull();
106+
}
107+
});
108+
109+
test('each helper invocation mints a fresh request-id', async () => {
110+
const { requester, requests } = createRecordingRequester();
111+
const client = algoliasearch('APP_ID', 'API_KEY', { requester });
112+
113+
await client.chunkedBatch({ indexName: 'foo', objects: [{ objectID: '1' }] });
114+
await client.chunkedBatch({ indexName: 'foo', objects: [{ objectID: '1' }] });
115+
116+
expect(requests).toHaveLength(2);
117+
expect(requests[0].headers['request-id']).not.toBe(requests[1].headers['request-id']);
118+
});
119+
120+
test('a caller-supplied request-id flows through helpers untouched', async () => {
121+
const { requester, requests } = createRecordingRequester();
122+
const client = algoliasearch('APP_ID', 'API_KEY', { requester });
123+
124+
await client.chunkedBatch(
125+
{ indexName: 'foo', objects: [{ objectID: '1' }], waitForTasks: true },
126+
{ headers: { 'request-id': 'CallerChose' } },
127+
);
128+
129+
expect(requests).toHaveLength(2);
130+
expect(requests.map((request) => request.headers['request-id'])).toEqual(['CallerChose', 'CallerChose']);
131+
});
132+
27133
test('with logger', async () => {
28134
const consoleInfo = vi.spyOn(console, 'info').mockImplementation(() => {});
29135

packages/algoliasearch/builds/browser.ts

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +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

33
import type { ClientOptions, RequestOptions } from '@algolia/client-common';
4-
import { DEFAULT_REPLACE_ALL_OBJECTS_MAX_RETRIES, logWarning } from '@algolia/client-common';
4+
import { DEFAULT_REPLACE_ALL_OBJECTS_MAX_RETRIES, logWarning, withRequestId } from '@algolia/client-common';
55

66
import type { AbtestingV3Client } from '@algolia/abtesting';
77
import { abtestingV3Client } from '@algolia/abtesting';
@@ -222,6 +222,8 @@ export function algoliasearch(
222222
);
223223
}
224224

225+
const searchRequestOptions = withRequestId(client.transporter, requestOptions);
226+
225227
if (objects.length === 0) {
226228
logWarning(
227229
client.transporter.logger,
@@ -246,7 +248,7 @@ export function algoliasearch(
246248
scope: scopes,
247249
},
248250
},
249-
requestOptions,
251+
searchRequestOptions,
250252
);
251253

252254
const watchResponses = await ingestionTransporter.chunkedPush(
@@ -261,11 +263,14 @@ export function algoliasearch(
261263
requestOptions,
262264
);
263265

264-
await this.waitForTask({
265-
indexName: tmpIndexName,
266-
taskID: copyOperationResponse.taskID,
267-
maxRetries,
268-
});
266+
await this.waitForTask(
267+
{
268+
indexName: tmpIndexName,
269+
taskID: copyOperationResponse.taskID,
270+
maxRetries,
271+
},
272+
searchRequestOptions,
273+
);
269274

270275
copyOperationResponse = await this.operationIndex(
271276
{
@@ -276,30 +281,36 @@ export function algoliasearch(
276281
scope: scopes,
277282
},
278283
},
279-
requestOptions,
284+
searchRequestOptions,
285+
);
286+
await this.waitForTask(
287+
{
288+
indexName: tmpIndexName,
289+
taskID: copyOperationResponse.taskID,
290+
maxRetries,
291+
},
292+
searchRequestOptions,
280293
);
281-
await this.waitForTask({
282-
indexName: tmpIndexName,
283-
taskID: copyOperationResponse.taskID,
284-
maxRetries,
285-
});
286294

287295
const moveOperationResponse = await this.operationIndex(
288296
{
289297
indexName: tmpIndexName,
290298
operationIndexParams: { operation: 'move', destination: indexName },
291299
},
292-
requestOptions,
300+
searchRequestOptions,
301+
);
302+
await this.waitForTask(
303+
{
304+
indexName: tmpIndexName,
305+
taskID: moveOperationResponse.taskID,
306+
maxRetries,
307+
},
308+
searchRequestOptions,
293309
);
294-
await this.waitForTask({
295-
indexName: tmpIndexName,
296-
taskID: moveOperationResponse.taskID,
297-
maxRetries,
298-
});
299310

300311
return { copyOperationResponse, watchResponses, moveOperationResponse };
301312
} catch (error) {
302-
await this.deleteIndex({ indexName: tmpIndexName });
313+
await this.deleteIndex({ indexName: tmpIndexName }, searchRequestOptions);
303314

304315
throw error;
305316
}

packages/algoliasearch/builds/fetch.ts

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +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

33
import type { ClientOptions, RequestOptions } from '@algolia/client-common';
4-
import { DEFAULT_REPLACE_ALL_OBJECTS_MAX_RETRIES, logWarning } from '@algolia/client-common';
4+
import { DEFAULT_REPLACE_ALL_OBJECTS_MAX_RETRIES, logWarning, withRequestId } from '@algolia/client-common';
55

66
import type { AbtestingV3Client } from '@algolia/abtesting';
77
import { abtestingV3Client } from '@algolia/abtesting';
@@ -222,6 +222,8 @@ export function algoliasearch(
222222
);
223223
}
224224

225+
const searchRequestOptions = withRequestId(client.transporter, requestOptions);
226+
225227
if (objects.length === 0) {
226228
logWarning(
227229
client.transporter.logger,
@@ -246,7 +248,7 @@ export function algoliasearch(
246248
scope: scopes,
247249
},
248250
},
249-
requestOptions,
251+
searchRequestOptions,
250252
);
251253

252254
const watchResponses = await ingestionTransporter.chunkedPush(
@@ -261,11 +263,14 @@ export function algoliasearch(
261263
requestOptions,
262264
);
263265

264-
await this.waitForTask({
265-
indexName: tmpIndexName,
266-
taskID: copyOperationResponse.taskID,
267-
maxRetries,
268-
});
266+
await this.waitForTask(
267+
{
268+
indexName: tmpIndexName,
269+
taskID: copyOperationResponse.taskID,
270+
maxRetries,
271+
},
272+
searchRequestOptions,
273+
);
269274

270275
copyOperationResponse = await this.operationIndex(
271276
{
@@ -276,30 +281,36 @@ export function algoliasearch(
276281
scope: scopes,
277282
},
278283
},
279-
requestOptions,
284+
searchRequestOptions,
285+
);
286+
await this.waitForTask(
287+
{
288+
indexName: tmpIndexName,
289+
taskID: copyOperationResponse.taskID,
290+
maxRetries,
291+
},
292+
searchRequestOptions,
280293
);
281-
await this.waitForTask({
282-
indexName: tmpIndexName,
283-
taskID: copyOperationResponse.taskID,
284-
maxRetries,
285-
});
286294

287295
const moveOperationResponse = await this.operationIndex(
288296
{
289297
indexName: tmpIndexName,
290298
operationIndexParams: { operation: 'move', destination: indexName },
291299
},
292-
requestOptions,
300+
searchRequestOptions,
301+
);
302+
await this.waitForTask(
303+
{
304+
indexName: tmpIndexName,
305+
taskID: moveOperationResponse.taskID,
306+
maxRetries,
307+
},
308+
searchRequestOptions,
293309
);
294-
await this.waitForTask({
295-
indexName: tmpIndexName,
296-
taskID: moveOperationResponse.taskID,
297-
maxRetries,
298-
});
299310

300311
return { copyOperationResponse, watchResponses, moveOperationResponse };
301312
} catch (error) {
302-
await this.deleteIndex({ indexName: tmpIndexName });
313+
await this.deleteIndex({ indexName: tmpIndexName }, searchRequestOptions);
303314

304315
throw error;
305316
}

0 commit comments

Comments
 (0)