smart-track
Version:
A TypeScript SDK for integrating with tracking services using the Beckn protocol. Provides adapters for package tracking with built-in error handling, retry logic, and type safety.
132 lines • 4.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseAdapter = void 0;
const error_1 = require("./utils/error");
const payload_types_1 = require("../types/payload.types");
class BaseAdapter {
constructor(config) {
this.baseUrl = config.baseURL.replace(/\/$/, "");
this.timeout = config.timeout || 5000;
this.headers = config.headers || {};
this.retryAttempts = config.retryAttempts || 3;
this.beckn_metadata = payload_types_1.becknMetadataSchema.parse({
...payload_types_1.becknMetadataSchema.parse({}),
...config.beckn_metadata,
});
}
async request(method, endpoint, data, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const config = {
method,
headers: new Headers({
"Content-Type": "application/json",
...this.cleanHeaders(this.headers),
...this.cleanHeaders(options.headers),
}),
signal: options.signal || null,
};
// Handle different types of request bodies
if (data) {
config.body = JSON.stringify({
context: this.beckn_metadata,
message: data,
});
}
return this.executeWithRetry(() => this.makeRequest(url, config, options.timeout || this.timeout));
}
cleanHeaders(headers = {}) {
const cleaned = {};
for (const [key, value] of Object.entries(headers)) {
if (typeof value === "string") {
cleaned[key] = value;
}
else if (Array.isArray(value)) {
cleaned[key] = value.join(", ");
}
}
return cleaned;
}
async makeRequest(url, config, timeout) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...config,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
await this.handleErrorResponse(response);
}
return await this.parseResponse(response);
}
catch (error) {
clearTimeout(timeoutId);
const err = error;
if (err.name === "AbortError") {
throw new error_1.TimeoutError(timeout);
}
if (error instanceof error_1.AdapterError) {
throw error;
}
throw new error_1.NetworkError("Network request failed", err);
}
}
// Handles error responses and creates appropriate error objects
async handleErrorResponse(response) {
let errorData;
try {
const contentType = response.headers.get("content-type");
if (contentType?.includes("application/json")) {
errorData = await response.json();
}
else {
errorData = await response.text();
}
}
catch {
errorData = null;
}
const message = errorData?.message ||
errorData?.error ||
`HTTP ${response.status}: ${response.statusText}`;
throw new error_1.AdapterError(message, response.status, errorData, response.status >= 500 || response.status === 429);
}
// Parses response based on content type
async parseResponse(response) {
const contentType = response.headers.get("content-type");
if (contentType?.includes("application/json")) {
return response.json();
}
return response.text();
}
// Implements retry logic with exponential backoff
async executeWithRetry(operation) {
let lastError;
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
try {
return await operation();
}
catch (error) {
lastError = error;
// Only retry if error is retryable
if (!(error instanceof error_1.AdapterError) || !error.isRetryable) {
throw error;
}
if (attempt === this.retryAttempts) {
throw error;
}
// Exponential backoff with jitter to prevent thundering herd
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw lastError;
}
// Basic health check endpoint
async healthCheck() {
return this.request("GET", "/health");
}
}
exports.BaseAdapter = BaseAdapter;
//# sourceMappingURL=base.adapter.js.map