/** * SummaryRepository - CRUD + get, insert for summaries table (§17) * * Implements Repository per contracts §6. * * @module packages/runtime/src/storage/repositories/SummaryRepository */ import type { Repository, TransactionHandle, SessionID, SummaryID, MessageID, ISOTimeString, } from '@aircoding/contracts' import { DatabaseHandle } from '../MigrationRunner.js' import { assertEnumValues } from '../assertEnum.js' // ============================================================================= // Types - per db-schema §17 // ============================================================================= export type SummaryType = 'compaction' | 'checkpoint' | 'review' | 'other' export interface SummaryRecord { id: SummaryID session_id: SessionID type: SummaryType range_start_message_id?: MessageID range_end_message_id?: MessageID content_json: string created_at: ISOTimeString metadata_json?: string } export type SummaryInsert = Omit & { id?: SummaryID } export type SummaryUpdate = Partial> // ============================================================================= // SummaryRepository // ============================================================================= export class SummaryRepository implements Repository { private db: DatabaseHandle constructor(db: DatabaseHandle) { this.db = db } /** * Get a summary by ID. */ async get(id: SummaryID, tx?: TransactionHandle): Promise { const stmt = (tx?.db ?? this.db).prepare('SELECT * FROM summaries WHERE id = ?') const row = stmt.get(id) as SummaryRecord | undefined return row } /** * Insert a new summary. */ async insert(record: SummaryInsert, tx?: TransactionHandle): Promise { // Validate enum columns assertEnumValues('summaries', { type: record.type, }) const stmt = (tx?.db ?? this.db).prepare(` INSERT INTO summaries ( id, session_id, type, range_start_message_id, range_end_message_id, content_json, created_at, metadata_json ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `) stmt.run( record.id, record.session_id, record.type, record.range_start_message_id ?? null, record.range_end_message_id ?? null, record.content_json, record.created_at, record.metadata_json ?? null, ) } /** * Update an existing summary. */ async update(id: SummaryID, patch: SummaryUpdate, tx?: TransactionHandle): Promise { // Validate enum columns if present if (patch.type !== undefined) { assertEnumValues('summaries', { type: patch.type }) } const fields: string[] = [] const values: unknown[] = [] if (patch.type !== undefined) { fields.push('type = ?') values.push(patch.type) } if (patch.range_start_message_id !== undefined) { fields.push('range_start_message_id = ?') values.push(patch.range_start_message_id) } if (patch.range_end_message_id !== undefined) { fields.push('range_end_message_id = ?') values.push(patch.range_end_message_id) } if (patch.content_json !== undefined) { fields.push('content_json = ?') values.push(patch.content_json) } if (patch.metadata_json !== undefined) { fields.push('metadata_json = ?') values.push(patch.metadata_json) } if (fields.length === 0) { return // Nothing to update } values.push(id) const stmt = (tx?.db ?? this.db).prepare(`UPDATE summaries SET ${fields.join(', ')} WHERE id = ?`) stmt.run(...values) } // ============================================================================= // Extra methods // ============================================================================= /** * Get the most recent summary of a specific type for a session. */ async get_latest(session_id: SessionID, type: SummaryType): Promise { const stmt = this.db.prepare( 'SELECT * FROM summaries WHERE session_id = ? AND type = ? ORDER BY created_at DESC LIMIT 1', ) const row = stmt.get(session_id, type) as SummaryRecord | undefined return row } /** * List all summaries for a session. */ async list_by_session(session_id: SessionID): Promise { const stmt = this.db.prepare( 'SELECT * FROM summaries WHERE session_id = ? ORDER BY created_at DESC', ) return stmt.all(session_id) as SummaryRecord[] } }