UNPKG

open-lineage-client

Version:

TypeScript/JavaScript client for creating and sending OpenLineage standard events.

163 lines 6.36 kB
import validator from 'validator'; import axios from 'axios'; import dotenv from 'dotenv'; import { HttpConfigBuilder } from '../builders/HttpConfigBuilder.js'; dotenv.config(); /** * Configuration for HTTP transport. */ export class HttpConfig { /** * Constructs an HttpConfig instance. * @param url - The URL for the HTTP transport. * @param options - Additional Axios request options. * @param token - Optional authorization token. * @param maxRetries - Maximum number of retry attempts (default: 3). * @param initialRetryDelay - Initial delay in milliseconds before retrying (default: 1000). * @param maxRetryDelay - Maximum delay in milliseconds between retries (default: 30000). * @param retryStatusCodes - HTTP status codes that should trigger a retry (default: [408, 429, 500, 502, 503, 504]). */ constructor(url, options = {}, token = null, maxRetries = 3, initialRetryDelay = 1000, maxRetryDelay = 30000, retryStatusCodes = [408, 429, 500, 502, 503, 504]) { validateUrlAndToken(url, token); this.url = url; this.token = token; this.options = { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...(token && { Authorization: `Bearer ${token}` }), }, ...options, }; this.maxRetries = maxRetries; this.initialRetryDelay = initialRetryDelay; this.maxRetryDelay = maxRetryDelay; this.retryStatusCodes = retryStatusCodes; } } /** * Validates the URL and token. * @param url - The URL to validate. * @param token - The token to validate (optional). * @throws {Error} If the URL or token is invalid. */ function validateUrlAndToken(url, token = null) { if (!validator.isURL(url, { require_host: false, require_tld: false })) { throw new Error('Invalid URL'); } // Only validate JWT format in production environments // In test environments, we'll accept any token format const isTestEnvironment = process.env.NODE_ENV === 'test' || process.env.VITEST; if (token && !isTestEnvironment && !validator.isJWT(token)) { throw new Error('Invalid token'); } } /** * Sleep function for implementing delays between retries. * @param ms - The number of milliseconds to sleep. * @returns A promise that resolves after the specified time. */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * HTTP transport implementation for emitting events. */ export class HttpTransport { /** * Constructs an HttpTransport instance. * @param config - The HTTP configuration. */ constructor(config) { this.url = config.url; this.options = config.options; this.maxRetries = config.maxRetries; this.initialRetryDelay = config.initialRetryDelay; this.maxRetryDelay = config.maxRetryDelay; this.retryStatusCodes = config.retryStatusCodes; } /** * Calculates the delay for the next retry attempt using exponential backoff with jitter. * @param retryCount - The current retry attempt number (0-based). * @returns The delay in milliseconds before the next retry. */ calculateRetryDelay(retryCount) { // Exponential backoff: initialDelay * 2^retryCount const exponentialDelay = this.initialRetryDelay * Math.pow(2, retryCount); // Add jitter (random value between 0 and 1) to avoid thundering herd problem const jitter = Math.random(); // Apply jitter and cap at maxRetryDelay return Math.min(exponentialDelay * (1 + jitter), this.maxRetryDelay); } /** * Determines if a request should be retried based on the error. * @param error - The error from the failed request. * @returns True if the request should be retried, false otherwise. */ shouldRetry(error) { // Retry on network errors if (!error.response) { return true; } // Retry on specific status codes return this.retryStatusCodes.includes(error.response.status); } /** * Emits an event to the configured HTTP endpoint with retry logic. * @param event - The event to emit. * @returns A promise that resolves with the Axios response. */ async emit(event) { const data = JSON.stringify(event); const config = { ...this.options, url: this.url, data, }; let retryCount = 0; let lastError = new Error('Unknown error'); while (retryCount <= this.maxRetries) { try { const response = await axios(config); if (process.env.DEBUG === 'true') { console.debug(response); } return response; } catch (error) { lastError = error; if (process.env.DEBUG === 'true') { console.debug(`Request failed (attempt ${retryCount + 1}/${this.maxRetries + 1}):`, error.message); } // If we've reached max retries or shouldn't retry this error, throw if (retryCount >= this.maxRetries || !this.shouldRetry(error)) { break; } // Calculate delay with exponential backoff const delay = this.calculateRetryDelay(retryCount); if (process.env.DEBUG === 'true') { console.debug(`Retrying in ${delay}ms...`); } // Wait before retrying await sleep(delay); retryCount++; } } // If we've exhausted all retries, throw the last error throw lastError; } /** * Creates an HttpTransport instance from a configuration object. * @param config - The transport configuration. * @returns A new HttpTransport instance. * @throws {Error} If the configuration is invalid. */ static fromFile(config) { if (!config.url) { throw new Error('Missing URL in config'); } return new HttpTransport(HttpConfigBuilder.fromConfig(config)); } } //# sourceMappingURL=HttpTransport.js.map