Skip to content

Commit

Permalink
feat: archives loopkup
Browse files Browse the repository at this point in the history
Closes #81
  • Loading branch information
Igorgro committed Apr 1, 2024
1 parent f1c20b2 commit d29a55a
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 34 deletions.
34 changes: 1 addition & 33 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,13 @@
import path from 'path';

import axios, { Method } from 'axios';
import axiosRetry, { IAxiosRetryConfig, isNetworkOrIdempotentRequestError } from 'axios-retry';
import chalk from 'chalk';
import { pickBy } from 'lodash';
import ms from 'ms';

import { getConfig } from '../config';

const API_DEBUG = process.env.API_DEBUG === 'true';

const DEFAULT_RETRY: IAxiosRetryConfig = {
retries: 10,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: isNetworkOrIdempotentRequestError,
};

axiosRetry(axios, DEFAULT_RETRY);

let version = 'unknown';
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
version = require(path.resolve(__dirname, '../../package.json')).version;
} catch (e) {}

export class ApiError extends Error {
constructor(
public request: { status: number; method: string; url: string },
public body: {
error: string;
message?: string;
invalidFields?: { path: string[]; message: string; type: string }[];
},
) {
super();

if (body?.message) {
this.message = body.message;
}
}
}
import { ApiError, DEFAULT_RETRY, version, API_DEBUG } from './common';

export function debugLog(...args: any[]) {
if (!API_DEBUG) return;
Expand Down
117 changes: 117 additions & 0 deletions src/api/archives-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import axios, { Method } from 'axios';
import chalk from 'chalk';
import { pickBy } from 'lodash';
import ms from 'ms';

import { API_DEBUG, ApiError, DEFAULT_RETRY, version } from './common';

export type Provider = {
provider: string;
dataSourceUrl: string;
release: string;
};

export type Archive = {
network: string;
providers: Provider[];
};

export type ArchivesResponse = {
archives: Archive[];
};

export async function api<T = any>({
method,
path,
data,
query = {},
headers = {},
auth,
responseType = 'json',
abortController,
retry,
}: {
method: Method;
path: string;
query?: Record<string, string | string[] | boolean | number | undefined>;
data?: unknown;
headers?: Record<string, string>;
auth?: { apiUrl: string; credentials: string };
responseType?: 'json' | 'stream';
abortController?: AbortController;
retry?: number;
}): Promise<{ body: T }> {
const started = Date.now();

const url = !path.startsWith('https') ? `https://cdn.subsquid.io/archives${path}` : path;

const finalHeaders = {
'X-CLI-Version': version,
...headers,
};

const response = await axios(url, {
method,
headers: finalHeaders,
data,
timeout: responseType === 'stream' ? 0 : undefined,
responseType,
params: pickBy(query, (v) => v),
signal: abortController ? (abortController.signal as any) : undefined,
validateStatus: () => true,
'axios-retry': retry
? {
...DEFAULT_RETRY,
retries: retry,
}
: undefined,
});

if (API_DEBUG) {
console.log(
chalk.dim(new Date().toISOString()),
chalk.cyan`[${method.toUpperCase()}]`,
response.config.url,
chalk.cyan(response.status),
ms(Date.now() - started),
chalk.dim(JSON.stringify({ headers: response.headers })),
);
if (response.data && responseType === 'json') {
console.log(chalk.dim(JSON.stringify(response.data)));
}
}

switch (response.status) {
case 200:
case 201:
case 204:
return { body: response.data };
default:
throw new ApiError(
{
method: method.toUpperCase(),
url: response.config.url || 'Unknown URL',
status: response.status,
},
response.data,
);
}
}

export async function listSubstrate() {
const { body } = await api<ArchivesResponse>({
method: 'get',
path: '/substrate.json',
});

return body;
}

export async function listEVM() {
const { body } = await api<ArchivesResponse>({
method: 'get',
path: '/evm.json',
});

return body;
}
37 changes: 37 additions & 0 deletions src/api/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import path from 'path';

import axios from 'axios';
import axiosRetry, { IAxiosRetryConfig, isNetworkOrIdempotentRequestError } from 'axios-retry';

export const API_DEBUG = process.env.API_DEBUG === 'true';

export const DEFAULT_RETRY: IAxiosRetryConfig = {
retries: 10,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: isNetworkOrIdempotentRequestError,
};

axiosRetry(axios, DEFAULT_RETRY);

export let version = 'unknown';
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
version = require(path.resolve(__dirname, '../../package.json')).version;
} catch (e) {}

export class ApiError extends Error {
constructor(
public request: { status: number; method: string; url: string },
public body: {
error: string;
message?: string;
invalidFields?: { path: string[]; message: string; type: string }[];
},
) {
super();

if (body?.message) {
this.message = body.message;
}
}
}
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './api';
export * from './common';
export * from './squids';
export * from './deploy';
export * from './secrets';
Expand Down
3 changes: 2 additions & 1 deletion src/api/profile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { api, ApiError } from './api';
import { api } from './api';
import { ApiError } from './common';
import { HttpResponse, SquidResponse } from './types';

export type Profile = {
Expand Down
50 changes: 50 additions & 0 deletions src/commands/archives/lookup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Args, ux as CliUx, Flags } from '@oclif/core';

import { listEVM, listSubstrate } from '../../api/archives-api';
import { CliCommand } from '../../command';

export default class Lookup extends CliCommand {
static description = 'Lookup for archive address';

static flags = {
type: Flags.string({
char: 't',
description: 'network type: EVM/Substrate',
options: ['evm', 'substrate'],
helpValue: '<evm|substrate>',
required: true,
}),
};

static args = {
name: Args.string({
description: 'name of archive to lookup',
required: true,
}),
};

async run(): Promise<void> {
const {
args: { name },
flags: { type },
} = await this.parse(Lookup);

const archives = type === 'evm' ? await listEVM() : await listSubstrate();
const archive = archives.archives.find((a) => a.network.toLocaleLowerCase() === name.toLocaleLowerCase());

if (!archive) return this.error('No archive with such name');

CliUx.ux.table(
[archive].map(({ network, providers }) => ({
network,
release: providers[0].release,
url: providers[0].dataSourceUrl,
})),
{
network: { header: 'Name' },
release: { header: 'Release' },
url: { header: 'URL' },
},
);
}
}
34 changes: 34 additions & 0 deletions src/commands/archives/ls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ux as CliUx, Flags } from '@oclif/core';

import { listEVM, listSubstrate } from '../../api/archives-api';
import { CliCommand } from '../../command';

export default class Ls extends CliCommand {
static description = 'List available archives';

static flags = {
type: Flags.string({
char: 't',
description: 'network type: EVM/Substrate',
options: ['evm', 'substrate'],
helpValue: '<evm|substrate>',
required: true,
}),
};

async run(): Promise<void> {
const {
flags: { type },
} = await this.parse(Ls);

const archives = type === 'evm' ? await listEVM() : await listSubstrate();

CliUx.ux.table(
archives.archives.map(({ network, providers }) => ({ network, release: providers[0].release })),
{
network: { header: 'Name' },
release: { header: 'Release' },
},
);
}
}

0 comments on commit d29a55a

Please sign in to comment.