Files
peeringdb/ts/peeringdb.classes.client.ts
2025-11-18 20:47:48 +00:00

297 lines
8.8 KiB
TypeScript

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 });
},
};
}