142 lines
4.1 KiB
TypeScript
142 lines
4.1 KiB
TypeScript
import { ConvexError, v } from "convex/values";
|
|
|
|
import type { Doc } from "./_generated/dataModel";
|
|
import { internalMutation, internalQuery } from "./_generated/server";
|
|
|
|
const artifactKind = v.union(
|
|
v.literal("project_setup"),
|
|
v.literal("summary"),
|
|
v.literal("plan"),
|
|
v.literal("preview"),
|
|
v.literal("blocker"),
|
|
v.literal("report")
|
|
);
|
|
|
|
const buildStatus = v.union(
|
|
v.literal("pending"),
|
|
v.literal("building"),
|
|
v.literal("ready"),
|
|
v.literal("failed")
|
|
);
|
|
|
|
/**
|
|
* Create an ArtifactBuild keyed idempotently by
|
|
* `flow:<flowRunId>:artifact:<kind>:revision:<revision>` (per contract). The
|
|
* build records the typed source manifest; the rendered artifact + storage
|
|
* upload are linked via {@link markReady}.
|
|
*/
|
|
export const create = internalMutation({
|
|
args: {
|
|
artifactKind,
|
|
flowRunId: v.optional(v.id("flowRuns")),
|
|
idempotencyKey: v.string(),
|
|
organizationId: v.id("organizations"),
|
|
projectId: v.optional(v.id("projects")),
|
|
sourceManifest: v.any(),
|
|
threadId: v.optional(v.id("threads")),
|
|
workId: v.optional(v.id("works")),
|
|
},
|
|
handler: async (ctx, args): Promise<Doc<"artifactBuilds">> => {
|
|
// Idempotency: reuse an existing build for the same flow run + kind.
|
|
if (args.flowRunId) {
|
|
const existing = await ctx.db
|
|
.query("artifactBuilds")
|
|
.withIndex("by_organizationId_and_flow_run", (q) =>
|
|
q
|
|
.eq("organizationId", args.organizationId)
|
|
.eq("flowRunId", args.flowRunId)
|
|
)
|
|
.filter((q) => q.eq(q.field("artifactKind"), args.artifactKind))
|
|
.unique();
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
}
|
|
const timestamp = Date.now();
|
|
const buildId = await ctx.db.insert("artifactBuilds", {
|
|
artifactKind: args.artifactKind,
|
|
createdAt: timestamp,
|
|
flowRunId: args.flowRunId,
|
|
idempotencyKey: args.idempotencyKey,
|
|
organizationId: args.organizationId,
|
|
projectId: args.projectId,
|
|
sourceManifest: args.sourceManifest,
|
|
status: "pending",
|
|
threadId: args.threadId,
|
|
updatedAt: timestamp,
|
|
workId: args.workId,
|
|
});
|
|
const build = await ctx.db.get(buildId);
|
|
if (!build) {
|
|
throw new ConvexError("Artifact build could not be read after insert");
|
|
}
|
|
return build;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Link a finished artifact build to its rendered Convex Storage id and the
|
|
* created artifact row. A ready build is terminal.
|
|
*/
|
|
export const markReady = internalMutation({
|
|
args: {
|
|
artifactId: v.id("artifacts"),
|
|
buildId: v.id("artifactBuilds"),
|
|
outputStorageId: v.optional(v.id("_storage")),
|
|
},
|
|
handler: async (ctx, args): Promise<Doc<"artifactBuilds">> => {
|
|
const build = await ctx.db.get(args.buildId);
|
|
if (!build) {
|
|
throw new ConvexError("Artifact build not found");
|
|
}
|
|
if (build.status === "ready") {
|
|
return build;
|
|
}
|
|
await ctx.db.patch(args.buildId, {
|
|
artifactId: args.artifactId,
|
|
outputStorageId: args.outputStorageId,
|
|
status: "ready",
|
|
updatedAt: Date.now(),
|
|
});
|
|
const updated = await ctx.db.get(args.buildId);
|
|
if (!updated) {
|
|
throw new ConvexError("Artifact build could not be read after update");
|
|
}
|
|
return updated;
|
|
},
|
|
});
|
|
|
|
export const markFailed = internalMutation({
|
|
args: {
|
|
buildId: v.id("artifactBuilds"),
|
|
error: v.string(),
|
|
},
|
|
handler: async (ctx, args): Promise<Doc<"artifactBuilds">> => {
|
|
const build = await ctx.db.get(args.buildId);
|
|
if (!build) {
|
|
throw new ConvexError("Artifact build not found");
|
|
}
|
|
if (build.status === "ready") {
|
|
return build;
|
|
}
|
|
await ctx.db.patch(args.buildId, {
|
|
lastError: args.error,
|
|
status: "failed",
|
|
updatedAt: Date.now(),
|
|
});
|
|
const updated = await ctx.db.get(args.buildId);
|
|
if (!updated) {
|
|
throw new ConvexError("Artifact build could not be read after update");
|
|
}
|
|
return updated;
|
|
},
|
|
});
|
|
|
|
export const get = internalQuery({
|
|
args: { buildId: v.id("artifactBuilds") },
|
|
handler: async (ctx, args): Promise<Doc<"artifactBuilds"> | null> =>
|
|
await ctx.db.get(args.buildId),
|
|
});
|
|
|
|
export { buildStatus };
|