tfl-ts
Version:
🚇 Fully-typed TypeScript client for Transport for London (TfL) API • Zero dependencies • Auto-generated types • Real-time arrivals • Journey planning • Universal compatibility
191 lines (190 loc) • 6.73 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TflErrorHandler = exports.TflConfigError = exports.TflTimeoutError = exports.TflValidationError = exports.TflNetworkError = exports.TflHttpError = exports.TflError = void 0;
/**
* Base error class for TfL API errors
*/
class TflError extends Error {
constructor(message, statusCode, originalError, requestId) {
super(message);
this.statusCode = statusCode;
this.originalError = originalError;
this.isTflError = true;
this.timestamp = new Date();
this.name = 'TflError';
this.requestId = requestId;
// Maintain proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TflError);
}
}
}
exports.TflError = TflError;
/**
* HTTP error class for API response errors
*/
class TflHttpError extends TflError {
constructor(message, statusCode, statusText, responseBody, url, originalError, requestId) {
super(message, statusCode, originalError, requestId);
this.statusCode = statusCode;
this.statusText = statusText;
this.responseBody = responseBody;
this.url = url;
this.isHttpError = true;
this.name = 'TflHttpError';
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TflHttpError);
}
}
/**
* Check if this is a client error (4xx)
*/
isClientError() {
return this.statusCode >= 400 && this.statusCode < 500;
}
/**
* Check if this is a server error (5xx)
*/
isServerError() {
return this.statusCode >= 500 && this.statusCode < 600;
}
/**
* Check if this is an authentication error (401)
*/
isAuthError() {
return this.statusCode === 401;
}
/**
* Check if this is a rate limit error (429)
*/
isRateLimitError() {
return this.statusCode === 429;
}
}
exports.TflHttpError = TflHttpError;
/**
* Network error class for connection issues
*/
class TflNetworkError extends TflError {
constructor(message, code, url, originalError, requestId) {
super(message, undefined, originalError, requestId);
this.code = code;
this.url = url;
this.isNetworkError = true;
this.name = 'TflNetworkError';
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TflNetworkError);
}
}
}
exports.TflNetworkError = TflNetworkError;
/**
* Validation error class for invalid parameters
*/
class TflValidationError extends TflError {
constructor(message, field, value, originalError, requestId) {
super(message, undefined, originalError, requestId);
this.field = field;
this.value = value;
this.isValidationError = true;
this.name = 'TflValidationError';
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TflValidationError);
}
}
}
exports.TflValidationError = TflValidationError;
/**
* Timeout error class for request timeouts
*/
class TflTimeoutError extends TflError {
constructor(message, timeoutMs, url, originalError, requestId) {
super(message, undefined, originalError, requestId);
this.timeoutMs = timeoutMs;
this.url = url;
this.isTimeoutError = true;
this.name = 'TflTimeoutError';
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TflTimeoutError);
}
}
}
exports.TflTimeoutError = TflTimeoutError;
/**
* Configuration error class for setup issues
*/
class TflConfigError extends TflError {
constructor(message, configField, originalError, requestId) {
super(message, undefined, originalError, requestId);
this.configField = configField;
this.isConfigError = true;
this.name = 'TflConfigError';
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TflConfigError);
}
}
}
exports.TflConfigError = TflConfigError;
/**
* Error handler utility class
*/
class TflErrorHandler {
/**
* Handle API errors and convert them to appropriate TflError types
*/
static handleApiError(error, url, requestId) {
// If it's already a TflError, return it
if (error instanceof TflError) {
return error;
}
// Handle fetch errors (network issues)
if (error instanceof TypeError && error.message.includes('fetch')) {
return new TflNetworkError(`Network error: ${error.message}`, 'NETWORK_ERROR', url, error, requestId);
}
// Handle timeout errors
if (error.name === 'AbortError' || error.message.includes('timeout')) {
return new TflTimeoutError(`Request timeout: ${error.message}`, 30000, // Default timeout
url, error, requestId);
}
// Handle HTTP response errors
if (error.status || error.statusCode) {
const statusCode = error.status || error.statusCode;
const statusText = error.statusText || 'Unknown Error';
const responseBody = error.data || error.body || error.message;
return new TflHttpError(`HTTP ${statusCode}: ${statusText}`, statusCode, statusText, responseBody, url, error, requestId);
}
// Handle validation errors
if (error.message && (error.message.includes('validation') ||
error.message.includes('invalid') ||
error.message.includes('required'))) {
return new TflValidationError(`Validation error: ${error.message}`, undefined, undefined, error, requestId);
}
// Generic error fallback
return new TflError(`Unexpected error: ${error.message || 'Unknown error occurred'}`, undefined, error, requestId);
}
/**
* Check if an error is retryable
*/
static isRetryableError(error) {
if (error instanceof TflHttpError) {
// Retry on server errors (5xx) and rate limits (429)
return error.isServerError() || error.isRateLimitError();
}
if (error instanceof TflNetworkError || error instanceof TflTimeoutError) {
return true;
}
return false;
}
/**
* Get retry delay for an error
*/
static getRetryDelay(error, attempt, baseDelay = 1000) {
if (error instanceof TflHttpError && error.isRateLimitError()) {
// Exponential backoff for rate limits
return Math.min(baseDelay * Math.pow(2, attempt), 30000);
}
// Linear backoff for other retryable errors
return baseDelay * (attempt + 1);
}
}
exports.TflErrorHandler = TflErrorHandler;