npm package: @shyamraghuonec/link-shortener
Description: A self-hosted link shortener component for Convex with click analytics and UTM tracking.
Latest version: 0.1.1

# 🔗 Convex Link Shortener Component

A self-hosted link shortener component for [Convex](https://convex.dev). Create short links with analytics, password protection, and UTM tracking.

[![npm version](https://badge.fury.io/js/@shyamraghuonec%2Flink-shortener.svg)](https://www.npmjs.com/package/@shyamraghuonec/link-shortener)

## ✨ Features

- 🔗 Create/update/revoke short links
- 🔒 Password protection
- 📊 Click analytics with geo tracking
- 🏷️ UTM parameter handling (capture/strip/passthrough)
- ⏰ Expiration dates

## 📦 Installation

```bash
npm install @shyamraghuonec/link-shortener
```

## 🚀 Quick Start

### 1. Add to Your App

```ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import linkShortener from "@shyamraghuonec/link-shortener/convex.config.js";

const app = defineApp();
app.use(linkShortener);

export default app;
```

### 2. Create Links

```ts
// convex/links.ts
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { LinkShortener } from "@shyamraghuonec/link-shortener";
import { v } from "convex/values";

const linkShortener = new LinkShortener(components.linkShortener);

export const create = mutation({
  args: { destinationUrl: v.string() },
  handler: async (ctx, args) => {
    return await linkShortener.create(ctx, {
      destinationUrl: args.destinationUrl,
    });
  },
});

export const get = query({
  args: { code: v.string() },
  handler: async (ctx, args) => {
    return await linkShortener.getByCode(ctx, args.code);
  },
});
```

### 3. Set Up Redirects

```ts
// convex/http.ts
import { httpRouter } from "convex/server";
import { registerRedirectRoutes } from "@shyamraghuonec/link-shortener";
import { components } from "./_generated/api";

const http = httpRouter();

registerRedirectRoutes(http, components.linkShortener, {
  pathPrefix: "/r",
});

export default http;
```

Now `your-site.convex.site/r/abc123` redirects to the destination!

## 📖 API

### LinkShortener Class

```ts
const linkShortener = new LinkShortener(components.linkShortener);

// Create a link
await linkShortener.create(ctx, {
  destinationUrl: "https://example.com",
  code: "my-link",        // optional custom code
  password: "secret",     // optional password
  expiresAt: Date.now() + 86400000, // optional expiry
});

// Get link
await linkShortener.getByCode(ctx, "my-link");

// List links
await linkShortener.list(ctx, { limit: 10 });

// Update
await linkShortener.update(ctx, linkId, { destinationUrl: "https://new-url.com" });

// Revoke
await linkShortener.revoke(ctx, linkId);

// Analytics
await linkShortener.summary(ctx, linkId);
await linkShortener.clicksOverTime(ctx, linkId, { days: 7 });
await linkShortener.topReferrers(ctx, linkId);
await linkShortener.geoBreakdown(ctx, linkId);
```

## 🔒 Password Protection

```ts
// Create password-protected link
await linkShortener.create(ctx, {
  destinationUrl: "https://secret.com",
  password: "mysecret",
});

// Resolve with password
await linkShortener.resolve(ctx, "abc123", "mysecret");
```

Set up password page in HTTP routes:

```ts
registerRedirectRoutes(http, components.linkShortener, {
  pathPrefix: "/r",
  passwordPageUrl: "https://your-site.convex.site/password",
});
```

## 📄 License

Apache-2.0