Skip to content

Commit 2288d17

Browse files
authored
feat(javascript): add waitForApiKey helper method (#738)
1 parent 18477b8 commit 2288d17

File tree

9 files changed

+187
-36
lines changed

9 files changed

+187
-36
lines changed

scripts/cli/utils.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ type Prompt = {
2525
interactive: boolean;
2626
};
2727

28-
export function getClientChoices(job: Job, language?: LangArg): string[] {
29-
const withoutAlgoliaSearch = PROMPT_CLIENTS.filter(
28+
export function getClientChoices(
29+
job: Job,
30+
language?: LangArg,
31+
clientList = PROMPT_CLIENTS
32+
): string[] {
33+
const withoutAlgoliaSearch = clientList.filter(
3034
(client) => client !== 'algoliasearch'
3135
);
3236

3337
if (!language) {
34-
return job === 'specs' ? withoutAlgoliaSearch : PROMPT_CLIENTS;
38+
return job === 'specs' ? withoutAlgoliaSearch : clientList;
3539
}
3640

3741
const isJavaScript = language === ALL || language === 'javascript';
@@ -41,7 +45,7 @@ export function getClientChoices(job: Job, language?: LangArg): string[] {
4145
case 'build':
4246
// Only `JavaScript` provide a lite client, others can build anything but it.
4347
if (isJavaScript) {
44-
return PROMPT_CLIENTS.filter((client) => client !== 'lite');
48+
return clientList.filter((client) => client !== 'lite');
4549
}
4650

4751
return withoutAlgoliaSearch.filter((client) => client !== 'lite');
@@ -56,7 +60,7 @@ export function getClientChoices(job: Job, language?: LangArg): string[] {
5660

5761
return withoutAlgoliaSearch.filter((client) => client !== 'lite');
5862
default:
59-
return PROMPT_CLIENTS;
63+
return clientList;
6064
}
6165
}
6266

@@ -107,7 +111,7 @@ export async function prompt({
107111
decision.language = langArg;
108112
}
109113

110-
decision.clientList = getClientChoices(job, decision.language);
114+
decision.clientList = getClientChoices(job, decision.language, CLIENTS);
111115

112116
if (!clientArg || !clientArg.length) {
113117
if (interactive) {
@@ -117,15 +121,15 @@ export async function prompt({
117121
name: 'client',
118122
message: 'Select a client',
119123
default: ALL,
120-
choices: decision.clientList,
124+
choices: getClientChoices(job, decision.language),
121125
},
122126
]);
123127

124128
decision.client = [client];
125129
}
126130
} else {
127131
clientArg.forEach((client) => {
128-
if (!decision.clientList.includes(client)) {
132+
if (!PROMPT_CLIENTS.includes(client)) {
129133
throw new Error(
130134
`The '${clientArg}' client can't run with the given job: '${job}'.\n\nAllowed choices are: ${decision.clientList.join(
131135
', '

templates/javascript/api-single.mustache

+2-25
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function create{{capitalizedApiName}}({
1818
hosts: getDefaultHosts({{^hasRegionalHost}}appIdOption{{/hasRegionalHost}}{{#hasRegionalHost}}regionOption{{/hasRegionalHost}}),
1919
...options,
2020
algoliaAgent: getAlgoliaAgent({
21-
algoliaAgents: algoliaAgents,
21+
algoliaAgents,
2222
client: '{{{algoliaAgent}}}',
2323
version: apiClientVersion,
2424
}),
@@ -40,30 +40,7 @@ export function create{{capitalizedApiName}}({
4040
return {
4141
addAlgoliaAgent,
4242
{{#isSearchClient}}
43-
/**
44-
* Wait for a task to complete with `indexName` and `taskID`.
45-
*
46-
* @summary Wait for a task to complete.
47-
* @param waitForTaskProps - The waitForTaskProps object.
48-
* @param waitForTaskProps.indexName - The index in which to perform the request.
49-
* @param waitForTaskProps.taskID - The unique identifier of the task to wait for.
50-
*/
51-
waitForTask({
52-
indexName,
53-
taskID,
54-
...createRetryablePromiseOptions,
55-
}: {
56-
indexName: string;
57-
taskID: number;
58-
} & Omit<CreateRetryablePromiseOptions<GetTaskResponse>, 'func' | 'validate'>): Promise<void> {
59-
return new Promise<void>((resolve, reject) => {
60-
createRetryablePromise<GetTaskResponse>({
61-
...createRetryablePromiseOptions,
62-
func: () => this.getTask({ indexName, taskID }),
63-
validate: (response) => response.status === 'published',
64-
}).then(() => resolve()).catch(reject);
65-
});
66-
},
43+
{{> api/helpers}}
6744
{{/isSearchClient}}
6845
{{#operation}}
6946
{{> api/operation/jsdoc}}
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Helper: Wait for a task to complete with `indexName` and `taskID`.
3+
*
4+
* @summary Wait for a task to complete.
5+
* @param waitForTaskOptions - The waitForTaskOptions object.
6+
* @param waitForTaskOptions.indexName - The `indexName` where the operation was performed.
7+
* @param waitForTaskOptions.taskID - The `taskID` returned in the method response.
8+
*/
9+
waitForTask({
10+
indexName,
11+
taskID,
12+
...createRetryablePromiseOptions
13+
}: WaitForTaskOptions): Promise<GetTaskResponse> {
14+
return createRetryablePromise({
15+
...createRetryablePromiseOptions,
16+
func: () => this.getTask({ indexName, taskID }),
17+
validate: (response) => response.status === 'published',
18+
});
19+
},
20+
21+
/**
22+
* Helper: Wait for an API key to be valid, updated or deleted based on a given `operation`.
23+
*
24+
* @summary Wait for an API key task to be processed.
25+
* @param waitForApiKeyOptions - The waitForApiKeyOptions object.
26+
* @param waitForApiKeyOptions.operation - The `operation` that was done on a `key`.
27+
* @param waitForApiKeyOptions.key - The `key` that has been added, deleted or updated.
28+
* @param waitForApiKeyOptions.apiKey - Necessary to know if an `update` operation has been processed, compare fields of the response with it.
29+
*/
30+
waitForApiKey({
31+
operation,
32+
key,
33+
apiKey,
34+
...createRetryablePromiseOptions
35+
}: WaitForApiKeyOptions): Promise<ApiError | Key> {
36+
if (operation === 'update') {
37+
return createRetryablePromise({
38+
...createRetryablePromiseOptions,
39+
func: () => this.getApiKey({ key }),
40+
validate: (response) => {
41+
for (const [entry, values] of Object.entries(apiKey)) {
42+
if (Array.isArray(values)) {
43+
if (
44+
values.length !== response[entry].length ||
45+
values.some((val, index) => val !== response[entry][index])
46+
) {
47+
return false;
48+
}
49+
} else if (values !== response[entry]) {
50+
return false;
51+
}
52+
}
53+
54+
return true;
55+
},
56+
});
57+
}
58+
59+
return createRetryablePromise({
60+
...createRetryablePromiseOptions,
61+
func: () => this.getApiKey({ key }).catch((error) => error),
62+
validate: (error: ApiError) =>
63+
operation === 'add' ? error.status !== 404 : error.status === 404,
64+
});
65+
},

templates/javascript/api/imports.mustache

+5-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
RequestOptions,
1616
QueryParameters,
1717
{{#isSearchClient}}
18-
CreateRetryablePromiseOptions,
18+
ApiError,
1919
{{/isSearchClient}}
2020
} from '{{{npmNamespace}}}/client-common';
2121

@@ -25,6 +25,10 @@ import { {{classname}} } from '{{filename}}';
2525

2626
{{#operations}}
2727
import type {
28+
{{#isSearchClient}}
29+
WaitForTaskOptions,
30+
WaitForApiKeyOptions,
31+
{{/isSearchClient}}
2832
{{#operation}}
2933
{{#vendorExtensions}}
3034
{{#x-create-wrapping-object}}

templates/javascript/clientMethodProps.mustache

+47
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { {{classname}} } from '{{filename}}';
77
{{! Imports for the legacy search method signature }}
88
{{#operations}}{{#operation}}{{#vendorExtensions.x-legacy-signature}}{{> api/operation/legacySearchCompatible/imports}}{{/vendorExtensions.x-legacy-signature}}{{/operation}}{{/operations}}
99

10+
{{! Imports for the helpers method of the search client }}
11+
{{#isSearchClient}}import type { CreateRetryablePromiseOptions } from '@experimental-api-clients-automation/client-common';{{/isSearchClient}}
12+
1013
{{#operations}}
1114
{{#operation}}
1215

@@ -33,4 +36,48 @@ export type {{#lambda.titlecase}}{{nickname}}{{/lambda.titlecase}}Props = {
3336

3437
{{/operation}}
3538
{{/operations}}
39+
40+
{{#isSearchClient}}
41+
type WaitForOptions<T> = Omit<
42+
CreateRetryablePromiseOptions<T>,
43+
'func' | 'validate'
44+
>;
45+
46+
export type WaitForTaskOptions = WaitForOptions<GetTaskResponse> & {
47+
/**
48+
* The `indexName` where the operation was performed.
49+
*/
50+
indexName: string;
51+
/**
52+
* The `taskID` returned by the method response.
53+
*/
54+
taskID: number;
55+
};
56+
57+
export type WaitForApiKeyOptions = WaitForOptions<Key> & {
58+
/**
59+
* The API Key.
60+
*/
61+
key: string;
62+
} & (
63+
| {
64+
/**
65+
* The operation that has been performed, used to compute the stop condition.
66+
*/
67+
operation: 'add' | 'delete';
68+
apiKey?: never;
69+
}
70+
| {
71+
/**
72+
* The operation that has been performed, used to compute the stop condition.
73+
*/
74+
operation: 'update';
75+
/**
76+
* The updated fields, used to compute the stop condition.
77+
*/
78+
apiKey: Partial<ApiKey>;
79+
}
80+
);
81+
{{/isSearchClient}}
82+
3683
{{/apiInfo.apis.0}}

website/docs/clients/guides/copy-index-to-another-application.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Copy or move an index
2+
title: Copy an index to another application
33
---
44

55
:::caution
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
title: Wait for an API key to be valid
3+
---
4+
5+
import { TabsLanguage } from '../../../src/components/TabsLanguage';
6+
import TabItem from '@theme/TabItem';
7+
8+
> The `waitForApiKey` method is only available in the `search` client context.
9+
10+
Adding, updating or deleting API keys is not always instantaneous, which is why you might want to ensure the job has been processed before jumping to an other task.
11+
12+
We provide a `waitForApiKey` helper method for you to easily wait for a specific `operation` made on a `key`.
13+
14+
<TabsLanguage>
15+
<TabItem value="javascript">
16+
17+
> An `operation` can either be `add` | `update` | `delete`
18+
19+
```js
20+
import { algoliasearch } from '@experimental-api-clients-automation/algoliasearch';
21+
22+
const client = algoliasearch('<YOUR_APP_ID>', '<YOUR_API_KEY>');
23+
24+
const { key } = await client.addApiKey({
25+
acl: ['analytics', 'browse', 'editSettings'],
26+
});
27+
28+
// Poll the task status with defaults values
29+
await client.waitForApiKey({ operation: 'add', key });
30+
31+
// The fields to update on your API key
32+
const updatesToPerform: ApiKey = {
33+
acl: ['analytics', 'search'],
34+
indexes: ['products'],
35+
};
36+
37+
// Call for update
38+
await client.updateApiKey({
39+
key,
40+
apiKey: updatesToPerform,
41+
});
42+
43+
// Wait for update to be done
44+
await client.waitForApiKey({
45+
operation: 'update',
46+
key,
47+
// We provide the updated fields to check if the changes have been applied
48+
apiKey: updatesToPerform,
49+
});
50+
```
51+
52+
</TabItem>
53+
</TabsLanguage>

website/sidebars.js

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ const sidebars = {
7979
'clients/guides/retrieving-facets',
8080
'clients/guides/customized-client-usage',
8181
'clients/guides/wait-for-a-task-to-finish',
82+
'clients/guides/wait-for-api-key-to-be-valid',
8283
'clients/guides/copy-or-move-index',
8384
'clients/guides/copy-index-to-another-application',
8485
],

website/src/components/TabsLanguage.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const languagesTabValues = [
99

1010
export function TabsLanguage(props) {
1111
return (
12-
<Tabs groupId="language" defaultValue="java" values={props.values}>
12+
<Tabs groupId="language" defaultValue="javascript" values={props.values}>
1313
{props.children}
1414
</Tabs>
1515
);

0 commit comments

Comments
 (0)