From 1efaa1edf85438f64663aa1866a25eb7a9b0d9f0 Mon Sep 17 00:00:00 2001 From: Lee Danilek Date: Wed, 20 Nov 2024 11:32:30 -0500 Subject: [PATCH] nice error on nested triggers --- .../convex-helpers/server/triggers.test.ts | 28 +++++++++++++++++++ packages/convex-helpers/server/triggers.ts | 10 +++++++ 2 files changed, 38 insertions(+) diff --git a/packages/convex-helpers/server/triggers.test.ts b/packages/convex-helpers/server/triggers.test.ts index 4028ad23..9c6fb371 100644 --- a/packages/convex-helpers/server/triggers.test.ts +++ b/packages/convex-helpers/server/triggers.test.ts @@ -48,9 +48,20 @@ export const createUser = mutation({ }, }); +export const incorrectMutationCallingOtherMutation = mutation({ + args: { firstName: v.string(), lastName: v.string() }, + handler: async (ctx, args) => { + // createUser is a mutation so you aren't supposed to call it like this. + // But if you happen to do it anyway, we should throw an informative error. + const id = await createUser(ctx, args); + return id; + }, +}); + const testApi: ApiFromModules<{ fns: { createUser: typeof createUser; + incorrectMutationCallingOtherMutation: typeof incorrectMutationCallingOtherMutation; }; }>["fns"] = anyApi["triggers.test"] as any; @@ -65,3 +76,20 @@ test("trigger denormalizes field", async () => { expect(user!.fullName).toStrictEqual("John Doe"); }); }); + +test("incorrect mutation calling other mutation", async () => { + const t = convexTest(schema, modules); + await expect(t.mutation(testApi.incorrectMutationCallingOtherMutation, { + firstName: "John", + lastName: "Doe", + }), + ).rejects.toThrow( + new RegExp( + `Triggers\\.wrapDB called multiple times in a single mutation\\.\\s+` + + `Not allowed due to potential deadlock\\.\\s+` + + `Call it once in a single \`customMutation\`\\.\\s+` + + `Do not call mutations directly as functions\\.\\s+` + + `See https:\\/\\/docs\\.convex\\.dev\\/production\\/best-practices\\/#use-helper-functions-to-write-shared-code`, + ), + ); +}); diff --git a/packages/convex-helpers/server/triggers.ts b/packages/convex-helpers/server/triggers.ts index 4da55af3..1248eed3 100644 --- a/packages/convex-helpers/server/triggers.ts +++ b/packages/convex-helpers/server/triggers.ts @@ -90,6 +90,15 @@ export class Triggers< } wrapDB = (ctx: C): C => { + if (wrapDBCalled) { + throw new Error( + `Triggers.wrapDB called multiple times in a single mutation. Not allowed due to potential deadlock. + Call it once in a single \`customMutation\`. + Do not call mutations directly as functions. + See https://docs.convex.dev/production/best-practices/#use-helper-functions-to-write-shared-code`, + ); + } + wrapDBCalled = true; return { ...ctx, db: new DatabaseWriterWithTriggers(ctx, ctx.db, this) }; }; } @@ -148,6 +157,7 @@ class Lock { let innerWriteLock = new Lock(); let outerWriteLock = new Lock(); const triggerQueue: (() => Promise)[] = []; +let wrapDBCalled = false; export class DatabaseWriterWithTriggers< DataModel extends GenericDataModel,