Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zod esm fix #8

Merged
merged 5 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ The above can be invoked with either `yarn` or `yarn install`.
### API docs

<!-- codegen:start {preset: markdownFromJsdoc, source: src/index.ts, export: trpcCli} -->
#### [trpcCli](./src/index.ts#L29)
#### [trpcCli](./src/index.ts#L30)

Run a trpc router as a CLI.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -181,9 +182,9 @@ export const trpcCli = <R extends AnyRouter>({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 => {
Expand Down
17 changes: 17 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -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 = <T>(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
}
30 changes: 17 additions & 13 deletions src/zod-procedure.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -20,7 +21,9 @@ export function parseProcedureInputs(inputs: unknown[]): Result<ParsedProcedure>
}
}

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,
Expand All @@ -38,8 +41,8 @@ export function parseProcedureInputs(inputs: unknown[]): Result<ParsedProcedure>
return parseLiteralInput(mergedSchema)
}

if (mergedSchema instanceof z.ZodTuple) {
return parseTupleInput(mergedSchema as z.ZodTuple<never>)
if (looksLikeInstanceof<z.ZodTuple<never>>(mergedSchema, z.ZodTuple)) {
return parseTupleInput(mergedSchema)
}

if (!acceptsObject(mergedSchema)) {
Expand Down Expand Up @@ -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<ZodTarget extends z.ZodType>(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<z.ZodEffects<z.ZodType>>(innerType, z.ZodEffects)) return test(innerType.innerType())
if (looksLikeInstanceof<z.ZodIntersection<z.ZodType, z.ZodType>>(innerType, z.ZodIntersection))
return test(innerType._def.left) && test(innerType._def.right)

return false
}
return test
Expand Down
Loading