@developers-joyride/shortify
Version:
High performance URL shortener library with multi-database support (MongoDB, SQLite, PostgreSQL, MySQL)
175 lines (148 loc) • 4.88 kB
text/typescript
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;
}
}