Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/SaltyAom/mobius

End-to-end type safe TypeScript GraphQL Client
https://github.com/SaltyAom/mobius

graphql graphql-mobius mobius typescript

Last synced: 3 months ago
JSON representation

End-to-end type safe TypeScript GraphQL Client

Awesome Lists containing this project

README

        

# 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](https://elysiajs.com)

![mobius](https://github.com/SaltyAom/mobius/assets/35027979/0bb3291e-49f2-45da-9bcf-3e283ec3cc4d)

---

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)

```ts
import { Mobius } from 'graphql-mobius'

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

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

const mobius = new Mobius({
// 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` with the base of following:
- Query: `Record`
- Mutation: `Record`
- Subscription: `Record`
- Fragment: `Record`
- Rest of the types declarations infers from GraphQL Schema

[@see Utility Types for an example usage and more detailed explaination](#utility-type)

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

```ts
import { Mobius } from 'graphql-mobius'

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

type Scalars = {
Data: Date
}

const client = new Mobius()

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
```ts
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
```ts
import type { CreateMobius, Resolvers } from 'graphql-mobius'

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

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

type Resolver = Resolvers>

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.

```ts
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.

```ts
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
```

### Structured
`CreateMobius` will returned type structured as extends `Record` the base of following:
- Query: `Record`
- Mutation: `Record`
- Subscription: `Record`
- Fragment: `Record`
- 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:
```ts
type Fetcher = (query: string) => Promise
```

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](https://elysiajs.com)