Giter VIP home page Giter VIP logo

mobius's Introduction

GraphQL Mobius

GraphQL to TypeScript type, no code gen with ith Prisma-like query syntax, fully type-safe.

Written purely in TypeScript type.

Brought to you by ElysiaJS

mobius


Mobius can parse GraphQL schema to TypeScript to create End-to-end type safe GraphQL client in TypeScript.

Made possible by Template Literal and various dark magic.

Known Caveat:

  • Comment must not have "{}" (bracket) otherwise type will not be resolved
  • Nested fragment is not supported
  • TypeScript has limited total stack, pass around ~8-9 locs / 14k generated around ~900 types (compacted, only types)

Why

This is a proof that you can run GraphQL with end-to-end type safety.

This is a bare minimum utility library, not intent to replace GraphQL client like URQL and GraphQL Apollo.

Mobius acts as a companion library or internal engine to add Type Safety layer over a new or an existing one.

Mobius does 2 things:

  1. Infers GraphQL to TypeScript types
  2. A bare minimum client that use Prisma-like syntax to query GraphQL

You can use Mobius in your library / framework, just please keep the LICENSE mentioned that you are using GraphQL Mobius (It's MIT License, feels free to fork or improve on it)

Prerequisted

  1. TypeScript > 5.0
  2. Set strict to true in tsconfig.json

Getting Start

  1. Define a GraphQL Schema in string (must be const)
  2. Cast schema to type using typeof (or pass it as literal params in constructor)
import { Mobius } from 'graphql-mobius'

const typeDefs = `
    type A {
        A: String!
        B: String!
    }

    type Query {
        Hello(word: String!): A!
    }
`

const mobius = new Mobius<typeof typeDefs>({
    // Using Mobius default fetch client
    url: 'https://api.saltyaom.com/graphql'
})

// This is also fine, if you don't care about TypeDefs being available on client-side
const mobius2 = new Mobius({
    url: 'https://api.saltyaom.com/graphql'
    typeDefs
})

// Call query to execute query
const result = await mobius.query({
    Hello: {
        where: {
            word: 'Hi'
        },
        select: {
            A: true
        }
    }
})

result
    .then(x => x?.Hello.A)
    .then(console.log)

Mobius Client

Mobius client provided the following method:

  • $: Query all types of query at once
  • query: Query GraphQL
  • mutate: Mutate GraphQL
  • subscription: Subscribe GraphQL

Mobius client provided the following properties:

  • mobius: For type declaration only
  • fragments: Type-safe GraphQL fragments (type is always provided, literal code is available if typeDefs is passed)

Mobius Types

Mobius type extends Record<string, unknown> with the base of following:

  • Query: Record<string, unknown>
  • Mutation: Record<string, unknown>
  • Subscription: Record<string, unknown>
  • Fragment: Record<string, unknown>
  • Rest of the types declarations infers from GraphQL Schema

@see Utility Types for an example usage and more detailed explaination

Scalar

You can add custom scalars type by passing types as second generic.

import { Mobius } from 'graphql-mobius'

const typeDefs = `
    type A {
        A: String!
        B: Date!
    }
`

type Scalars = {
    Data: Date
}

const client = new Mobius<typeof typeDefs>()

client.klein
/**
 * A: {
 *   A: string
 *   B: Date
 * }
 */

If scalars isn't provided but is defined in GraphQL anyway, it should resolved as unknown

Resolvers

You can use Mobius to strictly type Resolvers function for GraphQL Apollo and GraphQL Yoga.

Using Mobius Instance

import { Mobius } from 'graphql-mobius'

const typeDefs = `
    type A {
        A: String!
        B: String!
    }

    type Query {
        Hello(word: String!): A!
    }
`

const mobius = new Mobius({
    typeDefs
})

const resolvers = {
    Query: {
        Hello(_, { word }) {
            return {
                A: "Hello",
                B: "Hello"
            }
        }
    }
} satisfies typeof mobius.resolvers

Using Type Definitions

import type { CreateMobius, Resolvers } from 'graphql-mobius'

const typeDefs = `
    type A {
        A: String!
        B: String!
    }

    type Query {
        Hello(word: String!): A!
    }
`

type Resolver = Resolvers<CreateMobius<typeof typeDefs>>

const resolvers = {
    Query: {
        Hello(_, { word }) {
            return {
                A: "Hello",
                B: "Hello"
            }
        }
    }
} satisfies Resolver

Fragment

You use use mobius.fragment if you provided typeDefs as literal code

Fragment syntax works like rest parameters which looks like GraphQL fragment syntax.

const typeDefs = `
    interface A {
        A: String!
        B: String!
        C: String!
        D: String!
    }

    fragment APart on A {
        A
        B
    }

    type Query {
        GetA: A!
    }
`

const mobius = new Mobius({
    typeDefs
})

const { APart } = mobius.fragments!

mobius.query({
    GetA: {
        ...APart,
        C: true
    }
})

Utility type

For framework, and library author.

You can use exported utilities types from graphql-mobius to achieve End-to-end type safety while not increasing any byte att all for your project's bundle.

import type { CreateMobius } from 'graphql-mobius'

const typeDefs = `
    # Hello World
    type A {
        A: String!
        B: String!
    }

    # Hello World
    type Query {
        Hello(word: String!): A!
    }
`

// This is an equivalent to calling new Mobius().klein
type Engine = CreateMobius<typeof typeDefs>

Structured

CreateMobius will returned type structured as extends Record<string, unknown> the base of following:

  • Query: Record<string, unknown>
  • Mutation: Record<string, unknown>
  • Subscription: Record<string, unknown>
  • Fragment: Record<string, unknown>
  • Rest of the types declarations infers from GraphQL Schema

Others utilities

  • CreateMobius (Type) - Infers GraphQL types to TypeScript
  • Resolver (Type) - Infers GraphQL types to TypeScript
  • RemoveComment (Type) - Remove GraphQL comment in type-level
  • CreateQuery (Type) - Create Prisma-like argument syntax for Client
  • MakeExecutable (Type) - Create Prisma-like function for GraphQL
  • mobiusToGraphQL - Map Prisma-like JSON to GraphQL query (string)
  • createFragment - Create fragments for usage in Prisma-like client

About fetch

As mentioned that Mobius is not intent to replace existing GraphQL client, but designed to create an abstraction over.

Allowing you to integrate with existing library by providing a custom fetcher with the following type:

type Fetcher = (query: string) => Promise<unknown>

Fetch function is a callback function that executed when new request is calls, and will accept a stringified GraphQL query and expected to return a GraphQL response.

It's intent to be use like the following:

// Using URQL
new Mobius({
    fetch: urql.query
})

// Using native fetch (default)
new Mobius({
    fetch: (query) => fetch(this.config.url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            query,
            variables: {}
        }),
    })
    .then((res) => res.json())
})

