What is the prefered way to add meta-data to some mutation in Slash?

Slash provides many useful mutations by default. But if your use-case is, let’s say, to add some comment or post, and if your backend (Slash) should add timestamp to that mutation by default, what is the preferred way for me to do it, without creating a custom mutation?

If not, this may be interpreted as a proposal.

1 Like

Did you mean amend a field? If that’s the case, wouldn’t updateXXX with the set field work?

You can achieve this by writing a lambda Graphql operation that can wrap over the crud API. You could control created by, created time, updated by, updated time and so on and so forth in the lambda.

1 Like

But doing it right now with lambda removes all of the connectivity. You would have to use a custom mutation and could not add related data at once in a nested graph because the nested children would not go through the custom mutation. So this is not a complete workable solution as it would allow for nodes to be created that did not have the for instance a created time. Hence why we need hooks on top of the existing mutations instead of the ability to just create a custom mutation resolver through lambda

IMO lamdbas need to be thought of as a “Composite API” or “Coarse grained API”. We need to implement lambdas such that all concerns are taken care, i.e. all nodes (parent/child) are appropriately updated. We should be able to roll in all the required mutations (parents, several children etc.) in one lambda call right?

Then I would find myself not using any of the generated mutations and writing everything with custom lambdas. Sort of why we are using Dgraph GraphQL right now, so we don’t have to write custom resolvers.

Consider this schema:

type User {
  username: String @id
  name: String!
  created: DateTime!
  updated: DateTime
  posts: [Post] @hasInverse(field: author)
  comments: [Comment] @hasInverse(field: author)
  assigned: [Tasks] @hasInverse(field: assignee)
  friends: [User] @hasInverse(field: friends)
}
type Post {
  id: ID
  title: String!
  author: User!
  comments: [Comment] @hasInverse(field: onPost)
  replies: [Comment] @hasInverse(field: replyTo)
  revisions: [Post]
  created: DateTime!
  updated: DateTime
}
type Comment {
  id: ID
  content: String!
  onPost: Post
  replyTo: Comment
  revisions: [Comment]
  created: DateTime!
  updated: DateTime
}
type Task {
  id: ID
  content: String!
  due: DateTime!
  assignee: User
  created: DateTime!
  updated: DateTime
}

So of course all of the updated fields would work somewhat well as all using custom lambda mutations. I could then turn off all of the generated updateType mutations with the @generate function. But what about adding a complete subgraph at once. Right now it works with addUser, addPost, addComment, addTask. I can add something complex like this:

from a new User:

const newUser = {
  username: "foo",
  posts: [{
    title: "bar",
    comments: [{
      content: "Lorem Ipsum",
    }]
  }]
  assigned: [{
    content: "Do This",
    due: "2022-01-11"
  }]
}

Or from a new Task:

const newTask = {
  content: "Do This",
  due: "2022-01-11",
  assignee: {
    username: "foo",
    posts: [{
      title: "bar",
      comments: [{
        content: "Lorem Ipsum",
      }]
    }]  
  }
}

Or from a new Post:

const newPost = {
  title: "bar",
  comments: [{
    content: "Lorem Ipsum",
  }]
  author: {
    username: "foo",
    assigned: [{
      content: "Do This",
      due: "2022-01-11"
    }]
  }
}

Or from a new Comment

const newComment = {
  content: "Lorem Ipsum",
  author: {
    username: "foo",
    assigned: [{
      content: "Do This",
      due: "2022-01-11"
    }]
  }
  onPost: {
    title: "bar",
    author: {
      username: "foo"
    }
  }
}

So I would have to create custom add mutations for each of these handling also all of the connected custom add mutations because they all have a required created field that should be filled with a lambda.

Now I want to expand my schema and add a new type such as Tag that does not need to manage created and updated fields, so how do I do that? I would then need to use a custom addTag mutation for it because if I use just the generated addTag then if I added a new tag with any referenced post it would not add the created Field as required.

So here is the need for hooks on generated mutations instead of just the ability to write custom mutation resolvers in a lambda.

Edit:

Furthermore, to make all of these custom addMutations, I would have to rethink disable all of the generated add mutations. But then how would I add the data from a lambda without having the generated graphql add mutations? I guess I could use DQL, but then that bypasses all of the auth rules. So then I find myself not using the auth rules in GraphQL except for query. At this point instead of generated queries and mutations, I would just have generated queries and all of my mutations would be custom resolvers. At what point then do I realize that I am doing more work instead of less and stop using the graphql endpoint and just go and create my own using DQL or some other database. If everything is custom resolved then honestly I lost all the benefit of using the GraphQL endpoint at all.

1 Like

The flow you are proposing is Mutations -> hook -> lambda with security context preserved. This makes sense to me and I have seen such constructs work well in ERP systems. There is certainly good precedent for such an approach.

1 Like

So what you are saying is that I need to create lambda for each mutation I need created timestamp or such primitive customization? I totally agree with @amaster507, we use Slash so we don’t have to write many mutations ourselves, and many use cases are that some timestamp trigger is required. In the worst case, I will end-up with only queries written, and all mutations rewritten which I believe is not the idea of Slash.
I mean, if I have to do half of the work for that, why not do everything? Then you have one project and a free open source database. I know that deployment is also a plus, but I guess, this is not something really hard to do. Some simple directive can do it, and you have many more complex things implemented…

1 Like

I mean some data should not be at clients control. I can do it within a mutation if I understood you correctly, but then, if you use burp and change timestamp to anything you want, that data is not right. Client should not be able to manipulate some data, system should be responsible for such thing. I need to manipulate simple things such as created timestamp or even soft delete using deleted at. If you imagine that I need those kind of things for each node type, I have a serious problem implementing lambda for each mutation…

2 Likes