getcronit / pylon

The next generation of building GraphQL APIs
https://pylon.cronit.io
Apache License 2.0
76 stars 2 forks source link

Interfaces & Union support #33

Open schettn opened 10 hours ago

schettn commented 10 hours ago

Is your feature request related to a problem? Please describe. Interfaces & Unions are currently not fully supported.

Describe the solution you'd like

import {app, ID} from '@getcronit/pylon'

type Node = User | Post

interface User {
  id: ID
  name: string
}

interface Post {
  id: ID
  title: string
}

const nodes: Node[] = [
  {
    id: '1',
    name: 'John Doe'
  },
  {
    id: '2',
    name: 'Jane Doe'
  },
  {
    id: '3',
    title: 'Hello, World!'
  },
  {
    id: '4',
    title: 'Hello, Pylon!'
  }
]

export const graphql = {
  Query: {
    post: (id: ID) => {
      return nodes.find(node => node.id === id && 'title' in node) as Post
    },
    user: (id: ID) => {
      return nodes.find(node => node.id === id && 'name' in node) as User
    },
    node: (id: ID): Node => {
      const node = nodes.find(node => node.id === id)

      if (!node) {
        throw new Error('Node not found')
      }

      return node
    }
  },
  Mutation: {}
}

export default app

This will result in the following schema:

interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  name: String!
}

type Post implements Node {
  id: ID!
  title: String!
}

type Query {
  post(id: ID!): Post
  user(id: ID!): User
  node(id: ID!): Node
}

And can be queried like this:

{
  node(id: "1") {
    __typename
    id
    ... on User {
      name
    }
    ... on Post{
      title
    }
  }
}

Whenever you use a TypeScript union like type Foo = Bar | Baz Pylon will create a Foo interface that is implemented by Bar and Baz. The __resolveType function that you would typically need to support interfaces, for example when using apollo server , is not required. Pylon will automatically resolve the correct type for you.

Whenever there are shared properties in a union, Pylon will create a interface for you.

type Bar = {
  id: ID
  title: string
}

type Foo = {
  id: ID
  name: string
}

type Example = Foo | Bar

This will generate the following GraphQL schema:

interface Example {
  id: ID!
}

type Foo implements Example {
  id: ID!
  name: String!
}

type Bar implements Example {
  id: ID!
  title: String!
}

But if the properties are not shared, Pylon will create a union type for you.

type Foo = {
  birthday: Date
}

type Bar = {
  age: number
}

type Example = Foo | Bar

This will generate the following GraphQL schema:

type Foo {
  birthday: Date
}

type Bar {
  age: Int
}

union Example = Foo | Bar
interface NodeInterface {
  id: string
}

class NodeC1lass implements NodeInterface {
  constructor(public id: string) {}
}

class NodeC2lass implements NodeInterface {
  constructor(public id: string) {}

  public extra = 'extra'
}

const nodes = [
  new NodeC1lass('1'),
  new NodeC2lass('2'),
]

export const graphql = {
  Query: {
    node: (id: ID) => {
      return nodes.find(node => node.id === id)
    }
  }
}

Even this will work through Pylons type inference and name generation:

const nodes = [
  {
    id: '1',
    name: 'John Doe'
  },

  {
    id: '4',
    title: 'Hello, Pylon!'
  }
]

export const graphql = {
  Query: {
    node: (id: ID) => {
      const node = nodes.find(node => node.id === id)

      return node
    }
  }
}

This will generate a interface and two types with generated names

The graphql specs allow for mulitple implements

type Node implement Foo & Bar {}

This would be the equivalent to:

interface Foo {
  id: string
  name: string
}

interface Bar {
  id: string
  title: string
}

class Node implements Foo, Bar {
  constructor(public id: string, public name: string, public title: string) {}
}
schettn commented 10 hours ago

https://discord.com/channels/1270327745662029854/1293210010880315392