54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import type { LiveQueryEvent, QueryRow } from "@/lib/types";
|
|
|
|
/** A live stream row; `key` is a client-side monotonic counter (SSE frames carry no id). */
|
|
export interface LiveRow extends LiveQueryEvent {
|
|
key: number;
|
|
}
|
|
|
|
export const RING_CAPACITY = 500;
|
|
|
|
/** Prepend `row` (rows are newest-first) and drop the oldest beyond `capacity`. */
|
|
export function pushRow(rows: LiveRow[], row: LiveRow, capacity: number = RING_CAPACITY): LiveRow[] {
|
|
const next = [row, ...rows];
|
|
return next.length > capacity ? next.slice(0, capacity) : next;
|
|
}
|
|
|
|
// `since` on GET /api/queries is inclusive, so the re-sync fetch returns the
|
|
// last-seen row(s) again; live rows have no id, so identity is this tuple.
|
|
function signature(row: LiveQueryEvent): string {
|
|
return `${row.ts}|${row.domain}|${row.client_ip}|${row.qtype ?? -1}|${row.blocked}|${row.upstream}`;
|
|
}
|
|
|
|
/**
|
|
* Merge rows fetched for a reconnect gap (newest-first, from GET /api/queries)
|
|
* into the buffer. Rows already present are skipped; `missed` counts what was
|
|
* actually added. The result stays newest-first (stable sort by ts) and capped.
|
|
*/
|
|
export function mergeGap(
|
|
rows: LiveRow[],
|
|
fetched: QueryRow[],
|
|
nextKey: () => number,
|
|
capacity: number = RING_CAPACITY,
|
|
): { rows: LiveRow[]; missed: number } {
|
|
const seen = new Set(rows.map(signature));
|
|
const added: LiveRow[] = [];
|
|
for (const row of fetched) {
|
|
const event: LiveQueryEvent = {
|
|
ts: row.ts,
|
|
domain: row.domain,
|
|
client_ip: row.client_ip,
|
|
qtype: row.qtype,
|
|
blocked: row.blocked,
|
|
block_reason: row.block_reason,
|
|
response_time_us: row.response_time_us,
|
|
cache_hit: row.cache_hit,
|
|
upstream: row.upstream,
|
|
};
|
|
if (seen.has(signature(event))) continue;
|
|
added.push({ ...event, key: nextKey() });
|
|
}
|
|
if (added.length === 0) return { rows, missed: 0 };
|
|
const merged = [...added, ...rows].sort((a, b) => b.ts - a.ts).slice(0, capacity);
|
|
return { rows: merged, missed: added.length };
|
|
}
|