nexus icon indicating copy to clipboard operation
nexus copied to clipboard

excess fields from root resolver not typed

Open villesau opened this issue 2 years ago • 5 comments

The code explains it all:

export const SomeObjectType = objectType({
  name: SomeObject.$name,
  description: SomeObject.$description,
  definition(t) {
    t.field(SomeObject.id);
    t.field(SomeObject.name);
    t.string("url", {
      resolve(root) {
        return `www.url.com/${root.slug}`; // <- id not typed
      },
    });
  },
});

const queries = extendType({
  type: "Query",
  definition: (t) => {
    t.list.field("someObject", {
      type: "SomeObject",
      args: {
        someObjectId: nonNull(stringArg()),
      },
      resolve: (_, { someObjectId }, ctx) => {
        return ctx.prisma.someObject.findMany({ // returns also slug property
          where: {
            someObjectId,
          },
        });
      },
    });
  },
});

When the root resolver returns slug, the field resolver does not know about it. Should I do something differently, or is this valid use case & a bug? I don't want to reveal the slug to the client as is since it's unnecessary. This example is simplified and in reality I'm returning signed url and the data needed for it is not to be shown to the client side.

I'm using nexus-prisma which might affect but I think the issue is in nexus instead.

villesau avatar Dec 27 '21 21:12 villesau

I encounter this same issue, maybe it would be nice to declare private values that are not expose to the client but rather only available on server with their own typesafety

Arturo-Lopez avatar Dec 29 '21 15:12 Arturo-Lopez

Hmm yeah, something like t.private.field(SomeObject.slug); could work. Or then in the resolver:

    t.string("url", {
      requires: [t.field(SomeObject.slug)],
      resolve(root) {
        return `www.url.com/${root.slug}`;
      },
    });

Both are somewhat verbose, but would roughly follow the current conventions.

villesau avatar Dec 29 '21 16:12 villesau

@jasonkuhrt would not want to push this, but this is maybe the biggest issue type wise in nexus at the moment. The types of root variable are virtually always wrong.

villesau avatar Mar 23 '22 15:03 villesau

Maybe something like:

type InputType = {
   id: string;
   name: string,
   slug: string;
}

export const SomeObjectType = objectType<InputType>({
  name: SomeObject.$name,
  description: SomeObject.$description,
  definition(t) {
    t.field(SomeObject.id);
    t.field(SomeObject.name);
    t.string("url", {
      resolve(root) { // <- InputType
        return `www.url.com/${root.slug}`; // <- id typed now!
      },
    });
  },
});

could work? And when you do:

const queries = extendType({
  type: "Query",
  definition: (t) => {
    t.list.field("someObject", {
      type: SomeObjectType,
      args: {
        someObjectId: nonNull(stringArg()),
      },
      resolve: (_, { someObjectId }, ctx) => {
        return {
          smthng: 123 // type error since it does not match the InputType of SomeObjectType
        }
      },
    });
  },
});

This would separate the GraphQL type from the input for that type. There might also be a smoother way to do this, but at least this could be a good MVP.

villesau avatar Mar 23 '22 15:03 villesau

This is my only pain point wit nexus. For now, we've gone with deprecating the private field:

t.string("user_id", { deprecation: `Use "user" instead` });
t.string("user", {
  resolve(root) {
    return getUser(root.user_id);
  },
});

amccloud avatar Sep 09 '22 01:09 amccloud