From 495e875887fddd12105d224fe52ebe1f1fae5d99 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 31 May 2024 12:56:36 -0400 Subject: [PATCH 1/5] use constructor.name-based instanceof helper --- src/index.ts | 5 +++-- src/uitl.ts | 17 +++++++++++++++++ src/zod-procedure.ts | 26 +++++++++++++++----------- 3 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 src/uitl.ts diff --git a/src/index.ts b/src/index.ts index bcf1d4b..560c4b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import {type JsonSchema7Type} from 'zod-to-json-schema' import * as zodValidationError from 'zod-validation-error' import {flattenedProperties, incompatiblePropertyPairs, getDescription} from './json-schema' import {lineByLineConsoleLogger} from './logging' +import {looksLikeInstanceof} from './uitl' import {Logger, TrpcCliParams} from './types' import {parseProcedureInputs} from './zod-procedure' @@ -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/uitl.ts b/src/uitl.ts new file mode 100644 index 0000000..672c65e --- /dev/null +++ b/src/uitl.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..ddcc62c 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 './uitl' 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,7 +41,7 @@ export function parseProcedureInputs(inputs: unknown[]): Result return parseLiteralInput(mergedSchema) } - if (mergedSchema instanceof z.ZodTuple) { + if (looksLikeInstanceof(mergedSchema, z.ZodTuple)) { return parseTupleInput(mergedSchema as z.ZodTuple) } @@ -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) + 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 as unknown[]).some(o => target.safeParse(o).success) + if (looksLikeInstanceof(innerType, z.ZodUnion)) return innerType.options.some(test) + if (looksLikeInstanceof(innerType, 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, z.ZodEffects)) return test(innerType.innerType() as z.ZodType) return false } return test From 0386c82060c76bcd8e23dbc540e0d09578bfcd3b Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 31 May 2024 12:57:50 -0400 Subject: [PATCH 2/5] lint --- README.md | 2 +- src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/src/index.ts b/src/index.ts index 560c4b4..122c32f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,8 +7,8 @@ import {type JsonSchema7Type} from 'zod-to-json-schema' import * as zodValidationError from 'zod-validation-error' import {flattenedProperties, incompatiblePropertyPairs, getDescription} from './json-schema' import {lineByLineConsoleLogger} from './logging' -import {looksLikeInstanceof} from './uitl' import {Logger, TrpcCliParams} from './types' +import {looksLikeInstanceof} from './uitl' import {parseProcedureInputs} from './zod-procedure' export * from './types' From 9175ac3b1c55d4ab860f1fcd8048ca6f6ce40437 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 31 May 2024 12:58:19 -0400 Subject: [PATCH 3/5] tyop --- src/index.ts | 2 +- src/{uitl.ts => util.ts} | 0 src/zod-procedure.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{uitl.ts => util.ts} (100%) diff --git a/src/index.ts b/src/index.ts index 122c32f..d4c0a2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +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 './uitl' +import {looksLikeInstanceof} from './util' import {parseProcedureInputs} from './zod-procedure' export * from './types' diff --git a/src/uitl.ts b/src/util.ts similarity index 100% rename from src/uitl.ts rename to src/util.ts diff --git a/src/zod-procedure.ts b/src/zod-procedure.ts index ddcc62c..24ef964 100644 --- a/src/zod-procedure.ts +++ b/src/zod-procedure.ts @@ -1,7 +1,7 @@ import {z} from 'zod' import zodToJsonSchema from 'zod-to-json-schema' import type {Result, ParsedProcedure} from './types' -import {looksLikeInstanceof} from './uitl' +import {looksLikeInstanceof} from './util' function getInnerType(zodType: z.ZodType): z.ZodType { if (looksLikeInstanceof(zodType, z.ZodOptional) || looksLikeInstanceof(zodType, z.ZodNullable)) { From 53acdf686ce6d1f5b5c0c2a32991bbfe31dfc236 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 31 May 2024 12:59:45 -0400 Subject: [PATCH 4/5] 0.3.1-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From f5e7c3287c49529bd28f0369fb7350e7b652660f Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 31 May 2024 14:28:06 -0400 Subject: [PATCH 5/5] less casting --- src/zod-procedure.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zod-procedure.ts b/src/zod-procedure.ts index 24ef964..31ffb43 100644 --- a/src/zod-procedure.ts +++ b/src/zod-procedure.ts @@ -41,8 +41,8 @@ export function parseProcedureInputs(inputs: unknown[]): Result return parseLiteralInput(mergedSchema) } - if (looksLikeInstanceof(mergedSchema, z.ZodTuple)) { - return parseTupleInput(mergedSchema as z.ZodTuple) + if (looksLikeInstanceof>(mergedSchema, z.ZodTuple)) { + return parseTupleInput(mergedSchema) } if (!acceptsObject(mergedSchema)) { @@ -230,12 +230,12 @@ export function accepts(target: ZodTarget) { const innerType = getInnerType(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 as unknown[]).some(o => target.safeParse(o).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.ZodIntersection)) - return test(innerType._def.left as z.ZodType) && test(innerType._def.right as z.ZodType) - if (looksLikeInstanceof(innerType, z.ZodEffects)) return test(innerType.innerType() as z.ZodType) + 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