Live Queries
TopGun provides a powerful reactive query system that allows you to subscribe to changes in your data. Unlike traditional databases where you have to poll for updates, TopGun pushes updates to your client in real-time.
Real-time Updates
Changes are pushed to subscribers immediately as they happen, no polling required.
Powerful Filters
Use simple equality matching or complex predicates with logical operators.
React Integration
First-class React hooks for seamless integration with your UI components.
Basic Usage
To create a live query, use the client.query() method.
This returns a QueryHandle that you can subscribe to.
const query = client.query('todos', {
where: { completed: false },
sort: { createdAt: 'desc' }
});
const unsubscribe = query.subscribe((results) => {
console.log('Active todos:', results);
});
// Later, when you're done:
unsubscribe(); Query Filters
The QueryFilter object supports several options:
where: Simple equality matchingpredicate: Complex conditions using logic operatorssort: Sort orderlimit: Max number of results (coming soon)
Complex Predicates
For more complex filtering, use the predicate field:
import { Predicates } from '@topgunbuild/client';
const query = client.query('products', {
predicate: Predicates.and(
Predicates.greaterThan('price', 100),
Predicates.equal('category', 'electronics')
)
}); Available Predicate Methods
| Method | Description | Example |
|---|---|---|
equal(attr, value) | Exact match | Predicates.equal('status', 'active') |
notEqual(attr, value) | Not equal | Predicates.notEqual('type', 'draft') |
greaterThan(attr, value) | Greater than | Predicates.greaterThan('price', 100) |
greaterThanOrEqual(attr, value) | Greater or equal | Predicates.greaterThanOrEqual('stock', 0) |
lessThan(attr, value) | Less than | Predicates.lessThan('age', 18) |
lessThanOrEqual(attr, value) | Less or equal | Predicates.lessThanOrEqual('priority', 5) |
like(attr, pattern) | SQL-like pattern (% = any, _ = single char) | Predicates.like('name', '%john%') |
regex(attr, pattern) | Regular expression | Predicates.regex('email', '^.*@gmail\\.com$') |
between(attr, from, to) | Range (inclusive) | Predicates.between('price', 10, 100) |
and(...predicates) | Logical AND | Predicates.and(p1, p2, p3) |
or(...predicates) | Logical OR | Predicates.or(p1, p2) |
not(predicate) | Logical NOT | Predicates.not(p1) |
Change Tracking
TopGun tracks not just the current state, but what changed. This enables:
- Efficient UI updates (only re-render changed items)
- Add/remove animations (framer-motion, react-spring)
- Notifications (“New item added”)
- Optimistic UI rollback on specific keys
const query = client.query('todos');
// Subscribe to changes
const unsubscribe = query.onChanges((changes) => {
for (const change of changes) {
console.log(`${change.type}: ${change.key}`, change.value);
// change.type: 'add' | 'update' | 'remove'
// change.previousValue: available for update/remove
}
});
// Get pending changes without subscribing
const pending = query.getPendingChanges();
// Consume and clear changes
const consumed = query.consumeChanges();
React Integration
If you are using React, we recommend using the useQuery hook which handles subscription management automatically.
See the React Hooks reference for detailed documentation on useQuery (including change tracking), useMutation, and other hooks.
Distributed Live Queries (Cluster Mode)
In clustered environments, live queries automatically work across all nodes. When you subscribe to a query, you receive real-time updates for matching documents regardless of which node owns the data.
Cluster-Wide Updates
Changes on any cluster node automatically push to all relevant subscribers.
Transparent Coordination
The node you connect to becomes the coordinator, aggregating updates from all nodes.
How It Works
// Live queries work seamlessly in clustered environments
// The client connects to any node - queries see data from ALL nodes
const client = new TopGunClient({
serverUrl: 'ws://node1:8080' // Connect to any cluster node
});
// This subscription receives updates from all cluster nodes
const query = client.query('orders', {
predicate: Predicates.greaterThan('total', 100)
});
// Updates are pushed automatically when:
// - New matching documents are added on ANY node
// - Existing documents are modified to match/unmatch on ANY node
// - Documents are removed from ANY node
query.subscribe((results) => {
console.log('All matching orders across cluster:', results.length);
}); Architecture
When a client subscribes to a live query in a cluster:
- Registration Phase: The connected node (coordinator) broadcasts the subscription to all cluster nodes
- Initial Results: Each node evaluates the query against its local data and sends results
- Result Merging: The coordinator deduplicates and merges results from all nodes
- Live Updates: When data changes on any node, that node evaluates affected subscriptions and sends targeted updates to the coordinator
- Client Delivery: The coordinator forwards updates to the client
Client ──QUERY_SUB──▶ Coordinator ──CLUSTER_SUB_REGISTER──▶ All Nodes
│ │
│◀───────────────────────────────────┘
│ Initial results + ACKs
│
▼
Merge & Send to Client
│
┌─────────────┼─────────────┐
│ │ │
On change: On change: On change:
Node A Node B Node C
│ │ │
└─────────────┼─────────────┘
│
CLUSTER_SUB_UPDATE
│
▼
Forward to Client
Delta Updates
Instead of resending full result sets, nodes send targeted delta updates:
| Update Type | Description |
|---|---|
ENTER | Document now matches the query predicate |
UPDATE | Document still matches but its value changed |
LEAVE | Document no longer matches the query predicate |
This approach minimizes network traffic and enables efficient UI updates.
Automatic failover: If a cluster node disconnects, the coordinator automatically removes its results from the subscription state. When the node rejoins, its data becomes visible again through normal query updates.