gestalt icon indicating copy to clipboard operation
gestalt copied to clipboard

Resolving connections without relationships?

Open sgwilym opened this issue 6 years ago • 2 comments

Hey! I'm in the process of making a simple, single user blog. I have a Post type, and I'd like to expose a PostsConnection from Session so that I can just grab however many posts I'd like to render a front page (using all the standard first/last/after/etc args). But because Session doesn't have a real presence in the DB, it seems weird to put a @relationship directive on Session — and even then, it doesn't work.

I'd still like to use the gestalt-provided way for resolving a connection, though, so how can I do this from the Session type?

Also, apropos of nothing, I'm floored by the level of care put into this repo, it's so well put together!

sgwilym avatar Sep 27 '17 13:09 sgwilym

Hey Sam - great point, making a relay connection of all of some type feels like a really common use case, and the way you would have to do it now is to define connection and edge types manually, and then define resolution either entirely manually or using graphql-relay-js helpers.

Something like:

type Session {
  posts(first: Number, before: String, last: Number, after: String): PostsConnection
}
type Post implements Node {
  id: ID!
  text: String
}
type PostsConnection {
  edges: [PostEdge]
  pageInfo: PageInfo
}
type PostEdge {
  node: Post
  cursor: String
}
import { connectionFromPromisedArray } from 'graphql-relay';

export default {
  posts: (obj, args, context) =>
     // maybe paginate in a more efficient way here :)
     connectionFromPromisedArray(context.db.query('SELECT * FROM posts')),
};

There are a few ways I can imagine supporting this.

The first is to add an @all field directive, which would generate connection and edge types and add resolution similarly to relationships.

type Session {
  posts: Post @all
}

This feels pretty clean to me, but it falls apart if you want to filter the results (ie: SELECT * FROM posts WHERE published = true) so it might require some kind of filtering to be added to the directive.

type Session {
  posts: Post @all(where: {published: true})
}

The other is to add connection resolution to the gestalt-graphql query helper - something like a connectionBy(table: string, conditions: Object, args: ConnectionArgs): Promise<Connection>. This has the drawback of leaving the syntax up to the database adapter, but keeps the directive API smaller which I think is valuable.

It could maybe be paired w/ auto creation of any referenced connection and edge types, giving us:

type Session {
  posts: PostsConnection
}
export default {
  posts: (obj, args, context) =>
    context.db.connectionBy('posts', { published: true }, args)),
};

What do you think? does one of those feel more friendly than the other? Or do you another idea about how it should work?

charlieschwabacher avatar Oct 01 '17 03:10 charlieschwabacher

There's a couple of 'ideal' APIs I can imagine depending on how much you want. I think there are three scenarios you'd need to cover. The first two you've suggested above already.

  1. The simplest case is if you just need a straight Relay connection with no frills. The nicest way I think would be the closest to straight GraphQL definition:
type Session {
  posts: PostsConnection
}

And that's it.

  1. You want just a straight Relay connection, but want to constrain the returned results by some condition (eg. whether a post is published or not). That would be like your suggestion above:
type Session {
  posts: PostsConnection
}
export default {
  posts: (obj, args, context) =>
    context.db.connectionBy('posts', { published: true }, args)),
};
  1. You want all of the above and you want to add another argument (e.g. to get a post by which category it's in). There are two questions here which are 'how do I define the arguments?' and
type Session {
  posts(catgeory: String): PostConnection
}
export default {
  posts: (obj, args, context) =>
    context.db.connectionBy('posts', { published: true, category: args.category }, args)),
};

So yeah, thinking this through, it feels like your last suggestion combines the best of all worlds, from the simplest to the most complex case - while still giving a nice API for it.

sgwilym avatar Oct 02 '17 09:10 sgwilym