/** * Drizzle Change Set Repository Adapter * * Implements ChangeSetRepository interface using Drizzle ORM. */ import { eq, desc, asc, and } from 'drizzle-orm'; import { nanoid } from 'nanoid'; import type { DrizzleDatabase } from '../../index.js'; import { changeSets, changeSetEntries, type ChangeSet } from '../../schema.js'; import type { ChangeSetRepository, CreateChangeSetData, CreateChangeSetEntryData, ChangeSetWithEntries, } from '../change-set-repository.js'; export class DrizzleChangeSetRepository implements ChangeSetRepository { constructor(private db: DrizzleDatabase) {} async createWithEntries(data: CreateChangeSetData, entries: CreateChangeSetEntryData[]): Promise { const id = nanoid(); const now = new Date(); // Use transaction for atomicity return this.db.transaction(async (tx) => { const [created] = await tx.insert(changeSets).values({ id, agentId: data.agentId, agentName: data.agentName, initiativeId: data.initiativeId, mode: data.mode, summary: data.summary ?? null, status: 'applied', createdAt: now, }).returning(); if (entries.length > 0) { const entryRows = entries.map((e, i) => ({ id: nanoid(), changeSetId: id, entityType: e.entityType, entityId: e.entityId, action: e.action, previousState: e.previousState ?? null, newState: e.newState ?? null, sortOrder: e.sortOrder ?? i, createdAt: now, })); await tx.insert(changeSetEntries).values(entryRows); } return created; }); } async findById(id: string): Promise { const result = await this.db .select() .from(changeSets) .where(eq(changeSets.id, id)) .limit(1); return result[0] ?? null; } async findByIdWithEntries(id: string): Promise { const cs = await this.findById(id); if (!cs) return null; const entries = await this.db .select() .from(changeSetEntries) .where(eq(changeSetEntries.changeSetId, id)) .orderBy(asc(changeSetEntries.sortOrder)); return { ...cs, entries }; } async findByInitiativeId(initiativeId: string): Promise { return this.db .select() .from(changeSets) .where(eq(changeSets.initiativeId, initiativeId)) .orderBy(desc(changeSets.createdAt)); } async findByAgentId(agentId: string): Promise { return this.db .select() .from(changeSets) .where(eq(changeSets.agentId, agentId)) .orderBy(desc(changeSets.createdAt)); } async findAppliedByCreatedEntity(entityType: string, entityId: string): Promise { // Find changeset entries matching the entity const matchingEntries = await this.db .select({ changeSetId: changeSetEntries.changeSetId }) .from(changeSetEntries) .where( and( eq(changeSetEntries.entityType, entityType as any), eq(changeSetEntries.entityId, entityId), eq(changeSetEntries.action, 'create'), ), ); const results: ChangeSetWithEntries[] = []; const seen = new Set(); for (const { changeSetId } of matchingEntries) { if (seen.has(changeSetId)) continue; seen.add(changeSetId); const cs = await this.findByIdWithEntries(changeSetId); if (cs && cs.status === 'applied') { results.push(cs); } } return results; } async markReverted(id: string): Promise { const [updated] = await this.db .update(changeSets) .set({ status: 'reverted', revertedAt: new Date() }) .where(eq(changeSets.id, id)) .returning(); if (!updated) { throw new Error(`ChangeSet not found: ${id}`); } return updated; } }