@unitio-code/url-shortener
Version:
A simple URL shortening library
168 lines (167 loc) • 4.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultStorage = exports.DEFAULT_SHORT_URL_OPTIONS = exports.RedisStorage = exports.MemoryStorage = void 0;
exports.isValidUrl = isValidUrl;
exports.encodeId = encodeId;
exports.decodeShortUrl = decodeShortUrl;
exports.storeUrlMapping = storeUrlMapping;
exports.getOriginalUrl = getOriginalUrl;
exports.hasUrlMapping = hasUrlMapping;
exports.buildShortUrl = buildShortUrl;
const CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const BASE = CHARACTERS.length;
/**
* In-memory storage implementation
*/
class MemoryStorage {
constructor() {
this.storage = new Map();
}
async get(id) {
return this.storage.get(id);
}
async set(id, url, ttl) {
this.storage.set(id, url);
if (ttl) {
setTimeout(() => this.storage.delete(id), ttl);
}
}
async has(id) {
return this.storage.has(id);
}
}
exports.MemoryStorage = MemoryStorage;
/**
* Redis storage implementation (optional)
*/
class RedisStorage {
constructor(client) {
this.client = client;
}
async get(id) {
const result = await this.client.get(`url:${id}`);
return result || undefined;
}
async set(id, url, ttl) {
const key = `url:${id}`;
if (ttl) {
await this.client.setex(key, ttl, url);
}
else {
await this.client.set(key, url);
}
}
async has(id) {
const exists = await this.client.exists(`url:${id}`);
return exists === 1;
}
}
exports.RedisStorage = RedisStorage;
/**
* Validates if a string is a proper URL
*/
function isValidUrl(url) {
try {
new URL(url);
return true;
}
catch (_a) {
return false;
}
}
/**
* Generates a short URL code from a numeric ID using base62 encoding
* @param id - The numeric ID to encode
* @returns The base62 encoded string
*/
function encodeId(id) {
let shortUrl = "";
let num = id;
while (num > 0) {
shortUrl = CHARACTERS[num % BASE] + shortUrl;
num = Math.floor(num / BASE);
}
return shortUrl || CHARACTERS[0];
}
/**
* Decodes a short URL code back to its numeric ID
* @param shortCode - The base62 encoded string
* @returns The decoded numeric ID
*/
function decodeShortUrl(shortCode) {
let id = 0;
for (let i = 0; i < shortCode.length; i++) {
const char = shortCode[i];
const charIndex = CHARACTERS.indexOf(char);
if (charIndex === -1) {
throw new Error(`Invalid character in short URL: ${char}`);
}
id = id * BASE + charIndex;
}
return id;
}
/**
* Default options for building a short URL
*/
exports.DEFAULT_SHORT_URL_OPTIONS = {
domain: "short.url",
includeRedirectPath: true,
redirectPathSegment: "r",
includeProtocol: false,
protocol: "https",
pathSeparator: "/",
};
/**
* Default storage instance (in-memory)
*/
exports.defaultStorage = new MemoryStorage();
/**
* Stores a URL mapping using the specified storage
* @param id - The numeric ID
* @param originalUrl - The original URL
* @param storage - Storage instance to use
* @param ttl - Time to live in seconds (optional)
*/
async function storeUrlMapping(id, originalUrl, storage = exports.defaultStorage, ttl) {
await storage.set(id, originalUrl, ttl);
}
/**
* Retrieves the original URL for a given ID
* @param id - The numeric ID
* @param storage - Storage instance to use
* @returns The original URL if found, undefined otherwise
*/
async function getOriginalUrl(id, storage = exports.defaultStorage) {
return await storage.get(id);
}
/**
* Checks if a URL mapping exists
* @param id - The numeric ID
* @param storage - Storage instance to use
* @returns True if the mapping exists, false otherwise
*/
async function hasUrlMapping(id, storage = exports.defaultStorage) {
return await storage.has(id);
}
/**
* Builds a complete short URL with domain and customizable options
* @param id - The numeric ID to encode
* @param options - Configuration options for the short URL
* @returns The complete short URL
*/
function buildShortUrl(id, options) {
const opts = typeof options === "string"
? { ...exports.DEFAULT_SHORT_URL_OPTIONS, domain: options }
: { ...exports.DEFAULT_SHORT_URL_OPTIONS, ...options };
const shortCode = encodeId(id);
let url = "";
if (opts.includeProtocol && opts.protocol) {
url += `${opts.protocol}://`;
}
url += opts.domain;
if (opts.includeRedirectPath && opts.redirectPathSegment) {
url += `${opts.pathSeparator}${opts.redirectPathSegment}`;
}
url += `${opts.pathSeparator}${shortCode}`;
return url;
}