KevinEdry / nestjs-trpc

Providing native support for decorators and implement an opinionated approach that aligns with NestJS conventions.
https://NestJS-tRPC.io
MIT License
71 stars 6 forks source link

generated types are not resolved correctly when using methods like `pick` #5

Closed Mnigos closed 1 month ago

Mnigos commented 1 month ago

user.schema.ts

export const userSchema = z.object({
  id: z.string(),
  name: z.string(),
  createdAt: z.date(),
})

export const createUserSchema = userSchema.pick({ name: true })

user.router.ts

@Router({ alias: 'user' })
export class UsersRouter {
  constructor(private readonly prisma: PrismaService) {}

  @Query({ input: createUserSchema, output: userSchema })
  async login(@Input('name') name: string) {
    const foundUser = await this.prisma.user.findUnique({
      where: {
        name,
      },
    })

    if (foundUser) return foundUser

    return this.prisma.user.create({
      data: {
        name,
      },
    })
  }

  @Query({ input: z.string(), output: userSchema })
  async byName(@Input() name: string) {
    const foundUser = await this.prisma.user.findUnique({
      where: {
        name,
      },
    })

    if (!foundUser) throw new TRPCError({ code: 'NOT_FOUND' })

    return foundUser
  }
}
Screenshot 2024-09-27 at 12 16 30
KevinEdry commented 1 month ago

Thanks for opening this issue with so much details, im on it!

KevinEdry commented 1 month ago

So the expected behavior is that it should flatten the userSchema to

z.object({
  id: z.string(),
  name: z.string(),
  createdAt: z.date()}).pick...

Which isn't happening, I'm still checking to see why.

Mnigos commented 1 month ago

Thanks for opening this issue with so much details, im on it!

Not long ago I was also thinking on creating package like that so I'm really glad that you've created it and that you're contributing to it actively.

Mnigos commented 1 month ago

So the expected behavior is that it should flatten the userSchema to

z.object({
  id: z.string(),
  name: z.string(),
  createdAt: z.date()}).pick...

Which isn't happening, I'm still checking to see why.

exactly

Mnigos commented 1 month ago

@KevinEdry Your pr fixes pick usage, but when i use nested objects schema it still doesn't resolve correctly.

thought.schema.ts

export const thoughtSchema = z.object({
  id: z.string(),
  content: z.string(),
  author: userSchema,
  createdAt: z.date(),
})
Screenshot 2024-09-28 at 19 24 27
KevinEdry commented 1 month ago

Thanks for bringing it to my attention, it should work for nested, let me push a fix for it.

KevinEdry commented 1 month ago

Yup, it seems that when its from a different file, it doesn't work. The zod schema flattening functionality is the most difficult one to fix and update, but I am on it 👍.

KevinEdry commented 1 month ago

The 1.3.2 release should have fixed this, can you confirm?

Mnigos commented 1 month ago

Unfortunately no, I updated nestjs-trpc to 1.3.2, but issue still occurs.

This is repo btw. -> https://github.com/Mnigos/share-your-thought

KevinEdry commented 1 month ago

Unfortunately no, I updated nestjs-trpc to 1.3.2, but issue still occurs.

This is repo btw. -> https://github.com/Mnigos/share-your-thought

Oh you are using barrel files! Try importing it from the actual file, skipping the index.ts barrel file. I need to think of an elegant solution to deal with barrel files.

The functionality for flattening the zod schema takes the current file that it is on, and creates an import map for it (what are you importing from where) and when it wants to flatten the imported schema, it goes to the file you referenced in in the import statement.

Because you are using barrel files, the import file you specified technically doesn't mention anything about the imported schema.

I can go on an search every file referenced in the barrel file, but thats highly inefficient 🤔

Mnigos commented 1 month ago

Unfortunately no, I updated nestjs-trpc to 1.3.2, but issue still occurs. This is repo btw. -> https://github.com/Mnigos/share-your-thought

