UNPKG

@developers-joyride/shortify

Version:

High performance URL shortener library with multi-database support (MongoDB, SQLite, PostgreSQL, MySQL)

175 lines (148 loc) 4.88 kB
import mongoose from "mongoose"; import { EventEmitter } from "events"; import { IDatabaseAdapter, IUrl } from "../interfaces/database.interface"; interface MongoConfig { mongoUri: string; maxRetries?: number; retryDelay?: number; collectionName?: string; } export class MongoAdapter extends EventEmitter implements IDatabaseAdapter { private mongoUri: string; private maxRetries: number; private retryDelay: number; private collectionName: string; private retryCount: number = 0; private _isConnected: boolean = false; constructor(config: MongoConfig) { super(); this.mongoUri = config.mongoUri; this.maxRetries = config.maxRetries || 5; this.retryDelay = config.retryDelay || 5000; this.collectionName = config.collectionName || "urls"; // Configure mongoose for high performance mongoose.set("strictQuery", true); } async connect(): Promise<boolean> { try { if (this._isConnected) { return true; } // Optimized connection options for high throughput await mongoose.connect(this.mongoUri, { serverSelectionTimeoutMS: 5000, maxPoolSize: 100, // Increased connection pool for high throughput }); this._isConnected = true; this.retryCount = 0; this.emit("connected"); // Handle connection events mongoose.connection.on("error", (err: Error) => { console.error("MongoDB connection error:", err); this._isConnected = false; this.emit("error", err); this.retryConnection(); }); mongoose.connection.on("disconnected", () => { this._isConnected = false; this.emit("disconnected"); this.retryConnection(); }); return true; } catch (error) { console.error("Failed to connect to MongoDB:", error); this.emit("error", error); return this.retryConnection(); } } private retryConnection(): boolean { if (this.retryCount < this.maxRetries) { this.retryCount++; // Exponential backoff const delay = this.retryDelay * Math.pow(2, this.retryCount - 1); console.log( `Retrying MongoDB connection in ${delay}ms (attempt ${this.retryCount}/${this.maxRetries})` ); setTimeout(() => { this.connect(); }, delay); return true; } else { this.emit("maxRetriesReached"); return false; } } async disconnect(): Promise<void> { if (mongoose.connection.readyState !== 0) { await mongoose.disconnect(); this._isConnected = false; this.emit("disconnected"); } } isConnected(): boolean { return this._isConnected; } async createUrl(url: Omit<IUrl, "createdAt">): Promise<IUrl> { const { createUrlModel } = await import("../models/url.model"); const UrlModel = createUrlModel(this.collectionName); const newUrl = new UrlModel({ urlId: url.urlId, originalUrl: url.originalUrl, shortUrl: url.shortUrl, clicks: url.clicks, expiresAt: url.expiresAt, }); const savedUrl = await newUrl.save(); return { urlId: savedUrl.urlId, originalUrl: savedUrl.originalUrl, shortUrl: savedUrl.shortUrl, clicks: savedUrl.clicks, createdAt: savedUrl.createdAt, expiresAt: savedUrl.expiresAt, }; } async findUrlByUrlId(urlId: string): Promise<IUrl | null> { const { createUrlModel } = await import("../models/url.model"); const UrlModel = createUrlModel(this.collectionName); const url = await UrlModel.findOne({ urlId }).exec(); if (!url) { return null; } return { urlId: url.urlId, originalUrl: url.originalUrl, shortUrl: url.shortUrl, clicks: url.clicks, createdAt: url.createdAt, expiresAt: url.expiresAt, }; } async findUrlByOriginalUrl(originalUrl: string): Promise<IUrl | null> { const { createUrlModel } = await import("../models/url.model"); const UrlModel = createUrlModel(this.collectionName); const url = await UrlModel.findOne({ originalUrl }).exec(); if (!url) { return null; } return { urlId: url.urlId, originalUrl: url.originalUrl, shortUrl: url.shortUrl, clicks: url.clicks, createdAt: url.createdAt, expiresAt: url.expiresAt, }; } async updateUrlClicks(urlId: string, clicks: number): Promise<void> { const { createUrlModel } = await import("../models/url.model"); const UrlModel = createUrlModel(this.collectionName); await UrlModel.updateOne({ urlId }, { clicks }); } async deleteUrl(urlId: string): Promise<boolean> { const { createUrlModel } = await import("../models/url.model"); const UrlModel = createUrlModel(this.collectionName); const result = await UrlModel.deleteOne({ urlId }); return result.deletedCount > 0; } }