Overview
The QueryService class provides high-level queries over the local JSONL mirror for sessions, timelines, contributors, and file diffs.
Class: QueryService
Constructor
constructor(rootDir: string = ".codaph")
Root directory of the mirror
Methods
listSessions
Lists all captured sessions for a repository.
async listSessions(repoId: string): Promise<SessionSummary[]>
Earliest event timestamp (ISO 8601)
Latest event timestamp (ISO 8601)
Number of threads in session
Returns: Sessions sorted by last activity (most recent first)
listContributors
Lists all contributors (actors) for a repository.
async listContributors(
repoId: string,
sessionId?: string
): Promise<ContributorSummary[]>
Filter to specific session
Actor/contributor identifier
First contribution timestamp
Most recent contribution timestamp
Total events by this actor
Number of sessions by this actor
Returns: Contributors sorted by event count (most active first)
getTimeline
Retrieves filtered timeline of events.
async getTimeline(
filter: TimelineFilter
): Promise<CapturedEventEnvelope[]>
Filter to specific session
Filter to specific thread
Start timestamp (inclusive)
End timestamp (inclusive)
Filter to specific item type (e.g., “reasoning”, “agent_message”)
Returns: Chronologically sorted events matching filter
getTimelineStream
Streams timeline events for large result sets.
async *getTimelineStream(
filter: TimelineFilter
): AsyncGenerator<CapturedEventEnvelope>
Yields: Individual events
getDiffSummary
Extracts file change summary from a session.
async getDiffSummary(
repoId: string,
sessionId: string,
pathFilter?: string
): Promise<FileDiffSummary[]>
Filter to files matching path pattern
kind
'add' | 'delete' | 'update'
Change type
Supporting Types
TimelineFilter
interface TimelineFilter {
repoId: string;
sessionId?: string;
threadId?: string;
actorId?: string;
from?: string;
to?: string;
itemType?: string;
}
SessionSummary
interface SessionSummary {
sessionId: string;
from: string;
to: string;
eventCount: number;
threadCount: number;
}
ContributorSummary
interface ContributorSummary {
actorId: string;
from: string;
to: string;
eventCount: number;
sessionCount: number;
}
Usage Examples
List Recent Sessions
import { QueryService } from './lib/query-service';
import { repoIdFromPath } from './lib/core-types';
const query = new QueryService('.codaph');
const repoId = repoIdFromPath(process.cwd());
const sessions = await query.listSessions(repoId);
console.log('Recent sessions:');
for (const session of sessions.slice(0, 5)) {
console.log(` ${session.sessionId}`);
console.log(` ${session.eventCount} events, ${session.threadCount} threads`);
console.log(` ${session.from} → ${session.to}`);
}
Query Timeline with Filters
const events = await query.getTimeline({
repoId,
sessionId: 'session-abc-123',
itemType: 'reasoning',
from: '2026-03-01T00:00:00Z',
});
console.log(`Found ${events.length} reasoning events`);
for (const event of events) {
const item = event.payload.item as { text?: string };
console.log(`[${event.ts}] ${item?.text}`);
}
Stream Large Timelines
for await (const event of query.getTimelineStream({ repoId })) {
if (event.eventType === 'prompt.submitted') {
const prompt = event.payload.prompt as string;
console.log('Prompt:', prompt);
}
}
View Session Diff
const diffs = await query.getDiffSummary(repoId, 'session-abc-123');
console.log('File changes:');
for (const diff of diffs) {
const sign = diff.kind === 'add' ? '+' : diff.kind === 'delete' ? '-' : 'M';
console.log(`${sign} ${diff.path} (+${diff.plus} -${diff.minus})`);
}
List Contributors
const contributors = await query.listContributors(repoId);
console.log('Top contributors:');
for (const contributor of contributors.slice(0, 3)) {
console.log(` ${contributor.actorId}`);
console.log(` ${contributor.eventCount} events across ${contributor.sessionCount} sessions`);
}
Date Range Query
const lastWeek = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const events = await query.getTimeline({
repoId,
from: lastWeek,
actorId: 'alice',
});
console.log(`Alice had ${events.length} events in the last week`);
Sparse Index Usage
The query service leverages sparse indexes to minimize segment reads:
- Session queries: Only read segments containing the session
- Thread queries: Only read segments containing the thread
- Actor queries: Only read segments with actor activity
Memory Usage
For large timelines, use getTimelineStream() to avoid loading all events into memory:
let count = 0;
for await (const event of query.getTimelineStream({ repoId })) {
count++;
if (count % 1000 === 0) {
console.log(`Processed ${count} events...`);
}
}
Filtering
Filters are applied in two stages:
- Segment-level: Uses sparse index to skip irrelevant segments
- Event-level: Filters individual events after loading
For best performance, provide sessionId or threadId when possible.