From a3140da9bcd342a49e6e6e8ade8814776a404f7a Mon Sep 17 00:00:00 2001 From: Misha Kaletsky <15040698+mmkal@users.noreply.github.com> Date: Sun, 2 Jun 2024 10:16:19 -0400 Subject: [PATCH] Zod esm fix (#8) --- README.md | 2 +- package.json | 2 +- src/index.ts | 5 +++-- src/util.ts | 17 +++++++++++++++++ src/zod-procedure.ts | 30 +++++++++++++++++------------- 5 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 src/util.ts diff --git a/README.md b/README.md index b00bd97..47f9e6a 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ The above can be invoked with either `yarn` or `yarn install`. ### API docs -#### [trpcCli](./src/index.ts#L29) +#### [trpcCli](./src/index.ts#L30) Run a trpc router as a CLI. diff --git a/package.json b/package.json index 63eed10..ea39cdd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "trpc-cli", - "version": "0.3.0", + "version": "0.3.1-0", "description": "Turn a tRPC router into a type-safe, fully-functional, documented CLI", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index bcf1d4b..d4c0a2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import * as zodValidationError from 'zod-validation-error' import {flattenedProperties, incompatiblePropertyPairs, getDescription} from './json-schema' import {lineByLineConsoleLogger} from './logging' import {Logger, TrpcCliParams} from './types' +import {looksLikeInstanceof} from './util' import {parseProcedureInputs} from './zod-procedure' export * from './types' @@ -181,9 +182,9 @@ export const trpcCli = ({router, ...params}: TrpcCliParams< type Fail = (message: string, options?: {cause?: unknown; help?: boolean}) => never function transformError(err: unknown, fail: Fail): unknown { - if (err instanceof TRPCError) { + if (looksLikeInstanceof(err, TRPCError)) { const cause = err.cause - if (cause instanceof ZodError) { + if (looksLikeInstanceof(cause, ZodError)) { const originalIssues = cause.issues try { cause.issues = cause.issues.map(issue => { diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..672c65e --- /dev/null +++ b/src/util.ts @@ -0,0 +1,17 @@ +/** + * Pretty much like the `instanceof` operator, but should work across different realms. Necessary for zod because some installations + * might result in this library using the commonjs zod export, while the user's code uses the esm export. + * https://github.com/mmkal/trpc-cli/issues/7 + * + * Tradeoff: It's possible that this function will return false positives if the target class has the same name as an unrelated class in the current realm. + * So, only use it for classes that are unlikely to have name conflicts like `ZodAbc` or `TRPCDef`. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const looksLikeInstanceof = (value: unknown, target: new (...args: any[]) => T): value is T => { + let current = value?.constructor + do { + if (current?.name === target.name) return true + current = Object.getPrototypeOf(current) as Function + } while (current?.name) + return false +} diff --git a/src/zod-procedure.ts b/src/zod-procedure.ts index 01ea20a..31ffb43 100644 --- a/src/zod-procedure.ts +++ b/src/zod-procedure.ts @@ -1,12 +1,13 @@ import {z} from 'zod' import zodToJsonSchema from 'zod-to-json-schema' import type {Result, ParsedProcedure} from './types' +import {looksLikeInstanceof} from './util' function getInnerType(zodType: z.ZodType): z.ZodType { - if (zodType instanceof z.ZodOptional || zodType instanceof z.ZodNullable) { + if (looksLikeInstanceof(zodType, z.ZodOptional) || looksLikeInstanceof(zodType, z.ZodNullable)) { return getInnerType(zodType._def.innerType as z.ZodType) } - if (zodType instanceof z.ZodEffects) { + if (looksLikeInstanceof(zodType, z.ZodEffects)) { return getInnerType(zodType.innerType() as z.ZodType) } return zodType @@ -20,7 +21,9 @@ export function parseProcedureInputs(inputs: unknown[]): Result } } - const allZodTypes = inputs.every(input => input instanceof z.ZodType) + const allZodTypes = inputs.every(input => + looksLikeInstanceof(input, z.ZodType as new (...args: unknown[]) => z.ZodType), + ) if (!allZodTypes) { return { success: false, @@ -38,8 +41,8 @@ export function parseProcedureInputs(inputs: unknown[]): Result return parseLiteralInput(mergedSchema) } - if (mergedSchema instanceof z.ZodTuple) { - return parseTupleInput(mergedSchema as z.ZodTuple) + if (looksLikeInstanceof>(mergedSchema, z.ZodTuple)) { + return parseTupleInput(mergedSchema) } if (!acceptsObject(mergedSchema)) { @@ -222,16 +225,17 @@ const parameterName = (s: z.ZodType, position: number) => { * acceptsString(z.intersection(z.string(), z.number())) // false * acceptsString(z.intersection(z.string(), z.string().max(10))) // true */ -export function accepts(target: z.ZodType) { +export function accepts(target: ZodTarget) { const test = (zodType: z.ZodType): boolean => { const innerType = getInnerType(zodType) - if (innerType instanceof target.constructor) return true - if (innerType instanceof z.ZodLiteral) return target.safeParse(innerType.value).success - if (innerType instanceof z.ZodEnum) return (innerType.options as unknown[]).some(o => target.safeParse(o).success) - if (innerType instanceof z.ZodUnion) return (innerType.options as z.ZodType[]).some(test) - if (innerType instanceof z.ZodIntersection) - return test(innerType._def.left as z.ZodType) && test(innerType._def.right as z.ZodType) - if (innerType instanceof z.ZodEffects) return test(innerType.innerType() as z.ZodType) + if (looksLikeInstanceof(innerType, target.constructor as new (...args: unknown[]) => ZodTarget)) return true + if (looksLikeInstanceof(innerType, z.ZodLiteral)) return target.safeParse(innerType.value).success + if (looksLikeInstanceof(innerType, z.ZodEnum)) return innerType.options.some(o => target.safeParse(o).success) + if (looksLikeInstanceof(innerType, z.ZodUnion)) return innerType.options.some(test) + if (looksLikeInstanceof>(innerType, z.ZodEffects)) return test(innerType.innerType()) + if (looksLikeInstanceof>(innerType, z.ZodIntersection)) + return test(innerType._def.left) && test(innerType._def.right) + return false } return test