From bdb2151cd5f90ec6aac8e19f5094015960601363 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Mon, 29 Sep 2025 14:54:27 +0200 Subject: [PATCH] fix: make create mutation input fields optional for beforeCreate hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the create mutation's input validator required all schema fields, causing validation errors when beforeCreate hooks tried to add required fields. This change uses partial() to make all input fields optional during validation, allowing beforeCreate hooks to add or modify any required fields. The actual schema validation still occurs when inserting into the database. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .changeset/fix-beforecreate-validation.md | 9 +++++++++ src/api.ts | 21 +++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 .changeset/fix-beforecreate-validation.md diff --git a/.changeset/fix-beforecreate-validation.md b/.changeset/fix-beforecreate-validation.md new file mode 100644 index 0000000..4dcc86c --- /dev/null +++ b/.changeset/fix-beforecreate-validation.md @@ -0,0 +1,9 @@ +--- +"better-auth-convex": patch +--- + +Fix beforeCreate hook validation error + +Previously, the `create` mutation's input validator required all schema fields to be present, causing validation errors when `beforeCreate` hooks tried to add required fields like `username`. + +This fix makes all input fields optional during validation, allowing `beforeCreate` hooks to add or modify any required fields. The actual schema validation still occurs when inserting into the database, ensuring data integrity. \ No newline at end of file diff --git a/src/api.ts b/src/api.ts index 4a820a3..2191e58 100644 --- a/src/api.ts +++ b/src/api.ts @@ -68,13 +68,14 @@ export const createHandler = async ( beforeCreateHandle?: string; select?: string[]; onCreateHandle?: string; + skipBeforeHooks?: boolean; }, schema: Schema, betterAuthSchema: any ) => { let data = args.input.data; - if (args.beforeCreateHandle) { + if (!args.skipBeforeHooks && args.beforeCreateHandle) { const transformedData = await ctx.runMutation( args.beforeCreateHandle as FunctionHandle<'mutation'>, { @@ -300,6 +301,7 @@ export const deleteOneHandler = async ( }; beforeDeleteHandle?: string; onDeleteHandle?: string; + skipBeforeHooks?: boolean; }, schema: Schema, betterAuthSchema: any @@ -312,7 +314,7 @@ export const deleteOneHandler = async ( let hookDoc = doc; - if (args.beforeDeleteHandle) { + if (!args.skipBeforeHooks && args.beforeDeleteHandle) { const transformedDoc = await ctx.runMutation( args.beforeDeleteHandle as FunctionHandle<'mutation'>, { @@ -348,6 +350,7 @@ export const deleteManyHandler = async ( paginationOpts: any; beforeDeleteHandle?: string; onDeleteHandle?: string; + skipBeforeHooks?: boolean; }, schema: Schema, betterAuthSchema: any @@ -359,7 +362,7 @@ export const deleteManyHandler = async ( await asyncMap(page, async (doc: any) => { let hookDoc = doc; - if (args.beforeDeleteHandle) { + if (!args.skipBeforeHooks && args.beforeDeleteHandle) { const transformedDoc = await ctx.runMutation( args.beforeDeleteHandle as FunctionHandle<'mutation'>, { @@ -401,12 +404,14 @@ export const createApi = >( args: { beforeCreateHandle: v.optional(v.string()), input: v.union( - ...Object.entries(schema.tables).map(([model, table]) => - v.object({ - data: v.object((table as any).validator.fields), + ...Object.entries(schema.tables).map(([model, table]) => { + const fields = partial((table as any).validator.fields); + + return v.object({ + data: v.object(fields), model: v.literal(model), - }) - ) + }); + }) ), select: v.optional(v.array(v.string())), onCreateHandle: v.optional(v.string()),