initial
This commit is contained in:
296
ts/peeringdb.classes.client.ts
Normal file
296
ts/peeringdb.classes.client.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
import { smartlog, smartrequest } from './plugins.js';
|
||||
import {
|
||||
type IPeeringDbResponse,
|
||||
type THttpMethod,
|
||||
type IQueryOptions,
|
||||
} from './peeringdb.types.js';
|
||||
|
||||
// Import manager classes (will be created next)
|
||||
import { OrganizationManager } from './peeringdb.classes.organizationmanager.js';
|
||||
import { NetworkManager } from './peeringdb.classes.networkmanager.js';
|
||||
import { FacilityManager } from './peeringdb.classes.facilitymanager.js';
|
||||
import { ExchangeManager } from './peeringdb.classes.exchangemanager.js';
|
||||
import { NetIxLanManager } from './peeringdb.classes.netixlanmanager.js';
|
||||
import { NetFacManager } from './peeringdb.classes.netfacmanager.js';
|
||||
import { IxLanManager } from './peeringdb.classes.ixlanmanager.js';
|
||||
import { IxFacManager } from './peeringdb.classes.ixfacmanager.js';
|
||||
import { IxPfxManager } from './peeringdb.classes.ixpfxmanager.js';
|
||||
import { PocManager } from './peeringdb.classes.pocmanager.js';
|
||||
|
||||
/**
|
||||
* PeeringDB API Client
|
||||
* Provides access to the PeeringDB API using native fetch
|
||||
*/
|
||||
export class PeeringDbClient {
|
||||
private apiKey: string | null = null;
|
||||
private baseUrl: string = 'https://www.peeringdb.com/api';
|
||||
private logger: smartlog.Smartlog;
|
||||
|
||||
// Manager instances
|
||||
public organizations: OrganizationManager;
|
||||
public networks: NetworkManager;
|
||||
public facilities: FacilityManager;
|
||||
public exchanges: ExchangeManager;
|
||||
public netIxLans: NetIxLanManager;
|
||||
public netFacs: NetFacManager;
|
||||
public ixLans: IxLanManager;
|
||||
public ixFacs: IxFacManager;
|
||||
public ixPfxs: IxPfxManager;
|
||||
public pocs: PocManager;
|
||||
|
||||
/**
|
||||
* Create a new PeeringDB API client
|
||||
* @param apiKey Optional API key for authenticated requests
|
||||
*/
|
||||
constructor(apiKey?: string) {
|
||||
this.apiKey = apiKey || null;
|
||||
this.logger = new smartlog.Smartlog({
|
||||
logContext: {
|
||||
company: 'Task Venture Capital',
|
||||
companyunit: '@apiclient.xyz/peeringdb',
|
||||
containerName: 'PeeringDbClient',
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize managers
|
||||
this.organizations = new OrganizationManager(this);
|
||||
this.networks = new NetworkManager(this);
|
||||
this.facilities = new FacilityManager(this);
|
||||
this.exchanges = new ExchangeManager(this);
|
||||
this.netIxLans = new NetIxLanManager(this);
|
||||
this.netFacs = new NetFacManager(this);
|
||||
this.ixLans = new IxLanManager(this);
|
||||
this.ixFacs = new IxFacManager(this);
|
||||
this.ixPfxs = new IxPfxManager(this);
|
||||
this.pocs = new PocManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a request to the PeeringDB API
|
||||
* @param endpoint API endpoint (e.g., 'net', 'org', 'fac')
|
||||
* @param method HTTP method
|
||||
* @param options Query options and parameters
|
||||
* @param body Request body for POST/PUT/PATCH
|
||||
* @returns Array of results (unwrapped from meta wrapper)
|
||||
*/
|
||||
public async request<T = any>(
|
||||
endpoint: string,
|
||||
method: THttpMethod = 'GET',
|
||||
options: IQueryOptions = {},
|
||||
body?: any
|
||||
): Promise<T[]> {
|
||||
const url = this.buildUrl(endpoint, options);
|
||||
|
||||
this.logger.log('info', `${method} ${url}`);
|
||||
|
||||
// Build request using fluent API
|
||||
let requestClient = smartrequest.SmartRequestClient.create()
|
||||
.url(url)
|
||||
.header('Accept', 'application/json')
|
||||
.header('User-Agent', '@apiclient.xyz/peeringdb/1.0.1 (Node.js)')
|
||||
.retry(3); // Retry up to 3 times (handles 429)
|
||||
|
||||
// Add API key if available
|
||||
if (this.apiKey) {
|
||||
requestClient = requestClient.header('Authorization', `Api-Key ${this.apiKey}`);
|
||||
}
|
||||
|
||||
// Add body for POST/PUT/PATCH requests
|
||||
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
||||
requestClient = requestClient.json(body);
|
||||
}
|
||||
|
||||
try {
|
||||
// Execute the appropriate HTTP method
|
||||
let response;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await requestClient.get();
|
||||
break;
|
||||
case 'POST':
|
||||
response = await requestClient.post();
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await requestClient.put();
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await requestClient.delete();
|
||||
break;
|
||||
case 'PATCH':
|
||||
response = await requestClient.patch();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported HTTP method: ${method}`);
|
||||
}
|
||||
|
||||
// Response body is automatically parsed as JSON by smartrequest
|
||||
const data: IPeeringDbResponse<T> = response.body;
|
||||
|
||||
// Check meta status
|
||||
if (data?.meta?.status === 'error') {
|
||||
this.logger.log('error', `API returned error: ${data.meta.message}`);
|
||||
throw new Error(`PeeringDB API error: ${data.meta.message}`);
|
||||
}
|
||||
|
||||
// Handle pagination if autoPaginate is enabled
|
||||
if (options.autoPaginate && data.data.length === (options.limit || 0)) {
|
||||
const allResults = [...data.data];
|
||||
let currentSkip = (options.skip || 0) + data.data.length;
|
||||
|
||||
while (true) {
|
||||
const nextOptions = { ...options, skip: currentSkip };
|
||||
const nextUrl = this.buildUrl(endpoint, nextOptions);
|
||||
|
||||
const nextResponse = await smartrequest.SmartRequestClient.create()
|
||||
.url(nextUrl)
|
||||
.header('Accept', 'application/json')
|
||||
.header('User-Agent', '@apiclient.xyz/peeringdb/1.0.1 (Node.js)')
|
||||
.header('Authorization', this.apiKey ? `Api-Key ${this.apiKey}` : '')
|
||||
.retry(3)
|
||||
.get();
|
||||
|
||||
const nextData: IPeeringDbResponse<T> = nextResponse.body;
|
||||
|
||||
if (nextData.data.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
allResults.push(...nextData.data);
|
||||
currentSkip += nextData.data.length;
|
||||
|
||||
// Safety check to prevent infinite loops
|
||||
if (nextData.data.length < (options.limit || 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return allResults;
|
||||
}
|
||||
|
||||
// Return unwrapped data array
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
this.logger.log('error', `Request failed: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build API URL with query parameters
|
||||
*/
|
||||
private buildUrl(endpoint: string, options: IQueryOptions = {}): string {
|
||||
const url = new URL(`${this.baseUrl}/${endpoint}`);
|
||||
|
||||
// Add standard query parameters
|
||||
if (options.limit !== undefined) {
|
||||
url.searchParams.set('limit', options.limit.toString());
|
||||
}
|
||||
|
||||
if (options.skip !== undefined) {
|
||||
url.searchParams.set('skip', options.skip.toString());
|
||||
}
|
||||
|
||||
if (options.fields) {
|
||||
url.searchParams.set('fields', options.fields);
|
||||
}
|
||||
|
||||
if (options.depth !== undefined) {
|
||||
url.searchParams.set('depth', options.depth.toString());
|
||||
}
|
||||
|
||||
if (options.since !== undefined) {
|
||||
url.searchParams.set('since', options.since.toString());
|
||||
}
|
||||
|
||||
// Add any additional query parameters (field filters, etc.)
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (
|
||||
!['limit', 'skip', 'fields', 'depth', 'since', 'autoPaginate'].includes(key)
|
||||
) {
|
||||
const value = options[key];
|
||||
if (value !== undefined && value !== null) {
|
||||
url.searchParams.set(key, value.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience methods for common operations
|
||||
*/
|
||||
public convenience = {
|
||||
/**
|
||||
* Get a network by ASN
|
||||
*/
|
||||
getNetworkByAsn: async (asn: number) => {
|
||||
const results = await this.request('net', 'GET', { asn });
|
||||
return results[0] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an organization by ID
|
||||
*/
|
||||
getOrganizationById: async (id: number) => {
|
||||
return this.organizations.getById(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a facility by ID
|
||||
*/
|
||||
getFacilityById: async (id: number) => {
|
||||
return this.facilities.getById(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an exchange by ID
|
||||
*/
|
||||
getExchangeById: async (id: number) => {
|
||||
return this.exchanges.getById(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Search networks by name
|
||||
*/
|
||||
searchNetworks: async (query: string) => {
|
||||
return this.request('net', 'GET', { name__contains: query });
|
||||
},
|
||||
|
||||
/**
|
||||
* Search facilities by name
|
||||
*/
|
||||
searchFacilities: async (query: string) => {
|
||||
return this.request('fac', 'GET', { name__contains: query });
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all facilities where a network is present
|
||||
*/
|
||||
getNetworkFacilities: async (asn: number) => {
|
||||
const network = await this.convenience.getNetworkByAsn(asn);
|
||||
if (!network) {
|
||||
return [];
|
||||
}
|
||||
return this.request('netfac', 'GET', { net_id: network.id, depth: 2 });
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all exchanges where a network peers
|
||||
*/
|
||||
getNetworkExchanges: async (asn: number) => {
|
||||
const network = await this.convenience.getNetworkByAsn(asn);
|
||||
if (!network) {
|
||||
return [];
|
||||
}
|
||||
return this.request('netixlan', 'GET', { net_id: network.id, depth: 2 });
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all networks present at a facility
|
||||
*/
|
||||
getFacilityNetworks: async (facId: number) => {
|
||||
return this.request('netfac', 'GET', { fac_id: facId, depth: 2 });
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user