npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ikhrustalev/convex-debouncer

v0.1.0

Published

A debouncer component for Convex.

Readme

Convex Debouncer

npm version

A server-side debouncing component for Convex. Debounce expensive operations like LLM calls, metrics computation, or any heavy processing that should only run after a period of inactivity.

Why use this?

When users rapidly trigger expensive operations (like typing in a chat that triggers AI responses, or updating data that requires recomputation), you often want to:

  1. Wait for a pause before processing (avoid wasted computation)
  2. Guarantee the latest state is processed (don't lose the final update)
  3. Provide immediate feedback in some cases (don't make users wait)

This component provides three debouncing modes to handle these scenarios.

Installation

npm install @ikhrustalev/convex-debouncer

Create a convex.config.ts file in your app's convex/ folder and install the component:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import debouncer from "@ikhrustalev/convex-debouncer/convex.config.js";

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

export default app;

Usage

import { Debouncer } from "@ikhrustalev/convex-debouncer";
import { components, internal } from "./_generated/api";

// Create a Debouncer instance with default configuration
const debouncer = new Debouncer(components.debouncer, {
  delay: 5000,      // 5 second delay
  mode: "sliding",  // Options: "eager" | "fixed" | "sliding"
});

// In your mutation
export const onPropertyUpdate = mutation({
  args: { propertyId: v.string(), data: v.any() },
  handler: async (ctx, args) => {
    // Schedule a debounced metrics computation
    await debouncer.schedule(
      ctx,
      "property-metrics",        // namespace
      args.propertyId,           // key (unique within namespace)
      internal.metrics.compute,  // function to call
      { propertyId: args.propertyId }  // arguments
    );
  },
});

Debouncing Modes

Sliding Mode (default)

Each call resets the timer. The function only executes after the delay passes with no new calls.

Call 1 ──▶ Start 5s timer
           ↓ 2s later
Call 2 ──▶ Reset timer to 5s
           ↓ 5s later (no calls)
           Execute with Call 2's args

Best for: Search-as-you-type, auto-save, real-time validation

Fixed Mode

Timer stays fixed from the first call. Subsequent calls update the arguments but don't extend the timer.

Call 1 ──▶ Start 5s timer ──────────────┐
           ↓ 2s later                   │
Call 2 ──▶ Update args (timer unchanged)│
           ↓ 3s later                   │
           Execute with Call 2's args ◀─┘

Best for: Batch processing, rate-limited APIs, periodic syncs

Eager Mode

Execute immediately on first call, then queue a trailing call with the latest arguments if more calls come in.

Call 1 ──▶ Execute immediately + start 5s timer ──┐
           ↓ 1s later                              │
Call 2 ──▶ Queue trailing (update args)           │
           ↓ 2s later                              │
Call 3 ──▶ Queue trailing (update to latest args) │
           ↓ timer ends                            │
           Execute with Call 3's args ◀────────────┘

Best for: Real-time collaboration, AI responses (immediate feedback + final state)

API Reference

Constructor

const debouncer = new Debouncer(components.debouncer, {
  delay: 5000,       // Delay in milliseconds
  mode: "sliding",   // "eager" | "fixed" | "sliding"
});

schedule()

Schedule a debounced function call.

const result = await debouncer.schedule(
  ctx,                           // Mutation context
  "namespace",                   // Logical grouping
  "key",                         // Unique identifier within namespace
  internal.myModule.myFunction,  // Function reference to call
  { arg1: "value" },             // Arguments for the function
  { delay: 3000, mode: "fixed" } // Optional: override defaults
);

// result: { executed: boolean, scheduledFor: number }

status()

Check the status of a pending debounced call.

const status = await debouncer.status(ctx, "namespace", "key");

// status: null (if no pending call) or:
// {
//   pending: true,
//   scheduledFor: 1234567890,  // Unix timestamp
//   retriggerCount: 3,         // Number of times schedule was called
//   mode: "sliding",
//   hasTrailingCall: false     // (eager mode) whether trailing execution is queued
// }

cancel()

Cancel a pending debounced call.

const cancelled = await debouncer.cancel(ctx, "namespace", "key");
// cancelled: true if a call was cancelled, false if nothing was pending

Examples

Debouncing Metrics Computation

const debouncer = new Debouncer(components.debouncer, {
  delay: 5000,
  mode: "sliding",
});

export const onDataChange = mutation({
  args: { entityId: v.string() },
  handler: async (ctx, args) => {
    // Multiple rapid changes will only trigger one computation
    await debouncer.schedule(
      ctx,
      "metrics",
      args.entityId,
      internal.metrics.recompute,
      { entityId: args.entityId }
    );
  },
});

Eager AI Responses

const debouncer = new Debouncer(components.debouncer, {
  delay: 10000,
  mode: "eager",  // Respond immediately, then ensure we process final state
});

export const onUserMessage = mutation({
  args: { conversationId: v.string(), message: v.string() },
  handler: async (ctx, args) => {
    const result = await debouncer.schedule(
      ctx,
      "ai-responses",
      args.conversationId,
      internal.ai.generateResponse,
      { conversationId: args.conversationId }
    );

    // result.executed is true for the first message (immediate execution)
    // Subsequent messages will queue a trailing execution
  },
});

Fixed Interval Batch Processing

const debouncer = new Debouncer(components.debouncer, {
  delay: 30000,   // Process every 30 seconds
  mode: "fixed",  // Timer doesn't extend
});

export const queueItem = mutation({
  args: { batchId: v.string(), itemId: v.string() },
  handler: async (ctx, args) => {
    // First item starts the timer, subsequent items just update
    await debouncer.schedule(
      ctx,
      "batches",
      args.batchId,
      internal.batch.process,
      { batchId: args.batchId }
    );
  },
});

Development

npm i
npm run dev

Run tests:

npm test

License

Apache-2.0

Found a bug? Feature request? File it here.