Skip to content

Commit 16b8c0a

Browse files
fix(js client): increase scope of retries (#384)
* fix(js client): increase scope of retries * refactor: use decorators * fix: comments
1 parent 445810f commit 16b8c0a

File tree

4 files changed

+51
-7
lines changed

4 files changed

+51
-7
lines changed

clients/javascript/lib/accountClient.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NitteiBaseClient } from './baseClient'
1+
import { IdempotentRequest, NitteiBaseClient } from './baseClient'
22
import type {
33
AccountSearchEventsRequestBody,
44
SearchEventsAPIResponse,
@@ -9,6 +9,8 @@ import type { CreateAccountRequestBody } from './gen_types/CreateAccountRequestB
99
import type { CreateAccountResponseBody } from './gen_types/CreateAccountResponseBody'
1010
import { convertEventDates } from './helpers/datesConverters'
1111

12+
const ACCOUNT_SEARCH_EVENTS_ENDPOINT = '/account/events/search'
13+
1214
/**
1315
* Client for the account endpoints
1416
* This is an admin client
@@ -83,9 +85,10 @@ export class NitteiAccountClient extends NitteiBaseClient {
8385
* @param params - search parameters, check {@link AccountSearchEventsRequestBody} for more details
8486
* @returns - the events found
8587
*/
88+
@IdempotentRequest(ACCOUNT_SEARCH_EVENTS_ENDPOINT)
8689
public async searchEventsInAccount(params: AccountSearchEventsRequestBody) {
8790
const res = await this.post<SearchEventsAPIResponse>(
88-
'/account/events/search',
91+
ACCOUNT_SEARCH_EVENTS_ENDPOINT,
8992
params
9093
)
9194

clients/javascript/lib/baseClient.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ export const createAxiosInstanceFrontend = (
336336
axiosRetry(axiosClient, {
337337
retries: args.retry.maxRetries ?? 3,
338338
retryDelay: axiosRetry.exponentialDelay,
339-
retryCondition: isNetworkOrIdempotentRequestError, // Retry on network errors or idempotent requests (GET, PUT, DELETE)
339+
retryCondition: canRequestBeRetried,
340340
shouldResetTimeout: true,
341341
})
342342
}
@@ -419,10 +419,46 @@ export const createAxiosInstanceBackend = async (
419419
axiosRetry(axiosClient, {
420420
retries: args.retry.maxRetries ?? 3,
421421
retryDelay: axiosRetry.exponentialDelay,
422-
retryCondition: isNetworkOrIdempotentRequestError, // Retry on network errors or idempotent requests (GET, PUT, DELETE)
422+
retryCondition: canRequestBeRetried,
423423
shouldResetTimeout: true,
424424
})
425425
}
426426

427427
return axiosClient
428428
}
429+
430+
// Idempotent endpoints (populated by the decorator)
431+
const idempotentEndpoints = new Set<string>()
432+
433+
/**
434+
* Mark the method as idempotent, which effectively adds it to the list of endpoints that can be retried without side-effects
435+
* @param endpoint - the endpoint to mark as idempotent (url)
436+
* @returns the decorator function
437+
*/
438+
export function IdempotentRequest(endpoint: string) {
439+
// Register this endpoint as idempotent
440+
idempotentEndpoints.add(endpoint)
441+
442+
return (
443+
_target: unknown,
444+
_propertyKey: string | symbol,
445+
descriptor: PropertyDescriptor
446+
): PropertyDescriptor => descriptor
447+
}
448+
449+
/**
450+
* Internal function for checking if an HTTP request can be retried without side-effects
451+
*/
452+
const canRequestBeRetried = (error: AxiosError): boolean => {
453+
// Default condition
454+
if (isNetworkOrIdempotentRequestError(error)) {
455+
return true
456+
}
457+
458+
// Allow some POST requests to be retried (e.g. searches)
459+
if (idempotentEndpoints.has(error.config?.url ?? '')) {
460+
return true
461+
}
462+
463+
return false
464+
}

clients/javascript/lib/eventClient.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NitteiBaseClient } from './baseClient'
1+
import { IdempotentRequest, NitteiBaseClient } from './baseClient'
22
import type {
33
CreateBatchEventsAPIResponse,
44
CreateBatchEventsRequestBody,
@@ -28,6 +28,8 @@ export type Timespan = {
2828
endTime: Date
2929
}
3030

31+
const EVENT_SEARCH_ENDPOINT = '/events/search'
32+
3133
/**
3234
* Client for the events' endpoints
3335
* This is an admin client (usually backend)
@@ -151,11 +153,12 @@ export class NitteiEventClient extends NitteiBaseClient {
151153
* @param options - options - see {@link SearchEventsRequestBody} for more details
152154
* @returns - the events found
153155
*/
156+
@IdempotentRequest(EVENT_SEARCH_ENDPOINT)
154157
public async searchEvents(
155158
options: SearchEventsRequestBody
156159
): Promise<SearchEventsAPIResponse> {
157160
const res = await this.post<SearchEventsAPIResponse>(
158-
'/events/search',
161+
EVENT_SEARCH_ENDPOINT,
159162
options
160163
)
161164

clients/javascript/tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"sourceMap": true,
1212
"esModuleInterop": true,
1313
"allowSyntheticDefaultImports": true,
14-
"resolveJsonModule": true
14+
"resolveJsonModule": true,
15+
"experimentalDecorators": true,
16+
"emitDecoratorMetadata": true
1517
},
1618
"include": ["lib/**/*", "tests/**/*", "scripts/**/*"],
1719
"exclude": ["node_modules"]

0 commit comments

Comments
 (0)