evOo-local/apps/api/src/bot-update.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

65 lines
2.0 KiB
TypeScript

import { appendEventInTransaction, type Prisma, type PrismaClient } from "@rakazo/db";
import { getLogger } from "@rakazo/logging";
type AppendEvent = typeof appendEventInTransaction;
/**
* Persist a bot row. When profile labels change, write `bot.updated` in the same
* transaction so clients never observe a successful rename without a durable event.
* Realtime notify stays best-effort after commit.
*/
export async function commitBotUpdate(
options: {
prisma: PrismaClient;
notify: (threadId: string, seq: number) => Promise<void>;
spaceId: string;
threadId: string;
botId: string;
data: Prisma.BotUncheckedUpdateInput;
emitBotUpdated: boolean;
},
appendEvent: AppendEvent = appendEventInTransaction,
): Promise<{ id: string; name: string; title: string; description: string }> {
if (!options.emitBotUpdated) {
return options.prisma.bot.update({
where: { id: options.botId },
data: options.data,
select: { id: true, name: true, title: true, description: true },
});
}
const committed = await options.prisma.$transaction(async (tx) => {
const updated = await tx.bot.update({
where: { id: options.botId },
data: options.data,
select: { id: true, name: true, title: true, description: true },
});
const event = await appendEvent(tx, {
spaceId: options.spaceId,
threadId: options.threadId,
botId: options.botId,
type: "bot.updated",
payload: {
botId: updated.id,
name: updated.name,
title: updated.title,
description: updated.description,
},
});
return { updated, seq: event.seq };
});
await options.notify(options.threadId, committed.seq).catch((error) => {
getLogger().error("bot.updated realtime notification", error);
});
return committed.updated;
}
export function botProfileLabelsChanged(input: {
name?: unknown;
title?: unknown;
description?: unknown;
}): boolean {
return input.name !== undefined || input.title !== undefined || input.description !== undefined;
}