fix(platform): throw ConvexError for customer and vendor duplicate-add (#1993)#2240
fix(platform): throw ConvexError for customer and vendor duplicate-add (#1993)#2240larryro wants to merge 4 commits into
Conversation
#1993) Convert the raw `throw new Error(...)` duplicate-add rejections on the customer and vendor create paths to structured `ConvexError({ code })`, matching the website fix (#2056). Raw errors are redacted to an uncaught "Server Error" in the client console in prod and force downstream string-matching. - createCustomerPublic: CUSTOMER_DUPLICATE_EMAIL / CUSTOMER_DUPLICATE_EXTERNAL_ID / CUSTOMER_EMAIL_REQUIRED - createVendor (internal_mutations): VENDOR_DUPLICATE_EMAIL / VENDOR_DUPLICATE_EXTERNAL_ID - REST helper httpStatusForConvexCode maps the duplicate codes to 409 (and CUSTOMER_EMAIL_REQUIRED to 400) instead of 500 - Regression tests asserting each path throws a ConvexError with the correct code Closes #1993
Desk review — PR #2240 (fixes #1993)Verdict: NOT READY — changes required. The vendor fix is correct and reachable; the customer fix targets dead code, so issue #1993's customer scope is not resolved on any reachable path. State (verified locally)
What's good
BLOCKING1. The customer fix changes dead code — no reachable customer-add path is affected. The reachable "add customer" path is REST Required — pick one and make it explicit:
Non-blocking notes
|
…path (#1993) The customer fix previously lived in createCustomerPublic, a dead helper imported only by its own test and never registered as a Convex endpoint. Move duplicate detection (email + externalId) into create_customer.ts, the helper backing internal_mutations.createCustomer, which is the path REST POST /api/v1/customers, the agent customer tool, and the workflow customer action all run. Normalize email to match the dup index and keep findOrCreateCustomer idempotent. Drop the dead helper, its test, and the now-unreachable CUSTOMER_EMAIL_REQUIRED 400 mapping (the reachable path allows email-optional adds). Add create_customer.test.ts covering both duplicate codes and the happy paths.
Re-work — addressed desk review (customer fix now on the reachable path)The earlier customer change targeted What changed in this round
Verification
|
larryro
left a comment
There was a problem hiding this comment.
Desk review — #1993 duplicate-add ConvexError fix
Verdict: READY TO MERGE.
Reviewed the full implementation (not just the diff happy-path), ran the project's tests, and confirmed CI.
CI & tests
- CI: all green. Every required check on
gh pr checkspassed (Type check, Lint, Format, Knip, Unit, UI, Build *, all 16 Playwright platform shards, Smoke test, Migrations, Opengrep, Trivy). The only non-pass jobs areskippingfork-PR-only jobs — expected on a same-repo PR. - Tests pass locally.
bunx vitest --run --project server convex/vendors/ convex/customers/ convex/websites/ convex/lib/rest/→ 33 passed. Full--project serverregression run → 6506 passed (544 files). The email-normalization change introduces no regressions.
What's correct
- Reachable customer-add path (REST
POST /api/v1/customers, agent customer tool, workflow customer action) all funnel throughinternal_mutations.createCustomer→ business-logiccreateCustomer, which now rejects dups withConvexError({ code: CUSTOMER_DUPLICATE_EMAIL | CUSTOMER_DUPLICATE_EXTERNAL_ID }). Pre-PR this path had no duplicate guard at all, so this both fixes the raw-Error redaction and closes a missing-guard gap. createVendorconverts both rawthrow new Errorsites toConvexError({ code: VENDOR_DUPLICATE_EMAIL | VENDOR_DUPLICATE_EXTERNAL_ID }).helpers.ts409 mapping is correctly wired: the REST wrapper catchesinstanceof ConvexError, readserror.data.code(object form), and returns a real 409; unknown codes still fall through to a logged 500 (no silent swallow).- Deleting
create_customer_public.tsis safe — it was dead code onmain(flagged by knip, imported nowhere). The_generated/api.d.tsedit removes exactly the two lines codegen would. findOrCreateCustomernormalizes email before its pre-check, keeping it idempotent (returns existing instead of tripping the new guard).- New tests genuinely regression-guard: they fail against pre-PR code, and a wrong/raw error cannot false-pass (
codeOfreturns undefined → assertion fails).
Non-blocking notes (for the author / follow-up — NOT required to merge)
- Guard inconsistency (pre-existing):
createCustomerusesif (args.externalId !== undefined)whilecreateVendor/bulkCreate*use the truthyif (args.externalId). For the degenerateexternalId: 0/'', the vendor path skips dedup. Pre-existing (this PR didn't change the condition), orthogonal to #1993. - Cross-path externalId type:
externalIdisstring | numberand stored raw; a REST/JSON numeric3and a bulk-import string"3"occupy distinct index keys, so duplicates can be missed across paths. Pre-existing limitation, not introduced here. - Update paths still throw raw
Error(update_customer.ts, vendor update mutations) — out of scope for #1993 (duplicate-add); tracked in #2004/#2209. - PR description drift: the body describes editing
create_customer_public.tsand aCUSTOMER_EMAIL_REQUIRED→ 400 mapping; neither exists in the actual diff (the file is deleted, and no code throws/maps that code). Cosmetic — code is correct. - Nice-to-have tests:
findOrCreateCustomeridempotency-under-normalization, and a vendor email-less add.
None of the above blocks the issue's fix, which is correct and complete on every reachable duplicate-add path.
Summary
Duplicate website/customer/vendor add rejected correctly in the UI but threw a raw
throw new Error(...)instead of aConvexError. In production Convex redacts rawErrormessages to "Server Error", so the rejection was logged to the client console as an uncaught Convex Server Error, and downstream code had to string-match the message.The website path was already fixed in #2056. This PR converts the remaining create / add duplicate paths for customers and vendors to structured
ConvexError({ code }), following the sameWEBSITE_DUPLICATE_DOMAINprecedent.Changes
convex/customers/create_customer_public.ts— raw throws →ConvexError:CUSTOMER_DUPLICATE_EMAILCUSTOMER_DUPLICATE_EXTERNAL_IDCUSTOMER_EMAIL_REQUIREDconvex/vendors/internal_mutations.ts(createVendor, reachable via RESTPOST /api/v1/vendors, agent tools, and workflow actions):VENDOR_DUPLICATE_EMAILVENDOR_DUPLICATE_EXTERNAL_IDconvex/lib/rest/helpers.ts—httpStatusForConvexCodenow maps the duplicate codes to 409 (andCUSTOMER_EMAIL_REQUIREDto 400) so REST clients get an actionable conflict instead of an opaque 500.create_customer_public.test.ts,create_vendor_error_codes.test.ts) asserting every duplicate-add path throws aConvexErrorwith the correct code, plus happy-path inserts.Scope notes
vendorsupdate/delete not-found and conflict cases are handled separately in fix(platform): throw ConvexError for vendor not-found and conflict cases (#2004) #2209 (issue Bug: vendors mutations throw raw Error for not-found and conflict cases — REST API and UI get opaque 500s instead of actionable 4xx/ConvexError #2004); this PR keeps to the duplicate-add scope of Bug: Duplicate website/customer/vendor add throws a raw Error (not ConvexError) → uncaught Server Error in the client console #1993 to avoid overlap.bulkCreateCustomers/bulkCreateVendors) already accumulate typederrorCodestrings into their result objects rather than throwing uncaught, so they are left unchanged.Verification
bunx vitest --run --project server convex/vendors/ convex/customers/ convex/websites/ convex/lib/rest/→ 33 passedbunx tsc --noEmit→ cleanbunx oxlint --type-aware→ 0 warnings / 0 errorsCloses #1993