Mobius
End-to-end type safe TypeScript GraphQL Client
GraphQL to TypeScript type, **no code gen** with ith Prisma-like query syntax, fully type-safe. The project is written primarily in TypeScript, distributed under the MIT License license, first published in 2023. Key topics include: graphql, graphql-mobius, mobius, typescript.
GraphQL Mobius
GraphQL to TypeScript type, no code gen with ith Prisma-like query syntax, fully type-safe.
Written purely in TypeScript type.
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:
- Infers GraphQL to TypeScript types
- 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
- TypeScript > 5.0
- Set
strictto true in tsconfig.json
Getting Start
- Define a GraphQL Schema in string (must be const)
- Cast schema to type using
typeof(or pass it as literal params in constructor)
tsimport { 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
typeDefsis 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.
tsimport { 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
tsimport { 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
tsimport 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.
tsconst 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.
tsimport 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 TypeScriptResolver (Type)- Infers GraphQL types to TypeScriptRemoveComment (Type)- Remove GraphQL comment in type-levelCreateQuery (Type)- Create Prisma-like argument syntax for ClientMakeExecutable (Type)- Create Prisma-like function for GraphQLmobiusToGraphQL- 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:
tstype 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:
ts// 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
Contributors
Showing top 1 contributor by commit count.
