Skip to content

Commit 65406b9

Browse files
refactor(customer): use discriminated union for lookup schema
Replace refinement-based validation with a discriminated union for CustomerLookupInputSchema. This ensures exactly-one-of validation translates properly to JSON Schema for MCP/AI tool compatibility.
1 parent 73d0431 commit 65406b9

2 files changed

Lines changed: 16 additions & 18 deletions

File tree

src/contracts/customer.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,28 @@ export type ListCustomersPaginatedOutput = z.infer<
3131
typeof ListCustomersPaginatedOutputSchema
3232
>;
3333

34-
// Flexible customer lookup - exactly one of id, email, or externalId
35-
// Base shape without refinement (for MCP tool schemas)
36-
export const CustomerLookupBaseSchema = z.object({
37-
id: z.string().optional().describe("The customer ID"),
38-
email: z.string().optional().describe("The customer email address"),
39-
externalId: z
40-
.string()
41-
.optional()
42-
.describe("The external ID from your system"),
34+
// Customer lookup by exactly one identifier (discriminated union for JSON Schema compatibility)
35+
const CustomerLookupByIdSchema = z.object({
36+
id: z.string().describe("The customer ID"),
37+
});
38+
const CustomerLookupByEmailSchema = z.object({
39+
email: z.string().describe("The customer email address"),
40+
});
41+
const CustomerLookupByExternalIdSchema = z.object({
42+
externalId: z.string().describe("The external ID from your system"),
4343
});
4444

45-
// With refinement for runtime validation
46-
export const CustomerLookupInputSchema = CustomerLookupBaseSchema.refine(
47-
(data) => [data.id, data.email, data.externalId].filter(Boolean).length === 1,
48-
{ message: "Exactly one of id, email, or externalId must be provided" },
49-
);
45+
export const CustomerLookupInputSchema = z.union([
46+
CustomerLookupByIdSchema,
47+
CustomerLookupByEmailSchema,
48+
CustomerLookupByExternalIdSchema,
49+
]);
5050
export type CustomerLookupInput = z.infer<typeof CustomerLookupInputSchema>;
5151

52-
// Aliases for specific operations
53-
export const GetCustomerInputSchema = CustomerLookupBaseSchema;
52+
export const GetCustomerInputSchema = CustomerLookupInputSchema;
5453
export type GetCustomerInput = z.infer<typeof GetCustomerInputSchema>;
5554

56-
export const DeleteCustomerInputSchema = CustomerLookupBaseSchema;
55+
export const DeleteCustomerInputSchema = CustomerLookupInputSchema;
5756
export type DeleteCustomerInput = z.infer<typeof DeleteCustomerInputSchema>;
5857

5958
export const CreateCustomerInputSchema = z.object({

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export {
6565
GetCustomerInputSchema,
6666
DeleteCustomerInputSchema,
6767
CustomerLookupInputSchema,
68-
CustomerLookupBaseSchema,
6968
} from "./contracts/customer";
7069
export type { Checkout } from "./schemas/checkout";
7170
export { CheckoutSchema } from "./schemas/checkout";

0 commit comments

Comments
 (0)