graphql-nexus / nexus

Code-First, Type-Safe, GraphQL Schema Construction
https://nexusjs.org
MIT License
3.4k stars 274 forks source link

Add ability to extend/wrap/apply modifications to output schema in plugin hook #959

Open roman-vanesyan opened 3 years ago

roman-vanesyan commented 3 years ago

As far as I understand it isn't possible to update/apply middlewares to generate schema right in the plugin.

To better cover the requested feature I'd like to provide a small example: I'd like to use graphql-shield package to add authorization logic to my GraphQL layer and I'd like to define graphql-shield rules right in the mutationField and queryField calls under the shield field, for instance:

export const createWorkspace = mutationField("createWorkspace", {
  type: "Workspace",
  args: {
    name: nonNull(stringArg()),
    description: stringArg(),
  },

  shield: isAuthenticated,

  resolve: (_, args, { workspace, currentUser }) =>
    workspace.createWorkspace({ userId: currentUser!.id, ...args }),
});

The primary use of graphql-shield as middleware applied by graphql-middleware package (to better understand how graphql-shield is working please checkout official doc page: https://www.graphql-shield.com/docs). So normally to apply graphql-shield to schema we need to do:

const permissions = shield({
  Mutation: {
    createWorkspace: isAuthenticated,
  }
})

// later
schema = applyMiddleware(schema, permissions);

So as it can be seen because graphql-shield applies middleware to the schema I need to somehow to apply it to the generated schema by nexus and taking into the account that I'd like to define shield permissions right on the nexus schema definition I need a way to apply modifications in the plugin to the final schema. So imagine onAfterBuild plugin hook would allow us to provide a custom schema, then something like this would be possible:

export const shieldPlugin = (): NexusPlugin => {
  let rules;

  return plugin({
    name: "ShieldPlugin",
    description: "Add support for graphql-shield.",
    fieldDefTypes,
    onBeforeBuild() {
      rules = Object.create(null);
    },
    onAddOutputField(field) {
      if (!(field.parentType in rules)) {
        rules[field.parentType] = {};
      }

      if (!isNil(field.shield)) {
        rules[field.parentType][field.name] = field.shield;
      }
    },
    onAfterBuild(schema) {
      const {schema: ret } = applyMiddleware(schema, shield(rules));
      return ret;
    },
  });
};

As it already been said, I propose to add ability to onAfterBuild plugin hook to return a new modified schema.

tgriesser commented 3 years ago

I think this seems like a reasonable thing to add.

Basically you want to change the signature to onAfterBuild(schema): GraphQLSchema | void, where if a new schema is returned, it's used?

So:

onAfterBuildFns.forEach((fn) => fn(schema))

return { schema, missingTypes, finalConfig }

becomes:

let finalSchema = schema
onAfterBuildFns.forEach((fn) => {
  finalSchema = fn(finalSchema) ?? finalSchema
})

return { schema: finalSchema, missingTypes, finalConfig }

Want to open a PR?