Oh you are using barrel files! Try importing it from the actual file, skipping the index.ts barrel file. I need to think of an elegant solution to deal with barrel files.

The functionality for flattening the zod schema takes the current file that it is on, and creates an import map for it (what are you importing from where) and when it wants to flatten the imported schema, it goes to the file you referenced in in the import statement.

Because you are using barrel files, the import file you specified technically doesn't mention anything about the imported schema.

I can go on an search every file referenced in the barrel file, but thats highly inefficient 🤔

Not the best solution, but I guess I will have to do it like that for now.

KevinEdry commented 1 month ago

@Mnigos the latest release should solve the barrel files issue.

KevinEdry commented 1 month ago

It closes the issue automatically when I merge the branch, can you confirm that this worked?

Mnigos commented 1 month ago

Yes, now it works perfectly!

Mnigos commented 1 month ago

Yes, now it works perfectly!

@KevinEdry Or not. I was pretty sure that generated types were resolved correctly, but now I'm running this application and it still contains unresolved userSchema inside even without barrel files. I don't know why i thought that this was resolved, maybe vscode just didn't show me the errors and I just took a quick look.

KevinEdry commented 1 month ago

Do you have the "sourceMap": true option enabled in your tsconfig?

Mnigos commented 1 month ago

I didn't, I added it right now, but the issue still exist.

Mnigos commented 1 month ago

thought.schema.ts

import { userSchema } from '~/users/user.schema'

export const thoughtSchema = z.object({
  id: z.string(),
  content: z.string(),
  author: userSchema,
  createdAt: z.date(),
})

server.ts

import { initTRPC } from "@trpc/server";
import { z } from "zod";

const t = initTRPC.create();
const publicProcedure = t.procedure;

const appRouter = t.router({
  thoughts: t.router({
    all: publicProcedure.output(z.array(z.object({
      id: z.string(),
      content: z.string(),
      author: userSchema,
      createdAt: z.date(),
    }))).query(async () => "PLACEHOLDER_DO_NOT_REMOVE" as any)
  }),
});
export type AppRouter = typeof appRouter;

tsconfig.json

{
  "extends": "@repo/typescript-config/nestjs.json",
  "compilerOptions": {
      "esModuleInterop": true,
    "skipLibCheck": true,
    "allowJs": true,
    "target": "ES2022",
    "lib": ["ESNext"],
    "resolveJsonModule": true,
    "moduleDetection": "force",
    "isolatedModules": true,
    "verbatimModuleSyntax": true,

    "strict": true,
    "strictNullChecks": true,
    "strictBindCallApply": false,
    "strictPropertyInitialization": false,
    "noUncheckedIndexedAccess": true,
    "noImplicitAny": true,
    "noImplicitOverride": true,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false,

    "declaration": true,
    "declarationMap": true,
    "removeComments": true

    "module": "ESNext",
    "moduleResolution": "Bundler",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "incremental": true,
    "sourceMap": true,
    "outDir": "./dist",
    "types": ["vitest/globals"],
    "paths": {
      "~/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}
KevinEdry commented 1 month ago

Yeah I don't think I'm handling this case:

    "paths": {
      "~/*": ["./src/*"]
    }

I'm looking at your imports, and if they are obscured by a the tsconfig path I can't resolve them.

Mnigos commented 1 month ago

Yeah I don't think I'm handling this case:

    "paths": {
      "~/*": ["./src/*"]
    }

I'm looking at your imports, and if they are obscured by a the tsconfig path I can't resolve them.

You're right it works without path alias. But i would love to use path aliases with this library since they're making my imports more clear.

Maybe you can add property in TRPCModule options to configure custom aliases like this.

@Module({
  imports: [
    TRPCModule.forRoot({
      autoSchemaFile: '.',
      alias: [
        {
          find: '~',
          replacement: fileURLToPath(new URL('src', import.meta.url)),
        },
      ],
    }),
  ],
})
export class AppModule {}

This is how it's done in vitest config.

KevinEdry commented 1 month ago

can you open a new issue for this? I will try to get to it.