Typescript error in query resolver

I couldn’t find the answer to this but it’s probably an easy solution.

Here’s a simplified version of my situation →
I have a type defined in my gql schema:

  type Journal {
    id: String!
    title: String!
}

In my service file, I have this query:

export const journal: QueryResolvers['journal'] = ({ id }) => {
  return db.journal.findFirst({
    where: { id },
  })
}

Note that the journal model in my prisma schema does not include a title attribute in this simplified example. Only an id.

In my service file’s query resolver at the bottom of the file, I have this:

export const Journal: JournalRelationResolvers = {
  title: () => 'My Custom Title',
}

The result here is expected. Even tho my prisma model doesn’t include a title, it’s returned from the gql request because of the resolver.

However, there is a typescript error raised here on the query defined above in the service file because title doesn’t exist there.

Error:
Property 'title' is missing in type ....

I’ve re-run the yarn rw g types and still get this TS error. Any way to resolve this error without having to manually fill this field in the query (which sort of defeats the purpose of having the resolver)?

the only solution I found so far is

export const journal: QueryResolvers['journal'] = async ({ id }) => {
  const journal = await db.journal.findFirst({
    where: { id },
  })
  return { ...journal, title: null }
}

Thanks @xmaxcooking!

This is an unfortunate solution for me tho because in strict mode this will still complain since null is not a string. I could of course just return an empty string here I guess but then I’d have to do this for all of the different queries I have that return this (or a list of this) type.

I had assumed there was a solution for this built into RW but maybe there isn’t.

1 Like

would really need to be

type Journal {
    id: String!
    title: String
}

then.

My suggestion was originally from a case where the relationresolvers fetch user metadata from an authentication provider ( the table just stored the userId foreign key but without a user table in prisma).

I fully agree that even though this solution works for relations which aren’t prisma types in some cases… it can quickly become tedious.

Jumping in late and haven’t fully processed but wanted to share Prisma’s new ‘omit’:

Might that help?

One way is to keep the Journal having a required id and then optional title and then enforce requirement based on types, then use

interface Journal {
  id: String
}

type JournalCompleted implements Journal {
  id: String!
  title: String!
}

type JournalSimple implements Journal {
  id: String!
  title: String
}

... or maybe just 

type JournalSimple implements Journal {
  id: String!
}


And then make services for those specific types.

1 Like

I’ve created a simple repository to showcase the typescript issues.

Omitted prisma fields would only help if you ‘removed a field from the sdl which is persisted in prisma and you don’t want the resolvers to handle it’.

We’re trying to ‘add a field to the sdl which isn’t persisted in prisma and we specifically want the resolvers to handle it’.

As far as we know the relation resolvers are the way to do that in redwood.

This approach works very well as you can see in the repository above …
but typescript complains very much about it.

as for the interface… that’d change the sdl spec to an optional field because it then becomes

  type Query { 
    journal(id: Int!): JournalSimple @requireAuth
  }

but if you know that every Journal has a title… and if you query it from the frontend knowing it will never be null or undefined … the api spec should reflect that.

Thanks for the reproduction here @xmaxcooking . This is great.

@dthyresson I appreciate your input above but is there a less heavyhanded way to do this if you know that 100% of the time you’re going to return the required field on the sdl when it doesn’t exist on the model?

An example of why this is helpful/important would be the idea of a computed field. For example, you have a date field and you want to return a relative date string to now (ie. ‘Yesterday’ or ‘5 hours ago’).

Oh - apologies, I misunderstood.

Let me explore this idea as I can off hadn’t think a few ways to accomplish this, but I’ve never actually tried it in a practical way.

  • Prisma client extension computed field (have to see if they release this yet, know they had plans)
  • relation solver
  • service that enriches result
  • transformer directive that enriches a “empty field”

I’ll expose these use case (add others if makes sense):

  • return a relative date string to now
  • based on a title, make a slug on demand (vs transfer and save a slug)

I’ll give it a go this weekend.

@xmaxcooking and @nathanmacfarlane

I just had another realization and will try out on that demo repo … redwood has an alternative api side codegen that Orta made and uses in Puzzmo:

