This commit is contained in:
2025-11-18 20:47:48 +00:00
commit 747fb787e1
38 changed files with 18518 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build

View File

@@ -0,0 +1,124 @@
name: Default (tags)
on:
push:
tags:
- '*'
env:
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @git.zone/tsdoc
npmci command tsdoc
continue-on-error: true

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.nogit/
# artifacts
coverage/
public/
# installs
node_modules/
# caches
.yarn/
.cache/
.rpt2_cache
# builds
dist/
dist_*/
# AI
.claude/
.serena/
#------# custom

11
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "npm test",
"name": "Run npm test",
"request": "launch",
"type": "node-terminal"
}
]
}

26
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"schema": {
"type": "object",
"properties": {
"npmci": {
"type": "object",
"description": "settings for npmci"
},
"gitzone": {
"type": "object",
"description": "settings for gitzone",
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}
}
}
}
]
}

7175
deno.lock generated Normal file

File diff suppressed because it is too large Load Diff

18
npmextra.json Normal file
View File

@@ -0,0 +1,18 @@
{
"gitzone": {
"projectType": "npm",
"module": {
"githost": "code.foss.global",
"gitscope": "apiclient.xyz",
"gitrepo": "peeringdb",
"description": "an unofficial package for the peeringdb API",
"npmPackagename": "@apiclient.xyz/peeringdb",
"license": "MIT",
"projectDomain": "apiclient.xyz"
}
},
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
}
}

51
package.json Normal file
View File

@@ -0,0 +1,51 @@
{
"name": "@apiclient.xyz/peeringdb",
"version": "1.0.1",
"private": false,
"description": "an unofficial package for the peeringdb API",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"type": "module",
"author": "Task Venture Capital GmbH",
"license": "MIT",
"scripts": {
"test": "(tstest test/ --verbose)",
"build": "(tsbuild --web --allowimplicitany)",
"buildDocs": "(tsdoc)"
},
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
"repository": {
"type": "git",
"url": "https://code.foss.global/apiclient.xyz/peeringdb.git"
},
"bugs": {
"url": "https://code.foss.global/apiclient.xyz/peeringdb/issues"
},
"homepage": "https://code.foss.global/apiclient.xyz/peeringdb#readme",
"files": [
"ts/**/*",
"ts_web/**/*",
"dist/**/*",
"dist_*/**/*",
"dist_ts/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
],
"pnpm": {
"overrides": {}
},
"dependencies": {
"@push.rocks/smartlog": "^3.0.0",
"@push.rocks/smartrequest": "^2.0.0"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.25",
"@git.zone/tsbundle": "^2.0.5",
"@git.zone/tstest": "^2.7.0",
"@push.rocks/qenv": "^6.0.0",
"@types/node": "^24.10.1"
}
}

8538
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

89
readme.hints.md Normal file
View File

@@ -0,0 +1,89 @@
# Implementation Notes
## Architecture
This package implements a comprehensive TypeScript client for the PeeringDB API.
### Key Components
1. **Client Class** (`PeeringDbClient`)
- Main entry point for the API
- Uses @push.rocks/smartrequest fluent API
- Automatic retry with 429 rate limiting handling
- Supports both anonymous and authenticated requests
2. **Manager Classes** (10 managers)
- OrganizationManager - Organizations
- NetworkManager - Networks/ASNs
- FacilityManager - Colocation facilities
- ExchangeManager - Internet Exchanges
- NetIxLanManager - Network-IX connections
- NetFacManager - Network-facility connections
- IxLanManager - IX LAN information
- IxFacManager - IX-facility connections
- IxPfxManager - IX IP prefixes
- PocManager - Points of contact
3. **Type Definitions**
- Comprehensive TypeScript interfaces for all resources
- Type-safe query options
- Field filter types
### HTTP Client
Uses @push.rocks/smartrequest with the fluent API:
- `.retry(3)` for automatic retry on failures including 429 rate limits
- Chainable methods for building requests
- Automatic JSON parsing
- **User-Agent header required**: PeeringDB API blocks requests without proper User-Agent
- Format: `@apiclient.xyz/peeringdb/1.0.1 (Node.js)`
- Without it, API returns 403 Forbidden
### Response Handling
PeeringDB API returns responses in this format:
```json
{
"meta": {
"status": "ok",
"message": "optional"
},
"data": [...]
}
```
The client automatically unwraps the `data` array for convenience.
### Query Options
All manager list/search methods support:
- `limit` - Pagination limit
- `skip` - Offset for pagination
- `fields` - Field selection
- `depth` - Nested object expansion (0, 1, 2)
- `since` - Unix timestamp for recent updates
- `autoPaginate` - Auto-fetch all pages
- Field filters with operators (`__contains`, `__startswith`, `__in`, `__lt`, `__gte`, etc.)
### Testing
Tests use @git.zone/tstest and hit the real PeeringDB API anonymously.
To run tests: `pnpm test`
**Test Results:**
- Node.js: All 18 tests pass ✅
- Bun/Deno: Limited support due to smartrequest runtime compatibility issues
The package is fully functional and tested in Node.js, which is the primary target runtime.
### Rate Limiting
The smartrequest library automatically handles 429 responses with exponential backoff retry.
## Development Notes
- Follow sibling project patterns (DigitalOcean, GitLab, Cloudflare)
- Use fluent API for all HTTP requests
- Manager classes provide CRUD operations
- Convenience methods for common use cases
- Logging via @push.rocks/smartlog

464
readme.md Normal file
View File

