-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #81
- Loading branch information
Showing
7 changed files
with
242 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' }, | ||
}, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' }, | ||
}, | ||
); | ||
} | ||
} |