Skip to main content

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")
rootDir
string
default:".codaph"
Root directory of the mirror

Methods

listSessions

Lists all captured sessions for a repository.
async listSessions(repoId: string): Promise<SessionSummary[]>
repoId
string
required
Repository ID
sessionId
string
Session UUID
from
string
Earliest event timestamp (ISO 8601)
to
string
Latest event timestamp (ISO 8601)
eventCount
number
Total events in session
threadCount
number
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[]>
repoId
string
required
Repository ID
sessionId
string
Filter to specific session
actorId
string
Actor/contributor identifier
from
string
First contribution timestamp
to
string
Most recent contribution timestamp
eventCount
number
Total events by this actor
sessionCount
number
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.repoId
string
required
Repository ID
filter.sessionId
string
Filter to specific session
filter.threadId
string
Filter to specific thread
filter.actorId
string
Filter to specific actor
filter.from
string
Start timestamp (inclusive)
filter.to
string
End timestamp (inclusive)
filter.itemType
string
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>
filter
TimelineFilter
required
Timeline filter
Yields: Individual events

getDiffSummary

Extracts file change summary from a session.
async getDiffSummary(
  repoId: string,
  sessionId: string,
  pathFilter?: string
): Promise<FileDiffSummary[]>
repoId
string
required
Repository ID
sessionId
string
required
Session ID
pathFilter
string
Filter to files matching path pattern
path
string
File path
plus
number
Lines added
minus
number
Lines removed
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`);

Performance Considerations

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:
  1. Segment-level: Uses sparse index to skip irrelevant segments
  2. Event-level: Filters individual events after loading
For best performance, provide sessionId or threadId when possible.

Build docs developers (and LLMs) love