Debouncer
Debounced background processing
git clone https://github.com/bijectionhq/debouncer.git
cd debouncer
git checkout 3204fcdc8729984dfe82e20ac0ce5a4a1687ad0bBijection repository access is required. Request access.
Description
Debounced background processing
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: bijectionhq/debouncer. Based on ikhrustalev/convex-debouncer. License: Apache-2.0. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
A debouncer component for Convex.
Upstream overview
From the upstream package documentation.
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
From the upstream package documentation.
- 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
From the upstream package documentation.
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 package FAQ
How does Convex Debouncer handle concurrent requests?
Convex Debouncer cancels previous pending operations when new requests arrive within the debounce window. Only the most recent operation executes after the specified delay period, preventing race conditions and redundant processing.
Can I configure different debounce delays for different operations?
Yes, Convex Debouncer allows you to set custom delay periods per operation. You can use short delays for UI updates and longer delays for expensive operations like LLM calls or complex database aggregations.
Does Convex Debouncer work with both mutations and actions?
Convex Debouncer works with any Convex backend operation including mutations, actions, and queries. It provides a wrapper that manages the debouncing logic while preserving the original function signatures and return types.
What happens if my Convex function fails during debounced execution?
Convex Debouncer preserves error handling from your original functions. Failed operations throw errors normally, and you can implement retry logic or error boundaries as needed without affecting the debouncing mechanism.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Debouncer with Bijection.
Read: https://bijection.com/components/debouncer/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from ikhrustalev/convex-debouncer, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
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:
- Wait for a pause before processing (avoid wasted computation)
- Guarantee the latest state is processed (don't lose the final update)
- 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.