feat: wire convex data foundations

This commit is contained in:
2026-06-04 10:30:05 +02:00
parent df7a955736
commit 011e35cb17
62 changed files with 6335 additions and 14 deletions

170
convex/runs.ts Normal file
View File

@@ -0,0 +1,170 @@
import { v } from "convex/values";
import { normalizeListLimit } from "./domain";
import { mutation, query } from "./_generated/server";
const runType = v.union(
v.literal("campaign"),
v.literal("lead_discovery"),
v.literal("audit"),
v.literal("outreach"),
v.literal("lifecycle"),
);
const runStatus = v.union(
v.literal("pending"),
v.literal("running"),
v.literal("succeeded"),
v.literal("failed"),
v.literal("canceled"),
);
const eventLevel = v.union(
v.literal("info"),
v.literal("warning"),
v.literal("error"),
);
export const create = mutation({
args: {
type: runType,
campaignId: v.optional(v.id("campaigns")),
leadId: v.optional(v.id("leads")),
auditId: v.optional(v.id("audits")),
status: v.optional(runStatus),
currentStep: v.optional(v.string()),
},
handler: async (ctx, args) => {
const now = Date.now();
return await ctx.db.insert("agentRuns", {
...args,
status: args.status ?? "pending",
counters: {
leadsFound: 0,
leadsCreated: 0,
auditsCreated: 0,
outreachPrepared: 0,
errors: 0,
},
createdAt: now,
updatedAt: now,
});
},
});
export const updateStatus = mutation({
args: {
id: v.id("agentRuns"),
status: runStatus,
currentStep: v.optional(v.string()),
errorSummary: v.optional(v.string()),
},
handler: async (ctx, args) => {
const now = Date.now();
const patch: {
status: typeof args.status;
updatedAt: number;
currentStep?: string;
errorSummary?: string;
startedAt?: number;
finishedAt?: number;
} = {
status: args.status,
updatedAt: now,
};
if (args.currentStep !== undefined) {
patch.currentStep = args.currentStep;
}
if (args.errorSummary !== undefined) {
patch.errorSummary = args.errorSummary;
}
if (args.status === "running") {
patch.startedAt = now;
}
if (
args.status === "succeeded" ||
args.status === "failed" ||
args.status === "canceled"
) {
patch.finishedAt = now;
}
await ctx.db.patch(args.id, patch);
return args.id;
},
});
export const list = query({
args: {
status: v.optional(runStatus),
type: v.optional(runType),
limit: v.optional(v.number()),
},
handler: async (ctx, args) => {
const limit = normalizeListLimit(args.limit);
if (args.type && args.status) {
const type = args.type;
const status = args.status;
return await ctx.db
.query("agentRuns")
.withIndex("by_type_and_status", (q) =>
q.eq("type", type).eq("status", status),
)
.order("desc")
.take(limit);
}
if (args.status) {
const status = args.status;
return await ctx.db
.query("agentRuns")
.withIndex("by_status", (q) => q.eq("status", status))
.order("desc")
.take(limit);
}
return await ctx.db.query("agentRuns").order("desc").take(limit);
},
});
export const appendEvent = mutation({
args: {
runId: v.id("agentRuns"),
level: eventLevel,
message: v.string(),
details: v.optional(
v.array(
v.object({
label: v.string(),
value: v.string(),
source: v.optional(v.string()),
}),
),
),
},
handler: async (ctx, args) => {
return await ctx.db.insert("agentRunEvents", {
...args,
createdAt: Date.now(),
});
},
});
export const listEvents = query({
args: {
runId: v.id("agentRuns"),
limit: v.optional(v.number()),
},
handler: async (ctx, args) => {
const limit = normalizeListLimit(args.limit);
return await ctx.db
.query("agentRunEvents")
.withIndex("by_runId_and_createdAt", (q) => q.eq("runId", args.runId))
.order("desc")
.take(limit);
},
});