diff --git a/js/util/zod_util.ts b/js/util/zod_util.ts index 843223ac5..1ae290ffc 100644 --- a/js/util/zod_util.ts +++ b/js/util/zod_util.ts @@ -1,6 +1,15 @@ import { z } from "zod/v3"; import { forEachMissingKey } from "./object_util"; +type NullishObjectShape = { + [k in keyof T]: z.ZodOptional>; +}; + +type NullishObject = z.ZodObject< + NullishObjectShape, + z.UnknownKeysParam +>; + export class ExtraFieldsError extends Error { constructor( public readonly key: string, @@ -51,20 +60,20 @@ export function parseNoStrip(schema: T, input: unknown) { // // Basically the same as `z.partial()`, except instead of marking fields just // optional, it marks them nullish. -export function objectNullish< - T extends z.ZodRawShape, - UnknownKeys extends z.UnknownKeysParam, - Catchall extends z.ZodTypeAny, ->(object: z.ZodObject) { - return new z.ZodObject({ - ...object._def, - shape: () => - Object.fromEntries( - Object.entries(object.shape).map(([k, v]) => [k, v.nullish()]), - ), - }) as z.ZodObject< - { [k in keyof T]: z.ZodOptional> }, - UnknownKeys, - Catchall - >; +export function objectNullish( + object: z.ZodObject, +): NullishObject { + const originalShape = object.shape as Record; + const newShape: Record = {}; + for (const [k, v] of Object.entries(originalShape)) { + newShape[k] = v.nullish(); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (typeof (object as any).extend === "function") { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (object as any).extend(newShape) as unknown as NullishObject; + } + + return z.object(newShape) as unknown as NullishObject; }