perf: server-side search + pagination for topics (30 per page)

Loads only 30 topics at a time with server-side search instead
of fetching all 6000+ topics. Debounced search input, pagination,
selected-only filter. Much faster for large Victron brokers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 04:19:54 +02:00
co-authored by Claude Opus 4.6
parent 68174d16c2
commit e6b366862c
3 changed files with 117 additions and 68 deletions
@@ -11,11 +11,15 @@ import { getDb } from '../../db/index';
const router = Router({ mergeParams: true });
// GET /api/mqtt/brokers/:brokerId/topics
// GET /api/mqtt/brokers/:brokerId/topics?search=grid&selected=1&limit=50&offset=0
router.get('/', (req, res): void => {
try {
const brokerId = parseInt(req.params.brokerId);
res.json(getTopicsForBroker(brokerId));
const search = req.query.search as string | undefined;
const selectedOnly = req.query.selected === '1';
const limit = parseInt(req.query.limit as string) || 50;
const offset = parseInt(req.query.offset as string) || 0;
res.json(getTopicsForBroker(brokerId, { search, selectedOnly, limit, offset }));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
@@ -19,11 +19,32 @@ export interface UpdateTopicData {
ignored?: number;
}
export function getTopicsForBroker(brokerId: number): MqttTopic[] {
export function getTopicsForBroker(brokerId: number, opts?: { search?: string; selectedOnly?: boolean; limit?: number; offset?: number }): { topics: MqttTopic[]; total: number } {
const db = getDb();
return db
.prepare('SELECT * FROM mqtt_topics WHERE broker_id = ? ORDER BY id ASC')
.all(brokerId) as MqttTopic[];
const conditions = ['broker_id = ?'];
const params: any[] = [brokerId];
if (opts?.search) {
conditions.push('topic LIKE ?');
params.push(`%${opts.search}%`);
}
if (opts?.selectedOnly) {
conditions.push('selected = 1');
}
conditions.push('ignored = 0');
const where = conditions.join(' AND ');
const total = (db.prepare(`SELECT count(*) as c FROM mqtt_topics WHERE ${where}`).get(...params) as any).c;
const limit = opts?.limit || 50;
const offset = opts?.offset || 0;
params.push(limit, offset);
const topics = db
.prepare(`SELECT * FROM mqtt_topics WHERE ${where} ORDER BY selected DESC, last_payload IS NOT NULL DESC, topic ASC LIMIT ? OFFSET ?`)
.all(...params) as MqttTopic[];
return { topics, total };
}
export function updateTopic(id: number, data: UpdateTopicData): MqttTopic | undefined {