# Debouncer Debounced background processing - Package: @bijectionhq/debouncer - Category: Durable Functions - Status: Preview - License: Apache-2.0 - Bijection source (repository access required): https://github.com/bijectionhq/debouncer/tree/3204fcdc8729984dfe82e20ac0ce5a4a1687ad0b - Upstream: https://github.com/ikhrustalev/convex-debouncer/tree/9dc4c2ec2b9b9da98563926e9066437aa31c4c5f - Documentation: https://docs.bijection.com/components/using - Directory: https://bijection.com/components/debouncer ## Get the source Bijection repository access is required. Request access at https://bijection.com/contact. ```sh git clone https://github.com/bijectionhq/debouncer.git cd debouncer git checkout 3204fcdc8729984dfe82e20ac0ce5a4a1687ad0b ``` ## Before using this component Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute. ## Upstream package documentation Recorded 2026-10-23. This describes @ikhrustalev/convex-debouncer; check the Bijection adaptation separately. A server-side debouncing component that prevents expensive operations from running too frequently by waiting for periods of inactivity. It provides three debouncing modes (sliding, fixed, eager) to handle different scenarios like LLM calls, metrics computation, or batch processing. The component schedules functions to run after configurable delays and automatically manages cancellation and rescheduling based on incoming triggers. ### Use cases • **Debounce AI/LLM responses** when users are rapidly typing messages to avoid wasted API calls while ensuring the final message gets processed • **Throttle expensive recomputation** like analytics, metrics, or search indexing when data changes frequently but you only need the final state • **Batch process rate-limited APIs** by collecting multiple updates and processing them together at fixed intervals • **Implement auto-save functionality** that waits for typing pauses before persisting data to avoid excessive database writes • **Optimize real-time collaboration features** by providing immediate feedback while ensuring eventual consistency with a trailing execution ### How it works The component uses Convex's scheduled functions infrastructure to manage debounced execution. You create a `Debouncer` instance with your desired delay and mode, then call `schedule()` with a namespace, key, target function, and arguments. The component tracks pending executions and automatically handles cancellation and rescheduling based on the selected mode. Three modes handle different scenarios: sliding mode resets the timer on each call (ideal for search-as-you-type), fixed mode maintains the original timer while updating arguments (perfect for batch processing), and eager mode executes immediately then queues a trailing call (great for real-time feedback with eventual consistency). The API provides `schedule()` for triggering debounced calls, `status()` for checking pending executions, and `cancel()` for manual cancellation. Each debounced call is uniquely identified by a namespace/key pair, allowing you to manage multiple independent debouncing contexts within the same application. ### Upstream README # Convex Debouncer 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 ```bash npm install @ikhrustalev/convex-debouncer ``` Create a `convex.config.ts` file in your app's `convex/` folder and install the component: ```ts // 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 ```ts 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 ```ts const debouncer = new Debouncer(components.debouncer, { delay: 5000, // Delay in milliseconds mode: "sliding", // "eager" | "fixed" | "sliding" }); ``` ### schedule() Schedule a debounced function call. ```ts 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. ```ts 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. ```ts const cancelled = await debouncer.cancel(ctx, "namespace", "key"); // cancelled: true if a call was cancelled, false if nothing was pending ``` ## Examples ### Debouncing Metrics Computation ```ts 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 ```ts 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 ```ts 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 ```sh npm i npm run dev ``` Run tests: ```sh npm test ``` ## License Apache-2.0 Found a bug? Feature request? [File it here](https://github.com/ikhrustalev/convex-debouncer/issues).