Skip to content

Conversation

@ianmacartney
Copy link
Collaborator

@ianmacartney ianmacartney commented Dec 8, 2025

Fixes #865


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Summary by CodeRabbit

  • New Features

    • Option to skip automatic Convex validation for custom function builders retained.
  • Improvements

    • Centralized input/output validation for consistent behavior and clearer error messages.
    • When skipping Convex validation, argument and return validation behavior is now predictable for Zod-backed custom functions.
  • Tests

    • Added tests for skip-validation scenarios: extra-arg handling, input-type errors, and invalid return-data errors.
  • Chores

    • Package version bumped.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 8, 2025

Walkthrough

Adds a local skipConvexValidation handling path in Zod custom builders, centralizes Zod parsing into parseOrThrow, conditions args/returns wiring on the flag, updates error messages, adds tests for skip behavior, and bumps the package version and changelog.

Changes

Cohort / File(s) Summary
Zod validation helpers
packages/convex-helpers/server/zod3.ts, packages/convex-helpers/server/zod4.ts
Destructure skipConvexValidation from function descriptors. Add parseOrThrow to centralize Zod safeParse/safeParseAsync → throw ConvexError. Gate return validation and args wiring on skipConvexValidation (omit args when true). Enforce explicit error when skipConvexValidation is true but input args exist. Adjust custom input invocation to pass extra and explicitly merge/select input/all args. Replace inline parsing with parseOrThrow for inputs and outputs.
Tests for skip behavior
packages/convex-helpers/server/zod4.functions.test.ts
Add testQueryWithSkipConvexValidation (configured skipConvexValidation: true) and tests validating extra-arg stripping, Zod arg validation errors, and Zod return validation errors. Export the test function in the test API types.
Changelog and package
packages/convex-helpers/CHANGELOG.md, packages/convex-helpers/package.json, manifest_file
Add Unreleased changelog entry about keeping Zod validation for custom functions and bump package version to 0.1.107-alpha.0. Update manifest/package metadata files included in diff.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas to inspect:
    • Correct and consistent use of the local skipConvexValidation variable across all branches in both zod3.ts and zod4.ts.
    • parseOrThrow behavior for sync and async parsing and the ConvexError/Zod error shape it produces.
    • The explicit error path when skipConvexValidation is true but input args are provided.
    • Builder return shape differences when args is omitted vs included and any consumer assumptions in tests.
    • New tests in zod4.functions.test.ts for expected behaviors and failure messages.

Poem

🐰 A tiny flag beneath the moon,
I nudge the validators to tune,
parseOrThrow hops in with grace,
extras trimmed, errors take their place.
A joyful thump — code finds its groove.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: ensuring Zod validation still runs even when skipConvexValidation is true, which is the core purpose of this changeset.
Linked Issues check ✅ Passed The PR implements the core requirement from #865 by introducing skipConvexValidation flag that allows Zod validators to strip unknown fields while still performing Zod validation.
Out of Scope Changes check ✅ Passed All changes in zod3.ts, zod4.ts, and test files are directly aligned with implementing skipConvexValidation functionality to allow Zod validation while skipping Convex validation. Version bump and changelog are standard maintenance.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ian/skip-convex-validation

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 8, 2025

Open in StackBlitz

npm i https://pkg.pr.new/get-convex/convex-helpers@874

commit: 8014887

Copy link
Member

@Nicolapps Nicolapps left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix! Could you please make sure that the unit tests (at least in Zod 4) verify that validation works as intended?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/convex-helpers/server/zod4.ts (1)

911-916: Consider improving error message clarity.

The validation logic is correct, but the error message could be more explicit about why this combination is unsupported.

Apply this diff to clarify:

     if (skipConvexValidation && Object.keys(inputArgs).length > 0) {
       throw new Error(
-        "If you're using a custom function with arguments for the input " +
-          "customization, you cannot skip convex validation.",
+        "Cannot skip Convex validation when the customization requires input arguments. " +
+          "The input arguments need Convex validation to be passed to the customization.",
       );
     }
packages/convex-helpers/server/zod3.ts (1)

407-412: Consider improving error message clarity.

The validation logic is correct, but the error message could be more explicit, matching the suggestion for zod4.ts.

