Skip to content

Commit dcf33de

Browse files
v1.1.3 (#16)
- Exported `axios` and `axiosClient` with exponential backoff retry mechanism for HTTP requests. - Resolved issues with circular structure logging. - Fixed attachments metadata normalization bug. - Improved repository logging.
1 parent baf79e1 commit dcf33de

18 files changed

+183
-57
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,4 @@ dist
130130
.pnp.*
131131

132132
.npmrc
133-
.idea
133+
.idea

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
## Release Notes
44

5+
### v1.1.3
6+
7+
- Exported `axios` and `axiosClient` with exponential backoff retry mechanism for HTTP requests and omitting Authorization headers from Axios errors.
8+
- Resolved issues with circular structure logging.
9+
- Fixed attachments metadata normalization bug.
10+
- Improved repository logging.
11+
512
#### v1.1.2
613

714
- Unified incoming and outgoing event context.

package-lock.json

Lines changed: 48 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devrev/ts-adaas",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"description": "Typescript library containing the ADaaS(AirDrop as a Service) control protocol.",
55
"type": "commonjs",
66
"main": "./dist/index.js",
@@ -37,7 +37,8 @@
3737
},
3838
"dependencies": {
3939
"@devrev/typescript-sdk": "^1.1.27",
40-
"axios": "^1.5.1",
40+
"axios": "^1.7.9",
41+
"axios-retry": "^4.5.0",
4142
"form-data": "^4.0.1",
4243
"js-jsonl": "^1.1.1",
4344
"lambda-log": "^3.1.0",

src/common/control-protocol.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import axios from 'axios';
2-
1+
import { axios, axiosClient } from '../http/axios-client';
32
import {
43
AirdropEvent,
54
EventData,
@@ -33,7 +32,7 @@ export const emit = async ({
3332
console.info('Emitting event', JSON.stringify(newEvent));
3433

3534
try {
36-
await axios.post(
35+
await axiosClient.post(
3736
event.payload.event_context.callback_url,
3837
{ ...newEvent },
3938
{

src/common/helpers.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,18 @@ export function addReportToLoaderReport({
143143

144144
return loaderReports;
145145
}
146+
147+
// https://stackoverflow.com/a/53731154
148+
export function getCircularReplacer() {
149+
const seen = new WeakSet();
150+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
151+
return (key: any, value: any) => {
152+
if (typeof value === 'object' && value !== null) {
153+
if (seen.has(value)) {
154+
return;
155+
}
156+
seen.add(value);
157+
}
158+
return value;
159+
};
160+
}

src/common/install-initial-domain-mapping.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import axios from 'axios';
1+
import { axios, axiosClient } from '../http/axios-client';
22
import { FunctionInput } from '@devrev/typescript-sdk/dist/snap-ins';
33

44
import { InitialDomainMapping } from '../types/common';
@@ -18,7 +18,7 @@ export async function installInitialDomainMapping(
1818
}
1919

2020
try {
21-
const snapInResponse = await axios.get(
21+
const snapInResponse = await axiosClient.get(
2222
devrevEndpoint + '/internal/snap-ins.get',
2323
{
2424
headers: {
@@ -50,7 +50,7 @@ export async function installInitialDomainMapping(
5050
Object.keys(startingRecipeBlueprint).length !== 0
5151
) {
5252
try {
53-
const recipeBlueprintResponse = await axios.post(
53+
const recipeBlueprintResponse = await axiosClient.post(
5454
`${devrevEndpoint}/internal/airdrop.recipe.blueprints.create`,
5555
{
5656
...startingRecipeBlueprint,
@@ -83,7 +83,7 @@ export async function installInitialDomainMapping(
8383
// 2. Install the initial domain mappings
8484
const additionalMappings =
8585
initialDomainMappingJson.additional_mappings || {};
86-
const initialDomainMappingInstallResponse = await axios.post(
86+
const initialDomainMappingInstallResponse = await axiosClient.post(
8787
`${devrevEndpoint}/internal/airdrop.recipe.initial-domain-mappings.install`,
8888
{
8989
external_system_type: 'ADaaS',

src/http/axios-client.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import axios, { AxiosError } from 'axios';
2+
import axiosRetry from 'axios-retry';
3+
4+
const axiosClient = axios.create();
5+
6+
// Exponential backoff algorithm: Retry 3 times and there will be a delay of more than 1 * no. of retries second + random number of milliseconds between each retry.
7+
axiosRetry(axiosClient, {
8+
retries: 3,
9+
retryDelay: (retryCount, error) => {
10+
console.log(`Retry attempt: ${retryCount} of ${error.config?.url}.`);
11+
return axiosRetry.exponentialDelay(retryCount, error, 1000);
12+
},
13+
retryCondition: (error: AxiosError) => {
14+
if (
15+
error.response?.status &&
16+
error.response?.status >= 500 &&
17+
error.response?.status <= 599
18+
) {
19+
return true;
20+
} else if (error.response?.status === 429) {
21+
console.log(
22+
'Rate limit exceeded. Delay: ' + error.response.headers['retry-after']
23+
);
24+
return false;
25+
} else {
26+
return false;
27+
}
28+
},
29+
onMaxRetryTimesExceeded(error: AxiosError, retryCount) {
30+
console.log(`Max retries attempted: ${retryCount}`);
31+
delete error.config?.headers.Authorization;
32+
delete error.request._header;
33+
},
34+
});
35+
36+
export { axios, axiosClient };

src/http/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export const defaultResponse: HTTPResponse = {
1919
success: false,
2020
};
2121

22+
/**
23+
* HTTPClient class to make HTTP requests
24+
* @deprecated
25+
*/
2226
export class HTTPClient {
2327
private retryAfter = 0;
2428
private retryAt = 0;

src/http/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './client';
22
export * from './types';
3+
export * from './axios-client';

src/http/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/**
2+
* HTTP Response type
3+
* @deprecated
4+
*/
15
export type HTTPResponse = {
26
success: boolean;
37
message: string;

src/logger/logger.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { isMainThread, parentPort } from 'node:worker_threads';
1111
import { WorkerAdapterOptions, WorkerMessageSubject } from '../types/workers';
1212
import { AxiosError } from 'axios';
13+
import { getCircularReplacer } from '../common/helpers';
1314

1415
export class Logger extends Console {
1516
private options?: WorkerAdapterOptions;
@@ -38,7 +39,7 @@ export class Logger extends Console {
3839
parentPort?.postMessage({
3940
subject: WorkerMessageSubject.WorkerMessageLog,
4041
payload: {
41-
args: JSON.parse(JSON.stringify(args)),
42+
args: JSON.parse(JSON.stringify(args, getCircularReplacer())),
4243
level,
4344
},
4445
});

src/mappers/mappers.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import axios, { AxiosResponse } from 'axios';
1+
import { axiosClient } from '../http/axios-client';
2+
import { AxiosResponse } from 'axios';
23

34
import {
45
MappersFactoryInterface,
@@ -23,7 +24,7 @@ export class Mappers {
2324
params: MappersGetByTargetIdParams
2425
): Promise<AxiosResponse<MappersGetByTargetIdResponse>> {
2526
const { sync_unit, target } = params;
26-
return axios.get<MappersGetByTargetIdResponse>(
27+
return axiosClient.get<MappersGetByTargetIdResponse>(
2728
`${this.endpoint}/internal/airdrop.sync-mapper-record.get-by-target`,
2829
{
2930
headers: {
@@ -37,7 +38,7 @@ export class Mappers {
3738
async create(
3839
params: MappersCreateParams
3940
): Promise<AxiosResponse<MappersCreateResponse>> {
40-
return axios.post<MappersCreateResponse>(
41+
return axiosClient.post<MappersCreateResponse>(
4142
`${this.endpoint}/internal/airdrop.sync-mapper-record.create`,
4243
params,
4344
{
@@ -51,7 +52,7 @@ export class Mappers {
5152
async update(
5253
params: MappersUpdateParams
5354
): Promise<AxiosResponse<MappersUpdateResponse>> {
54-
return axios.post<MappersUpdateResponse>(
55+
return axiosClient.post<MappersUpdateResponse>(
5556
`${this.endpoint}/internal/airdrop.sync-mapper-record.update`,
5657
params,
5758
{

src/repo/repo.test.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,24 +54,36 @@ describe('Repo class push method', () => {
5454
expect(normalize).not.toHaveBeenCalled();
5555
});
5656

57-
describe('should not normalize items if they are airdrop default item types', () => {
58-
it.each(Object.values(AIRDROP_DEFAULT_ITEM_TYPES))(
59-
'item type: %s',
60-
async (itemType) => {
61-
repo = new Repo({
62-
event: createEvent({ eventType: EventType.ExtractionDataStart }),
63-
itemType,
64-
normalize,
65-
onUpload: jest.fn(),
66-
options: {},
67-
});
68-
69-
const items = createItems(10);
70-
await repo.push(items);
71-
72-
expect(normalize).not.toHaveBeenCalled();
73-
}
74-
);
57+
describe('should not normalize items if type is "external_domain_metadata" or "ssor_attachment"', () => {
58+
it('item type: external_domain_metadata', async () => {
59+
repo = new Repo({
60+
event: createEvent({ eventType: EventType.ExtractionDataStart }),
61+
itemType: AIRDROP_DEFAULT_ITEM_TYPES.EXTERNAL_DOMAIN_METADATA,
62+
normalize,
63+
onUpload: jest.fn(),
64+
options: {},
65+
});
66+
67+
const items = createItems(10);
68+
await repo.push(items);
69+
70+
expect(normalize).not.toHaveBeenCalled();
71+
});
72+
73+
it('item type: ssor_attachment', async () => {
74+
repo = new Repo({
75+
event: createEvent({ eventType: EventType.ExtractionDataStart }),
76+
itemType: AIRDROP_DEFAULT_ITEM_TYPES.SSOR_ATTACHMENT,
77+
normalize,
78+
onUpload: jest.fn(),
79+
options: {},
80+
});
81+
82+
const items = createItems(10);
83+
await repo.push(items);
84+
85+
expect(normalize).not.toHaveBeenCalled();
86+
});
7587
});
7688

7789
it('should leave 5 items in the items array after pushing 2005 items with batch size of 2000', async () => {

0 commit comments

Comments
 (0)