I think switching to it might alleviate some of your problems – since he built it to solve similar problems he encountered.

Have a read and then give:

[experimental]
  useSDLCodeGenForGraphQLTypes = true

And maybe that will help?

@xmaxcooking and @nathanmacfarlane

Here’s the example Post service with sdlcodeGen turned on, yarn rw g types and some type import changes:

No red squiggles type complaining :slight_smile:

This is the new types/posts.d.ts that’s generated:

And when running dev, I made a Post:

and then query query posts:

and then I update …

and now relation resolvers:

Looks good, no?

1 Like

that is definitely exactly what we need. the only thing missing for me is that this doesn’t allow for extending the RedwoodGraphQLContext type.

so I’d need the type

export interface CreatePostResolver {
  (
    args: { input: CreatePostInput },
    obj: {
      root: Mutation
      context: RedwoodGraphQLContext
      info: GraphQLResolveInfo
    }
  ): RTPost | Promise<RTPost> | (() => Promise<RTPost>)
}

to be generated like this instead

export interface CreatePostResolver<TContext extends RedwoodGraphQLContext> {
  (
    args: { input: CreatePostInput },
    obj: {
      root: Mutation
      context: TContext
      info: GraphQLResolveInfo
    }
  ): RTPost | Promise<RTPost> | (() => Promise<RTPost>)
}

to be usable with a type-safe @redwoodjs/realtime context.

Going to take a look at the code if this is achievable sometime soon.

Nice. I’ve wondered if it should not be the default codegen, but then it needs some service template updates for generators.

@orta Do you have some ideas how:

export interface CreatePostResolver<TContext extends RedwoodGraphQLContext> {

might be possible in GitHub - puzzmo-com/sdl-codegen: GraphQL .d.ts file generation for SDL-first projects ?

@dthyresson thanks so much for the help here!

useSDLCodeGenForGraphQLTypes was exactly what I needed. It’s working perfectly. Thanks!

1 Like

As it is an interface, you can re-declare the interface to extend it, here is what I do in Puzzmo:

declare module "@redwoodjs/graphql-server/dist/types" {
  interface RedwoodGraphQLContext {
    gamePlayUserID: string
    environment: "development" | "staging" | "production"
    startRequestTime: number
  }
}

1 Like

How about we move this out of experimental and into the GraphQL toml config?

Yea? No?

So you’ve run into the requirement yourself.

I was mainly suggesting a generic approach because the team is actively adding new functionality to the context object like the realtime livequery and subscriptions. Even third parties like zenstack used it to simplify code passthroughs and I can’t really imagine them shipping a setup with that kind of typescript ‘patch’ … and there may be others like them in the future.

zenstack source: New "How To" for implementing flexible authorization

Is there a reason why you didn’t choose a more generic approach here?
Is it just not possible?

I would assume this should work fine for them too

If a generic is only extending the base of the graphql context though via RedwoodGraphQLContext then the right answer really is to extend the interface, this is similar to how CurrentUser is done in Redwood:

declare module '@redwoodjs/context' {
  interface GlobalContext {
    currentUser?: Overwrite<UndefinedRoles, InferredCurrentUser>
  }
}

But I’m open to PRs showing showing what you think might work for them too.

I’m still a bit concerned about potential conflicts if multiple libraries start extending the context, but I appreciate your openness.

While this approach seems common in the JavaScript/TypeScript ecosystem, I’m still a bit hesitant through my .NET background.

You’re right in pointing out this pattern in redwoodjs. So it may just be my taste in the end. I’ll check if a PR is feasible.

Currently the default type generators types can be extended like this:

type ExtendedGraphQLContext = RedwoodGraphQLContext & { gamePlayUserID: string }

export const createPost: MutationResolvers<ExtendedGraphQLContext>['createPost'] =
  ({ input }, request) => {
    console.log(request.context?.gamePlayUserID)
    return db.post.create({
      data: input,
    })
  }

and by modifying the service generator you wouldn’t really need to manually write out the extended type each time.

nevermind. they actually shipped it with module declaration and ran into issues.