Skip to content

Export axiosClient, fix circular structure logging. Add normalization for attachments. #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@ dist
.pnp.*

.npmrc
.idea
.idea
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Release Notes

### v1.1.3

- Exported `axios` and `axiosClient` with exponential backoff retry mechanism for HTTP requests and omitting Authorization headers from Axios errors.
- Resolved issues with circular structure logging.
- Fixed attachments metadata normalization bug.
- Improved repository logging.

#### v1.1.2

- Unified incoming and outgoing event context.
Expand Down
57 changes: 48 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devrev/ts-adaas",
"version": "1.1.2",
"version": "1.1.3",
"description": "Typescript library containing the ADaaS(AirDrop as a Service) control protocol.",
"type": "commonjs",
"main": "./dist/index.js",
Expand Down Expand Up @@ -37,7 +37,8 @@
},
"dependencies": {
"@devrev/typescript-sdk": "^1.1.27",
"axios": "^1.5.1",
"axios": "^1.7.9",
"axios-retry": "^4.5.0",
"form-data": "^4.0.1",
"js-jsonl": "^1.1.1",
"lambda-log": "^3.1.0",
Expand Down
5 changes: 2 additions & 3 deletions src/common/control-protocol.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import axios from 'axios';

import { axios, axiosClient } from '../http/axios-client';
import {
AirdropEvent,
EventData,
Expand Down Expand Up @@ -33,7 +32,7 @@ export const emit = async ({
console.info('Emitting event', JSON.stringify(newEvent));

try {
await axios.post(
await axiosClient.post(
event.payload.event_context.callback_url,
{ ...newEvent },
{
Expand Down
15 changes: 15 additions & 0 deletions src/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,18 @@ export function addReportToLoaderReport({

return loaderReports;
}

// https://stackoverflow.com/a/53731154
export function getCircularReplacer() {
const seen = new WeakSet();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (key: any, value: any) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
8 changes: 4 additions & 4 deletions src/common/install-initial-domain-mapping.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import axios from 'axios';
import { axios, axiosClient } from '../http/axios-client';
import { FunctionInput } from '@devrev/typescript-sdk/dist/snap-ins';

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

try {
const snapInResponse = await axios.get(
const snapInResponse = await axiosClient.get(
devrevEndpoint + '/internal/snap-ins.get',
{
headers: {
Expand Down Expand Up @@ -50,7 +50,7 @@ export async function installInitialDomainMapping(
Object.keys(startingRecipeBlueprint).length !== 0
) {
try {
const recipeBlueprintResponse = await axios.post(
const recipeBlueprintResponse = await axiosClient.post(
`${devrevEndpoint}/internal/airdrop.recipe.blueprints.create`,
{
...startingRecipeBlueprint,
Expand Down Expand Up @@ -83,7 +83,7 @@ export async function installInitialDomainMapping(
// 2. Install the initial domain mappings
const additionalMappings =
initialDomainMappingJson.additional_mappings || {};
const initialDomainMappingInstallResponse = await axios.post(
const initialDomainMappingInstallResponse = await axiosClient.post(
`${devrevEndpoint}/internal/airdrop.recipe.initial-domain-mappings.install`,
{
external_system_type: 'ADaaS',
Expand Down
36 changes: 36 additions & 0 deletions src/http/axios-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import axios, { AxiosError } from 'axios';
import axiosRetry from 'axios-retry';

const axiosClient = axios.create();

// 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.
axiosRetry(axiosClient, {
retries: 3,
retryDelay: (retryCount, error) => {
console.log(`Retry attempt: ${retryCount} of ${error.config?.url}.`);
return axiosRetry.exponentialDelay(retryCount, error, 1000);
},
retryCondition: (error: AxiosError) => {
if (
error.response?.status &&
error.response?.status >= 500 &&
error.response?.status <= 599
) {
return true;
} else if (error.response?.status === 429) {
console.log(
'Rate limit exceeded. Delay: ' + error.response.headers['retry-after']
);
return false;
} else {
return false;
}
},
onMaxRetryTimesExceeded(error: AxiosError, retryCount) {
console.log(`Max retries attempted: ${retryCount}`);
delete error.config?.headers.Authorization;
delete error.request._header;
},
});

export { axios, axiosClient };
4 changes: 4 additions & 0 deletions src/http/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const defaultResponse: HTTPResponse = {
success: false,
};

/**
* HTTPClient class to make HTTP requests
* @deprecated
*/
export class HTTPClient {
private retryAfter = 0;
private retryAt = 0;
Expand Down
1 change: 1 addition & 0 deletions src/http/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './client';
export * from './types';
export * from './axios-client';
4 changes: 4 additions & 0 deletions src/http/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* HTTP Response type
* @deprecated
*/
export type HTTPResponse = {
success: boolean;
message: string;
Expand Down
3 changes: 2 additions & 1 deletion src/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { isMainThread, parentPort } from 'node:worker_threads';
import { WorkerAdapterOptions, WorkerMessageSubject } from '../types/workers';
import { AxiosError } from 'axios';
import { getCircularReplacer } from '../common/helpers';

export class Logger extends Console {
private options?: WorkerAdapterOptions;
Expand Down Expand Up @@ -38,7 +39,7 @@ export class Logger extends Console {
parentPort?.postMessage({
subject: WorkerMessageSubject.WorkerMessageLog,
payload: {
args: JSON.parse(JSON.stringify(args)),
args: JSON.parse(JSON.stringify(args, getCircularReplacer())),
level,
},
});
Expand Down
9 changes: 5 additions & 4 deletions src/mappers/mappers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios, { AxiosResponse } from 'axios';
import { axiosClient } from '../http/axios-client';
import { AxiosResponse } from 'axios';

import {
MappersFactoryInterface,
Expand All @@ -23,7 +24,7 @@ export class Mappers {
params: MappersGetByTargetIdParams
): Promise<AxiosResponse<MappersGetByTargetIdResponse>> {
const { sync_unit, target } = params;
return axios.get<MappersGetByTargetIdResponse>(
return axiosClient.get<MappersGetByTargetIdResponse>(
`${this.endpoint}/internal/airdrop.sync-mapper-record.get-by-target`,
{
headers: {
Expand All @@ -37,7 +38,7 @@ export class Mappers {
async create(
params: MappersCreateParams
): Promise<AxiosResponse<MappersCreateResponse>> {
return axios.post<MappersCreateResponse>(
return axiosClient.post<MappersCreateResponse>(
`${this.endpoint}/internal/airdrop.sync-mapper-record.create`,
params,
{
Expand All @@ -51,7 +52,7 @@ export class Mappers {
async update(
params: MappersUpdateParams
): Promise<AxiosResponse<MappersUpdateResponse>> {
return axios.post<MappersUpdateResponse>(
return axiosClient.post<MappersUpdateResponse>(
`${this.endpoint}/internal/airdrop.sync-mapper-record.update`,
params,
{
Expand Down
48 changes: 30 additions & 18 deletions src/repo/repo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,36 @@ describe('Repo class push method', () => {
expect(normalize).not.toHaveBeenCalled();
});

describe('should not normalize items if they are airdrop default item types', () => {
it.each(Object.values(AIRDROP_DEFAULT_ITEM_TYPES))(
'item type: %s',
async (itemType) => {
repo = new Repo({
event: createEvent({ eventType: EventType.ExtractionDataStart }),
itemType,
normalize,
onUpload: jest.fn(),
options: {},
});

const items = createItems(10);
await repo.push(items);

expect(normalize).not.toHaveBeenCalled();
}
);
describe('should not normalize items if type is "external_domain_metadata" or "ssor_attachment"', () => {
it('item type: external_domain_metadata', async () => {
repo = new Repo({
event: createEvent({ eventType: EventType.ExtractionDataStart }),
itemType: AIRDROP_DEFAULT_ITEM_TYPES.EXTERNAL_DOMAIN_METADATA,
normalize,
onUpload: jest.fn(),
options: {},
});

const items = createItems(10);
await repo.push(items);

expect(normalize).not.toHaveBeenCalled();
});

it('item type: ssor_attachment', async () => {
repo = new Repo({
event: createEvent({ eventType: EventType.ExtractionDataStart }),
itemType: AIRDROP_DEFAULT_ITEM_TYPES.SSOR_ATTACHMENT,
normalize,
onUpload: jest.fn(),
options: {},
});

const items = createItems(10);
await repo.push(items);

expect(normalize).not.toHaveBeenCalled();
});
});

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