1

So i feel like my question is most likely a JS syntax question regarding classes and this but my issue is directly related to graphql-tools class SchemaDirectiveVisitor which is used for creating custom directives in GraphQL.

A little context: When making a custom directive in GraphQL one way is to extend the SchemaDirectiveVisitor class provided by graphql-tools, and override any of their default methods which can be applied to different locations of a gql schema. More specifics can be seen here

In my specific case, i am using the method visitInputFieldDefinition because i want to add some custom logic that will validate whether a user has permission to edit a particular field or not. The main issue with visitInputFieldDefinition is that it only seems to be firing when the server is being built, since it has no resolver function. Meanwhile, other methods such as visitFieldDefinition does have a resolve() function that fires each time there is a new request with this directive.

What im trying to do is store some data in an array when visitInputFieldDefinition is called which is when the server starts up, after it reads my gql schema. I am able to save some data in an array outside of the class, and then use that data in my resolve() function to determine whether to proceed or return an error. I am able to do this and it works, but MY QUESTION is actually on how can I obtain the same behavior but store this data somehow in a var within my class instead of on a global variable outside the class

So my code, simplified, looks something like this

//variable outside my class where i store in memory some data from class
let outsideVar = []

class RestrictFieldsDirective extends SchemaDirectiveVisitor {
    visitFieldDefinition(field) {
        const originalResolve = field.resolve || defaultFieldResolver;

        field.resolve = async function(...args){
            //Do Stuff here with the stored array
            // return error or apply the resolver depending on data in array
            if(outsideVar) //more complicated checks on data
               return new ApolloError()

            return resolve.apply()
        }
    }

    visitInputFieldDefinition(field){
        // Do stuff here to store data provided on application build when this method gets 
        called
        outsideVar.push(someData)

    }
}

As I mentioned, this approach is working fine for me, but i dont like the fact i have a global variable outside the class, when i would like to have it inside. I have tried to store it in a variable within the class, or even in another method within the class, but i have two issues

  1. the class gets called multiple times when starting the server, so even if i manage to store the data within a var at the constructor level, it does not persist through multiple calls, it always gets reset if in constructor() i include something like this.myVar = []

  2. i dont seem to have access to this inside the field.resolve() function, and I can't figure out how to give it access to the class this so that i could either call another method or a class var such as this.myVar

The question is mainly about a best practice, what would be a possible improvement to this that might avoid using an external global variable, or is there simply no way to do that and this approach can be deemed "correct".

Thanks for reading! Any discussion is welcome and useful!

xunux
  • 1,531
  • 5
  • 20
  • 33

1 Answers1

0

Today I learned that the prototype of an object constructed by a class's constructor is the class itself. So some shenanigans with Object.getPrototypeOf lets us store a static property available to all instances of the class:

class Thing {
  constructor(){
    // If we need to initialize the value the first time we ever make a Thing object
    Object.getPrototypeOf(this).staticProp 
      = Object.getPrototypeOf(this).staticProp || 0;
  }
  setStaticProp(val){
    Object.getPrototypeOf(this).staticProp = val;
  }
  getStaticProp() {
    return Object.getPrototypeOf(this).staticProp;
  }
}

const thing1 = new Thing();
const thing2 = new Thing();

console.log(thing1.getStaticProp())

thing1.setStaticProp(42);

console.log(thing2.getStaticProp());

FWIW, proper static fields are coming to JavaScript, but Safari doesn't support them yet, which makes them a tough sell to most folks at this stage.

Cat
  • 4,141
  • 2
  • 10
  • 18