Apply this diff:

     if (skipConvexValidation && Object.keys(inputArgs).length > 0) {
       throw new Error(
-        "If you're using a custom function with arguments for the input " +
-          "customization, you cannot skip convex validation.",
+        "Cannot skip Convex validation when the customization requires input arguments. " +
+          "The input arguments need Convex validation to be passed to the customization.",
       );
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05e1939 and d4e2094.

📒 Files selected for processing (2)
  • packages/convex-helpers/server/zod3.ts (6 hunks)
  • packages/convex-helpers/server/zod4.ts (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

When modifying complex TypeScript types, run npm run typecheck in the repository root to ensure types are correct, rather than running tsc manually

Files:

  • packages/convex-helpers/server/zod3.ts
  • packages/convex-helpers/server/zod4.ts
🧬 Code graph analysis (2)
packages/convex-helpers/server/zod3.ts (3)
packages/convex-helpers/server/zod4.ts (1)
  • zodOutputToConvex (514-546)
packages/convex-helpers/server/zod.ts (1)
  • zodOutputToConvex (238-242)
packages/convex-helpers/validators.ts (1)
  • addFieldsToValidator (373-396)
packages/convex-helpers/server/zod4.ts (3)
packages/convex-helpers/server/zod3.ts (1)
  • zodOutputToConvex (1332-1466)
packages/convex-helpers/server/zod.ts (1)
  • zodOutputToConvex (238-242)
packages/convex-helpers/validators.ts (1)
  • addFieldsToValidator (373-396)
🪛 GitHub Check: Test and lint
packages/convex-helpers/server/zod4.ts

[failure] 939-939: server/zod4.functions.test.ts > zCustomQuery, zCustomMutation, zCustomAction > transform > calling a function with asynchronous transforms in arguments and return values
TypeError: Cannot read properties of undefined (reading 'length')
❯ eval ../../node_modules/zod/v4/core/doc.js:33:16
❯ ../../node_modules/zod/v4/core/schemas.js:880:34
❯ Object.inst._zod.parse ../../node_modules/zod/v4/core/schemas.js:905:23
❯ Module. ../../node_modules/zod/v4/core/parse.js:33:32
❯ _.inst.safeParse ../../node_modules/zod/v4/classic/schemas.js:28:46
❯ parseOrThrow server/zod4.ts:939:28
❯ handler server/zod4.ts:895:30
❯ invokeFunction ../../node_modules/convex/src/server/impl/registration_impl.ts:80:14
❯ invokeQuery ../../node_modules/convex/src/server/impl/registration_impl.ts:272:18

🔇 Additional comments (9)
packages/convex-helpers/server/zod4.ts (5)

852-858: LGTM!

The destructuring correctly extracts the skipConvexValidation flag with a safe default of false.


865-868: LGTM!

The conditional return validator correctly implements the skipConvexValidation behavior by skipping Convex validator generation when the flag is true.


870-909: LGTM!

The args handling correctly implements the dual validation strategy: skipping Convex validation when the flag is set while still performing Zod validation via parseOrThrow.


917-934: LGTM!

The no-args branch correctly mirrors the validation logic from the args branch, ensuring consistent behavior.


938-948: No async keyword is needed for this function—it is correctly implemented as synchronous.

The parseOrThrow function properly handles validation errors by throwing a ConvexError and returns parsed data on success. No changes are required.

packages/convex-helpers/server/zod3.ts (4)

348-354: LGTM!

The destructuring is consistent with zod4.ts and correctly implements the feature.


356-364: LGTM!

The return validator logic correctly implements the skipConvexValidation behavior, consistent with zod4.ts.


366-405: LGTM!

The args handling correctly implements the dual validation strategy, consistent with zod4.ts.


434-447: LGTM!

The parseOrThrow implementation is correct for Zod v3, properly declared as async and using parsed.error.errors (Zod v3) instead of parsed.error.issues (Zod v4).

@ianmacartney ianmacartney force-pushed the ian/skip-convex-validation branch from 9746c04 to 05e1939 Compare December 8, 2025 19:28
@ianmacartney ianmacartney merged commit 6933cbb into main Dec 8, 2025
4 of 5 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/convex-helpers/server/zod4.functions.test.ts (1)

451-464: Test could be clearer: missing required age parameter.

The test passes { name: 123 } but omits the required age parameter. While the test checks for a name type error, it's unclear whether the validation fails due to the wrong type for name or the missing age, or both. For clarity, include all required fields with only the specific field under test being invalid.

Apply this diff:

     await expect(
       t.query(testApi.testQueryWithSkipConvexValidation, {
         name: 123,
+        age: 30,
       } as any),
     ).rejects.toThrowError(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9746c04 and 8014887.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • packages/convex-helpers/CHANGELOG.md (1 hunks)
  • packages/convex-helpers/package.json (1 hunks)
  • packages/convex-helpers/server/zod4.functions.test.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/convex-helpers/package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

When modifying complex TypeScript types, run npm run typecheck in the repository root to ensure types are correct, rather than running tsc manually

Files:

  • packages/convex-helpers/server/zod4.functions.test.ts
🧬 Code graph analysis (1)
packages/convex-helpers/server/zod4.functions.test.ts (2)
convex/zodFunctionsExample.ts (1)
  • zQuery (6-6)
packages/convex-helpers/server/setup.test.ts (1)
  • modules (2-2)
🔇 Additional comments (2)
packages/convex-helpers/CHANGELOG.md (1)

6-6: LGTM!

The changelog entry clearly documents the fix for skipConvexValidation behavior.

packages/convex-helpers/server/zod4.functions.test.ts (1)

298-298: LGTM!

The testApi type correctly includes the new test function.

returnBadData: z.boolean().default(false),
},
handler: async (_ctx, args) => {
assertType<{ name: string; age: number }>(args);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incorrect type assertion: missing returnBadData field.

The assertType should include returnBadData: boolean in the type. According to Zod 4's .default() behavior, fields with defaults are always present in the parsed output type, even though they're optional in the input type.

Apply this diff:

-    assertType<{ name: string; age: number }>(args);
+    assertType<{ name: string; age: number; returnBadData: boolean }>(args);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assertType<{ name: string; age: number }>(args);
assertType<{ name: string; age: number; returnBadData: boolean }>(args);
🤖 Prompt for AI Agents
In packages/convex-helpers/server/zod4.functions.test.ts around line 192, the
assertType generic is missing the returnBadData field; update the asserted type
to include returnBadData: boolean (i.e., assertType<{ name: string; age: number;
returnBadData: boolean }>(args)) so the test reflects Zod 4’s .default()
behavior where fields with defaults are always present in the parsed output.

Comment on lines +433 to +465
describe("skipConvexValidation", () => {
test("zCustomQuery with skipConvexValidation", async () => {
const t = convexTest(schema, modules);
const args = {
name: "Alice",
age: 30,
// Should get dropped
extraArg: "extraArg",
};
const response = await t.query(
testApi.testQueryWithSkipConvexValidation,
args,
);
expect(response).toMatchObject({
message: "Hello Alice, you are 30 years old",
doubledAge: 60,
});
});
test("throwing ConvexError on zod arg validation", async () => {
const t = convexTest(schema, modules);
await expect(
t.query(testApi.testQueryWithSkipConvexValidation, {
name: 123,
} as any),
).rejects.toThrowError(
expect.objectContaining({
data: expect.stringMatching(
/(?=.*"ZodError")(?=.*"name")(?=.*"invalid_type")(?=.*"expected")(?=.*"string")/s,
),
}),
);
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing test case for return value validation.

The testQueryWithSkipConvexValidation handler includes logic to return invalid data when returnBadData: true (lines 197-202), but there's no test that exercises this path. This leaves return validation untested when skipConvexValidation is enabled.

Consider adding a test case like:

test("throwing ConvexError on zod return validation", async () => {
  const t = convexTest(schema, modules);
  await expect(
    t.query(testApi.testQueryWithSkipConvexValidation, {
      name: "Alice",
      age: 30,
      returnBadData: true,
    }),
  ).rejects.toThrowError(
    expect.objectContaining({
      data: expect.stringMatching(
        /(?=.*"ZodError")(?=.*"doubledAge")(?=.*"invalid_type")(?=.*"expected")(?=.*"number")/s,
      ),
    }),
  );
});
🤖 Prompt for AI Agents
In packages/convex-helpers/server/zod4.functions.test.ts around lines 433 to
465, add a test that exercises the handler's returnBadData path so return value
zod validation is covered: create a convexTest instance and call
t.query(testApi.testQueryWithSkipConvexValidation, { name: "Alice", age: 30,
returnBadData: true }) and assert it rejects with a ConvexError whose data
contains a ZodError mentioning the doubledAge field and number type (use a regex
similar to
/(?=.*"ZodError")(?=.*"doubledAge")(?=.*"invalid_type")(?=.*"expected")(?=.*"number")/s).

@ianmacartney ianmacartney deleted the ian/skip-convex-validation branch December 9, 2025 01:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Zod Argument Validation: Strip unknown fields

3 participants