SDK Reference
Error Handling
Complete guide to handling errors in the SweatHost SDK
Overview
The SweatHost SDK provides comprehensive error handling with typed errors and detailed error information.
Error Types
SweatHostError
Base error class for all SDK errors.
class SweatHostError extends Error {
/**
* Error code
*/
code: string;
/**
* HTTP status code
*/
statusCode: number;
/**
* Additional error details
*/
details?: Record<string, any>;
/**
* Request ID for debugging
*/
requestId?: string;
/**
* Timestamp when error occurred
*/
timestamp: string;
}Error Codes
| Code | Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Invalid or missing API key |
FORBIDDEN | 403 | No permission to access resource |
NOT_FOUND | 404 | Resource not found |
INVALID_PARAMS | 400 | Invalid request parameters |
VALIDATION_ERROR | 422 | Request validation failed |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
SERVER_ERROR | 500 | Internal server error |
SERVICE_UNAVAILABLE | 503 | Service temporarily unavailable |
TIMEOUT | 408 | Request timeout |
NETWORK_ERROR | 0 | Network connection error |
INSUFFICIENT_CAPACITY | 503 | No available server capacity |
QUOTA_EXCEEDED | 403 | Account limit reached |
PAYMENT_REQUIRED | 402 | Insufficient credits |
SERVER_BUSY | 409 | Resource is currently busy |
SERVER_NOT_RUNNING | 409 | Server must be running |
RCON_FAILED | 500 | RCON command failed |
Basic Error Handling
Try-Catch
import { SweatHostClient, SweatHostError } from '@sweathost/sdk';
const client = new SweatHostClient({
apiKey: process.env.SWEATHOST_API_KEY!,
});
try {
const server = await client.gameServers.get('server_123');
console.log(server);
} catch (error) {
if (error instanceof SweatHostError) {
console.error(`Error ${error.code}: ${error.message}`);
console.error(`Status: ${error.statusCode}`);
console.error(`Request ID: ${error.requestId}`);
} else {
console.error('Unexpected error:', error);
}
}Checking Error Codes
try {
const server = await client.gameServers.get('invalid_id');
} catch (error) {
if (error instanceof SweatHostError) {
switch (error.code) {
case 'NOT_FOUND':
console.error('Server not found');
break;
case 'UNAUTHORIZED':
console.error('Invalid API key');
break;
case 'FORBIDDEN':
console.error('No access to this server');
break;
default:
console.error(`Error: ${error.message}`);
}
}
}Specific Error Scenarios
Authentication Errors
try {
const servers = await client.gameServers.list();
} catch (error) {
if (error instanceof SweatHostError && error.code === 'UNAUTHORIZED') {
console.error('Authentication failed. Please check your API key.');
console.error('Get your API key from: https://dashboard.sweathost.com/settings/api-keys');
process.exit(1);
}
}Not Found Errors
async function getServerSafely(serverId: string) {
try {
return await client.gameServers.get(serverId);
} catch (error) {
if (error instanceof SweatHostError && error.code === 'NOT_FOUND') {
console.warn(`Server ${serverId} not found`);
return null;
}
throw error; // Re-throw other errors
}
}
const server = await getServerSafely('server_123');
if (server) {
console.log(`Found server: ${server.name}`);
} else {
console.log('Server does not exist');
}Rate Limiting
async function withRateLimitRetry<T>(
operation: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (error instanceof SweatHostError && error.code === 'RATE_LIMIT_EXCEEDED') {
const retryAfter = error.details?.retryAfter || 60;
console.warn(`Rate limited. Retrying in ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
// Usage
const servers = await withRateLimitRetry(() =>
client.gameServers.list()
);Validation Errors
try {
const server = await client.gameServers.create({
name: 'My Server',
game: 'cs2',
region: 'invalid-region', // Invalid region
config: {
maxPlayers: 100, // Exceeds maximum
tickRate: 128,
},
});
} catch (error) {
if (error instanceof SweatHostError && error.code === 'VALIDATION_ERROR') {
console.error('Validation failed:');
// Details contains field-specific errors
if (error.details?.errors) {
error.details.errors.forEach((err: any) => {
console.error(` ${err.field}: ${err.message}`);
});
}
}
}Capacity Errors
try {
const server = await client.gameServers.create({
name: 'My Server',
game: 'cs2',
region: 'us-east',
config: {
maxPlayers: 10,
tickRate: 128,
},
});
} catch (error) {
if (error instanceof SweatHostError) {
if (error.code === 'INSUFFICIENT_CAPACITY') {
console.error('No available capacity in this region');
console.error('Available regions:', error.details?.availableRegions);
// Try alternative region
const altRegion = error.details?.availableRegions?.[0];
if (altRegion) {
console.log(`Trying ${altRegion} instead...`);
// Retry with alternative region
}
} else if (error.code === 'QUOTA_EXCEEDED') {
console.error('Server limit reached for your account');
console.error(`Current: ${error.details?.current}`);
console.error(`Limit: ${error.details?.limit}`);
console.error('Upgrade at: https://dashboard.sweathost.com/billing');
}
}
}Payment Errors
try {
const server = await client.gameServers.create({
name: 'My Server',
game: 'cs2',
region: 'us-east',
config: {
maxPlayers: 10,
tickRate: 128,
},
});
} catch (error) {
if (error instanceof SweatHostError && error.code === 'PAYMENT_REQUIRED') {
console.error('Insufficient credits');
console.error(`Balance: $${error.details?.balance}`);
console.error(`Required: $${error.details?.required}`);
console.error('Add credits at: https://dashboard.sweathost.com/billing');
}
}Network Errors
try {
const servers = await client.gameServers.list();
} catch (error) {
if (error instanceof SweatHostError) {
if (error.code === 'NETWORK_ERROR') {
console.error('Network connection failed');
console.error('Please check your internet connection');
} else if (error.code === 'TIMEOUT') {
console.error('Request timed out');
console.error('The server took too long to respond');
} else if (error.code === 'SERVICE_UNAVAILABLE') {
console.error('Service temporarily unavailable');
console.error('Please try again later');
}
}
}Advanced Error Handling
Global Error Handler
class ErrorHandler {
static handle(error: unknown): void {
if (error instanceof SweatHostError) {
this.handleSweatHostError(error);
} else if (error instanceof Error) {
this.handleGenericError(error);
} else {
console.error('Unknown error:', error);
}
}
private static handleSweatHostError(error: SweatHostError): void {
console.error(`[${error.code}] ${error.message}`);
if (error.requestId) {
console.error(`Request ID: ${error.requestId}`);
}
// Log to monitoring service
this.logToMonitoring(error);
// Send to error tracking
this.sendToErrorTracking(error);
}
private static handleGenericError(error: Error): void {
console.error(`Unexpected error: ${error.message}`);
console.error(error.stack);
}
private static logToMonitoring(error: SweatHostError): void {
// Send to your monitoring service (DataDog, New Relic, etc.)
}
private static sendToErrorTracking(error: SweatHostError): void {
// Send to error tracking (Sentry, Rollbar, etc.)
}
}
// Usage
try {
await client.gameServers.create({...});
} catch (error) {
ErrorHandler.handle(error);
}Retry Logic
async function withRetry<T>(
operation: () => Promise<T>,
options: {
maxRetries?: number;
retryDelay?: number;
retryOn?: string[];
} = {}
): Promise<T> {
const {
maxRetries = 3,
retryDelay = 1000,
retryOn = ['NETWORK_ERROR', 'TIMEOUT', 'SERVICE_UNAVAILABLE'],
} = options;
let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (
error instanceof SweatHostError &&
retryOn.includes(error.code) &&
attempt < maxRetries
) {
console.warn(`Attempt ${attempt + 1} failed. Retrying in ${retryDelay}ms...`);
await new Promise(resolve => setTimeout(resolve, retryDelay * (attempt + 1)));
continue;
}
throw error;
}
}
throw lastError!;
}
// Usage
const server = await withRetry(
() => client.gameServers.get('server_123'),
{
maxRetries: 5,
retryDelay: 2000,
}
);Circuit Breaker
class CircuitBreaker {
private failures = 0;
private lastFailureTime = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
constructor(
private threshold = 5,
private timeout = 60000
) {}
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is open');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void {
this.failures = 0;
this.state = 'closed';
}
private onFailure(): void {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.threshold) {
this.state = 'open';
console.warn('Circuit breaker opened');
}
}
}
// Usage
const breaker = new CircuitBreaker();
try {
const server = await breaker.execute(() =>
client.gameServers.get('server_123')
);
} catch (error) {
console.error('Operation failed:', error);
}Error Logging
Structured Logging
import { SweatHostError } from '@sweathost/sdk';
function logError(error: unknown, context?: Record<string, any>): void {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'error',
context,
};
if (error instanceof SweatHostError) {
Object.assign(logEntry, {
errorCode: error.code,
errorMessage: error.message,
statusCode: error.statusCode,
requestId: error.requestId,
details: error.details,
});
} else if (error instanceof Error) {
Object.assign(logEntry, {
errorMessage: error.message,
stack: error.stack,
});
}
console.error(JSON.stringify(logEntry));
}
// Usage
try {
await client.gameServers.create({...});
} catch (error) {
logError(error, {
operation: 'create_server',
userId: 'user_123',
});
}Best Practices
1. Always Handle Errors
// ❌ Bad
const server = await client.gameServers.get('server_123');
// ✅ Good
try {
const server = await client.gameServers.get('server_123');
} catch (error) {
console.error('Failed to get server:', error);
}2. Check Error Types
// ❌ Bad
catch (error) {
console.error(error.code); // May not exist
}
// ✅ Good
catch (error) {
if (error instanceof SweatHostError) {
console.error(error.code);
}
}3. Provide Context
// ❌ Bad
throw new Error('Failed');
// ✅ Good
throw new Error(`Failed to create server "${serverName}" in region "${region}"`);4. Log Request IDs
catch (error) {
if (error instanceof SweatHostError && error.requestId) {
console.error(`Request ID: ${error.requestId}`);
// Include in support requests
}
}5. Handle Rate Limits Gracefully
// Implement exponential backoff
async function withBackoff<T>(operation: () => Promise<T>): Promise<T> {
let delay = 1000;
while (true) {
try {
return await operation();
} catch (error) {
if (error instanceof SweatHostError && error.code === 'RATE_LIMIT_EXCEEDED') {
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // Exponential backoff
continue;
}
throw error;
}
}
}Testing Error Handling
import { describe, it, expect } from '@jest/globals';
import { SweatHostClient, SweatHostError } from '@sweathost/sdk';
describe('Error Handling', () => {
it('should handle not found errors', async () => {
const client = new SweatHostClient({
apiKey: 'test_key',
});
try {
await client.gameServers.get('invalid_id');
expect.fail('Should have thrown error');
} catch (error) {
expect(error).toBeInstanceOf(SweatHostError);
expect((error as SweatHostError).code).toBe('NOT_FOUND');
expect((error as SweatHostError).statusCode).toBe(404);
}
});
it('should handle validation errors', async () => {
const client = new SweatHostClient({
apiKey: 'test_key',
});
try {
await client.gameServers.create({
name: '',
game: 'cs2',
region: 'us-east',
config: {
maxPlayers: -1,
tickRate: 128,
},
});
expect.fail('Should have thrown error');
} catch (error) {
expect(error).toBeInstanceOf(SweatHostError);
expect((error as SweatHostError).code).toBe('VALIDATION_ERROR');
}
});
});