Files
nxdns/admin/src/features/provenance/querySummary.ts
T
mokhtar cc23c97218
Gates / frontend (push) Successful in 1m34s
Gates / test (push) Successful in 2m3s
Gates / test-aarch64 (push) Failing after 3h13m33s
Gates / package (push) Successful in 5m20s
Gates / container (push) Successful in 15s
CI / gates (push) Failing after 6h30m45s
milestone 33: contract closure — samples, file-authority enumeration, dead code, bundle ceiling
2026-08-22 23:31:37 +02:00

89 lines
2.6 KiB
TypeScript

import type { LiveQueryEvent, PolicyReason, QueryRow, RouteKind } from "@/lib/types";
/**
* What the query-log table renders for one row, whichever surface it came from.
*
* The stored list row and the live stream's provenance event describe the same
* query in two different shapes — flat summary against nested full detail — and
* both pages share one set of cells, so both project into this.
*
* `id` is null for a streamed event: the frame precedes its own insert, so no
* row exists to link to yet.
*/
export interface QuerySummary {
id: number | null;
ts: number;
domain: string;
client_ip: string;
qtype: number | null;
blocked: boolean;
policy_reason: PolicyReason;
/** The twelve-bit extended code the client saw, including a synthesized SERVFAIL. */
rcode: number;
route_kind: RouteKind;
response_time_us: number | null;
cache_hit: boolean | null;
upstream: string;
}
/**
* Whether the cache answered, or null where it never applied. Mirrors
* `Context.cacheHit` in src/server/handler.zig, which derives the stored
* `cache_hit` column from the same route: a local record, a blocked answer and
* a protocol refusal all bypass the cache, and "miss" would claim a lookup that
* never happened.
*/
export function cacheHitFor(kind: RouteKind): boolean | null {
switch (kind) {
case "cache":
return true;
case "upstream":
case "forward_zone":
return false;
case "local":
case "blocked":
case "rejected":
return null;
}
}
export function summarizeRow(row: QueryRow): QuerySummary {
return {
id: row.id,
ts: row.ts,
domain: row.domain,
client_ip: row.client_ip,
qtype: row.qtype,
blocked: row.blocked,
policy_reason: row.policy_reason,
rcode: row.rcode,
route_kind: row.route_kind,
response_time_us: row.response_time_us,
cache_hit: row.cache_hit,
upstream: row.upstream,
};
}
/**
* The same summary out of a live frame. `blocked` and `cache_hit` are derived
* rather than sent: the server derives the stored columns from exactly these
* two fields (handler.zig's `Entry.init` call), so the projection reproduces
* them instead of the DTO carrying the same fact twice.
*/
export function summarizeEvent(event: LiveQueryEvent): QuerySummary {
return {
id: null,
ts: event.request.time,
domain: event.request.domain,
client_ip: event.request.client,
qtype: event.request.qtype,
blocked: event.policy.action === "block",
policy_reason: event.policy.reason,
rcode: event.response.rcode,
route_kind: event.route.kind,
response_time_us: event.response.duration_us,
cache_hit: cacheHitFor(event.route.kind),
upstream: event.route.upstream,
};
}