@hamzasaleemorg/convex-kv
v1.0.2
Published
Hierarchical Key-Value store for Convex
Readme
Convex KV Component
A hierarchical, ordered key-value store for Convex. Replace relational boilerplate with a simple get/set API, featuring automatic TTL and recursive prefix deletion.
Installation
npm install @hamzasaleemorg/convex-kv// convex/convex.config.ts
import { defineApp } from "convex/server";
import convexKv from "@hamzasaleemorg/convex-kv/convex.config.js";
const app = defineApp();
app.use(convexKv);
export default app;Quick Start: Expiring Invite Links 🚀
Manage one-time magic links or invite codes without cluttering your main schema or writing cleanup logic.
// convex/invites.ts
import { kvClientFactory } from "@hamzasaleemorg/convex-kv";
import { components } from "./_generated/api";
import { mutation } from "./_generated/server";
import { v } from "convex/values";
const kv = kvClientFactory(components.convexKv);
const invites = kv.use<{ email: string; role: string }>(["invites"]);
export const createInvite = mutation({
args: { email: v.string(), role: v.string() },
handler: async (ctx, args) => {
const inviteCode = Math.random().toString(36).substring(7);
// Store invite that automatically EXPIRES in 48 hours
await invites.set(ctx, [inviteCode], {
email: args.email,
role: args.role
}, { ttl: 48 * 60 * 60 * 1000 });
return inviteCode;
},
});
export const acceptInvite = mutation({
args: { code: v.string() },
handler: async (ctx, args) => {
// If the link is older than 48hrs, .get() returns null automatically
const invite = await invites.get(ctx, [args.code]);
if (!invite) throw new Error("Invite invalid or expired");
// ... create membership logic ...
await invites.delete(ctx, [args.code]);
},
});🛠️ Why use this?
- Zero Schema Maintenance: Store any JSON-serializable data instantly. No new tables, no indexes to wait for.
- Organized Hierarchy: Use array keys like
["org", orgId, "settings"]to naturally isolate data. - Automatic TTL: Set a
ttl(ms) on any key and it disappear from queries as soon as it expires. - Ordered Scans: Keys are stored lexicographically. Scan through millions of keys by prefix instantly.
- Batched Deletes: Clear an entire "folder" of data with
deleteAll(["prefix"]). It automatically handles large datasets in the background.
API Reference
Initializing the Client
You can use the global client or scope it to a specific recursive namespace:
const kv = kvClientFactory(components.convexKv);
// Everything sent through 'users' will stay in that folder
const users = kv.use<{ email: string }>(["users"]);Core Operations
| Method | Description |
| :--- | :--- |
| get(key) | Returns value or null if missing/expired. |
| set(key, val, options?) | Stores value. Options: ttl (ms), expiresAt, metadata. |
| has(key) | Fast boolean check for existence (returns false if expired). |
| delete(key) | Removes a specific key. |
Hierarchical & Range Operations
| Method | Description |
| :--- | :--- |
| list(prefix, options?) | Paginated scan within a namespace. |
| getAll(prefix) | Returns all entries under a prefix (max 1000). |
| deleteAll(prefix) | Recursive Delete. Safely clears an entire prefix tree in the background. |
Development & Testing
npm install
npm run dev # Starts the KV Explorer & Build Watcher
npm test # Runs full suite (Unit + Component Integration)License
Apache-2.0
