Understanding the Executor
The Executor is a core component that ensures database operations are executed safely and in the correct order. It prevents race conditions by serializing operations on the same collection.
Why You Need an Executor
Database adapters often cannot handle concurrent operations safely. Without an executor:
// Race condition without executor
await Promise.all([
db.users.add({ name: "Alice" }),
db.users.add({ name: "Bob" })
]);
// Both operations might interfere with each other
With an executor, operations are queued and executed sequentially:
// Safe with executor
await Promise.all([
db.users.add({ name: "Alice" }),
db.users.add({ name: "Bob" })
]);
// Operations are queued: Alice first, then Bob
Executor Types
Executor (Simple)
A basic FIFO queue. All operations are executed one after another, regardless of collection.
import { Executor, ValtheraClass } from "@wxn0brp/db-core";
const db = new ValtheraClass({
adapter: myAdapter,
executor: new Executor()
});
Characteristics:
- Single queue for all operations
- Strict sequential execution
- Simple and predictable
- Lower concurrency (bottleneck on single queue)
When to use:
- Simple adapters that cannot handle any concurrency
- When you want predictable, linear execution
- Testing and debugging
SmartExecutor (Advanced)
Per-collection queues with automatic cleanup. Operations on different collections can run in parallel.
import { SmartExecutor, ValtheraClass } from "@wxn0brp/db-core";
const db = new ValtheraClass({
adapter: myAdapter,
executor: new SmartExecutor(
ttl, // default: 300000 (5 min)
aware // default: false
)
});
Characteristics:
- Separate queue for each collection
- Parallel execution across collections
- Automatic cleanup of idle queues
- Higher concurrency
Parameters:
ttl(default:300000): Time-to-live for inactive queues (milliseconds)aware(default:false): Whether to isolate queues per collection
When to use:
- Most production scenarios
- Applications with multiple collections
- When you need better concurrency
How SmartExecutor Works
Queue Isolation
// These operations run in parallel (different collections)
await Promise.all([
db.users.add({ name: "Alice" }), // Queue: "users"
db.posts.add({ title: "Hello" }), // Queue: "posts"
db.comments.add({ text: "Nice!" }) // Queue: "comments"
]);
// These operations run sequentially (same collection)
await Promise.all([
db.users.add({ name: "Alice" }), // Queue: "users" - first
db.users.add({ name: "Bob" }), // Queue: "users" - second
db.users.find({}) // Queue: "users" - third
]);
Automatic Cleanup
SmartExecutor automatically removes idle queues after the TTL period:
const executor = new SmartExecutor(60000); // 1 minute TTL
// Operation creates a queue for "users"
await db.users.add({ name: "Alice" });
// Queue remains active for 1 minute
// If no operations on "users" for 1 minute, queue is removed
// Next operation on "users" creates a new queue
aware Mode
When aware = true, queues are isolated per collection:
const executor = new SmartExecutor(300000, true);
// "users" -> separate queue
// "posts" -> separate queue
// "comments" -> separate queue
When aware = false (default), all operations share a single queue:
const executor = new SmartExecutor(300000, false);
// All operations -> single shared queue
// Behaves like simple Executor but with TTL cleanup
Adapter Integration
Adapters can signal support for SmartExecutor:
class MyAdapter extends ActionsBase {
smartExecutor = true; // Signals support for per-collection queuing
// ... adapter methods
}
When an adapter has smartExecutor = true, ValtheraClass automatically enables aware mode on the SmartExecutor.
Custom Executor
You can implement your own executor:
import { ExecutorInterface } from "@wxn0brp/db-core";
class MyCustomExecutor implements ExecutorInterface {
async addOp(func: Function, query?: any, collection?: string): Promise<any> {
// Your custom logic here
// Must return a promise that resolves with the operation result
return await new Promise((resolve, reject) => {
// Queue the operation
// Call func(query) when ready
func(query).then(resolve).catch(reject);
});
}
}
const db = new ValtheraClass({
adapter: myAdapter,
executor: new MyCustomExecutor()
});
Performance Considerations
Executor vs SmartExecutor
| Scenario | Executor | SmartExecutor |
|---|---|---|
| Single collection | Same | Same |
| Multiple collections | Sequential | Parallel |
| Memory usage | Low | Higher (multiple queues) |
| Concurrency | Low | High |
| Complexity | Simple | Moderate |