From 279eccc55a2960e1bc8aef1f05bbf25083180804 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 01:47:21 +0000 Subject: [PATCH 1/8] Add finally callback to customFunction Co-Authored-By: Ian Macartney --- .../server/customFunctions.test.ts | 61 +++++++++++++++++++ .../convex-helpers/server/customFunctions.ts | 32 +++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/packages/convex-helpers/server/customFunctions.test.ts b/packages/convex-helpers/server/customFunctions.test.ts index 8b3156ce..dc596b8a 100644 --- a/packages/convex-helpers/server/customFunctions.test.ts +++ b/packages/convex-helpers/server/customFunctions.test.ts @@ -379,6 +379,10 @@ const testApi: ApiFromModules<{ create: typeof create; outerAdds: typeof outerAdds; outerRemoves: typeof outerRemoves; + successFn: typeof successFn; + errorFn: typeof errorFn; + mutationFn: typeof mutationFn; + actionFn: typeof actionFn; }; }>["fns"] = anyApi["customFunctions.test"] as any; @@ -560,3 +564,60 @@ describe("nested custom functions", () => { ).rejects.toThrow("Validator error: Expected `string`"); }); }); + +describe("finally callback", () => { + test("finally callback is called with result and context", async () => { + const finallyMock = vi.fn(); + const ctx = { foo: "bar" }; + const args = { test: "value" }; + const result = { success: true }; + + const handler = async () => result; + + const mod = { + args: {}, + input: async () => ({ ctx: {}, args: {} }), + finally: finallyMock + }; + + let actualResult; + let actualError; + try { + actualResult = await handler(ctx, args); + } catch (e) { + actualError = e; + throw e; + } finally { + if (mod.finally) { + await mod.finally({ ctx, result: actualResult, error: actualError }); + } + } + + expect(finallyMock).toHaveBeenCalledWith({ + ctx, + result, + error: undefined + }); + + finallyMock.mockClear(); + const testError = new Error("Test error"); + const errorHandler = async () => { + throw testError; + }; + + try { + await errorHandler(); + } catch (e) { + } finally { + if (mod.finally) { + await mod.finally({ ctx, result: undefined, error: testError }); + } + } + + expect(finallyMock).toHaveBeenCalledWith({ + ctx, + result: undefined, + error: testError + }); + }); +}); diff --git a/packages/convex-helpers/server/customFunctions.ts b/packages/convex-helpers/server/customFunctions.ts index 3e24bd26..e12fcc82 100644 --- a/packages/convex-helpers/server/customFunctions.ts +++ b/packages/convex-helpers/server/customFunctions.ts @@ -60,6 +60,7 @@ export type Mod< ) => | Promise<{ ctx: ModCtx; args: ModMadeArgs }> | { ctx: ModCtx; args: ModMadeArgs }; + finally?: (params: { ctx: Ctx & ModCtx; result?: any; error?: any }) => void | Promise; }; /** @@ -88,6 +89,7 @@ export const NoOp = { input() { return { args: {}, ctx: {} }; }, + finally() {}, }; /** @@ -339,7 +341,20 @@ function customFnBuilder( pick(allArgs, Object.keys(inputArgs)) as any, ); const args = omit(allArgs, Object.keys(inputArgs)); - return handler({ ...ctx, ...added.ctx }, { ...args, ...added.args }); + const finalCtx = { ...ctx, ...added.ctx }; + let result; + let error; + try { + result = await handler(finalCtx, { ...args, ...added.args }); + return result; + } catch (e) { + error = e; + throw e; + } finally { + if (mod.finally) { + await mod.finally({ ctx: finalCtx, result, error }); + } + } }, }); } @@ -353,7 +368,20 @@ function customFnBuilder( returns: fn.returns, handler: async (ctx: any, args: any) => { const added = await inputMod(ctx, args); - return handler({ ...ctx, ...added.ctx }, { ...args, ...added.args }); + const finalCtx = { ...ctx, ...added.ctx }; + let result; + let error; + try { + result = await handler(finalCtx, { ...args, ...added.args }); + return result; + } catch (e) { + error = e; + throw e; + } finally { + if (mod.finally) { + await mod.finally({ ctx: finalCtx, result, error }); + } + } }, }); }; From e402a8dab102f3fb76e457286dc991f110c784fc Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 01:50:42 +0000 Subject: [PATCH 2/8] Fix TypeScript errors in test file Co-Authored-By: Ian Macartney --- .../server/customFunctions.test.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/convex-helpers/server/customFunctions.test.ts b/packages/convex-helpers/server/customFunctions.test.ts index dc596b8a..f559caa9 100644 --- a/packages/convex-helpers/server/customFunctions.test.ts +++ b/packages/convex-helpers/server/customFunctions.test.ts @@ -379,10 +379,7 @@ const testApi: ApiFromModules<{ create: typeof create; outerAdds: typeof outerAdds; outerRemoves: typeof outerRemoves; - successFn: typeof successFn; - errorFn: typeof errorFn; - mutationFn: typeof mutationFn; - actionFn: typeof actionFn; + }; }>["fns"] = anyApi["customFunctions.test"] as any; @@ -567,7 +564,9 @@ describe("nested custom functions", () => { describe("finally callback", () => { test("finally callback is called with result and context", async () => { - const finallyMock = vi.fn(); + let finallyCalled = false; + let finallyParams = null; + const ctx = { foo: "bar" }; const args = { test: "value" }; const result = { success: true }; @@ -577,7 +576,10 @@ describe("finally callback", () => { const mod = { args: {}, input: async () => ({ ctx: {}, args: {} }), - finally: finallyMock + finally: (params) => { + finallyCalled = true; + finallyParams = params; + } }; let actualResult; @@ -593,13 +595,16 @@ describe("finally callback", () => { } } - expect(finallyMock).toHaveBeenCalledWith({ + expect(finallyCalled).toBe(true); + expect(finallyParams).toEqual({ ctx, result, error: undefined }); - finallyMock.mockClear(); + finallyCalled = false; + finallyParams = null; + const testError = new Error("Test error"); const errorHandler = async () => { throw testError; @@ -614,7 +619,8 @@ describe("finally callback", () => { } } - expect(finallyMock).toHaveBeenCalledWith({ + expect(finallyCalled).toBe(true); + expect(finallyParams).toEqual({ ctx, result: undefined, error: testError From 35ccd49e99aad14a3c86df89ebc20ce22f11bc6e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 01:52:02 +0000 Subject: [PATCH 3/8] Fix handler function in test to accept arguments Co-Authored-By: Ian Macartney --- packages/convex-helpers/server/customFunctions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/convex-helpers/server/customFunctions.test.ts b/packages/convex-helpers/server/customFunctions.test.ts index f559caa9..8685e328 100644 --- a/packages/convex-helpers/server/customFunctions.test.ts +++ b/packages/convex-helpers/server/customFunctions.test.ts @@ -571,7 +571,7 @@ describe("finally callback", () => { const args = { test: "value" }; const result = { success: true }; - const handler = async () => result; + const handler = async (_ctx, _args) => result; const mod = { args: {}, From d94142d74108bda06816d331020bee78294b4473 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 01:53:40 +0000 Subject: [PATCH 4/8] Fix formatting issues Co-Authored-By: Ian Macartney --- .../server/customFunctions.test.ts | 25 +++++++++---------- .../convex-helpers/server/customFunctions.ts | 6 ++++- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/convex-helpers/server/customFunctions.test.ts b/packages/convex-helpers/server/customFunctions.test.ts index 8685e328..abaf3910 100644 --- a/packages/convex-helpers/server/customFunctions.test.ts +++ b/packages/convex-helpers/server/customFunctions.test.ts @@ -379,7 +379,6 @@ const testApi: ApiFromModules<{ create: typeof create; outerAdds: typeof outerAdds; outerRemoves: typeof outerRemoves; - }; }>["fns"] = anyApi["customFunctions.test"] as any; @@ -566,22 +565,22 @@ describe("finally callback", () => { test("finally callback is called with result and context", async () => { let finallyCalled = false; let finallyParams = null; - + const ctx = { foo: "bar" }; const args = { test: "value" }; const result = { success: true }; - + const handler = async (_ctx, _args) => result; - + const mod = { args: {}, input: async () => ({ ctx: {}, args: {} }), finally: (params) => { finallyCalled = true; finallyParams = params; - } + }, }; - + let actualResult; let actualError; try { @@ -594,22 +593,22 @@ describe("finally callback", () => { await mod.finally({ ctx, result: actualResult, error: actualError }); } } - + expect(finallyCalled).toBe(true); expect(finallyParams).toEqual({ ctx, result, - error: undefined + error: undefined, }); - + finallyCalled = false; finallyParams = null; - + const testError = new Error("Test error"); const errorHandler = async () => { throw testError; }; - + try { await errorHandler(); } catch (e) { @@ -618,12 +617,12 @@ describe("finally callback", () => { await mod.finally({ ctx, result: undefined, error: testError }); } } - + expect(finallyCalled).toBe(true); expect(finallyParams).toEqual({ ctx, result: undefined, - error: testError + error: testError, }); }); }); diff --git a/packages/convex-helpers/server/customFunctions.ts b/packages/convex-helpers/server/customFunctions.ts index e12fcc82..8d5b39be 100644 --- a/packages/convex-helpers/server/customFunctions.ts +++ b/packages/convex-helpers/server/customFunctions.ts @@ -60,7 +60,11 @@ export type Mod< ) => | Promise<{ ctx: ModCtx; args: ModMadeArgs }> | { ctx: ModCtx; args: ModMadeArgs }; - finally?: (params: { ctx: Ctx & ModCtx; result?: any; error?: any }) => void | Promise; + finally?: (params: { + ctx: Ctx & ModCtx; + result?: any; + error?: any; + }) => void | Promise; }; /** From fb599bb06e68dbc28cc69edfdb69b48970f73776 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 02:04:27 +0000 Subject: [PATCH 5/8] Update finally callback API based on PR feedback Co-Authored-By: Ian Macartney --- .../server/customFunctions.finally.test.ts | 135 ++++++++++++++++++ .../convex-helpers/server/customFunctions.ts | 13 +- 2 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 packages/convex-helpers/server/customFunctions.finally.test.ts diff --git a/packages/convex-helpers/server/customFunctions.finally.test.ts b/packages/convex-helpers/server/customFunctions.finally.test.ts new file mode 100644 index 00000000..c7f89c26 --- /dev/null +++ b/packages/convex-helpers/server/customFunctions.finally.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, test, vi } from "vitest"; +import { customQuery, customMutation, customAction } from "./customFunctions.js"; +import { convexTest } from "convex-test"; +import { + actionGeneric, + anyApi, + DataModelFromSchemaDefinition, + defineSchema, + defineTable, + MutationBuilder, + mutationGeneric, + QueryBuilder, + queryGeneric, +} from "convex/server"; +import { v } from "convex/values"; +import { modules } from "./setup.test.js"; + +const schema = defineSchema({ + users: defineTable({ + tokenIdentifier: v.string(), + }).index("tokenIdentifier", ["tokenIdentifier"]), +}); + +type DataModel = DataModelFromSchemaDefinition; +const query = queryGeneric as QueryBuilder; +const mutation = mutationGeneric as MutationBuilder; +const action = actionGeneric; + +describe("finally callback", () => { + test("finally callback with query", async () => { + const t = convexTest(schema, modules); + const finallyMock = vi.fn(); + + const withFinally = customQuery(query, { + args: {}, + input: async () => ({ ctx: { foo: "bar" }, args: {} }), + finally: (ctx, params) => { + finallyMock(ctx, params); + } + }); + + const successFn = withFinally({ + args: {}, + handler: async (ctx) => { + return { success: true, foo: ctx.foo }; + }, + }); + + const result = await t.query(successFn, {}); + expect(result).toEqual({ success: true, foo: "bar" }); + expect(finallyMock).toHaveBeenCalledWith( + expect.objectContaining({ foo: "bar" }), + { result: { success: true, foo: "bar" }, error: undefined } + ); + + finallyMock.mockClear(); + + const errorFn = withFinally({ + args: {}, + handler: async () => { + throw new Error("Test error"); + }, + }); + + try { + await t.query(errorFn, {}); + } catch (e) { + expect(e.message).toContain("Test error"); + } + + expect(finallyMock).toHaveBeenCalledWith( + expect.objectContaining({ foo: "bar" }), + { + result: undefined, + error: expect.objectContaining({ message: expect.stringContaining("Test error") }) + } + ); + }); + + test("finally callback with mutation", async () => { + const t = convexTest(schema, modules); + const finallyMock = vi.fn(); + + const withFinally = customMutation(mutation, { + args: {}, + input: async () => ({ ctx: { foo: "bar" }, args: {} }), + finally: (ctx, params) => { + finallyMock(ctx, params); + } + }); + + const mutationFn = withFinally({ + args: {}, + handler: async (ctx) => { + return { updated: true, foo: ctx.foo }; + }, + }); + + const result = await t.mutation(mutationFn, {}); + expect(result).toEqual({ updated: true, foo: "bar" }); + + expect(finallyMock).toHaveBeenCalledWith( + expect.objectContaining({ foo: "bar" }), + { result: { updated: true, foo: "bar" }, error: undefined } + ); + }); + + test("finally callback with action", async () => { + const t = convexTest(schema, modules); + const finallyMock = vi.fn(); + + const withFinally = customAction(action, { + args: {}, + input: async () => ({ ctx: { foo: "bar" }, args: {} }), + finally: (ctx, params) => { + finallyMock(ctx, params); + } + }); + + const actionFn = withFinally({ + args: {}, + handler: async (ctx) => { + return { executed: true, foo: ctx.foo }; + }, + }); + + const result = await t.action(actionFn, {}); + expect(result).toEqual({ executed: true, foo: "bar" }); + + expect(finallyMock).toHaveBeenCalledWith( + expect.objectContaining({ foo: "bar" }), + { result: { executed: true, foo: "bar" }, error: undefined } + ); + }); +}); diff --git a/packages/convex-helpers/server/customFunctions.ts b/packages/convex-helpers/server/customFunctions.ts index 8d5b39be..0524194e 100644 --- a/packages/convex-helpers/server/customFunctions.ts +++ b/packages/convex-helpers/server/customFunctions.ts @@ -60,10 +60,9 @@ export type Mod< ) => | Promise<{ ctx: ModCtx; args: ModMadeArgs }> | { ctx: ModCtx; args: ModMadeArgs }; - finally?: (params: { - ctx: Ctx & ModCtx; - result?: any; - error?: any; + finally?: (ctx: Ctx & ModCtx, params: { + result?: unknown; + error?: unknown; }) => void | Promise; }; @@ -93,7 +92,7 @@ export const NoOp = { input() { return { args: {}, ctx: {} }; }, - finally() {}, + }; /** @@ -356,7 +355,7 @@ function customFnBuilder( throw e; } finally { if (mod.finally) { - await mod.finally({ ctx: finalCtx, result, error }); + await mod.finally(finalCtx, { result, error }); } } }, @@ -383,7 +382,7 @@ function customFnBuilder( throw e; } finally { if (mod.finally) { - await mod.finally({ ctx: finalCtx, result, error }); + await mod.finally(finalCtx, { result, error }); } } }, From ad26e41e4c332480c9c9a8e48fc80e78fedbc76e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 20:04:59 +0000 Subject: [PATCH 6/8] Address PR feedback: Update finally callback implementation and tests Co-Authored-By: Ian Macartney --- .../server/customFunctions.finally.test.ts | 135 --------------- .../server/customFunctions.test.ts | 159 ++++++++++++------ .../convex-helpers/server/customFunctions.ts | 20 +-- 3 files changed, 114 insertions(+), 200 deletions(-) delete mode 100644 packages/convex-helpers/server/customFunctions.finally.test.ts diff --git a/packages/convex-helpers/server/customFunctions.finally.test.ts b/packages/convex-helpers/server/customFunctions.finally.test.ts deleted file mode 100644 index c7f89c26..00000000 --- a/packages/convex-helpers/server/customFunctions.finally.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { describe, expect, test, vi } from "vitest"; -import { customQuery, customMutation, customAction } from "./customFunctions.js"; -import { convexTest } from "convex-test"; -import { - actionGeneric, - anyApi, - DataModelFromSchemaDefinition, - defineSchema, - defineTable, - MutationBuilder, - mutationGeneric, - QueryBuilder, - queryGeneric, -} from "convex/server"; -import { v } from "convex/values"; -import { modules } from "./setup.test.js"; - -const schema = defineSchema({ - users: defineTable({ - tokenIdentifier: v.string(), - }).index("tokenIdentifier", ["tokenIdentifier"]), -}); - -type DataModel = DataModelFromSchemaDefinition; -const query = queryGeneric as QueryBuilder; -const mutation = mutationGeneric as MutationBuilder; -const action = actionGeneric; - -describe("finally callback", () => { - test("finally callback with query", async () => { - const t = convexTest(schema, modules); - const finallyMock = vi.fn(); - - const withFinally = customQuery(query, { - args: {}, - input: async () => ({ ctx: { foo: "bar" }, args: {} }), - finally: (ctx, params) => { - finallyMock(ctx, params); - } - }); - - const successFn = withFinally({ - args: {}, - handler: async (ctx) => { - return { success: true, foo: ctx.foo }; - }, - }); - - const result = await t.query(successFn, {}); - expect(result).toEqual({ success: true, foo: "bar" }); - expect(finallyMock).toHaveBeenCalledWith( - expect.objectContaining({ foo: "bar" }), - { result: { success: true, foo: "bar" }, error: undefined } - ); - - finallyMock.mockClear(); - - const errorFn = withFinally({ - args: {}, - handler: async () => { - throw new Error("Test error"); - }, - }); - - try { - await t.query(errorFn, {}); - } catch (e) { - expect(e.message).toContain("Test error"); - } - - expect(finallyMock).toHaveBeenCalledWith( - expect.objectContaining({ foo: "bar" }), - { - result: undefined, - error: expect.objectContaining({ message: expect.stringContaining("Test error") }) - } - ); - }); - - test("finally callback with mutation", async () => { - const t = convexTest(schema, modules); - const finallyMock = vi.fn(); - - const withFinally = customMutation(mutation, { - args: {}, - input: async () => ({ ctx: { foo: "bar" }, args: {} }), - finally: (ctx, params) => { - finallyMock(ctx, params); - } - }); - - const mutationFn = withFinally({ - args: {}, - handler: async (ctx) => { - return { updated: true, foo: ctx.foo }; - }, - }); - - const result = await t.mutation(mutationFn, {}); - expect(result).toEqual({ updated: true, foo: "bar" }); - - expect(finallyMock).toHaveBeenCalledWith( - expect.objectContaining({ foo: "bar" }), - { result: { updated: true, foo: "bar" }, error: undefined } - ); - }); - - test("finally callback with action", async () => { - const t = convexTest(schema, modules); - const finallyMock = vi.fn(); - - const withFinally = customAction(action, { - args: {}, - input: async () => ({ ctx: { foo: "bar" }, args: {} }), - finally: (ctx, params) => { - finallyMock(ctx, params); - } - }); - - const actionFn = withFinally({ - args: {}, - handler: async (ctx) => { - return { executed: true, foo: ctx.foo }; - }, - }); - - const result = await t.action(actionFn, {}); - expect(result).toEqual({ executed: true, foo: "bar" }); - - expect(finallyMock).toHaveBeenCalledWith( - expect.objectContaining({ foo: "bar" }), - { result: { executed: true, foo: "bar" }, error: undefined } - ); - }); -}); diff --git a/packages/convex-helpers/server/customFunctions.test.ts b/packages/convex-helpers/server/customFunctions.test.ts index abaf3910..972e6bb0 100644 --- a/packages/convex-helpers/server/customFunctions.test.ts +++ b/packages/convex-helpers/server/customFunctions.test.ts @@ -563,66 +563,115 @@ describe("nested custom functions", () => { describe("finally callback", () => { test("finally callback is called with result and context", async () => { - let finallyCalled = false; - let finallyParams = null; - - const ctx = { foo: "bar" }; - const args = { test: "value" }; - const result = { success: true }; - - const handler = async (_ctx, _args) => result; - - const mod = { + const t = convexTest(schema, modules); + const finallyMock = vi.fn(); + + const withFinally = customQuery(query, { args: {}, - input: async () => ({ ctx: {}, args: {} }), - finally: (params) => { - finallyCalled = true; - finallyParams = params; + input: async () => ({ ctx: { foo: "bar" }, args: {} }), + finally: (ctx, params) => { + finallyMock(ctx, params); + } + }); + + const successFn = withFinally({ + args: {}, + handler: async (ctx) => { + return { success: true, foo: ctx.foo }; }, - }; - - let actualResult; - let actualError; - try { - actualResult = await handler(ctx, args); - } catch (e) { - actualError = e; - throw e; - } finally { - if (mod.finally) { - await mod.finally({ ctx, result: actualResult, error: actualError }); + }); + + await t.run(async (ctx) => { + const result = await (successFn as any)._handler(ctx, {}); + expect(result).toEqual({ success: true, foo: "bar" }); + + expect(finallyMock).toHaveBeenCalledWith( + expect.objectContaining({ foo: "bar" }), + { result: { success: true, foo: "bar" } } + ); + }); + + finallyMock.mockClear(); + + const errorFn = withFinally({ + args: {}, + handler: async () => { + throw new Error("Test error"); + }, + }); + + await t.run(async (ctx) => { + try { + await (errorFn as any)._handler(ctx, {}); + fail("Should have thrown an error"); + } catch (e) { + expect(e.message).toContain("Test error"); } - } - - expect(finallyCalled).toBe(true); - expect(finallyParams).toEqual({ - ctx, - result, - error: undefined, + + expect(finallyMock).toHaveBeenCalledWith( + expect.objectContaining({ foo: "bar" }), + { error: expect.objectContaining({ message: expect.stringContaining("Test error") }) } + ); }); - - finallyCalled = false; - finallyParams = null; - - const testError = new Error("Test error"); - const errorHandler = async () => { - throw testError; - }; - - try { - await errorHandler(); - } catch (e) { - } finally { - if (mod.finally) { - await mod.finally({ ctx, result: undefined, error: testError }); + }); + + test("finally callback with mutation", async () => { + const t = convexTest(schema, modules); + const finallyMock = vi.fn(); + + const withFinally = customMutation(mutation, { + args: {}, + input: async () => ({ ctx: { foo: "bar" }, args: {} }), + finally: (ctx, params) => { + finallyMock(ctx, params); } - } - - expect(finallyCalled).toBe(true); - expect(finallyParams).toEqual({ - ctx, - result: undefined, - error: testError, + }); + + const mutationFn = withFinally({ + args: {}, + handler: async (ctx) => { + return { updated: true, foo: ctx.foo }; + }, + }); + + await t.run(async (ctx) => { + const result = await (mutationFn as any)._handler(ctx, {}); + expect(result).toEqual({ updated: true, foo: "bar" }); + + expect(finallyMock).toHaveBeenCalledWith( + expect.objectContaining({ foo: "bar" }), + { result: { updated: true, foo: "bar" } } + ); + }); + }); + + test("finally callback with action", async () => { + const t = convexTest(schema, modules); + const finallyMock = vi.fn(); + + const withFinally = customAction(action, { + args: {}, + input: async () => ({ ctx: { foo: "bar" }, args: {} }), + finally: (ctx, params) => { + finallyMock(ctx, params); + } + }); + + const actionFn = withFinally({ + args: {}, + handler: async (ctx) => { + return { executed: true, foo: ctx.foo }; + }, + }); + + await t.run(async (ctx) => { + const result = await (actionFn as any)._handler(ctx, {}); + expect(result).toEqual({ executed: true, foo: "bar" }); + + expect(finallyMock).toHaveBeenCalledWith( + expect.objectContaining({ foo: "bar" }), + { result: { executed: true, foo: "bar" } } + ); }); }); }); diff --git a/packages/convex-helpers/server/customFunctions.ts b/packages/convex-helpers/server/customFunctions.ts index 0524194e..0253d990 100644 --- a/packages/convex-helpers/server/customFunctions.ts +++ b/packages/convex-helpers/server/customFunctions.ts @@ -346,17 +346,17 @@ function customFnBuilder( const args = omit(allArgs, Object.keys(inputArgs)); const finalCtx = { ...ctx, ...added.ctx }; let result; - let error; try { result = await handler(finalCtx, { ...args, ...added.args }); + if (mod.finally) { + await mod.finally(finalCtx, { result }); + } return result; } catch (e) { - error = e; - throw e; - } finally { if (mod.finally) { - await mod.finally(finalCtx, { result, error }); + await mod.finally(finalCtx, { error: e }); } + throw e; } }, }); @@ -373,17 +373,17 @@ function customFnBuilder( const added = await inputMod(ctx, args); const finalCtx = { ...ctx, ...added.ctx }; let result; - let error; try { result = await handler(finalCtx, { ...args, ...added.args }); + if (mod.finally) { + await mod.finally(finalCtx, { result }); + } return result; } catch (e) { - error = e; - throw e; - } finally { if (mod.finally) { - await mod.finally(finalCtx, { result, error }); + await mod.finally(finalCtx, { error: e }); } + throw e; } }, }); From c4a058979e6de4eea71ba432dfec84aeb848d01c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 20:07:13 +0000 Subject: [PATCH 7/8] Fix error handling in finally callback tests Co-Authored-By: Ian Macartney --- packages/convex-helpers/server/customFunctions.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/convex-helpers/server/customFunctions.test.ts b/packages/convex-helpers/server/customFunctions.test.ts index 972e6bb0..dac7c8ac 100644 --- a/packages/convex-helpers/server/customFunctions.test.ts +++ b/packages/convex-helpers/server/customFunctions.test.ts @@ -27,7 +27,7 @@ import { type Auth, } from "convex/server"; import { v } from "convex/values"; -import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { modules } from "./setup.test.js"; const schema = defineSchema({ @@ -601,11 +601,13 @@ describe("finally callback", () => { }); await t.run(async (ctx) => { + let error: Error | undefined; try { await (errorFn as any)._handler(ctx, {}); - fail("Should have thrown an error"); + expect.fail("Should have thrown an error"); } catch (e) { - expect(e.message).toContain("Test error"); + error = e as Error; + expect(error.message).toContain("Test error"); } expect(finallyMock).toHaveBeenCalledWith( From f45d7ecf430c09eaa60d1a46cbcd0611411834b0 Mon Sep 17 00:00:00 2001 From: Ian Macartney Date: Fri, 4 Apr 2025 13:19:04 -0700 Subject: [PATCH 8/8] fix lint --- packages/convex-helpers/server/customFunctions.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/convex-helpers/server/customFunctions.test.ts b/packages/convex-helpers/server/customFunctions.test.ts index dac7c8ac..3f4a71ad 100644 --- a/packages/convex-helpers/server/customFunctions.test.ts +++ b/packages/convex-helpers/server/customFunctions.test.ts @@ -601,12 +601,11 @@ describe("finally callback", () => { }); await t.run(async (ctx) => { - let error: Error | undefined; try { await (errorFn as any)._handler(ctx, {}); expect.fail("Should have thrown an error"); - } catch (e) { - error = e as Error; + } catch (e: unknown) { + const error = e as Error; expect(error.message).toContain("Test error"); }