fix: sqlite time to number

This commit is contained in:
Cristina Poncela Cubeiro 2026-08-11 10:07:53 +02:00
parent 75c7fd6623
commit a4453b79bb
9 changed files with 23 additions and 33 deletions

View file

@ -10,7 +10,7 @@ export interface SessionSearchOptions {
export interface SessionSearchHit<TMetadata extends SessionMetadata = SessionMetadata> {
metadata: TMetadata;
entryId: string;
timestamp: string;
timestamp: number;
snippet?: string;
score?: number;
}
@ -53,7 +53,7 @@ class ScanningSessionSearch<TMetadata extends SessionMetadata = SessionMetadata>
hits.push({
metadata,
entryId: entry.id,
timestamp: new Date(entry.timestamp).toISOString(),
timestamp: entry.timestamp,
snippet: payload,
});
}

View file

@ -1,6 +1,6 @@
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
created_at TEXT NOT NULL,
created_at INTEGER NOT NULL,
cwd TEXT NOT NULL,
parent_session_id TEXT NULL,
metadata TEXT NULL
@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS entries (
id TEXT NOT NULL,
parent_id TEXT NULL,
type TEXT NOT NULL,
timestamp TEXT NOT NULL,
timestamp INTEGER NOT NULL,
payload TEXT NOT NULL,
PRIMARY KEY (session_id, id),
UNIQUE (session_id, seq)
@ -71,7 +71,7 @@ CREATE TABLE IF NOT EXISTS records (
run_id TEXT NULL,
type TEXT NOT NULL,
op_kind TEXT NULL,
timestamp TEXT NOT NULL,
timestamp INTEGER NOT NULL,
payload TEXT NOT NULL,
PRIMARY KEY (session_id, id),
UNIQUE (session_id, seq)

View file

@ -175,14 +175,6 @@ function configureSqliteDatabase(db: SqliteDatabase): void {
sql`PRAGMA busy_timeout=5000`.exec(db);
}
function timestampToText(timestamp: number): string {
return new Date(timestamp).toISOString();
}
function timestampFromText(timestamp: string): number {
return Date.parse(timestamp);
}
function entryRowFromCached(row: CachedBranchEntryRow): EntryRow {
return { ...row, seq: row.entry_seq, type: row.type as Entry["type"] };
}
@ -198,9 +190,7 @@ function readObjectPayload(row: EntryRow): Record<string, unknown> {
function decodeEntry(row: EntryRow): Entry {
try {
const payload = readObjectPayload(row);
const timestamp = timestampFromText(row.timestamp);
if (!Number.isFinite(timestamp)) throw new Error(`Invalid timestamp ${row.timestamp}`);
const base = { id: row.id, seq: row.seq, parentId: row.parent_id, timestamp };
const base = { id: row.id, seq: row.seq, parentId: row.parent_id, timestamp: row.timestamp };
switch (row.type) {
case "message":
if (typeof payload.message !== "object" || payload.message === null) throw new Error("Missing message");
@ -283,14 +273,12 @@ function recordOpKind(record: NewRecord): string | undefined {
return record.type === "operation_started" ? record.intent.kind : undefined;
}
function decodeRecord(row: { seq: number; timestamp: string; payload: string }): LaneRecord {
function decodeRecord(row: { seq: number; timestamp: number; payload: string }): LaneRecord {
try {
const timestamp = timestampFromText(row.timestamp);
if (!Number.isFinite(timestamp)) throw new Error(`Invalid timestamp ${row.timestamp}`);
return {
...(JSON.parse(row.payload) as object),
seq: row.seq,
timestamp,
timestamp: row.timestamp,
} as LaneRecord;
} catch (error) {
throw new SessionError(
@ -476,7 +464,7 @@ class SqliteSessionStorage implements SessionStorage<SqliteSessionMetadata> {
id: committed.id,
parentId: committed.parentId,
type: committed.type,
timestamp: timestampToText(committed.timestamp),
timestamp: committed.timestamp,
payload: JSON.stringify(entryPayload(committed)),
});
setLaneLeaf(this.db, this.metadata.id, lane, committed.id);
@ -514,7 +502,7 @@ class SqliteSessionStorage implements SessionStorage<SqliteSessionMetadata> {
runId: recordRunId(record),
type: record.type,
opKind: recordOpKind(record),
timestamp: timestampToText(committed.timestamp),
timestamp: committed.timestamp,
payload: JSON.stringify(record),
});
if (record.type === "operation_finished") {
@ -739,7 +727,7 @@ export class SqliteSessionRepository
const lease = db.transaction(() => {
insertSessionRow(db, {
id,
createdAt: timestampToText(createdAt),
createdAt,
cwd: options.cwd,
parentSessionId: options.parentSessionId,
metadata: options.metadata,
@ -870,7 +858,7 @@ export class SqliteSessionRepository
lease = db.transaction(() => {
insertSessionRow(db, {
id,
createdAt: timestampToText(createdAt),
createdAt,
cwd: options.cwd,
parentSessionId: options.parentSessionId ?? source.id,
metadata,

View file

@ -121,7 +121,7 @@ class SqliteSessionSearch implements SessionSearch<SqliteSessionMetadata> {
WHERE f.session_id = s.id AND f.kind = 'name' AND f.key IS NULL
)
WHERE session_search_fts MATCH ${query} AND (${cwd} IS NULL OR s.cwd = ${cwd})
ORDER BY score`.all<SessionRow & { entry_id: string; timestamp: string; score: number }>(db);
ORDER BY score`.all<SessionRow & { entry_id: string; timestamp: number; score: number }>(db);
const path = await this.getDatabasePath();
return rows.map((row) => ({
metadata: decodeSessionMetadata(row, path),

View file

@ -14,7 +14,7 @@ export interface CachedBranchEntryRow {
entry_seq: number;
parent_id: string | null;
type: Entry["type"];
timestamp: string;
timestamp: number;
payload: string;
}

View file

@ -8,7 +8,7 @@ export interface EntryRow {
id: string;
parent_id: string | null;
type: Entry["type"];
timestamp: string;
timestamp: number;
payload: string;
}
@ -17,7 +17,7 @@ export interface NewEntryRow {
id: string;
parentId: string | null;
type: Entry["type"];
timestamp: string;
timestamp: number;
payload: string;
}

View file

@ -10,7 +10,7 @@ export interface RecordRow {
run_id: string | null;
type: string;
op_kind: string | null;
timestamp: string;
timestamp: number;
payload: string;
}
@ -21,7 +21,7 @@ export interface NewRecordRow {
runId?: string;
type: string;
opKind?: string;
timestamp: string;
timestamp: number;
payload: string;
}

View file

@ -4,7 +4,7 @@ import type { SqliteDatabase, SqliteSessionMetadata } from "../types.ts";
export interface SessionRow {
id: string;
created_at: string;
created_at: number;
metadata: string | null;
cwd: string;
parent_session_id: string | null;
@ -14,7 +14,7 @@ export interface SessionRow {
export interface NewSessionRow {
id: string;
createdAt: string;
createdAt: number;
cwd: string;
parentSessionId?: string;
metadata?: Record<string, unknown>;
@ -121,7 +121,7 @@ export function decodeSessionMetadata(row: SessionRow, path: string): SqliteSess
const name = row.has_session_name === 0 ? undefined : parseSessionName(row.session_name, row.id);
return {
id: row.id,
createdAt: Date.parse(row.created_at),
createdAt: row.created_at,
...(name === undefined ? {} : { name }),
cwd: row.cwd,
path,

View file

@ -30,8 +30,10 @@ describe("SQLite FTS5 session search", () => {
await expect(search.search({ text: "auth", cwd: root })).resolves.toEqual([
expect.objectContaining({
entryId,
timestamp: expect.any(Number),
metadata: expect.objectContaining({
id: "included",
createdAt: expect.any(Number),
name: "Canonical name",
metadata: { name: "application-owned" },
}),