Exercise 2: Merging Two Tags — Possible Solution ================================================== async function mergeTags(fromName: string, intoName: string) { if (fromName === intoName) throw new Error("Can't merge a tag into itself"); return prisma.$transaction(async (tx) => { const from = await tx.tag.findUnique({ where: { name: fromName }, include: { posts: { select: { id: true } } }, }); const into = await tx.tag.findUnique({ where: { name: intoName } }); if (!from || !into) throw new Error("Both tags must exist"); // Add the target tag to every post that had the old one. // connect is a no-op for posts that already have it. for (const post of from.posts) { await tx.post.update({ where: { id: post.id }, data: { tags: { connect: { id: into.id } } }, }); } // Deleting the tag removes its links from the implicit join table await tx.tag.delete({ where: { id: from.id } }); return from.posts.length; }); } // Usage const moved = await mergeTags("js", "javascript"); console.log(`Moved ${moved} posts`); WHY THIS WORKS AS AN ANSWER ------------------------------ The later steps depend on what the first queries find (which posts have the old tag, and whether both tags exist), so this needs an interactive transaction rather than the batch form. Every query uses tx, not prisma. Throwing when a tag is missing rolls back anything already done, so a half-finished merge is impossible. The loop is fine for a small blog; for thousands of posts you'd use raw SQL on the join table instead (Chapter 3) to keep the transaction short.