@@ -0,0 +1,464 @@
# @apiclient.xyz/peeringdb 🌐
> A modern, fully-typed TypeScript client for the [PeeringDB API](https://www.peeringdb.com/apidocs/) with automatic retry handling and smart request management.
## Why This Client? ✨
- **🎯 Full TypeScript Support** - Complete type definitions for all PeeringDB resources
- **🚀 Smart Request Handling** - Automatic retry with exponential backoff for 429 rate limits
- **🔄 Fluent API** - Clean, chainable methods using `@push.rocks/smartrequest`
- **📦 Manager Pattern** - Organized access to all PeeringDB resources
- **🔍 Advanced Querying** - Field filters, pagination, depth control, and field selection
- **🔐 Auth Optional** - Works with anonymous access or API keys for write operations
- **✅ Production Ready** - Fully tested with 18/18 tests passing in Node.js
## Installation
```bash
# Using pnpm (recommended)
pnpm add @apiclient.xyz/peeringdb
# Using npm
npm install @apiclient.xyz/peeringdb
# Using yarn
yarn add @apiclient.xyz/peeringdb
```
## Quick Start 🚀
```typescript
import { PeeringDbClient } from '@apiclient.xyz/peeringdb';
// Create client (anonymous access - perfect for read operations)
const client = new PeeringDbClient();
// Fetch networks
const networks = await client.networks.list({ limit: 10 });
// Look up a specific network by ASN
const google = await client.networks.getByAsn(15169);
console.log(google?.name); // "Google LLC"
// Search for facilities
const equinixFacilities = await client.facilities.searchByName('Equinix');
// Get exchanges in a specific country
const usExchanges = await client.exchanges.getByCountry('US');
```
## Usage Examples 📚
### Working with Networks
```typescript
// List networks with pagination
const networks = await client.networks.list({
limit: 50,
skip: 0
});
// Get a specific network by ASN
const cloudflare = await client.networks.getByAsn(13335);
// Search networks by name
const results = await client.networks.searchByName('Amazon');
// Get all networks for an organization
const orgNetworks = await client.networks.getByOrgId(123);
// Get network with expanded nested objects (depth: 0, 1, or 2)
const networkWithDetails = await client.networks.getByAsn(15169, 2);
console.log(networkWithDetails?.org); // Full organization object included
```
### Working with Organizations
```typescript
// List organizations
const orgs = await client.organizations.list({ limit: 10 });
// Get a specific organization
const org = await client.organizations.getById(2);
// Search by name
const searchResults = await client.organizations.searchByName('Google');
// Get organizations by country
const usOrgs = await client.organizations.getByCountry('US');
```
### Working with Facilities
```typescript
// List data center facilities
const facilities = await client.facilities.list({ limit: 10 });
// Get a specific facility
const facility = await client.facilities.getById(1);
// Search by name
const equinix = await client.facilities.searchByName('Equinix');
// Get facilities in a country
const usFacilities = await client.facilities.getByCountry('US');
// Get facilities in a specific city
const londonDCs = await client.facilities.getByCity('London');
```
### Working with Internet Exchanges
```typescript
// List internet exchanges
const exchanges = await client.exchanges.list({ limit: 10 });
// Get a specific exchange
const exchange = await client.exchanges.getById(1);
// Search by name
const amsix = await client.exchanges.searchByName('AMS-IX');
// Get exchanges by country
const usIXs = await client.exchanges.getByCountry('US');
// Get exchanges by region
const europeIXs = await client.exchanges.getByRegion('Europe');
```
### Network Connections & Peering Points
```typescript
// Get network-to-IX connections (where a network peers)
const googlePeering = await client.netIxLans.getByAsn(15169);
// Get network-to-facility connections (where a network has presence)
const networkPresence = await client.netFacs.getByNetId(123);
// Get all networks present at a facility
const facilityNetworks = await client.netFacs.getByFacId(456);
// Get all networks at an exchange
const ixNetworks = await client.netIxLans.getByIxLanId(789);
```
### Convenience Methods 🎁
Quick shortcuts for common operations:
```typescript
// Quick network lookup by ASN
const network = await client.convenience.getNetworkByAsn(15169);
// Search networks (wraps networks.searchByName)
const networks = await client.convenience.searchNetworks('Google');
// Search facilities (wraps facilities.searchByName)
const facilities = await client.convenience.searchFacilities('Equinix');
// Get all facilities where a network is present
const networkFacilities = await client.convenience.getNetworkFacilities(15169);
// Get all exchanges where a network peers
const networkExchanges = await client.convenience.getNetworkExchanges(15169);
```
## Advanced Querying 🔍
### Query Options
All list and search methods support comprehensive query options:
```typescript
const networks = await client.networks.list({
limit: 50, // Limit number of results
skip: 100, // Offset for pagination
fields: 'id,asn,name', // Select only specific fields
depth: 2, // Expand nested objects (0, 1, or 2)
since: 1640000000, // Unix timestamp - get only updates since
autoPaginate: false, // Auto-fetch all pages (default: false)
// Field filters using PeeringDB's powerful query syntax
name__contains: 'Google',
asn__in: [15169, 16509, 13335],
info_traffic__gte: '100Gbps',
});
```
### Field Filter Operators
PeeringDB supports a wide range of filter operators:
```typescript
// String filters
name__contains: 'Google' // Contains substring (case-insensitive)
name__startswith: 'Google' // Starts with
name__in: ['Google', 'Amazon'] // Match any in list
// Number filters
asn__lt: 20000 // Less than
asn__lte: 20000 // Less than or equal to
asn__gt: 10000 // Greater than
asn__gte: 10000 // Greater than or equal to
asn__in: [15169, 16509] // Match any in list
// Date filters (Unix timestamps)
updated__gte: 1640000000 // Updated after timestamp
created__lt: 1650000000 // Created before timestamp
// Boolean filters
info_ipv6: true // Exact boolean match
```
### Depth Parameter
Control how much nested data is expanded in responses:
```typescript
// depth: 0 - Only IDs for nested objects (default)
const network0 = await client.networks.getByAsn(15169, 0);
console.log(network0?.org_id); // Just the ID
// depth: 1 - Basic nested object data
const network1 = await client.networks.getByAsn(15169, 1);
console.log(network1?.org?.name); // Organization name included
// depth: 2 - Full nested object expansion
const network2 = await client.networks.getByAsn(15169, 2);
console.log(network2?.org?.address); // Full organization details
```
### Field Selection
Request only the fields you need to reduce bandwidth:
```typescript
// Get only specific fields
const networks = await client.networks.list({
fields: 'id,asn,name,info_type',
limit: 100
});
// Each network object will only contain: id, asn, name, info_type
```
## Authentication 🔐
For write operations (create, update, delete), you need a PeeringDB API key:
```typescript
const client = new PeeringDbClient('your-api-key-here');
// Create a new organization
const newOrg = await client.organizations.create({
name: 'My Company',
website: 'https://example.com',
address1: '123 Main St',
city: 'San Francisco',
state: 'CA',
zipcode: '94102',
country: 'US',
});
// Update an organization
const updated = await client.organizations.update(123, {
website: 'https://newwebsite.com',
notes: 'Updated contact information'
});
// Delete an organization (careful!)
await client.organizations.delete(123);
```
> **Note:** Most use cases only need read access (anonymous), which doesn't require an API key.
## API Reference 📖
### Client
```typescript
class PeeringDbClient {
constructor(apiKey?: string)
// Resource managers
organizations: OrganizationManager
networks: NetworkManager
facilities: FacilityManager
exchanges: ExchangeManager
netIxLans: NetIxLanManager
netFacs: NetFacManager
ixLans: IxLanManager
ixFacs: IxFacManager
ixPfxs: IxPfxManager
pocs: PocManager
// Convenience methods
convenience: {
getNetworkByAsn(asn: number): Promise<INetwork | null>
searchNetworks(name: string): Promise<INetwork[]>
searchFacilities(name: string): Promise<IFacility[]>
getNetworkFacilities(asn: number): Promise<IFacility[]>
getNetworkExchanges(asn: number): Promise<IExchange[]>
}
}
```
### Manager Methods
All resource managers provide these standard methods:
```typescript
// List resources with optional filtering
list(options?: IQueryOptions): Promise<T[]>
// Get a single resource by ID
getById(id: number, depth?: 0 | 1 | 2): Promise<T | null>
// Create a new resource (requires API key)
create(data: Partial<T>): Promise<T>
// Update an existing resource (requires API key)
update(id: number, data: Partial<T>): Promise<T>
// Delete a resource (requires API key)
delete(id: number): Promise<void>
```
Plus specialized methods for each resource type (e.g., `getByAsn()`, `searchByName()`, `getByCountry()`).
### Available Resource Managers
| Manager | Endpoint | Description |
|---------|----------|-------------|
| `organizations` | `/org` | Companies and organizations |
| `networks` | `/net` | Autonomous systems and networks |
| `facilities` | `/fac` | Data centers and colocation facilities |
| `exchanges` | `/ix` | Internet exchange points |
| `netIxLans` | `/netixlan` | Network-to-exchange connections |
| `netFacs` | `/netfac` | Network-to-facility connections |
| `ixLans` | `/ixlan` | Exchange LAN information |
| `ixFacs` | `/ixfac` | Exchange-to-facility connections |
| `ixPfxs` | `/ixpfx` | Exchange IP prefixes |
| `pocs` | `/poc` | Points of contact |
## TypeScript Support 💙
Full TypeScript support with comprehensive interfaces:
```typescript
import type {
INetwork,
IOrganization,
IFacility,
IExchange,
IQueryOptions
} from '@apiclient.xyz/peeringdb';
// Type-safe queries
const options: IQueryOptions = {
limit: 50,
depth: 2,
asn__gte: 10000
};
const networks: INetwork[] = await client.networks.list(options);
// Full intellisense for all properties
networks.forEach(network => {
console.log(network.asn); // number
console.log(network.name); // string
console.log(network.info_ipv6); // boolean
console.log(network.created); // string (ISO date)
});
```
## Rate Limiting & Retry 🔄
The client automatically handles PeeringDB's rate limits:
- **Automatic Retry**: Up to 3 retries with exponential backoff
- **429 Handling**: Smartly backs off when rate limited
- **Connection Pooling**: Efficient HTTP connection reuse via `@push.rocks/smartrequest`
No configuration needed - it just works! 🎉
## Error Handling ⚠️
```typescript
try {
const network = await client.networks.getByAsn(15169);
if (!network) {
console.log('Network not found');
}
} catch (error) {
console.error('API Error:', error.message);
}
// The client throws errors for:
// - Network failures
// - Invalid API responses
// - PeeringDB API errors (returned in meta.error)
// - Missing required User-Agent (403 Forbidden)
```
## Important Notes 📝
### User-Agent Requirement
PeeringDB **requires** a proper User-Agent header or returns `403 Forbidden`. This client automatically sets:
```
User-Agent: @apiclient.xyz/peeringdb/1.0.1 (Node.js)
```
If you see 403 errors with other HTTP clients, make sure to set a User-Agent!
### Runtime Support
-**Node.js**: Fully supported and tested (18/18 tests passing)
- ⚠️ **Bun/Deno**: Limited support due to smartrequest compatibility
This package is designed primarily for Node.js environments.
## Resources 🔗
- [PeeringDB API Documentation](https://www.peeringdb.com/apidocs/)
- [PeeringDB Website](https://www.peeringdb.com/)
- [Source Code Repository](https://code.foss.global/apiclient.xyz/peeringdb)
- [Issue Tracker](https://code.foss.global/apiclient.xyz/peeringdb/issues)
## Real-World Use Cases 🌍
This client is perfect for:
- **Network Engineering Tools** - Build automation for peering management
- **Network Visualization** - Map internet topology and interconnections
- **Due Diligence** - Research networks and their peering policies
- **Capacity Planning** - Analyze where networks have presence
- **Monitoring Systems** - Track changes to network infrastructure
- **Research Projects** - Study internet topology and peering relationships
## Contributing 🤝
Found a bug? Have a feature request?
Please open an issue on our [issue tracker](https://code.foss.global/apiclient.xyz/peeringdb/issues).
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

153
test/test.node+bun+deno.ts Normal file
View File

@@ -0,0 +1,153 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { PeeringDbClient } from '../ts/index.js';
let client: PeeringDbClient;
// Initialize client
tap.test('should create a PeeringDB client instance', async () => {
client = new PeeringDbClient();
expect(client).toBeInstanceOf(PeeringDbClient);
expect(client.networks).toBeDefined();
expect(client.organizations).toBeDefined();
expect(client.facilities).toBeDefined();
expect(client.exchanges).toBeDefined();
});
// Test network queries
tap.test('should fetch networks list', async () => {
const networks = await client.networks.list({ limit: 5 });
expect(Array.isArray(networks)).toEqual(true);
expect(networks.length).toBeGreaterThan(0);
expect(networks.length).toBeLessThanOrEqual(5);
expect(networks[0]).toHaveProperty('id');
expect(networks[0]).toHaveProperty('asn');
expect(networks[0]).toHaveProperty('name');
});
tap.test('should fetch a network by ASN', async () => {
// Google's ASN
const network = await client.networks.getByAsn(15169);
expect(network).toBeDefined();
expect(network?.asn).toEqual(15169);
expect(network?.name).toBeDefined();
});
tap.test('should search networks by name', async () => {
const networks = await client.networks.searchByName('Google', { limit: 5 });
expect(Array.isArray(networks)).toEqual(true);
expect(networks.length).toBeGreaterThan(0);
});
// Test organization queries
tap.test('should fetch organizations list', async () => {
const orgs = await client.organizations.list({ limit: 5 });
expect(Array.isArray(orgs)).toEqual(true);
expect(orgs.length).toBeGreaterThan(0);
expect(orgs[0]).toHaveProperty('id');
expect(orgs[0]).toHaveProperty('name');
});
tap.test('should fetch organization by ID', async () => {
const org = await client.organizations.getById(2);
expect(org).toBeDefined();
expect(org?.id).toEqual(2);
expect(org?.name).toBeDefined();
});
// Test facility queries
tap.test('should fetch facilities list', async () => {
const facilities = await client.facilities.list({ limit: 5 });
expect(Array.isArray(facilities)).toEqual(true);
expect(facilities.length).toBeGreaterThan(0);
expect(facilities[0]).toHaveProperty('id');
expect(facilities[0]).toHaveProperty('name');
expect(facilities[0]).toHaveProperty('city');
});
tap.test('should search facilities by name', async () => {
const facilities = await client.facilities.searchByName('Equinix', { limit: 5 });
expect(Array.isArray(facilities)).toEqual(true);
expect(facilities.length).toBeGreaterThan(0);
});
tap.test('should get facilities by country', async () => {
const facilities = await client.facilities.getByCountry('US', { limit: 5 });
expect(Array.isArray(facilities)).toEqual(true);
expect(facilities.length).toBeGreaterThan(0);
expect(facilities[0].country).toEqual('US');
});
// Test exchange queries
tap.test('should fetch exchanges list', async () => {
const exchanges = await client.exchanges.list({ limit: 5 });
expect(Array.isArray(exchanges)).toEqual(true);
expect(exchanges.length).toBeGreaterThan(0);
expect(exchanges[0]).toHaveProperty('id');
expect(exchanges[0]).toHaveProperty('name');
});
tap.test('should search exchanges by name', async () => {
const exchanges = await client.exchanges.searchByName('AMS-IX', { limit: 5 });
expect(Array.isArray(exchanges)).toEqual(true);
expect(exchanges.length).toBeGreaterThan(0);
});
// Test query parameters
tap.test('should support field selection', async () => {
const networks = await client.networks.list({
limit: 1,
fields: 'id,asn,name',
});
expect(networks.length).toEqual(1);
expect(networks[0]).toHaveProperty('id');
expect(networks[0]).toHaveProperty('asn');
expect(networks[0]).toHaveProperty('name');
});
tap.test('should support depth parameter', async () => {
const network = await client.networks.getByAsn(15169, 2);
expect(network).toBeDefined();
expect(network?.asn).toEqual(15169);
// With depth=2, org should be expanded to full object
if (network?.org) {
expect(typeof network.org).toEqual('object');
}
});
// Test derived objects
tap.test('should fetch network-IX connections', async () => {
const netixlans = await client.netIxLans.list({ limit: 5 });
expect(Array.isArray(netixlans)).toEqual(true);
expect(netixlans.length).toBeGreaterThan(0);
expect(netixlans[0]).toHaveProperty('net_id');
expect(netixlans[0]).toHaveProperty('ixlan_id');
});
tap.test('should fetch network-facility connections', async () => {
const netfacs = await client.netFacs.list({ limit: 5 });
expect(Array.isArray(netfacs)).toEqual(true);
expect(netfacs.length).toBeGreaterThan(0);
expect(netfacs[0]).toHaveProperty('net_id');
expect(netfacs[0]).toHaveProperty('fac_id');
});
// Test convenience methods
tap.test('convenience: should get network by ASN', async () => {
const network = await client.convenience.getNetworkByAsn(15169);
expect(network).toBeDefined();
expect(network?.asn).toEqual(15169);
});
tap.test('convenience: should search networks', async () => {
const networks = await client.convenience.searchNetworks('Google');
expect(Array.isArray(networks)).toEqual(true);
expect(networks.length).toBeGreaterThan(0);
});
tap.test('convenience: should search facilities', async () => {
const facilities = await client.convenience.searchFacilities('Equinix');
expect(Array.isArray(facilities)).toEqual(true);
expect(facilities.length).toBeGreaterThan(0);
});
export default tap.start();

25
ts/index.ts Normal file
View File

@@ -0,0 +1,25 @@
/**
* @apiclient.xyz/peeringdb
* PeeringDB API Client for Node.js
*/
// Export main client
export { PeeringDbClient } from './peeringdb.classes.client.js';
// Export all manager classes
export { OrganizationManager } from './peeringdb.classes.organizationmanager.js';
export { NetworkManager } from './peeringdb.classes.networkmanager.js';
export { FacilityManager } from './peeringdb.classes.facilitymanager.js';
export { ExchangeManager } from './peeringdb.classes.exchangemanager.js';
export { NetIxLanManager } from './peeringdb.classes.netixlanmanager.js';
export { NetFacManager } from './peeringdb.classes.netfacmanager.js';
export { IxLanManager } from './peeringdb.classes.ixlanmanager.js';
export { IxFacManager } from './peeringdb.classes.ixfacmanager.js';
export { IxPfxManager } from './peeringdb.classes.ixpfxmanager.js';
export { PocManager } from './peeringdb.classes.pocmanager.js';
// Export types
export * from './peeringdb.types.js';
// Export interfaces
export * from './interfaces/index.js';

15
ts/interfaces/index.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* PeeringDB API Interfaces
* Export all interface definitions for PeeringDB API resources
*/
export * from './peeringdb.api.organization.js';
export * from './peeringdb.api.network.js';
export * from './peeringdb.api.facility.js';
export * from './peeringdb.api.exchange.js';
export * from './peeringdb.api.netixlan.js';
export * from './peeringdb.api.netfac.js';
export * from './peeringdb.api.ixlan.js';
export * from './peeringdb.api.ixfac.js';
export * from './peeringdb.api.ixpfx.js';
export * from './peeringdb.api.poc.js';

View File

@@ -0,0 +1,79 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* Internet Exchange object from PeeringDB API
* Represents an Internet Exchange Point (IXP)
*/
export interface IExchange extends IPeeringDbBaseObject {
/** Exchange ID */
id: number;
/** Organization ID */
org_id: number;
/** Organization object (when depth > 0) */
org?: any;
/** Exchange name */
name: string;
/** Also known as (alternate names) */
aka: string;
/** Name long */
name_long: string;
/** Website */
website: string;
/** City */
city: string;
/** Country code (ISO 3166-1 alpha-2) */
country: string;
/** Region/continent */
region_continent: string;
/** Media type (Ethernet, ATM, etc.) */
media: string;
/** Notes (markdown) */
notes: string;
/** Proto unicast */
proto_unicast: boolean;
/** Proto multicast */
proto_multicast: boolean;
/** Proto IPv6 */
proto_ipv6: boolean;
/** URL stats */
url_stats: string;
/** Tech email */
tech_email: string;
/** Tech phone */
tech_phone: string;
/** Policy email */
policy_email: string;
/** Policy phone */
policy_phone: string;
/** Sales email */
sales_email: string;
/** Sales phone */
sales_phone: string;
/** Related IX LANs */
ixlan_set?: number[] | any[];
/** Related IX-facility connections */
ixfac_set?: number[] | any[];
}

View File

@@ -0,0 +1,88 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* Facility object from PeeringDB API
* Represents a colocation facility or data center
*/
export interface IFacility extends IPeeringDbBaseObject {
/** Facility ID */
id: number;
/** Organization ID */
org_id: number;
/** Organization object (when depth > 0) */
org?: any;
/** Facility name */
name: string;
/** Also known as (alternate names) */
aka: string;
/** Website */
website: string;
/** CLLI code */
clli: string;
/** Renater code */
renater: string;
/** NPL code */
npanxx: string;
/** Notes (markdown) */
notes: string;
/** Address line 1 */
address1: string;
/** Address line 2 */
address2: string;
/** City */
city: string;
/** State/Province */
state: string;
/** Zip/Postal code */
zipcode: string;
/** Country code (ISO 3166-1 alpha-2) */
country: string;
/** Latitude */
latitude: number | null;
/** Longitude */
longitude: number | null;
/** Floor area (sqm) */
floor_area: number | null;
/** Available voltage services */
available_voltage_services: string;
/** Power redundancy */
power_redundancy: string;
/** Diverse serving substations */
diverse_serving_substations: boolean;
/** Property */
property: string;
/** Campus ID */
campus_id: number | null;
/** Campus object (when depth > 0) */
campus?: any;
/** Related network-facility connections */
netfac_set?: number[] | any[];
/** Related IX-facility connections */
ixfac_set?: number[] | any[];
}

View File

@@ -0,0 +1,22 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* IX Facility object from PeeringDB API
* Represents the connection between an Internet Exchange and a facility
*/
export interface IIxFac extends IPeeringDbBaseObject {
/** IxFac ID */
id: number;
/** Exchange ID */
ix_id: number;
/** Exchange object (when depth > 0) */
ix?: any;
/** Facility ID */
fac_id: number;
/** Facility object (when depth > 0) */
fac?: any;
}

View File

@@ -0,0 +1,43 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* IX LAN object from PeeringDB API
* Represents a LAN at an Internet Exchange
*/
export interface IIxLan extends IPeeringDbBaseObject {
/** IxLan ID */
id: number;
/** Exchange ID */
ix_id: number;
/** Exchange object (when depth > 0) */
ix?: any;
/** LAN name */
name: string;
/** Description */
descr: string;
/** MTU size */
mtu: number;
/** VLANs */
vlan: number | null;
/** Dot1q support */
dot1q_support: boolean;
/** Route server ASN */
rs_asn: number | null;
/** ARP sponge */
arp_sponge: string | null;
/** Related IX prefixes */
ixpfx_set?: number[] | any[];
/** Related network IX LANs */
netixlan_set?: number[] | any[];
}

View File

@@ -0,0 +1,25 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* IX Prefix object from PeeringDB API
* Represents an IP prefix used by an Internet Exchange
*/
export interface IIxPfx extends IPeeringDbBaseObject {
/** IxPfx ID */
id: number;
/** IX LAN ID */
ixlan_id: number;
/** IX LAN object (when depth > 0) */
ixlan?: any;
/** IP prefix */
prefix: string;
/** Protocol (IPv4 or IPv6) */
protocol: string;
/** Peering point */
in_dfz: boolean;
}

View File

@@ -0,0 +1,34 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* Network Facility object from PeeringDB API
* Represents a network's presence at a colocation facility
*/
export interface INetFac extends IPeeringDbBaseObject {
/** NetFac ID */
id: number;
/** Network ID */
net_id: number;
/** Network object (when depth > 0) */
net?: any;
/** Facility ID */
fac_id: number;
/** Facility object (when depth > 0) */
fac?: any;
/** Availability percentage */
avail_sonet: boolean;
/** Availability percentage */
avail_ethernet: boolean;
/** Availability percentage */
avail_atm: boolean;
/** Local ASN */
local_asn: number | null;
}

View File

@@ -0,0 +1,43 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* Network IX LAN object from PeeringDB API
* Represents a network's presence at an Internet Exchange
*/
export interface INetIxLan extends IPeeringDbBaseObject {
/** NetIxLan ID */
id: number;
/** Network ID */
net_id: number;
/** Network object (when depth > 0) */
net?: any;
/** IX LAN ID */
ixlan_id: number;
/** IX LAN object (when depth > 0) */
ixlan?: any;
/** Notes (markdown) */
notes: string;
/** Speed in Mbps */
speed: number;
/** Autonomous System Number (ASN) */
asn: number;
/** IPv4 address */
ipaddr4: string | null;
/** IPv6 address */
ipaddr6: string | null;
/** Is route server */
is_rs_peer: boolean;
/** Operational status */
operational: boolean;
}

View File

@@ -0,0 +1,91 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* Network object from PeeringDB API
* Represents an autonomous system (AS) and its peering information
*/
export interface INetwork extends IPeeringDbBaseObject {
/** Network ID */
id: number;
/** Organization ID */
org_id: number;
/** Organization object (when depth > 0) */
org?: any;
/** Network name */
name: string;
/** Also known as (alternate names) */
aka: string;
/** Website */
website: string;
/** Autonomous System Number (ASN) */
asn: number;
/** Looking glass URL */
looking_glass: string;
/** Route server URL */
route_server: string;
/** IRR as-set/route-set */
irr_as_set: string;
/** Network information (markdown) */
info_type: string;
/** Network traffic levels */
info_traffic: string;
/** Network ratios */
info_ratio: string;
/** Network scope */
info_scope: string;
/** Network types (NSP, Content, etc.) */
info_types: string;
/** Peering policy */
policy_url: string;
/** General policy (Open, Selective, etc.) */
policy_general: string;
/** Location requirements for peering */
policy_locations: string;
/** Ratio requirement */
policy_ratio: boolean;
/** Contract requirement */
policy_contracts: string;
/** Notes (markdown) */
notes: string;
/** Number of unicast IPv4 prefixes */
info_unicast: boolean;
/** Number of unicast IPv6 prefixes */
info_ipv6: boolean;
/** Number of multicast prefixes */
info_multicast: boolean;
/** Is never via route servers */
info_never_via_route_servers: boolean;
/** Related network contacts */
poc_set?: number[] | any[];
/** Related network-facility connections */
netfac_set?: number[] | any[];
/** Related network-IX connections */
netixlan_set?: number[] | any[];
}

View File

@@ -0,0 +1,55 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* Organization object from PeeringDB API
* Organizations are the root object in PeeringDB and represent companies or entities
*/
export interface IOrganization extends IPeeringDbBaseObject {
/** Organization ID */
id: number;
/** Organization name */
name: string;
/** Organization website */
website: string;
/** Organization notes (markdown) */
notes: string;
/** Address line 1 */
address1: string;
/** Address line 2 */
address2: string;
/** City */
city: string;
/** State/Province */
state: string;
/** Zip/Postal code */
zipcode: string;
/** Country code (ISO 3166-1 alpha-2) */
country: string;
/** Latitude */
latitude: number | null;
/** Longitude */
longitude: number | null;
/** Floor area (sqm) */
floor_area: number | null;
/** Related networks */
net_set?: number[] | any[];
/** Related facilities */
fac_set?: number[] | any[];
/** Related internet exchanges */
ix_set?: number[] | any[];
}

View File

@@ -0,0 +1,34 @@
import type { IPeeringDbBaseObject } from '../peeringdb.types.js';
/**
* Point of Contact object from PeeringDB API
* Represents a contact person for a network
*/
export interface IPointOfContact extends IPeeringDbBaseObject {
/** POC ID */
id: number;
/** Network ID */
net_id: number;
/** Network object (when depth > 0) */
net?: any;
/** Role/Position */
role: string;
/** Visible (public/private/users) */
visible: string;
/** Contact name */
name: string;
/** Phone number */
phone: string;
/** Email address */
email: string;
/** URL */
url: string;
}

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

View File

@@ -0,0 +1,102 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { IExchange } from './interfaces/peeringdb.api.exchange.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for Internet Exchange resources
*/
export class ExchangeManager {
constructor(private client: PeeringDbClient) {}
/**
* List exchanges with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<IExchange[]> {
return this.client.request<IExchange>('ix', 'GET', options);
}
/**
* Get a single exchange by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<IExchange | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<IExchange>('ix', 'GET', options);
return results[0] || null;
}
/**
* Search exchanges by name
*/
async searchByName(name: string, options: IQueryOptions = {}): Promise<IExchange[]> {
return this.client.request<IExchange>('ix', 'GET', {
...options,
name__contains: name,
});
}
/**
* Get exchanges by country
*/
async getByCountry(country: string, options: IQueryOptions = {}): Promise<IExchange[]> {
return this.client.request<IExchange>('ix', 'GET', {
...options,
country,
});
}
/**
* Get exchanges by city
*/
async getByCity(city: string, options: IQueryOptions = {}): Promise<IExchange[]> {
return this.client.request<IExchange>('ix', 'GET', {
...options,
city__contains: city,
});
}
/**
* Get exchanges by region/continent
*/
async getByRegion(region: string, options: IQueryOptions = {}): Promise<IExchange[]> {
return this.client.request<IExchange>('ix', 'GET', {
...options,
region_continent: region,
});
}
/**
* Get exchanges by organization ID
*/
async getByOrgId(orgId: number, options: IQueryOptions = {}): Promise<IExchange[]> {
return this.client.request<IExchange>('ix', 'GET', {
...options,
org_id: orgId,
});
}
/**
* Create a new exchange (requires authentication)
*/
async create(data: Partial<IExchange>): Promise<IExchange> {
const results = await this.client.request<IExchange>('ix', 'POST', {}, data);
return results[0];
}
/**
* Update an exchange (requires authentication)
*/
async update(id: number, data: Partial<IExchange>): Promise<IExchange> {
const results = await this.client.request<IExchange>(`ix/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete an exchange (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('ix', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,92 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { IFacility } from './interfaces/peeringdb.api.facility.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for Facility resources
*/
export class FacilityManager {
constructor(private client: PeeringDbClient) {}
/**
* List facilities with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<IFacility[]> {
return this.client.request<IFacility>('fac', 'GET', options);
}
/**
* Get a single facility by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<IFacility | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<IFacility>('fac', 'GET', options);
return results[0] || null;
}
/**
* Search facilities by name
*/
async searchByName(name: string, options: IQueryOptions = {}): Promise<IFacility[]> {
return this.client.request<IFacility>('fac', 'GET', {
...options,
name__contains: name,
});
}
/**
* Get facilities by country
*/
async getByCountry(country: string, options: IQueryOptions = {}): Promise<IFacility[]> {
return this.client.request<IFacility>('fac', 'GET', {
...options,
country,
});
}
/**
* Get facilities by city
*/
async getByCity(city: string, options: IQueryOptions = {}): Promise<IFacility[]> {
return this.client.request<IFacility>('fac', 'GET', {
...options,
city__contains: city,
});
}
/**
* Get facilities by organization ID
*/
async getByOrgId(orgId: number, options: IQueryOptions = {}): Promise<IFacility[]> {
return this.client.request<IFacility>('fac', 'GET', {
...options,
org_id: orgId,
});
}
/**
* Create a new facility (requires authentication)
*/
async create(data: Partial<IFacility>): Promise<IFacility> {
const results = await this.client.request<IFacility>('fac', 'POST', {}, data);
return results[0];
}
/**
* Update a facility (requires authentication)
*/
async update(id: number, data: Partial<IFacility>): Promise<IFacility> {
const results = await this.client.request<IFacility>(`fac/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete a facility (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('fac', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,72 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { IIxFac } from './interfaces/peeringdb.api.ixfac.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for IxFac resources (IX-facility connections)
*/
export class IxFacManager {
constructor(private client: PeeringDbClient) {}
/**
* List IX-facility connections with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<IIxFac[]> {
return this.client.request<IIxFac>('ixfac', 'GET', options);
}
/**
* Get a single IX-facility connection by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<IIxFac | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<IIxFac>('ixfac', 'GET', options);
return results[0] || null;
}
/**
* Get IX-facility connections by exchange ID
*/
async getByIxId(ixId: number, options: IQueryOptions = {}): Promise<IIxFac[]> {
return this.client.request<IIxFac>('ixfac', 'GET', {
...options,
ix_id: ixId,
});
}
/**
* Get IX-facility connections by facility ID
*/
async getByFacId(facId: number, options: IQueryOptions = {}): Promise<IIxFac[]> {
return this.client.request<IIxFac>('ixfac', 'GET', {
...options,
fac_id: facId,
});
}
/**
* Create a new IX-facility connection (requires authentication)
*/
async create(data: Partial<IIxFac>): Promise<IIxFac> {
const results = await this.client.request<IIxFac>('ixfac', 'POST', {}, data);
return results[0];
}
/**
* Update an IX-facility connection (requires authentication)
*/
async update(id: number, data: Partial<IIxFac>): Promise<IIxFac> {
const results = await this.client.request<IIxFac>(`ixfac/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete an IX-facility connection (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('ixfac', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,62 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { IIxLan } from './interfaces/peeringdb.api.ixlan.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for IxLan resources (IX LAN information)
*/
export class IxLanManager {
constructor(private client: PeeringDbClient) {}
/**
* List IX LANs with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<IIxLan[]> {
return this.client.request<IIxLan>('ixlan', 'GET', options);
}
/**
* Get a single IX LAN by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<IIxLan | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<IIxLan>('ixlan', 'GET', options);
return results[0] || null;
}
/**
* Get IX LANs by exchange ID
*/
async getByIxId(ixId: number, options: IQueryOptions = {}): Promise<IIxLan[]> {
return this.client.request<IIxLan>('ixlan', 'GET', {
...options,
ix_id: ixId,
});
}
/**
* Create a new IX LAN (requires authentication)
*/
async create(data: Partial<IIxLan>): Promise<IIxLan> {
const results = await this.client.request<IIxLan>('ixlan', 'POST', {}, data);
return results[0];
}
/**
* Update an IX LAN (requires authentication)
*/
async update(id: number, data: Partial<IIxLan>): Promise<IIxLan> {
const results = await this.client.request<IIxLan>(`ixlan/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete an IX LAN (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('ixlan', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,72 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { IIxPfx } from './interfaces/peeringdb.api.ixpfx.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for IxPfx resources (IX IP prefixes)
*/
export class IxPfxManager {
constructor(private client: PeeringDbClient) {}
/**
* List IX prefixes with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<IIxPfx[]> {
return this.client.request<IIxPfx>('ixpfx', 'GET', options);
}
/**
* Get a single IX prefix by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<IIxPfx | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<IIxPfx>('ixpfx', 'GET', options);
return results[0] || null;
}
/**
* Get IX prefixes by IX LAN ID
*/
async getByIxLanId(ixlanId: number, options: IQueryOptions = {}): Promise<IIxPfx[]> {
return this.client.request<IIxPfx>('ixpfx', 'GET', {
...options,
ixlan_id: ixlanId,
});
}
/**
* Get IX prefixes by protocol (IPv4/IPv6)
*/
async getByProtocol(protocol: string, options: IQueryOptions = {}): Promise<IIxPfx[]> {
return this.client.request<IIxPfx>('ixpfx', 'GET', {
...options,
protocol,
});
}
/**
* Create a new IX prefix (requires authentication)
*/
async create(data: Partial<IIxPfx>): Promise<IIxPfx> {
const results = await this.client.request<IIxPfx>('ixpfx', 'POST', {}, data);
return results[0];
}
/**
* Update an IX prefix (requires authentication)
*/
async update(id: number, data: Partial<IIxPfx>): Promise<IIxPfx> {
const results = await this.client.request<IIxPfx>(`ixpfx/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete an IX prefix (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('ixpfx', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,72 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { INetFac } from './interfaces/peeringdb.api.netfac.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for NetFac resources (network presence at facility)
*/
export class NetFacManager {
constructor(private client: PeeringDbClient) {}
/**
* List network-facility connections with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<INetFac[]> {
return this.client.request<INetFac>('netfac', 'GET', options);
}
/**
* Get a single network-facility connection by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<INetFac | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<INetFac>('netfac', 'GET', options);
return results[0] || null;
}
/**
* Get network-facility connections by network ID
*/
async getByNetId(netId: number, options: IQueryOptions = {}): Promise<INetFac[]> {
return this.client.request<INetFac>('netfac', 'GET', {
...options,
net_id: netId,
});
}
/**
* Get network-facility connections by facility ID
*/
async getByFacId(facId: number, options: IQueryOptions = {}): Promise<INetFac[]> {
return this.client.request<INetFac>('netfac', 'GET', {
...options,
fac_id: facId,
});
}
/**
* Create a new network-facility connection (requires authentication)
*/
async create(data: Partial<INetFac>): Promise<INetFac> {
const results = await this.client.request<INetFac>('netfac', 'POST', {}, data);
return results[0];
}
/**
* Update a network-facility connection (requires authentication)
*/
async update(id: number, data: Partial<INetFac>): Promise<INetFac> {
const results = await this.client.request<INetFac>(`netfac/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete a network-facility connection (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('netfac', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,82 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { INetIxLan } from './interfaces/peeringdb.api.netixlan.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for NetIxLan resources (network presence at IX)
*/
export class NetIxLanManager {
constructor(private client: PeeringDbClient) {}
/**
* List network-IX connections with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<INetIxLan[]> {
return this.client.request<INetIxLan>('netixlan', 'GET', options);
}
/**
* Get a single network-IX connection by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<INetIxLan | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<INetIxLan>('netixlan', 'GET', options);
return results[0] || null;
}
/**
* Get network-IX connections by network ID
*/
async getByNetId(netId: number, options: IQueryOptions = {}): Promise<INetIxLan[]> {
return this.client.request<INetIxLan>('netixlan', 'GET', {
...options,
net_id: netId,
});
}
/**
* Get network-IX connections by IX LAN ID
*/
async getByIxLanId(ixlanId: number, options: IQueryOptions = {}): Promise<INetIxLan[]> {
return this.client.request<INetIxLan>('netixlan', 'GET', {
...options,
ixlan_id: ixlanId,
});
}
/**
* Get network-IX connections by ASN
*/
async getByAsn(asn: number, options: IQueryOptions = {}): Promise<INetIxLan[]> {
return this.client.request<INetIxLan>('netixlan', 'GET', {
...options,
asn,
});
}
/**
* Create a new network-IX connection (requires authentication)
*/
async create(data: Partial<INetIxLan>): Promise<INetIxLan> {
const results = await this.client.request<INetIxLan>('netixlan', 'POST', {}, data);
return results[0];
}
/**
* Update a network-IX connection (requires authentication)
*/
async update(id: number, data: Partial<INetIxLan>): Promise<INetIxLan> {
const results = await this.client.request<INetIxLan>(`netixlan/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete a network-IX connection (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('netixlan', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,104 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { INetwork } from './interfaces/peeringdb.api.network.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for Network resources
*/
export class NetworkManager {
constructor(private client: PeeringDbClient) {}
/**
* List networks with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<INetwork[]> {
return this.client.request<INetwork>('net', 'GET', options);
}
/**
* Get a single network by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<INetwork | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<INetwork>('net', 'GET', options);
return results[0] || null;
}
/**
* Get a network by ASN
*/
async getByAsn(asn: number, depth?: 0 | 1 | 2): Promise<INetwork | null> {
const options: IQueryOptions = { asn };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<INetwork>('net', 'GET', options);
return results[0] || null;
}
/**
* Search networks by name
*/
async searchByName(name: string, options: IQueryOptions = {}): Promise<INetwork[]> {
return this.client.request<INetwork>('net', 'GET', {
...options,
name__contains: name,
});
}
/**
* Get networks by organization ID
*/
async getByOrgId(orgId: number, options: IQueryOptions = {}): Promise<INetwork[]> {
return this.client.request<INetwork>('net', 'GET', {
...options,
org_id: orgId,
});
}
/**
* Search networks by IRR as-set
*/
async searchByIrrAsSet(irrAsSet: string, options: IQueryOptions = {}): Promise<INetwork[]> {
return this.client.request<INetwork>('net', 'GET', {
...options,
irr_as_set__contains: irrAsSet,
});
}
/**
* Get networks by peering policy
*/
async getByPolicy(policy: string, options: IQueryOptions = {}): Promise<INetwork[]> {
return this.client.request<INetwork>('net', 'GET', {
...options,
policy_general: policy,
});
}
/**
* Create a new network (requires authentication)
*/
async create(data: Partial<INetwork>): Promise<INetwork> {
const results = await this.client.request<INetwork>('net', 'POST', {}, data);
return results[0];
}
/**
* Update a network (requires authentication)
*/
async update(id: number, data: Partial<INetwork>): Promise<INetwork> {
const results = await this.client.request<INetwork>(`net/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete a network (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('net', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,72 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { IOrganization } from './interfaces/peeringdb.api.organization.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for Organization resources
*/
export class OrganizationManager {
constructor(private client: PeeringDbClient) {}
/**
* List organizations with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<IOrganization[]> {
return this.client.request<IOrganization>('org', 'GET', options);
}
/**
* Get a single organization by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<IOrganization | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<IOrganization>('org', 'GET', options);
return results[0] || null;
}
/**
* Search organizations by name
*/
async searchByName(name: string, options: IQueryOptions = {}): Promise<IOrganization[]> {
return this.client.request<IOrganization>('org', 'GET', {
...options,
name__contains: name,
});
}
/**
* Get organizations by country
*/
async getByCountry(country: string, options: IQueryOptions = {}): Promise<IOrganization[]> {
return this.client.request<IOrganization>('org', 'GET', {
...options,
country,
});
}
/**
* Create a new organization (requires authentication)
*/
async create(data: Partial<IOrganization>): Promise<IOrganization> {
const results = await this.client.request<IOrganization>('org', 'POST', {}, data);
return results[0];
}
/**
* Update an organization (requires authentication)
*/
async update(id: number, data: Partial<IOrganization>): Promise<IOrganization> {
const results = await this.client.request<IOrganization>(`org/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete an organization (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('org', 'DELETE', { id });
}
}

View File

@@ -0,0 +1,72 @@
import type { PeeringDbClient } from './peeringdb.classes.client.js';
import type { IPointOfContact } from './interfaces/peeringdb.api.poc.js';
import type { IQueryOptions } from './peeringdb.types.js';
/**
* Manager for Point of Contact resources
*/
export class PocManager {
constructor(private client: PeeringDbClient) {}
/**
* List points of contact with optional filtering
*/
async list(options: IQueryOptions = {}): Promise<IPointOfContact[]> {
return this.client.request<IPointOfContact>('poc', 'GET', options);
}
/**
* Get a single point of contact by ID
*/
async getById(id: number, depth?: 0 | 1 | 2): Promise<IPointOfContact | null> {
const options: IQueryOptions = { id };
if (depth !== undefined) {
options.depth = depth;
}
const results = await this.client.request<IPointOfContact>('poc', 'GET', options);
return results[0] || null;
}
/**
* Get points of contact by network ID
*/
async getByNetId(netId: number, options: IQueryOptions = {}): Promise<IPointOfContact[]> {
return this.client.request<IPointOfContact>('poc', 'GET', {
...options,
net_id: netId,
});
}
/**
* Get points of contact by role
*/
async getByRole(role: string, options: IQueryOptions = {}): Promise<IPointOfContact[]> {
return this.client.request<IPointOfContact>('poc', 'GET', {
...options,
role__contains: role,
});
}
/**
* Create a new point of contact (requires authentication)
*/
async create(data: Partial<IPointOfContact>): Promise<IPointOfContact> {
const results = await this.client.request<IPointOfContact>('poc', 'POST', {}, data);
return results[0];
}
/**
* Update a point of contact (requires authentication)
*/
async update(id: number, data: Partial<IPointOfContact>): Promise<IPointOfContact> {
const results = await this.client.request<IPointOfContact>(`poc/${id}`, 'PUT', {}, data);
return results[0];
}
/**
* Delete a point of contact (requires authentication)
*/
async delete(id: number): Promise<void> {
await this.client.request('poc', 'DELETE', { id });
}
}

108
ts/peeringdb.types.ts Normal file
View File

@@ -0,0 +1,108 @@
/**
* Core types for PeeringDB API client
*/
/**
* HTTP methods supported by the API
*/
export type THttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
/**
* Depth levels for nested object expansion
* - 0: No expansion (IDs only)
* - 1: Expand to IDs for related objects
* - 2: Full object expansion for related objects
*/
export type TDepth = 0 | 1 | 2;
/**
* Meta information returned by the PeeringDB API
*/
export interface IPeeringDbMeta {
status: 'ok' | 'error';
message?: string;
generated?: number;
}
/**
* Standard API response wrapper from PeeringDB
*/
export interface IPeeringDbResponse<T> {
meta: IPeeringDbMeta;
data: T[];
}
/**
* Query options for API requests
*/
export interface IQueryOptions {
/** Limit the number of results */
limit?: number;
/** Skip/offset for pagination */
skip?: number;
/** Comma-separated list of fields to return */
fields?: string;
/** Depth of nested object expansion (0, 1, or 2) */
depth?: TDepth;
/** Unix timestamp - only return objects updated since this time */
since?: number;
/** Auto-paginate through all results (default: false) */
autoPaginate?: boolean;
/** Additional query parameters (field filters, etc.) */
[key: string]: any;
}
/**
* Field filter operators for string fields
*/
export interface IStringFilters {
/** Field contains value */
__contains?: string;
/** Field starts with value */
__startswith?: string;
/** Field is in list of values */
__in?: string[];
}
/**
* Field filter operators for number fields
*/
export interface INumberFilters {
/** Field is less than value */
__lt?: number;
/** Field is less than or equal to value */
__lte?: number;
/** Field is greater than value */
__gt?: number;
/** Field is greater than or equal to value */
__gte?: number;
/** Field is in list of values */
__in?: number[];
}
/**
* Base fields present in all PeeringDB objects
*/
export interface IPeeringDbBaseObject {
id: number;
status: string;
created: string;
updated: string;
}
/**
* Common status values for PeeringDB objects
*/
export type TStatus = 'ok' | 'pending' | 'deleted';

5
ts/plugins.ts Normal file
View File

@@ -0,0 +1,5 @@
// push.rocks scope
import * as smartlog from '@push.rocks/smartlog';
import * as smartrequest from '@push.rocks/smartrequest';
export { smartlog, smartrequest };

15
tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"useDefineForClassFields": false,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {}
},
"exclude": ["dist_*/**/*.d.ts"]
}