The library that you want to query GraphQL to use with Mobius is your choice, it's designed to be that way.


GraphQL Mobius is a library to convert GraphQL to TypeScript type without code generation, by using purely TypeScript type.

It's not intent to replace existing GraphQL client, but to create an abstraction over.

You can freely use Mobius in your source code / library / framework, just please keep the original LICENSE (MIT License)

Brought to you by ElysiaJS

mobius's People

Contributors

saltyaom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

mobius's Issues

How to get DateTime to resolve to Date?

I've used a third party package to use DateTime scalars. But Mobius doesn't recognize fields marked as DateTime to be of type Date and they're showing up as "unknown"

Incorrect type generation

CleanShot 2023-10-11 at 22 17 46@2x

Image shows it well.

My schema is:

export const typeDefs = `
"""
De-prioritizes a fragment, causing the fragment to be omitted in the initial response and delivered as a subsequent response afterward.
"""
directive @defer(
  """When true fragment may be deferred"""
  if: Boolean! = true

  """
  This label should be used by GraphQL clients to identify the data from patch responses and associate it with the correct fragment.
  """
  label: String
) on INLINE_FRAGMENT | FRAGMENT_SPREAD

"""Directs the executor to return values as a Streaming response."""
directive @live on QUERY

"""Indicates that an input object is a oneOf input object"""
directive @oneOf on INPUT_OBJECT

type GlobalLink {
  id: String!
  title: String!
  url: String!
  year: String
}

type Mutation {
  createUser(email: String!): String!
  updateLatestGlobalGuide(topicSummary: String!, sections: [section!]!): String!
}

type Query {
  publicGetGlobalTopics: [publicGetGlobalTopicsOutput!]!
  publicGetGlobalTopic(topicName: String!): publicGetGlobalTopicOutput!
  getGlobalLink(linkId: String!): publicGetGlobalLinkOutput!
  getGlobalLinks: getGlobalLinksOutput!
  checkForGlobalLink(linkUrl: String!): publicCheckForGlobalLinkOutput!
  stripe(plan: String!): String!
}

type getGlobalLinksOutput {
  id: String!
  title: String!
  url: String!
}

type globalGuideSection {
  title: String!
  links: [GlobalLink!]!
}

type latestGlobalGuide {
  summary: String!
  sections: [globalGuideSection!]!
}

type publicCheckForGlobalLinkOutput {
  url: String!
  title: String!
  year: Int
  description: String
}

type publicGetGlobalLinkOutput {
  title: String!
  url: String!
  verified: Boolean!
  public: Boolean!
  protocol: String
  fullUrl: String
  description: String
  urlTitle: String
  year: String
}

type publicGetGlobalTopicOutput {
  prettyName: String!
  topicSummary: String!
  topicPath: String
  latestGlobalGuide: latestGlobalGuide
  links: [GlobalLink!]!
}

type publicGetGlobalTopicsOutput {
  prettyName: String!
  name: String!
}

input section {
  title: String!
  summary: String
  linkIds: [String!]!
}

Circular types?

How would you handle something like the following, as far ordering the types? Thanks!

type Comment {
  user: User
  comment: String!
  id: ID!
}

type User {
  comments: [Comment!]!
  id: ID!
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.