├── .vitepress ├── theme │ ├── index.mjs │ └── custom.css └── config.mjs ├── www ├── docs │ ├── comparisons.md │ ├── integration │ │ ├── client │ │ │ ├── urql.md │ │ │ ├── fetch.md │ │ │ ├── vue-apollo.md │ │ │ └── gqty.md │ │ ├── examples │ │ │ ├── nuxt.md │ │ │ ├── remix.md │ │ │ └── nextjs.md │ │ ├── server │ │ │ ├── mercurius.md │ │ │ ├── apollo-server.md │ │ │ └── graphql-yoga.md │ │ └── chatgpt.md │ ├── advanced │ │ ├── auth.md │ │ ├── federation.md │ │ ├── extending-garph.md │ │ ├── default-nullability.md │ │ ├── file-uploads.md │ │ ├── pagination.md │ │ ├── subscriptions.md │ │ ├── relay.md │ │ ├── errors.md │ │ ├── context.md │ │ ├── validation.md │ │ ├── testing.md │ │ └── defer-stream.md │ ├── guide │ │ ├── migrate.md │ │ ├── resolvers.md │ │ ├── inferring-types.md │ │ ├── loaders.md │ │ └── schemas.md │ └── index.md ├── api │ ├── .nojekyll │ ├── classes │ │ ├── Type.md │ │ └── GarphSchema.md │ └── index.md ├── public │ ├── icons │ │ ├── chevron-right.svg │ │ ├── star.svg │ │ ├── typescript.svg │ │ ├── react.svg │ │ ├── zap.svg │ │ └── graphql.svg │ └── logo │ │ ├── r5m.svg │ │ ├── r6N.svg │ │ ├── r5y.svg │ │ └── r6p.svg └── index.md ├── tsconfig.test.json ├── tsconfig.json ├── examples ├── scalars.ts ├── client.ts ├── promo.ts ├── context.ts ├── errors.ts ├── uploads.ts ├── validation.ts ├── extend.ts ├── implements.ts ├── subscriptions.ts ├── pagination.ts ├── loader.ts ├── relay.ts ├── union.ts ├── demo.ts └── deferstream.ts ├── src ├── utils.ts ├── client.ts ├── schema.ts └── index.ts ├── LICENSE ├── package.json ├── .github └── workflows │ └── www.yml ├── README.md └── .gitignore /.vitepress/theme/index.mjs: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | import './custom.css' 3 | 4 | export default DefaultTheme 5 | -------------------------------------------------------------------------------- /www/docs/comparisons.md: -------------------------------------------------------------------------------- 1 | # Comparisons 2 | 3 | ## tRPC 4 | 5 | ## Blitz.js 6 | 7 | ## Pothos 8 | 9 | ## graphql-codegen 10 | -------------------------------------------------------------------------------- /www/docs/integration/client/urql.md: -------------------------------------------------------------------------------- 1 | # urql 2 | 3 | This is a stub article. Help writing it by clicking "Edit this page on GitHub" 4 | -------------------------------------------------------------------------------- /www/docs/integration/examples/nuxt.md: -------------------------------------------------------------------------------- 1 | # Nuxt 2 | 3 | This is a stub article. Help writing it by clicking "Edit this page on GitHub" 4 | -------------------------------------------------------------------------------- /www/docs/integration/examples/remix.md: -------------------------------------------------------------------------------- 1 | # Remix 2 | 3 | This is a stub article. Help writing it by clicking "Edit this page on GitHub" 4 | -------------------------------------------------------------------------------- /www/docs/integration/client/fetch.md: -------------------------------------------------------------------------------- 1 | # Fetch API 2 | 3 | This is a stub article. Help writing it by clicking "Edit this page on GitHub" 4 | -------------------------------------------------------------------------------- /www/docs/advanced/auth.md: -------------------------------------------------------------------------------- 1 | # Auth 2 | 3 | This feature is currently work in progress, see: 4 | 5 | https://github.com/stepci/garph/issues/51 6 | -------------------------------------------------------------------------------- /www/docs/integration/client/vue-apollo.md: -------------------------------------------------------------------------------- 1 | # Vue Apollo 2 | 3 | This is a stub article. Help writing it by clicking "Edit this page on GitHub" 4 | -------------------------------------------------------------------------------- /www/docs/integration/server/mercurius.md: -------------------------------------------------------------------------------- 1 | # Mercurius 2 | 3 | This is a stub article. Help writing it by clicking "Edit this page on GitHub" 4 | -------------------------------------------------------------------------------- /www/docs/integration/server/apollo-server.md: -------------------------------------------------------------------------------- 1 | # Apollo Server 2 | 3 | This is a stub article. Help writing it by clicking "Edit this page on GitHub" 4 | -------------------------------------------------------------------------------- /www/docs/advanced/federation.md: -------------------------------------------------------------------------------- 1 | # Federation 2 | 3 | This feature is currently work in progress, see: 4 | 5 | https://github.com/stepci/garph/issues/26 6 | -------------------------------------------------------------------------------- /www/api/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /www/docs/integration/chatgpt.md: -------------------------------------------------------------------------------- 1 | # ChatGPT Plugin 2 | 3 | This feature is currently work in progress, see: 4 | 5 | https://github.com/stepci/garph/issues/52 6 | -------------------------------------------------------------------------------- /www/docs/advanced/extending-garph.md: -------------------------------------------------------------------------------- 1 | # Extending Garph 2 | 3 | This feature is currently work in progress, see: 4 | 5 | https://github.com/stepci/garph/issues/50 6 | -------------------------------------------------------------------------------- /www/docs/advanced/default-nullability.md: -------------------------------------------------------------------------------- 1 | # Default Nullability 2 | 3 | This feature is currently work in progress, see: 4 | 5 | https://github.com/stepci/garph/issues/5 6 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "skipLibCheck": true, 6 | }, 7 | "include": ["src/**/*.ts", "examples/**/*.ts"], 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "declaration": true, 5 | "lib": ["ESNext", "DOM"], 6 | "module": "NodeNext", 7 | "target": "ESNext", 8 | "moduleResolution": "NodeNext" 9 | }, 10 | "include": ["src/**/*.ts"], 11 | } 12 | -------------------------------------------------------------------------------- /www/public/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/public/icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /www/docs/guide/migrate.md: -------------------------------------------------------------------------------- 1 | # Migrate to Garph 2 | 3 | With our new migration tool, you can convert your legacy GraphQL SDL into a fully-typed Garph schema. 4 | 5 | ![GraphQL to Garph](https://i.imgur.com/csjD7X1.png) 6 | 7 | [→ gql2garph.vercel.app](https://gql2garph.vercel.app) 8 | 9 | Simply paste your GraphQL SDL in the left editor and you should see the result in the right pane. 10 | -------------------------------------------------------------------------------- /www/docs/integration/examples/nextjs.md: -------------------------------------------------------------------------------- 1 | # Next.JS 2 | 3 | A fully-featured demo app built using Garph + GQty, Next.js 13 (app directory) served by Yoga on Edge 4 | 5 | [→ Demo](https://next-garph.vercel.app) 6 | 7 | [→ Repository](https://github.com/mishushakov/next-garph) 8 | 9 | ## Installation 10 | 11 | 1. Clone the repository 12 | 13 | ```sh 14 | git clone https://github.com/mishushakov/next-garph 15 | ``` 16 | 17 | 2. Install the dependencies 18 | 19 | ```sh 20 | npm i 21 | ``` 22 | 23 | 3. Start the development server 24 | 25 | ```sh 26 | npm run dev 27 | ``` 28 | -------------------------------------------------------------------------------- /examples/scalars.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const date = g.scalarType('Test') 6 | 7 | const queryType = g.type('Query', { 8 | today: date 9 | }) 10 | 11 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 12 | Query: { 13 | today: () => new Date() 14 | } 15 | } 16 | 17 | const schema = buildSchema({ g, resolvers }) 18 | const yoga = createYoga({ schema }) 19 | const server = createServer(yoga) 20 | server.listen(4000, () => { 21 | console.info('Server is running on http://localhost:4000/graphql') 22 | }) 23 | -------------------------------------------------------------------------------- /www/public/icons/typescript.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/client.ts: -------------------------------------------------------------------------------- 1 | import { g } from '../src' 2 | import { InferClient, ClientTypes } from '../src/client' 3 | 4 | const tType = g.type('Test', { 5 | test: g.string().list().description('Greets a person') 6 | }) 7 | 8 | const queryType = g.type('Query', { 9 | greet: g.ref(() => tType) 10 | .list() 11 | .optional() 12 | .args({ 13 | name: g.ref(() => tType).optional().default({ test: ['sdf'] }), 14 | }) 15 | .description('Greets a person') 16 | }) 17 | 18 | type x = InferClient<{ query: typeof queryType }> 19 | 20 | export function createClient (): InferClient { 21 | return null as any 22 | } 23 | 24 | const client = createClient<{ query: typeof queryType }>() 25 | client.query.greet({ name: { test: ['sdf'] } })?.forEach((x) => x.test) 26 | -------------------------------------------------------------------------------- /www/public/icons/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/promo.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema, Infer } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const queryType = g.type('Query', { 6 | greet: g.string() 7 | .args({ 8 | name: g.string().optional().default('Max'), 9 | }) 10 | .description('Greets a person') 11 | }) 12 | 13 | type QueryType = Infer 14 | 15 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 16 | Query: { 17 | greet: (parent, args, context, info) => `Hello, ${args.name}` 18 | } 19 | } 20 | 21 | const schema = buildSchema({ g, resolvers }) 22 | const yoga = createYoga({ schema }) 23 | const server = createServer(yoga) 24 | server.listen(4000, () => { 25 | console.info('Server is running on http://localhost:4000/graphql') 26 | }) 27 | -------------------------------------------------------------------------------- /examples/context.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema } from './../src/index' 2 | import { createYoga, YogaInitialContext } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const queryType = g.type('Query', { 6 | context: g.string() 7 | }) 8 | 9 | const context = () => { 10 | return { 11 | hello: 'world' 12 | } 13 | } 14 | 15 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: YogaInitialContext & ReturnType }> = { 16 | Query: { 17 | context: (parent, args, context, info) => `Context: ${context.hello}` 18 | } 19 | } 20 | 21 | const schema = buildSchema({ g, resolvers }) 22 | const yoga = createYoga({ schema, context }) 23 | const server = createServer(yoga) 24 | server.listen(4001, () => { 25 | console.info('Server is running on http://localhost:4000/graphql') 26 | }) 27 | -------------------------------------------------------------------------------- /examples/errors.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema } from './../src/index' 2 | import { GraphQLError } from 'graphql' 3 | import { createYoga } from 'graphql-yoga' 4 | import { createServer } from 'http' 5 | 6 | const queryType = g.type('Query', { 7 | error: g.string(), 8 | error_extensions: g.string() 9 | }) 10 | 11 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 12 | Query: { 13 | error: () => { 14 | throw new GraphQLError('Expected error') 15 | }, 16 | error_extensions: () => { 17 | throw new GraphQLError('Expected error with extensions', { extensions: { code: 'EXPECTED_ERROR' } }) 18 | } 19 | } 20 | } 21 | 22 | const schema = buildSchema({ g, resolvers }) 23 | const yoga = createYoga({ schema }) 24 | const server = createServer(yoga) 25 | server.listen(4001, () => { 26 | console.info('Server is running on http://localhost:4000/graphql') 27 | }) 28 | -------------------------------------------------------------------------------- /examples/uploads.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const file = g.scalarType('File') 6 | 7 | const mutationType = g.type('Mutation', { 8 | readTextFile: g.string() 9 | .args({ 10 | file: g.ref(file), 11 | }) 12 | .description('Greets a person') 13 | }) 14 | 15 | const resolvers: InferResolvers<{ Mutation: typeof mutationType }, {}> = { 16 | Mutation: { 17 | readTextFile: async (parent, { file }, context, info) => { 18 | const textContent = await file.text() 19 | return textContent 20 | } 21 | } 22 | } 23 | 24 | const schema = buildSchema({ g, resolvers }) 25 | const yoga = createYoga({ schema }) 26 | const server = createServer(yoga) 27 | server.listen(4000, () => { 28 | console.info('Server is running on http://localhost:4000/graphql') 29 | }) 30 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export type UnionToIntersection = 2 | (T extends any ? (x: T) => any : never) extends 3 | (x: infer R) => any ? R : never 4 | 5 | export type ArrayToEnum = T[number] 6 | export type TSEnumType = { [s: number]: string } 7 | export type EnumToUnion = keyof T 8 | 9 | export function getEnumProperties(enumValue: TSEnumType) { 10 | return Object.keys(enumValue).filter((key) => isNaN(Number(key))) 11 | } 12 | 13 | export type ObjectToUnion = T[keyof T] 14 | export type MaybePromise = T | Promise 15 | export type MaybeFunction = T | (() => T) | (() => Promise) 16 | 17 | // Taken from Kysely 18 | // See the tweet: https://twitter.com/Riyaadh_Abr/status/1622736576303312899 19 | export type ExpandRecursively 20 | = T extends Record | Record[] | readonly Record[] 21 | ? T extends infer O 22 | ? { [K in keyof O]: ExpandRecursively } 23 | : never : T 24 | -------------------------------------------------------------------------------- /examples/validation.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { GraphQLError } from 'graphql' 4 | import { createServer } from 'http' 5 | 6 | const username = g.scalarType('Username', { 7 | serialize: (username) => username, 8 | parseValue: (username) => { 9 | if (username.length < 3) { 10 | throw new GraphQLError('Username must be at least 3 characters long') 11 | } 12 | 13 | return username 14 | } 15 | }) 16 | 17 | const queryType = g.type('Query', { 18 | login: g.string().args({ username }), 19 | }) 20 | 21 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 22 | Query: { 23 | login: (parent, args) => 'Success!' 24 | } 25 | } 26 | 27 | const schema = buildSchema({ g, resolvers }) 28 | const yoga = createYoga({ schema }) 29 | const server = createServer(yoga) 30 | server.listen(4001, () => { 31 | console.info('Server is running on http://localhost:4000/graphql') 32 | }) 33 | -------------------------------------------------------------------------------- /www/public/icons/zap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /www/public/icons/graphql.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mish Ushakov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/extend.ts: -------------------------------------------------------------------------------- 1 | import { g, Infer, InferResolvers, buildSchema } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const node = { 6 | id: g.id() 7 | } 8 | 9 | const name = { 10 | name: g.string() 11 | } 12 | 13 | const test = g.type('Test', {}).extend([node, name]) 14 | 15 | const queryType = g.type('Query', { 16 | test: g.ref(() => test) 17 | }) 18 | 19 | type Test = Infer 20 | 21 | const resolvers: InferResolvers<{ Query: typeof queryType, Test: typeof test }, {}> = { 22 | Query: { 23 | test: (parent, args, context, info) => { 24 | return { 25 | id: '123', 26 | name: 'Test' 27 | } 28 | } 29 | }, 30 | Test: { 31 | id: (parent, args, context, info) => { 32 | return parent.id 33 | }, 34 | name: (parent, args, context, info) => { 35 | return parent.name 36 | } 37 | } 38 | } 39 | 40 | const schema = buildSchema({ g, resolvers }) 41 | const yoga = createYoga({ schema }) 42 | const server = createServer(yoga) 43 | server.listen(4000, () => { 44 | console.info('Server is running on http://localhost:4000/graphql') 45 | }) 46 | -------------------------------------------------------------------------------- /examples/implements.ts: -------------------------------------------------------------------------------- 1 | import { g, Infer, InferResolvers, buildSchema } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const node = g.interface('Node', { 6 | id: g.id() 7 | }) 8 | 9 | const name = g.interface('Name', { 10 | name: g.string() 11 | }) 12 | 13 | const test = g.type('Test', {}).implements([node, name]) 14 | 15 | const queryType = g.type('Query', { 16 | test: g.ref(() => test) 17 | }) 18 | 19 | const resolvers: InferResolvers<{ Query: typeof queryType, Test: typeof test }, {}> = { 20 | Query: { 21 | test: (parent, args, context, info) => { 22 | return { 23 | id: '123', 24 | name: 'Test' 25 | } 26 | } 27 | }, 28 | Test: { 29 | id: (parent, args, context, info) => { 30 | return parent.id 31 | }, 32 | name: (parent, args, context, info) => { 33 | return parent.name 34 | } 35 | } 36 | } 37 | 38 | const schema = buildSchema({ g, resolvers }) 39 | const yoga = createYoga({ schema }) 40 | const server = createServer(yoga) 41 | server.listen(4000, () => { 42 | console.info('Server is running on http://localhost:4000/graphql') 43 | }) 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "garph", 3 | "description": "A tRPC-like schema-builder for GraphQL", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/stepci/garph" 7 | }, 8 | "version": "0.6.8", 9 | "license": "MIT", 10 | "main": "dist/index.js", 11 | "scripts": { 12 | "docs:dev": "vitepress dev", 13 | "docs:build": "vitepress build", 14 | "docs:preview": "vitepress preview", 15 | "build": "tsc -p tsconfig.json", 16 | "postbuild": "typedoc --readme none --entryDocument index.md --plugin typedoc-plugin-markdown --tsconfig tsconfig.json --hideInPageTOC true --out www/api src/index.ts", 17 | "ts:check": "tsc -p tsconfig.test.json", 18 | "test": "ts-node examples/promo.ts" 19 | }, 20 | "devDependencies": { 21 | "@graphql-yoga/plugin-defer-stream": "^2.0.4", 22 | "graphql": "^16.6.0", 23 | "graphql-yoga": "^4.0.4", 24 | "ts-node": "^10.9.1", 25 | "typedoc": "^0.23.27", 26 | "typedoc-plugin-markdown": "^3.14.0", 27 | "typescript": "^4.9.5", 28 | "vitepress": "^1.0.0-rc.15" 29 | }, 30 | "dependencies": { 31 | "graphql-compose": "^9.0.10", 32 | "single-user-cache": "^0.6.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/subscriptions.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema, Infer } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const queryType = g.type('Query', { 6 | greet: g.string() 7 | }) 8 | 9 | const subscriptionType = g.type('Subscription', { 10 | counter: g.int(), 11 | winner: g.string() 12 | }) 13 | 14 | const resolvers: InferResolvers<{ Subscription: typeof subscriptionType }, {}> = { 15 | Subscription: { 16 | counter: { 17 | subscribe: async function* (parent, args, context, info) { 18 | for (let i = 100; i >= 0; i--) { 19 | await new Promise((resolve) => setTimeout(resolve, 1000)) 20 | yield { counter: i } 21 | } 22 | } 23 | }, 24 | winner: { 25 | subscribe: async function* (parent, args, context, info) { 26 | for (let i = 100; i >= 0; i--) { 27 | await new Promise((resolve) => setTimeout(resolve, 1000)) 28 | yield { winner: `User ${i}` } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | const schema = buildSchema({ g, resolvers }) 36 | const yoga = createYoga({ schema }) 37 | const server = createServer(yoga) 38 | server.listen(4000, () => { 39 | console.info('Server is running on http://localhost:4000/graphql') 40 | }) 41 | -------------------------------------------------------------------------------- /.github/workflows/www.yml: -------------------------------------------------------------------------------- 1 | name: Deploy VitePress 2 | on: 3 | push: 4 | branches: ["main"] 5 | 6 | # Allows you to run this workflow manually from the Actions tab 7 | workflow_dispatch: 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow one concurrent deployment 16 | concurrency: 17 | group: "pages" 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | # Build job 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | - name: Setup Pages 28 | uses: actions/configure-pages@v2 29 | - name: Build with VitePress 30 | uses: actions/setup-node@v3 31 | - run: npm i 32 | - run: npm run docs:build 33 | - name: Upload artifact 34 | uses: actions/upload-pages-artifact@v1 35 | with: 36 | path: .vitepress/dist 37 | 38 | # Deployment job 39 | deploy: 40 | environment: 41 | name: github-pages 42 | url: ${{ steps.deployment.outputs.page_url }} 43 | runs-on: ubuntu-latest 44 | needs: build 45 | steps: 46 | - name: Deploy to GitHub Pages 47 | id: deployment 48 | uses: actions/deploy-pages@v1 49 | -------------------------------------------------------------------------------- /examples/pagination.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema, Infer } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const user = g.type('User', { 6 | id: g.string().description('User ID'), 7 | name: g.string().description('User name'), 8 | }) 9 | 10 | const queryType = g.type('Query', { 11 | users: g.ref(user).paginatedList().args({ ...g.pageInfoArgs }) 12 | }) 13 | 14 | type QueryType = Infer 15 | 16 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 17 | Query: { 18 | users: (parent, args, context, info) => { 19 | return { 20 | edges: [ 21 | { 22 | node: { 23 | id: '1', 24 | name: 'John', 25 | }, 26 | cursor: '1', 27 | }, 28 | ], 29 | pageInfo: { 30 | hasNextPage: false, 31 | hasPreviousPage: false, 32 | startCursor: '1', 33 | endCursor: '1', 34 | } 35 | } 36 | }, 37 | } 38 | } 39 | 40 | const schema = buildSchema({ g, resolvers }) 41 | const yoga = createYoga({ schema }) 42 | const server = createServer(yoga) 43 | server.listen(4000, () => { 44 | console.info('Server is running on http://localhost:4000/graphql') 45 | }) 46 | -------------------------------------------------------------------------------- /www/docs/integration/server/graphql-yoga.md: -------------------------------------------------------------------------------- 1 | # GraphQL Yoga 2 | 3 | GraphQL Yoga is a batteries-included cross-platform, spec-compliant GraphQL server, that runs anywhere. Yoga is focused on easy setup, performance and great developer experience. 4 | 5 | [→ Website](https://the-guild.dev/graphql/yoga-server) 6 | 7 | [→ Repository](https://github.com/dotansimha/graphql-yoga) 8 | 9 | [→ Docs](https://the-guild.dev/graphql/yoga-server/docs) 10 | 11 | ## Installation 12 | 13 | ::: code-group 14 | ```sh [npm] 15 | $ npm i graphql-yoga 16 | ``` 17 | 18 | ```sh [pnpm] 19 | $ pnpm add graphql-yoga 20 | ``` 21 | 22 | ```sh [yarn] 23 | $ yarn add graphql-yoga 24 | ``` 25 | 26 | ```sh [bun] 27 | $ bun i graphql-yoga 28 | ``` 29 | ::: 30 | 31 | ## Serving Garph schema 32 | 33 | ```ts 34 | import { g, InferResolvers, buildSchema } from 'garph' 35 | import { createYoga, YogaInitialContext } from 'graphql-yoga' 36 | 37 | const queryType = g.type('Query', { 38 | greet: g.string() 39 | }) 40 | 41 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: YogaInitialContext }> = { 42 | Query: { 43 | greet: () => `Hello, World!` 44 | } 45 | } 46 | 47 | const schema = buildSchema({ g, resolvers }) 48 | const yoga = createYoga({ schema }) 49 | ``` 50 | 51 | ## Types 52 | 53 | - Context: `YogaInitialContext` 54 | -------------------------------------------------------------------------------- /examples/loader.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema, Infer } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const Dog = g.type('Dog', { 6 | name: g.string(), 7 | owner: g.string().omitResolver() 8 | }) 9 | 10 | const queryType = g.type('Query', { 11 | dogs: g.ref(() => Dog).list() 12 | }) 13 | 14 | const owners = { 15 | Apollo: 'Mish', 16 | Buddy: 'Sebastian' 17 | } 18 | 19 | type resolverTypes = InferResolvers<{ Query: typeof queryType, Dog: typeof Dog }, {}> 20 | 21 | const resolvers: resolverTypes = { 22 | Query: { 23 | dogs: (parent, args, context, info) => { 24 | return [ 25 | { 26 | name: 'Apollo', 27 | }, 28 | { 29 | name: 'Buddy', 30 | } 31 | ] 32 | } 33 | }, 34 | Dog: { 35 | owner: { 36 | load (queries) { 37 | return new Promise(resolve => { 38 | setTimeout(() => { 39 | resolve(queries.map(q => owners[q.parent.name])) 40 | }, 1000) 41 | }) 42 | } 43 | } 44 | } 45 | } 46 | 47 | const schema = buildSchema({ g, resolvers }) 48 | const yoga = createYoga({ schema }) 49 | const server = createServer(yoga) 50 | server.listen(4000, () => { 51 | console.info('Server is running on http://localhost:4000/graphql') 52 | }) 53 | -------------------------------------------------------------------------------- /www/docs/advanced/file-uploads.md: -------------------------------------------------------------------------------- 1 | # File Uploads 2 | 3 | ::: warning 4 | The feature only works with compatible GraphQL servers and clients that have implemented the Multipart Request Specification. 5 | ::: 6 | 7 | Garph supports the GraphQL [Multipart Request Specification](https://github.com/jaydenseric/graphql-multipart-request-spec), allowing you to upload files and consume the binary data inside GraphQL Resolvers via HTTP. All you need to do is adding a `File` scalar to your schema. 8 | 9 | You can consume uploaded files or blobs as WHATWG standard [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) or [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects you might be familiar from the browser's API. 10 | 11 | ```ts 12 | import { g, InferResolvers, buildSchema } from 'garph' 13 | 14 | const file = g.scalarType('File') 15 | 16 | const mutationType = g.type('Mutation', { 17 | readTextFile: g.string() 18 | .args({ 19 | file: g.ref(file), 20 | }) 21 | }) 22 | 23 | const resolvers: InferResolvers<{ Mutation: typeof mutationType }, {}> = { 24 | Mutation: { 25 | readTextFile: async (parent, { file }, context, info) => { 26 | const textContent = await file.text() 27 | return textContent 28 | } 29 | } 30 | } 31 | 32 | const schema = buildSchema({ g, resolvers }) 33 | ``` 34 | -------------------------------------------------------------------------------- /www/docs/advanced/pagination.md: -------------------------------------------------------------------------------- 1 | # Pagination 2 | 3 | GraphQL cursor-based pagination is a method of paginating data in a GraphQL API that allows clients to request data in chunks or pages, while maintaining a stable and consistent ordering of the results. 4 | 5 | Garph creates a Relay-style paginated list when using `paginatedList` modifier and `g.pageInfoArgs` arg types. 6 | 7 | ```ts 8 | import { g, InferResolvers, buildSchema } from 'garph' 9 | 10 | const user = g.type('User', { 11 | id: g.string().description('User ID'), 12 | name: g.string().description('User name'), 13 | }) 14 | 15 | const queryType = g.type('Query', { 16 | users: g.ref(user).paginatedList().args({ ...g.pageInfoArgs }) 17 | }) 18 | 19 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 20 | Query: { 21 | users: (parent, args, context, info) => { 22 | return { 23 | edges: [ 24 | { 25 | node: { 26 | id: '1', 27 | name: 'John', 28 | }, 29 | cursor: '1' 30 | } 31 | ], 32 | pageInfo: { 33 | hasNextPage: false, 34 | hasPreviousPage: false, 35 | startCursor: '1', 36 | endCursor: '1' 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | const schema = buildSchema({ g, resolvers }) 44 | ``` 45 | -------------------------------------------------------------------------------- /examples/relay.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema, Infer } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const user = g.node('UserNode', { 6 | name: g.string().description('User name'), 7 | }) 8 | 9 | const userEdge = g.edge('UserEdge', g.ref(() => user)) 10 | const userConnection = g.connection('UserConnection', g.ref(() => userEdge)) 11 | 12 | const queryType = g.type('Query', { 13 | users: g.ref(userConnection).args({ ...g.pageInfoArgs }) 14 | }) 15 | 16 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 17 | Query: { 18 | users: (parent, args, context, info) => { 19 | return { 20 | edges: [ 21 | { 22 | node: { 23 | id: '1', 24 | name: 'John', 25 | }, 26 | cursor: '1', 27 | }, 28 | ], 29 | pageInfo: { 30 | hasNextPage: false, 31 | hasPreviousPage: false, 32 | startCursor: '1', 33 | endCursor: '1', 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | const schema = buildSchema({ g, resolvers }) 41 | const yoga = createYoga({ schema }) 42 | const server = createServer(yoga) 43 | server.listen(4000, () => { 44 | console.info('Server is running on http://localhost:4000/graphql') 45 | }) 46 | -------------------------------------------------------------------------------- /examples/union.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, Infer, buildSchema } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const x = g.type('X', { 6 | a: g.string(), 7 | }) 8 | 9 | const y = g.type('Y', { 10 | b: g.string(), 11 | }) 12 | 13 | const union = g.unionType('Union', { x, y }) 14 | type Union = Infer 15 | 16 | const queryType = g.type('Query', { 17 | greet: g.ref(() => union) 18 | .args({ 19 | name: g.string().optional().default('Max'), 20 | }) 21 | .description('Greets a person') 22 | }) 23 | 24 | type Query = Infer 25 | 26 | const resolvers: InferResolvers<{ Query: typeof queryType, Union: typeof union }, {}> = { 27 | Query: { 28 | greet: (parent, args, context, info) => { 29 | if (args.name === 'Max') { 30 | return { __typename: 'X', a: 'Hello Max' } 31 | } else { 32 | return { __typename: 'Y', b: 'Hello World' } 33 | } 34 | } 35 | }, 36 | Union: { 37 | __resolveType: (parent, context, info) => { 38 | return parent.__typename 39 | } 40 | } 41 | } 42 | 43 | const schema = buildSchema({ g, resolvers }) 44 | const yoga = createYoga({ schema }) 45 | const server = createServer(yoga) 46 | server.listen(4000, () => { 47 | console.info('Server is running on http://localhost:4000/graphql') 48 | }) 49 | -------------------------------------------------------------------------------- /www/docs/advanced/subscriptions.md: -------------------------------------------------------------------------------- 1 | # Subscriptions 2 | 3 | GraphQL subscriptions allow clients to subscribe to certain events on the server and receive updates as they occur. They are particularly useful for applications where real-time updates are important and where it would be inefficient or impractical to continuously poll the server for updates. 4 | 5 | In the following example we will add a subscription called "counter" that returns a counter, which counts from 100 to 0 once subscribed 6 | 7 | You can execute the following query in GraphQL playground to subscribe to the updates from the counter: 8 | 9 | ```graphql 10 | subscription { 11 | counter 12 | } 13 | ``` 14 | 15 | ```ts 16 | import { g, InferResolvers, buildSchema, Infer } from 'garph' 17 | 18 | const queryType = g.type('Query', { 19 | greet: g.string() 20 | }) 21 | 22 | const subscriptionType = g.type('Subscription', { 23 | counter: g.int() 24 | }) 25 | 26 | const resolvers: InferResolvers<{ Subscription: typeof subscriptionType }, {}> = { 27 | Subscription: { 28 | counter: { 29 | subscribe: async function* (parent, args, context, info) { 30 | for (let i = 100; i >= 0; i--) { 31 | await new Promise((resolve) => setTimeout(resolve, 1000)) 32 | yield { counter: i } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | const schema = buildSchema({ g, resolvers }) 40 | ``` 41 | -------------------------------------------------------------------------------- /www/docs/advanced/relay.md: -------------------------------------------------------------------------------- 1 | # Relay 2 | 3 | GraphQL Relay is a set of conventions and specifications for building GraphQL APIs that follow best practices for implementing pagination, node cursors, and connection types. These conventions were developed by Facebook to address common challenges in building complex, large-scale GraphQL APIs. 4 | 5 | Garph ships with Relay primitives, which allow you to faster develop Relay-compatible GraphQL APIs. 6 | 7 | ```ts 8 | import { g, InferResolvers, buildSchema } from 'garph' 9 | 10 | const user = g.node('UserNode', { 11 | name: g.string().description('User name'), 12 | }) 13 | 14 | const userEdge = g.edge('UserEdge', g.ref(user)) 15 | const userConnection = g.connection('UserConnection', g.ref(userEdge)) 16 | 17 | const queryType = g.type('Query', { 18 | users: g.ref(userConnection).args({ ...g.pageInfoArgs }) 19 | }) 20 | 21 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 22 | Query: { 23 | users: (parent, args, context, info) => { 24 | return { 25 | edges: [ 26 | { 27 | node: { 28 | id: '1', 29 | name: 'John', 30 | }, 31 | cursor: '1', 32 | }, 33 | ], 34 | pageInfo: { 35 | hasNextPage: false, 36 | hasPreviousPage: false, 37 | startCursor: '1', 38 | endCursor: '1', 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | const schema = buildSchema({ g, resolvers }) 46 | ``` 47 | -------------------------------------------------------------------------------- /examples/demo.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, Infer, InferArgs, buildSchema } from '../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { createServer } from 'http' 4 | 5 | const blogType = g.type('Blog', { 6 | title: g.string(), 7 | author: g.ref(() => userType).description('The author of the blog') 8 | }).description('The blog of the user') 9 | 10 | const userType = g.type('User', { 11 | age: g.int(), 12 | friends: g.ref(() => userType).description('The friends of the user').list().deprecated('wow'), 13 | }) 14 | 15 | const scalarType = g.scalarType('SC', { 16 | serialize: (value) => value.getTime(), 17 | parseValue: (value) => new Date(value) 18 | }).description('The scalar type') 19 | 20 | const inputType = g.inputType('UserInput', { 21 | name: g.string(), 22 | age: g.int().required(), 23 | }).description('The input type') 24 | 25 | const queryType = g.type('Query', { 26 | greet: g.string().args({ 27 | test: g.ref(() => scalarType).description('The wow').list(), 28 | }).description('The greet query'), 29 | }) 30 | 31 | type x = InferArgs 32 | 33 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 34 | Query: { 35 | greet: (parent, args) => 'Hello World' 36 | } 37 | } 38 | 39 | const schema = buildSchema({ g, resolvers }) 40 | const yoga = createYoga({ schema }) 41 | const server = createServer(yoga) 42 | server.listen(4000, () => { 43 | console.info('Server is running on http://localhost:4000/graphql') 44 | }) 45 | -------------------------------------------------------------------------------- /examples/deferstream.ts: -------------------------------------------------------------------------------- 1 | import { g, InferResolvers, buildSchema } from './../src/index' 2 | import { createYoga } from 'graphql-yoga' 3 | import { useDeferStream } from '@graphql-yoga/plugin-defer-stream' 4 | import { createServer } from 'node:http' 5 | 6 | const queryType = g.type('Query', { 7 | alphabet: g.string().list().description(`This field can be @stream'ed`), 8 | fastField: g.string().description('A field that resolves fast.'), 9 | slowField: g 10 | .string() 11 | .optional() 12 | .description( 13 | 'A field that resolves slowly. Maybe you want to @defer this field ;)' 14 | ) 15 | .args({ 16 | waitFor: g.int().default(5000), 17 | }) 18 | }) 19 | 20 | const wait = (time: number) => new Promise(resolve => setTimeout(resolve, time)) 21 | 22 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 23 | Query: { 24 | async *alphabet() { 25 | for (const character of ['a', 'b', 'c', 'd', 'e', 'f', 'g']) { 26 | yield character 27 | await wait(1000) 28 | } 29 | }, 30 | fastField: async () => { 31 | await wait(100) 32 | return 'I am speed' 33 | }, 34 | slowField: async (_, { waitFor }) => { 35 | await wait(waitFor) 36 | return 'I am slow' 37 | } 38 | } 39 | } 40 | 41 | const schema = buildSchema({ g, resolvers }) 42 | const yoga = createYoga({ 43 | schema, 44 | plugins: [useDeferStream()] 45 | }) 46 | 47 | const server = createServer(yoga) 48 | 49 | server.listen(4000, () => { 50 | console.info('Server is running on http://localhost:4000/graphql') 51 | }) 52 | -------------------------------------------------------------------------------- /www/docs/advanced/errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | GraphQL errors are used to indicate that an error occurred during the execution of a GraphQL query or mutation. These errors can be caused by a variety of reasons, such as invalid input data, server-side issues, or authentication errors. 4 | 5 | ## Exposing expected errors 6 | 7 | Sometimes it is feasible to throw errors within your GraphQL resolvers whose message should be sent to clients instead of being masked. This can be achieved by throwing a `GraphQLError` instead of a "normal" [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error). 8 | 9 | ```ts 10 | import { g, InferResolvers, buildSchema } from 'garph' 11 | import { GraphQLError } from 'graphql' 12 | 13 | const queryType = g.type('Query', { 14 | error: g.string(), 15 | }) 16 | 17 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 18 | Query: { 19 | error: () => { 20 | throw new GraphQLError('Expected error') 21 | } 22 | } 23 | } 24 | 25 | const schema = buildSchema({ g, resolvers }) 26 | ``` 27 | 28 | ## Error codes and other extensions 29 | 30 | Sometimes it is useful to enrich errors with additional information, such as an error code that can be interpreted by the client. Error extensions can be passed as the second parameter to the `GraphQLError` constructor. 31 | 32 | ```ts 33 | import { g, InferResolvers, buildSchema } from 'garph' 34 | import { GraphQLError } from 'graphql' 35 | 36 | const queryType = g.type('Query', { 37 | error: g.string(), 38 | }) 39 | 40 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 41 | Query: { 42 | error: () => { 43 | throw new GraphQLError('Expected error with extensions', { extensions: { code: 'EXPECTED_ERROR' } }) 44 | } 45 | } 46 | } 47 | 48 | const schema = buildSchema({ g, resolvers }) 49 | ``` 50 | -------------------------------------------------------------------------------- /www/docs/guide/resolvers.md: -------------------------------------------------------------------------------- 1 | # Resolvers 2 | 3 | GraphQL resolvers are functions for fetching the data for a particular field in a GraphQL query or mutation. When a client makes a GraphQL request, the GraphQL server invokes the corresponding resolver functions to retrieve the data for the requested fields. 4 | 5 | ## Parameters 6 | 7 | Resolver functions receive four arguments: 8 | 9 | - `parent` 10 | The object that contains the result returned by the parent resolver. This argument is not used for root-level resolvers. 11 | - `args` 12 | The arguments provided to the field in the GraphQL query or mutation. 13 | - `context` 14 | An object containing any data that is shared across all resolvers for a single request. This can include information such as the currently authenticated user or a database connection. 15 | - `info` 16 | An object that contains information about the execution state of the query, such as the name of the field being resolved and the selection set. 17 | 18 | ## Specifying resolver functions 19 | 20 | ```ts{13-17} [Example] 21 | import { g, InferResolvers, buildSchema } from 'garph' 22 | import { createYoga } from 'graphql-yoga' 23 | import { createServer } from 'http' 24 | 25 | const queryType = g.type('Query', { 26 | greet: g.string() 27 | .args({ 28 | name: g.string().optional().default('Max') 29 | }) 30 | .description('Greets a person') 31 | }) 32 | 33 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: any, info: any }> = { 34 | Query: { 35 | greet: (parent, args, context, info) => `Hello, ${args.name}` 36 | } 37 | } 38 | 39 | const schema = buildSchema({ g, resolvers }) 40 | ``` 41 | 42 | ## Type-safety 43 | 44 | Resolver types can be inferred into TypeScript using the [`InferResolvers`](/api/index.md#inferresolvers) utility 45 | 46 | [→ Inferring Resolvers](/docs/guide/inferring-types.md#inferring-resolvers) 47 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { InferArg, AnyInput, AnyInterface, AnyString, AnyID, AnyBoolean, AnyNumber, AnyList, AnyPaginatedList, AnyOptional, AnyArgs, AnyUnion, AnyEnum, AnyScalar, AnyRef, AnyObject, AnyOmitResolver } from './index' 2 | import { ExpandRecursively } from './utils' 3 | 4 | export type ClientTypes = { 5 | query: AnyObject 6 | mutation?: AnyObject 7 | subscription?: AnyObject 8 | } 9 | 10 | export type InferClient = { 11 | [K in keyof T]: InferClientTypes 12 | } 13 | 14 | export type InferClientTypes = ExpandRecursively> 15 | export type InferClientTypesRaw = T extends AnyInput | AnyObject | AnyInterface ? { 16 | __typename: T['_name'] 17 | } & { 18 | [K in keyof T['_shape'] as T['_shape'][K] extends AnyOptional ? never : K]: InferClientTypesRaw 19 | } & { 20 | [K in keyof T['_shape'] as T['_shape'][K] extends AnyOptional ? K : never]?: InferClientTypesRaw 21 | }: InferClientTypesShallow 22 | 23 | export type InferClientTypesShallow = 24 | T extends AnyString | AnyID | AnyNumber | AnyBoolean ? T['_shape'] : 25 | T extends AnyScalar ? T['_output'] : 26 | T extends AnyEnum ? T['_inner'] : 27 | T extends AnyUnion ? { 28 | $on: { 29 | __typename: keyof { 30 | [K in keyof T['_inner'] as T['_inner'][K]['_name']]: never 31 | } 32 | } & { 33 | [K in keyof T['_inner'] as T['_inner'][K]['_name']]?: InferClientTypesRaw 34 | } 35 | } : 36 | T extends AnyList ? InferClientTypesRaw[] : 37 | T extends AnyPaginatedList ? T['_inner'] : 38 | T extends AnyOptional ? InferClientTypesRaw | null | undefined : 39 | T extends AnyOmitResolver ? InferClientTypesRaw : 40 | T extends AnyArgs ? (args?: InferArg) => InferClientTypes : 41 | T extends AnyRef ? InferClientTypesRaw : 42 | T 43 | -------------------------------------------------------------------------------- /www/docs/advanced/context.md: -------------------------------------------------------------------------------- 1 | # Context 2 | 3 | In GraphQL, a context is an object containing any data that is shared across all resolvers for a single request. This can include information such as the currently authenticated user or a database connection. 4 | 5 | ## Default context 6 | 7 | The initial context refers to the context object that is created when the server receives a new request. This context object contains information that is common to all requests, such as the HTTP headers and the request method. 8 | 9 | ```ts 10 | import { g, InferResolvers, buildSchema } from 'garph' 11 | import { createYoga, YogaInitialContext } from 'graphql-yoga' 12 | 13 | const queryType = g.type('Query', { 14 | context: g.string() 15 | }) 16 | 17 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: YogaInitialContext }> = { 18 | Query: { 19 | context: (parent, args, context, info) => `Context: ${context}` 20 | } 21 | } 22 | 23 | const schema = buildSchema({ g, resolvers }) 24 | ``` 25 | 26 | ## Extending default context 27 | 28 | A custom context can include any information that is relevant to the execution of the query, such as authentication credentials, database connections, and other context-specific data. By passing a custom context to the resolvers, the application can avoid passing the same information to every resolver function as arguments. 29 | 30 | ```ts 31 | import { g, InferResolvers, buildSchema } from 'garph' 32 | import { createYoga, YogaInitialContext } from 'graphql-yoga' 33 | 34 | const queryType = g.type('Query', { 35 | context: g.string() 36 | }) 37 | 38 | const context = () => { 39 | return { 40 | hello: 'world' 41 | } 42 | } 43 | 44 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: YogaInitialContext & ReturnType }> = { 45 | Query: { 46 | context: (parent, args, context, info) => `Context: ${context.hello}` 47 | } 48 | } 49 | 50 | const schema = buildSchema({ g, resolvers }) 51 | const yoga = createYoga({ schema, context }) 52 | ``` 53 | -------------------------------------------------------------------------------- /.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-c-brand-1: #3230df; 3 | --vp-c-brand-dark: #3230df; 4 | --vp-code-block-bg: #f6f8fa; 5 | --vp-c-text-1: #3f3f46; 6 | --vp-c-divider: #e4e4e7; 7 | --vp-code-tab-divider: var(--vp-c-divider); 8 | --vp-code-tab-text-color: var(--vp-c-text-2); 9 | --vp-code-tab-active-text-color: var(--vp-c-text-1); 10 | --vp-code-tab-hover-text-color: var(--vp-c-text-1); 11 | --vp-code-line-highlight-color: rgba(0,0,0,.05); 12 | --vp-code-color: inherit 13 | } 14 | 15 | .dark { 16 | --vp-c-brand-1: rgb(94,92,230); 17 | --vp-c-brand-dark: rgb(94,92,230); 18 | --vp-c-bg-alt: #131316; 19 | --vp-c-bg: #18181B; 20 | --vp-code-block-bg: #0e0e10; 21 | --vp-code-line-highlight-color: rgba(94,92,230, .07); 22 | /* Alternative */ 23 | /* --vp-c-bg-alt: #000; 24 | --vp-c-bg: #000; */ 25 | /* Colored 0 */ 26 | /* --vp-c-bg-alt: #050520; 27 | --vp-c-bg: #050520; */ 28 | /* Colored 1 */ 29 | /* --vp-c-bg-alt: #040317; 30 | --vp-c-bg: #040317; */ 31 | /* Colored 2 */ 32 | /* --vp-c-bg-alt: #030312; 33 | --vp-c-bg: #030312; */ 34 | /* --vp-code-block-bg: rgba(0, 0, 0, 0.15); */ 35 | --vp-c-divider: rgba(255, 255, 255, 0.08); 36 | --vp-c-text-1: rgba(255, 255, 245, 0.86); 37 | } 38 | 39 | .VPSidebar { 40 | border-right: 1px solid var(--vp-c-divider) 41 | } 42 | 43 | .vp-doc div[class*='language-'] { 44 | border: 1px solid var(--vp-c-divider) 45 | } 46 | 47 | .vp-code-group .tabs { 48 | border: 1px solid var(--vp-c-divider); 49 | border-bottom: 0; 50 | } 51 | 52 | .vp-code-group div[class*='language-'] { 53 | border-top: 0 54 | } 55 | 56 | code.nav { 57 | border-radius: 4px; 58 | padding: 3px 6px; 59 | color: var(--vp-c-text-code); 60 | background-color: var(--vp-c-divider); 61 | } 62 | 63 | .badge-new { 64 | margin-left: 2px; 65 | box-sizing: border-box; 66 | padding: 2px 6px; 67 | background-color: var(--vp-c-brand); 68 | font-size: 10px; 69 | color: #fff; 70 | border-radius: 20px; 71 | font-weight: 700; 72 | text-transform: uppercase; 73 | } 74 | 75 | .vp-doc a { 76 | text-decoration: none; 77 | } 78 | -------------------------------------------------------------------------------- /www/docs/guide/inferring-types.md: -------------------------------------------------------------------------------- 1 | # Inferring Types 2 | 3 | ## Inferring Garph Types 4 | 5 | Garph types can be inferred into TypeScript using the [`Infer`](/api/index.md#infer) utility 6 | 7 | ```ts 8 | import { g, Infer } from 'garph' 9 | 10 | const nameType = g.type('Name', { 11 | greet: g.string() 12 | }) 13 | 14 | type NameType = Infer 15 | ``` 16 | 17 | Inferred type: 18 | 19 | ```ts 20 | type NameType = { 21 | greet: string 22 | } 23 | ``` 24 | 25 | ## Inferring Args 26 | 27 | Arguments on Garph types can be inferred into TypeScript using the [`InferArgs`](/api/index.md#inferargs) utility 28 | 29 | ```ts 30 | import { g, InferArgs } from 'garph' 31 | 32 | const nameType = g.type('Name', { 33 | greet: g.string().args({ name: g.string().optional() }) 34 | }) 35 | 36 | type NameType = InferArgs 37 | ``` 38 | 39 | Inferred type: 40 | 41 | ```ts 42 | type NameType = { 43 | greet: { 44 | name: string | null | undefined 45 | } 46 | } 47 | ``` 48 | 49 | ## Inferring Resolvers 50 | 51 | Resolver types can be inferred into TypeScript using the [`InferResolvers`](/api/index.md#inferresolvers) utility 52 | 53 | ```ts 54 | import { g, InferResolvers } from 'garph' 55 | 56 | const queryType = g.type('Query', { 57 | greet: g.string() 58 | .args({ 59 | name: g.string().optional().default('Max'), 60 | }) 61 | .description('Greets a person') 62 | }) 63 | 64 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: any, info: any }> = { 65 | Query: { 66 | greet: (parent, args, context, info) => `Hello, ${args.name}` 67 | } 68 | } 69 | ``` 70 | 71 | > You can specify the context and info types as a second parameter (see above) 72 | 73 | Inferred type: 74 | 75 | ```ts 76 | { 77 | Query: { 78 | greet?: (parent: any, args: { 79 | name: string | null | undefined 80 | }, context: any, info: any) => string | Promise 81 | } 82 | } 83 | ``` 84 | 85 | ### Strict Mode 86 | 87 | Using [`InferResolversStrict`](/api/index.md#inferresolversstrict) you can infer resolver types in strict mode, which requires all fields to be specified in the resolver configuration 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # garph 2 | 3 | https://user-images.githubusercontent.com/10400064/222474710-bc263775-06b8-4a78-8099-676a9ad3c7a4.mov 4 | 5 | > **Warning**: 6 | > We would love to hear your Feedback in our [Discord](https://discord.gg/KqJJzJ3BTu) 7 | 8 | > **Note**: 9 | > tRPC-style client for Garph has arrived! See [garph-gqty](https://github.com/stepci/garph-gqty) for more 🚀 10 | 11 | Garph is a fullstack GraphQL framework for TypeScript, that aims to deliver the best GraphQL Developer-Experience. 12 | 13 | ## Get started 14 | 15 | 1. Install the dependencies 16 | 17 | ``` 18 | npm i garph graphql-yoga 19 | ``` 20 | 21 | 2. Create example GraphQL API 22 | 23 | ```ts 24 | import { g, InferResolvers, buildSchema } from 'garph' 25 | import { createYoga } from 'graphql-yoga' 26 | import { createServer } from 'http' 27 | 28 | const queryType = g.type('Query', { 29 | greet: g.string() 30 | .args({ 31 | name: g.string().optional().default('Max') 32 | }) 33 | .description('Greets a person') 34 | }) 35 | 36 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 37 | Query: { 38 | greet: (parent, args, context, info) => `Hello, ${args.name}` 39 | } 40 | } 41 | 42 | const schema = buildSchema({ g, resolvers }) 43 | const yoga = createYoga({ schema }) 44 | const server = createServer(yoga) 45 | server.listen(4000, () => { 46 | console.info('Server is running on http://localhost:4000/graphql') 47 | }) 48 | ``` 49 | 50 | 3. Start the server 51 | 52 | ``` 53 | npx ts-node server.ts 54 | ``` 55 | 56 | 4. Query the API 57 | 58 | Go to: http://localhost:4000/graphl 59 | 60 | Enter the following query: 61 | 62 | ```graphql 63 | { 64 | greet(name: "Max") 65 | } 66 | ``` 67 | 68 | Click on the play button 69 | 70 | ## Documentation 71 | 72 | Documentation is available on [garph.dev/docs](https://garph.dev/docs) 73 | 74 | ## Examples 75 | 76 | Example projects can be found under [examples/](examples/) 77 | 78 | ## Feedback 79 | 80 | We would love to hear your Feedback in our [Discord](https://discord.gg/KqJJzJ3BTu) community 81 | -------------------------------------------------------------------------------- /www/docs/advanced/validation.md: -------------------------------------------------------------------------------- 1 | # Validation 2 | 3 | Custom scalars can be useful for handling specific data types that are not natively supported by GraphQL or you want a version of an existing type that does some validation. 4 | 5 | In this example, we create a new scalar type `Username`, which ensures that the username is longer than 3 characters. In case of success the request proceeds, otherwise an error is thrown. 6 | 7 | ```ts 8 | import { g, InferResolvers, buildSchema } from 'garph' 9 | import { GraphQLError } from 'graphql' 10 | 11 | const username = g.scalarType('Username', { 12 | serialize: (username) => username, 13 | parseValue: (username) => { 14 | if (username.length < 3) { 15 | throw new GraphQLError('Username must be at least 3 characters long') 16 | } 17 | 18 | return username 19 | } 20 | }) 21 | 22 | const queryType = g.type('Query', { 23 | login: g.string().args({ username }), 24 | }) 25 | 26 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 27 | Query: { 28 | login: (parent, args) => `Welcome back, ${args.username}!` 29 | } 30 | } 31 | 32 | const schema = buildSchema({ g, resolvers }) 33 | ``` 34 | 35 | ### Validating with Zod 36 | 37 | Same as the example above, except we are now using Zod validator instead of writing the logic ourselves. 38 | 39 | > Before you continue, make sure you have installed Zod from NPM 40 | 41 | ```ts 42 | import { g, InferResolvers, buildSchema } from 'garph' 43 | import { z } from 'zod' 44 | import { GraphQLError } from 'graphql' 45 | 46 | const usernameValidator = z.string().min(3) 47 | 48 | const username = g.scalarType('Username', { 49 | serialize: (username) => username, 50 | parseValue: (username) => { 51 | if (!usernameValidator.safeParse(username).success) { 52 | throw new GraphQLError('Username must be at least 3 characters long') 53 | } 54 | 55 | return username 56 | } 57 | }) 58 | 59 | const queryType = g.type('Query', { 60 | login: g.string().args({ username }), 61 | }) 62 | 63 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 64 | Query: { 65 | login: (parent, args) => `Welcome back, ${args.username}!` 66 | } 67 | } 68 | 69 | const schema = buildSchema({ g, resolvers }) 70 | ``` 71 | -------------------------------------------------------------------------------- /www/docs/advanced/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Testing GraphQL APIs is made easy using [Step CI](https://stepci.com), an open-source API-testing framework. 4 | 5 | Given the following Garph schema: 6 | 7 | ```ts 8 | import { g, InferResolvers, buildSchema } from 'garph' 9 | 10 | const queryType = g.type('Query', { 11 | greet: g.string() 12 | .args({ 13 | name: g.string().optional().default('Max') 14 | }) 15 | .description('Greets a person') 16 | }) 17 | 18 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: YogaInitialContext }> = { 19 | Query: { 20 | greet: (parent, args, context, info) => `Hello, ${args.name}` 21 | } 22 | } 23 | ``` 24 | 25 | We can come up a Step CI workflow that looks like this: 26 | 27 | ```yml 28 | version: "1.1" 29 | name: Status Check 30 | env: 31 | BASE_URL: "http://localhost:4000/graphql" 32 | tests: 33 | default_greeting: 34 | steps: 35 | - name: 'Get default greeting' 36 | http: 37 | url: '${{env.BASE_URL}}' 38 | graphql: 39 | query: '{ greet }' 40 | check: 41 | status: 200 42 | json: 43 | data: 44 | greet: 'Hello, Max' 45 | 46 | specific_greeting: 47 | steps: 48 | - name: 'Greet a specific person' 49 | http: 50 | url: '${{env.BASE_URL}}' 51 | graphql: 52 | query: | 53 | query ($name: String) { 54 | greet(name: $name) 55 | } 56 | variables: 57 | name: Alice 58 | check: 59 | status: 200 60 | json: 61 | data: 62 | greet: 'Hello, Alice' 63 | ``` 64 | 65 | The first test checks if the query "greet" is working and returns the default greeting. The second test checks if the API correctly greets a specific person (in this case, Alice) when provided with a name variable. 66 | 67 | **Run the workflow** 68 | 69 | ``` 70 | npx stepci run workflow.yml 71 | ``` 72 | 73 | Result: 74 | 75 | ``` 76 | PASS default_greeting 77 | PASS specific_greeting 78 | 79 | Tests: 0 failed, 2 passed, 2 total 80 | Steps: 0 failed, 0 skipped, 2 passed, 2 total 81 | Time: 0.035s, estimated 0s 82 | CO2: 0.00002g 83 | 84 | Workflow passed after 0.035s 85 | ``` 86 | -------------------------------------------------------------------------------- /www/docs/guide/loaders.md: -------------------------------------------------------------------------------- 1 | # Loaders 2 | 3 | In GraphQL, Data Loaders are used to batch and load data from a data source. They help address the N+1 problem, which is a common performance issue in GraphQL APIs. 4 | 5 | The N+1 problem occurs when a GraphQL query involves fetching a list of entities (e.g., users) along with a related field (e.g., their posts), and for each entity, an additional query is made to fetch the related data. This leads to multiple (database) queries, causing inefficiencies and potential performance bottlenecks. 6 | 7 | In Garph, you can add loaders to fields by making a field resolver return an object with `load` or `loadBatch` (no cache) function, which accepts a batch of queries that you can use to map to your data 8 | 9 | ## Parameters 10 | 11 | Loader functions receive one argument: 12 | 13 | - `queries` 14 | Array of query objects: 15 | - `parent` 16 | The object that contains the result returned by the parent resolver. This argument is not used for root-level resolvers. 17 | - `args` 18 | The arguments provided to the field in the GraphQL query or mutation. 19 | - `context` 20 | An object containing any data that is shared across all resolvers for a single request. This can include information such as the currently authenticated user or a database connection. 21 | - `info` 22 | An object that contains information about the execution state of the query, such as the name of the field being resolved and the selection set. 23 | 24 | ## Specifying resolver functions 25 | 26 | ```ts 27 | import { g, InferResolvers, buildSchema, Infer } from 'garph' 28 | 29 | const Dog = g.type('Dog', { 30 | name: g.string(), 31 | owner: g.string().omitResolver() 32 | }) 33 | 34 | const queryType = g.type('Query', { 35 | dogs: g.ref(() => Dog).list() 36 | }) 37 | 38 | const owners = { 39 | Apollo: 'Mish', 40 | Buddy: 'Sebastian' 41 | } 42 | 43 | type resolverTypes = InferResolvers<{ Query: typeof queryType, Dog: typeof Dog }, {}> 44 | 45 | const resolvers: resolverTypes = { 46 | Query: { 47 | dogs: (parent, args, context, info) => { 48 | return [ 49 | { 50 | name: 'Apollo', 51 | }, 52 | { 53 | name: 'Buddy', 54 | } 55 | ] 56 | } 57 | }, 58 | Dog: { 59 | owner: { 60 | load (queries) { 61 | // Promise with timeout added to demonstrate caching 62 | return new Promise(resolve => { 63 | setTimeout(() => { 64 | resolve(queries.map(q => owners[q.parent.name])) 65 | }, 1000) 66 | }) 67 | } 68 | } 69 | } 70 | } 71 | 72 | const schema = buildSchema({ g, resolvers }) 73 | ``` 74 | -------------------------------------------------------------------------------- /www/api/classes/Type.md: -------------------------------------------------------------------------------- 1 | [garph](../index.md) / Type 2 | 3 | # Class: Type 4 | 5 | ## Type parameters 6 | 7 | | Name | Type | 8 | | :------ | :------ | 9 | | `T` | `T` | 10 | | `X` | extends `GarphType` | 11 | 12 | ## Constructors 13 | 14 | ### constructor 15 | 16 | • **new Type**<`T`, `X`\>() 17 | 18 | #### Type parameters 19 | 20 | | Name | Type | 21 | | :------ | :------ | 22 | | `T` | `T` | 23 | | `X` | extends `GarphType` | 24 | 25 | ## Properties 26 | 27 | ### \_args 28 | 29 | • `Optional` **\_args**: [`Args`](../index.md#args) 30 | 31 | #### Defined in 32 | 33 | [index.ts:13](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L13) 34 | 35 | ___ 36 | 37 | ### \_inner 38 | 39 | • `Optional` **\_inner**: `any` 40 | 41 | #### Defined in 42 | 43 | [index.ts:11](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L11) 44 | 45 | ___ 46 | 47 | ### \_is 48 | 49 | • **\_is**: `X` 50 | 51 | #### Defined in 52 | 53 | [index.ts:10](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L10) 54 | 55 | ___ 56 | 57 | ### \_name 58 | 59 | • `Optional` **\_name**: `string` 60 | 61 | #### Defined in 62 | 63 | [index.ts:9](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L9) 64 | 65 | ___ 66 | 67 | ### \_output 68 | 69 | • `Optional` **\_output**: `any` 70 | 71 | #### Defined in 72 | 73 | [index.ts:12](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L12) 74 | 75 | ___ 76 | 77 | ### \_shape 78 | 79 | • **\_shape**: `T` 80 | 81 | #### Defined in 82 | 83 | [index.ts:14](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L14) 84 | 85 | ___ 86 | 87 | ### typeDef 88 | 89 | • **typeDef**: [`TypeDefinition`](../index.md#typedefinition)<`T`\> 90 | 91 | #### Defined in 92 | 93 | [index.ts:15](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L15) 94 | 95 | ## Methods 96 | 97 | ### deprecated 98 | 99 | ▸ **deprecated**(`reason`): [`Type`](Type.md)<`T`, `X`\> 100 | 101 | #### Parameters 102 | 103 | | Name | Type | 104 | | :------ | :------ | 105 | | `reason` | `string` | 106 | 107 | #### Returns 108 | 109 | [`Type`](Type.md)<`T`, `X`\> 110 | 111 | #### Defined in 112 | 113 | [index.ts:22](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L22) 114 | 115 | ___ 116 | 117 | ### description 118 | 119 | ▸ **description**(`text`): [`Type`](Type.md)<`T`, `X`\> 120 | 121 | #### Parameters 122 | 123 | | Name | Type | 124 | | :------ | :------ | 125 | | `text` | `string` | 126 | 127 | #### Returns 128 | 129 | [`Type`](Type.md)<`T`, `X`\> 130 | 131 | #### Defined in 132 | 133 | [index.ts:17](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L17) 134 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # VitePress 133 | .vitepress/cache 134 | .vitepress/dist 135 | -------------------------------------------------------------------------------- /www/docs/integration/client/gqty.md: -------------------------------------------------------------------------------- 1 | # GQty 2 | 3 | GQty is a fundamentally new approach to a GraphQL client. It makes using your API enjoyable, by generating queries at runtime based upon the data your app consumes. 4 | 5 | [→ Website](https://gqty.dev) 6 | 7 | [→ Repository](https://github.com/stepci/garph-gqty) 8 | 9 | [→ Docs](https://gqty.dev/getting-started) 10 | 11 | ## Installation 12 | 13 | ::: code-group 14 | ```sh [npm] 15 | $ npm i @garph/gqty 16 | ``` 17 | 18 | ```sh [pnpm] 19 | $ pnpm add @garph/gqty 20 | ``` 21 | 22 | ```sh [yarn] 23 | $ yarn add @garph/gqty 24 | ``` 25 | 26 | ```sh [bun] 27 | $ bun i @garph/gqty 28 | ``` 29 | ::: 30 | 31 | ## Initialization 32 | 33 | Example Schema: 34 | 35 | ::: code-group 36 | ```ts [schema.ts] 37 | import { g, buildSchema } from 'garph' 38 | 39 | export const queryType = g.type('Query', { 40 | greet: g.string() 41 | .args({ 42 | name: g.string().optional().default('Max'), 43 | }) 44 | .description('Greets a person') 45 | }) 46 | 47 | const schema = buildSchema({ g }) 48 | ``` 49 | ::: 50 | 51 | Initializing the client: 52 | 53 | ::: code-group 54 | ```ts [client.ts] 55 | import { InferClient, createClient } from '@garph/gqty' 56 | import { createScalarsEnumsHash, createGeneratedSchema } from '@garph/gqty/dist/utils' 57 | import { schema, queryType } from './schema.ts' 58 | 59 | type ClientTypes = InferClient<{ query: typeof queryType }> 60 | 61 | export const { useQuery, ... } = createClient({ 62 | generatedSchema: createGeneratedSchema(schema), 63 | scalarsEnumsHash: createScalarsEnumsHash(schema), 64 | url: 'http://localhost:4000/graphql' 65 | }) 66 | 67 | // Needed for the babel plugin 68 | export { schema as compiledSchema } 69 | ``` 70 | ::: 71 | 72 | Adding subscriptions support 73 | 74 | ``` 75 | npm i graphql-sse 76 | ``` 77 | 78 | ::: code-group 79 | ```ts [client.ts] 80 | import { createClient as createSubscriptionsClient } from 'graphql-sse' 81 | 82 | export const { useSubscription, ... } = createClient({ 83 | generatedSchema: createGeneratedSchema(schema), 84 | scalarsEnumsHash: createScalarsEnumsHash(schema), 85 | url: 'http://localhost:4000/graphql', 86 | subscriptionClient: createSubscriptionsClient({ 87 | url: 'http://localhost:4000/api/graphql/stream' 88 | }) 89 | }) 90 | ``` 91 | ::: 92 | 93 | ## Babel Plugin 94 | 95 | In production, you might want to use the babel plugin in order to replace the runtime dependencies (such as `generatedSchema`, `scalarsEnumsHash`) in your client config with statically-generated artefacts. 96 | 97 | ::: code-group 98 | ```json [.babelrc] 99 | { 100 | "plugins": [["@garph/gqty/dist/plugin", { 101 | "clientConfig": "./utils/client.ts" 102 | }]] 103 | } 104 | ``` 105 | ::: 106 | 107 | ## Usage 108 | 109 | ### Core Client 110 | 111 | Example: 112 | 113 | ```ts 114 | import { resolved, query } from './client' 115 | 116 | resolved(() => { 117 | return query.greet({ name: 'Mish' }) 118 | }) 119 | .then(data => { 120 | console.log(data) 121 | }) 122 | ``` 123 | 124 | [→ GQty Docs: Core Client](https://gqty.dev/guides/core/resolve) 125 | 126 | ### React 127 | 128 | Example: 129 | 130 | ```tsx 131 | import { useQuery } from './client' 132 | 133 | export default function Example() { 134 | const query = useQuery() 135 | return

{ query.greet({ name: 'Mish' }) }

136 | } 137 | ``` 138 | 139 | [→ GQty Docs: Usage with React](https://gqty.dev/guides/react/read) 140 | -------------------------------------------------------------------------------- /www/docs/advanced/defer-stream.md: -------------------------------------------------------------------------------- 1 | # Defer and Stream 2 | 3 | ::: warning 4 | Stream and Defer are experimental features. There is no yet a stable specification for the incremental delivery protocol. 5 | ::: 6 | 7 | Stream and defer are directives that allow you to improve latency for clients by sending the most important data as soon as it's ready. 8 | 9 | Install a yoga plugin to enable `@stream` and `@defer` directives (requires yoga v4 and higer) 10 | 11 | ::: code-group 12 | ```sh [npm] 13 | $ npm i @graphql-yoga/plugin-defer-stream 14 | ``` 15 | 16 | ```sh [pnpm] 17 | $ pnpm add @graphql-yoga/plugin-defer-stream 18 | ``` 19 | 20 | ```sh [yarn] 21 | $ yarn add @graphql-yoga/plugin-defer-stream 22 | ``` 23 | 24 | ```sh [bun] 25 | $ bun i @graphql-yoga/plugin-defer-stream 26 | ``` 27 | ::: 28 | 29 | Example Garph schema with streamed and slow fields: 30 | 31 | ```ts 32 | import { g, InferResolvers, buildSchema } from './../src/index' 33 | import { createYoga } from 'graphql-yoga' 34 | import { useDeferStream } from '@graphql-yoga/plugin-defer-stream' 35 | import { createServer } from 'node:http' 36 | 37 | const queryType = g.type('Query', { 38 | alphabet: g.string().list().description(`This field can be @stream'ed`), 39 | fastField: g.string().description('A field that resolves fast.'), 40 | slowField: g 41 | .string() 42 | .optional() 43 | .description( 44 | 'A field that resolves slowly. Maybe you want to @defer this field ;)' 45 | ) 46 | .args({ 47 | waitFor: g.int().default(5000), 48 | }) 49 | }) 50 | 51 | const wait = (time: number) => new Promise(resolve => setTimeout(resolve, time)) 52 | 53 | const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = { 54 | Query: { 55 | async *alphabet() { 56 | for (const character of ['a', 'b', 'c', 'd', 'e', 'f', 'g']) { 57 | yield character 58 | await wait(1000) 59 | } 60 | }, 61 | fastField: async () => { 62 | await wait(100) 63 | return 'I am speed' 64 | }, 65 | slowField: async (_, { waitFor }) => { 66 | await wait(waitFor) 67 | return 'I am slow' 68 | } 69 | } 70 | } 71 | 72 | const schema = buildSchema({ g, resolvers }) 73 | const yoga = createYoga({ 74 | schema, 75 | plugins: [useDeferStream()] 76 | }) 77 | 78 | const server = createServer(yoga) 79 | 80 | server.listen(4000, () => { 81 | console.info('Server is running on http://localhost:4000/graphql') 82 | }) 83 | ``` 84 | 85 | Start the server: 86 | 87 | ``` 88 | npx ts-node server.ts 89 | ``` 90 | 91 | ### Streamed fields 92 | 93 | The `@stream` directive allows you to stream the individual items of a field of the list type as the items are available. 94 | 95 | Visit http://localhost:4000/graphql and paste the following operation into the left panel: 96 | 97 | ```graphql 98 | query StreamAlphabet { 99 | alphabet @stream 100 | } 101 | ``` 102 | 103 | Then press the Play (Execute Query) button. 104 | 105 | Alternatively, you can also send the stream operation via curl: 106 | 107 | ```sh 108 | curl -g -X POST \ 109 | -H "accept:multipart/mixed" \ 110 | -H "content-type: application/json" \ 111 | -d '{"query":"query StreamAlphabet { alphabet @stream }"}' \ 112 | http://localhost:4000/graphql 113 | ``` 114 | 115 | ### Deferred fields 116 | 117 | The `@defer` directive allows you to post-pone the delivery of one or more (slow) fields grouped in an inlined or spread fragment. 118 | 119 | Visit http://localhost:4000/graphql and paste the following operation into the left panel: 120 | 121 | ```graphql 122 | query SlowAndFastFieldWithDefer { 123 | ... on Query @defer { 124 | slowField 125 | } 126 | fastField 127 | } 128 | ``` 129 | 130 | Then press the Play (Execute Query) button. 131 | 132 | Alternatively, you can also send the defer operation via curl: 133 | 134 | ```sh 135 | curl -g -X POST \ 136 | -H "accept:multipart/mixed" \ 137 | -H "content-type: application/json" \ 138 | -d '{"query":"query SlowAndFastFieldWithDefer { ... on Query @defer { slowField } fastField }"}' \ 139 | http://localhost:4000/graphql 140 | ``` 141 | -------------------------------------------------------------------------------- /.vitepress/config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | srcDir: 'www', 6 | ignoreDeadLinks: true, 7 | title: "Garph", 8 | description: "GraphQL. Reimagined", 9 | lastUpdated: true, 10 | markdown: { 11 | theme: { 12 | light: 'github-light', 13 | dark: 'github-dark', 14 | }, 15 | }, 16 | themeConfig: { 17 | // https://vitepress.dev/reference/default-theme-config 18 | logo: { 19 | light: '/logo/r6N.svg', 20 | dark: '/logo/r5m.svg', 21 | }, 22 | // logo: { 23 | // light: '/logo/r6p.svg', 24 | // dark: '/logo/r5y.svg' 25 | // }, 26 | siteTitle: false, 27 | nav: [ 28 | { text: 'Home', link: '/' }, 29 | { text: 'Docs', link: '/docs/index.md' }, 30 | { text: 'API', link: '/api/index.md' } 31 | ], 32 | socialLinks: [ 33 | { icon: 'github', link: 'https://github.com/stepci/garph' }, 34 | { icon: 'twitter', link: 'https://twitter.com/ci_step' }, 35 | { icon: 'discord', link: 'https://discord.gg/KqJJzJ3BTu' } 36 | ], 37 | editLink: { 38 | pattern: 'https://github.com/stepci/garph/edit/main/www/:path', 39 | text: 'Edit this page on GitHub' 40 | }, 41 | outline: [2, 3], 42 | sidebar: { 43 | '/docs': [ 44 | { 45 | text: 'Guide', 46 | items: [ 47 | { text: 'Quickstart', link: '/docs/index.md' }, 48 | { text: 'Schemas', link: '/docs/guide/schemas.md' }, 49 | { text: 'Resolvers', link: '/docs/guide/resolvers.md' }, 50 | { text: 'Loaders', link: '/docs/guide/loaders.md' }, 51 | { text: 'Inferring Types', link: '/docs/guide/inferring-types.md' }, 52 | { text: 'Migrate New', link: '/docs/guide/migrate.md' } 53 | ] 54 | }, 55 | { 56 | text: 'Advanced', 57 | items: [ 58 | { text: 'Auth', link: '/docs/advanced/auth.md' }, 59 | { text: 'Context', link: '/docs/advanced/context.md' }, 60 | { text: 'File Uploads', link: '/docs/advanced/file-uploads.md' }, 61 | { text: 'Defer and Stream', link: '/docs/advanced/defer-stream.md' }, 62 | { text: 'Subscriptions', link: '/docs/advanced/subscriptions.md' }, 63 | { text: 'Pagination', link: '/docs/advanced/pagination.md' }, 64 | { text: 'Relay', link: '/docs/advanced/relay.md' }, 65 | { text: 'Validation', link: '/docs/advanced/validation.md' }, 66 | { text: 'Federation', link: '/docs/advanced/federation.md' }, 67 | { text: 'Errors', link: '/docs/advanced/errors.md' }, 68 | { text: 'Testing', link: '/docs/advanced/testing.md' }, 69 | { text: 'Extending Garph', link: '/docs/advanced/extending-garph.md' }, 70 | ] 71 | }, 72 | { 73 | text: 'Integration', 74 | items: [ 75 | { 76 | text: 'Examples', collapsed: true, items: [ 77 | { text: `Next.js Yoga + GQty`, link:'/docs/integration/examples/nextjs.md' }, 78 | { text: 'Nuxt Yoga + Vue Apollo', link:'/docs/integration/examples/nuxt.md' }, 79 | { text: 'Remix Yoga + GQty', link:'/docs/integration/examples/remix.md' }, 80 | ] 81 | }, 82 | { 83 | text: 'Server', collapsed: true, items: [ 84 | { text: 'Yoga', link:'/docs/integration/server/graphql-yoga.md' }, 85 | { text: 'Apollo Server', link:'/docs/integration/server/apollo-server.md' }, 86 | { text: 'Mercurius', link:'/docs/integration/server/mercurius.md' }, 87 | ] 88 | }, 89 | { 90 | text: 'Client', collapsed: true, items: [ 91 | { text: 'GQty — Universal', link:'/docs/integration/client/gqty.md' }, 92 | { text: 'urql — React', link:'/docs/integration/client/urql.md' }, 93 | { text: 'Vue Apollo — Vue', link:'/docs/integration/client/vue-apollo.md' }, 94 | { text: 'Fetch API', link:'/docs/integration/client/fetch.md' }, 95 | ] 96 | }, 97 | // { text: 'ChatGPT', link:'/docs/integration/chatgpt.md' }, 98 | ] 99 | }, 100 | { 101 | text: 'Comparisons', link: '/docs/comparisons.md', 102 | } 103 | ], 104 | } 105 | } 106 | }) 107 | -------------------------------------------------------------------------------- /www/public/logo/r5m.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /www/public/logo/r6N.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /www/public/logo/r5y.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /www/public/logo/r6p.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /www/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: [2, 3] 3 | --- 4 | 5 | # Quickstart 6 | 7 | ## Overview 8 | 9 | Garph is a tool for building GraphQL APIs without codegen. It provides a fullstack TypeScript experience and makes it easy to create and maintain GraphQL APIs. This guide will show you how to install and set up Garph and create a simple GraphQL API. 10 | 11 | **Check out a video introduction to Garph by Jamie Barton** 12 | 13 | 14 | 15 | ## Prerequisites 16 | 17 | Before you begin, make sure you have the following installed: 18 | 19 | - [Node.js](https://nodejs.org/) (LTS and above) 20 | - npm (included with Node) 21 | 22 | ## Tutorial 23 | 24 | ### Step 1: Install Garph 25 | 26 | To install Garph, run the following command in your terminal: 27 | 28 | ::: code-group 29 | ```sh [npm] 30 | $ npm i garph graphql-yoga 31 | ``` 32 | 33 | ```sh [pnpm] 34 | $ pnpm add garph graphql-yoga 35 | ``` 36 | 37 | ```sh [yarn] 38 | $ yarn add garph graphql-yoga 39 | ``` 40 | 41 | ```sh [bun] 42 | $ bun i garph graphql-yoga 43 | ``` 44 | ::: 45 | 46 | This will install Garph and [Yoga](https://the-guild.dev/graphql/yoga-server) in your project. 47 | 48 | ### Step 2: Create a schema 49 | 50 | In GraphQL, schemas are used to define the types and operations that are available in a GraphQL API. 51 | 52 | The "Query" type is the entry point for queries in a GraphQL schema. It defines the top-level fields that can be queried by clients of the API. The fields defined on the "Query" type determine what data can be queried and returned by the server. 53 | 54 | Create a new file called `index.ts` and paste the following contents: 55 | 56 | ::: code-group 57 | ```ts [index.ts] 58 | import { g, InferResolvers, buildSchema } from 'garph' 59 | import { createYoga, YogaInitialContext } from 'graphql-yoga' 60 | import { createServer } from 'http' 61 | 62 | const queryType = g.type('Query', { 63 | greet: g.string() 64 | .args({ 65 | name: g.string().optional().default('Max') 66 | }) 67 | .description('Greets a person') 68 | }) 69 | ``` 70 | ::: 71 | 72 | This will import the required packages and create a new GraphQL schema a Query type, that has a field "greet", that takes an optional "name" argument of type string with the default value "Max". 73 | 74 | The example above produces the following GraphQL schema: 75 | 76 | ```graphql 77 | type Query { 78 | """ 79 | Greets a person 80 | """ 81 | greet(name: String = "Max"): String! 82 | } 83 | ``` 84 | 85 | [→ More about Schemas](./guide/schemas.md) 86 | 87 | ### Step 3: Add resolvers 88 | 89 | GraphQL resolvers are functions for fetching the data for a particular field in a GraphQL query or mutation. When a client makes a GraphQL request, the GraphQL server invokes the corresponding resolver functions to retrieve the data for the requested fields. 90 | 91 | ::: code-group 92 | ```ts{13-17} [index.ts] 93 | import { g, InferResolvers, buildSchema } from 'garph' 94 | import { createYoga, YogaInitialContext } from 'graphql-yoga' 95 | import { createServer } from 'http' 96 | 97 | const queryType = g.type('Query', { 98 | greet: g.string() 99 | .args({ 100 | name: g.string().optional().default('Max') 101 | }) 102 | .description('Greets a person') 103 | }) 104 | 105 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: YogaInitialContext }> = { 106 | Query: { 107 | greet: (parent, args, context, info) => `Hello, ${args.name}` 108 | } 109 | } 110 | ``` 111 | ::: 112 | 113 | The above code defines a resolver function for a "greet" field on the "Query" type in a GraphQL schema. When a client sends a query requesting the "greet" field, this resolver function will be invoked by the GraphQL server to fetch and return the data for the field. 114 | 115 | The resolver function above simply returns a string that contains a greeting message using the name passed in as an argument. 116 | 117 | [→ More about Resolvers](./guide/resolvers.md) 118 | 119 | ### Step 4: Serve the GraphQL API 120 | 121 | Although Garph holds no opinions of which server is being used to serve the GraphQL API, we **higly recommend** starting out with [Yoga](https://the-guild.dev/graphql/yoga-server) 122 | 123 | GraphQL Yoga can help simplify the process of building a GraphQL server, reduce boilerplate code, and provide useful features and tools to help you develop and debug your server more efficiently 124 | 125 | ::: code-group 126 | ```ts{19-24} [index.ts] 127 | import { g, InferResolvers, buildSchema } from 'garph' 128 | import { createYoga, YogaInitialContext } from 'graphql-yoga' 129 | import { createServer } from 'http' 130 | 131 | const queryType = g.type('Query', { 132 | greet: g.string() 133 | .args({ 134 | name: g.string().optional().default('Max') 135 | }) 136 | .description('Greets a person') 137 | }) 138 | 139 | const resolvers: InferResolvers<{ Query: typeof queryType }, { context: YogaInitialContext }> = { 140 | Query: { 141 | greet: (parent, args, context, info) => `Hello, ${args.name}` 142 | } 143 | } 144 | 145 | const schema = buildSchema({ g, resolvers }) 146 | const yoga = createYoga({ schema }) 147 | const server = createServer(yoga) 148 | server.listen(4000, () => { 149 | console.info('Server is running on http://localhost:4000/graphql') 150 | }) 151 | ``` 152 | ::: 153 | 154 | In the above code, we create a new GraphQL schema from our Garph schema and resolvers. After that, we create a new Yoga instance with the schema provided and serve Yoga using a http server included in Node.js on port 4000 155 | 156 | You can execute the following command to run the code above: 157 | 158 | ```sh 159 | $ npx ts-node index.ts 160 | ``` 161 | 162 | Once the server is up, you should see the following output in the console: 163 | 164 | ``` 165 | Server is running on http://localhost:4000/graphql 166 | ``` 167 | 168 | ### Step 5: Try the GraphQL API 169 | 170 | To try out your new GraphQL API, open your web browser and navigate to: http://localhost:4000/graphql 171 | 172 | This will open the GraphQL Playground (GraphiQL) where you can test your API by running queries. 173 | 174 | To test the "greet" query, enter the following in the left pane: 175 | 176 | ```graphql 177 | query { 178 | greet(name: "Max") 179 | } 180 | ``` 181 | 182 | Then, click the "Play" button to run the query. You should see the following response in the right pane: 183 | 184 | ```json 185 | { 186 | "data": { 187 | "greet": "Hello, Max" 188 | } 189 | } 190 | ``` 191 | 192 | Congratulations, you have created a GraphQL API with Garph! 193 | 194 | ## Conclusion 195 | 196 | In this Quickstart guide, you learned how to install and set up Garph and create a simple GraphQL API. For more information on how to use Garph, see other parts of the documentation 197 | -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | import { AnyType, Args, GarphSchema } from './index' 2 | import { SchemaComposer } from 'graphql-compose' 3 | import { Factory } from 'single-user-cache' 4 | const factory = new Factory() 5 | const dataLoader = factory.create() 6 | 7 | export type ConverterConfig = { 8 | defaultNullability?: boolean 9 | } 10 | 11 | export function printSchema(g: GarphSchema, config: ConverterConfig = { defaultNullability: false }) { 12 | const schemaComposer = new SchemaComposer(); 13 | g.types.forEach(type => schemaComposer.add(convertToGraphqlType(schemaComposer, type.typeDef.name, type, config))) 14 | return schemaComposer.toSDL() 15 | } 16 | 17 | export function buildSchema({ g, resolvers }: { g: GarphSchema, resolvers?: any }, config: ConverterConfig = { defaultNullability: false }) { 18 | const schemaComposer = new SchemaComposer(); 19 | g.types.forEach(type => schemaComposer.add(convertToGraphqlType(schemaComposer, type.typeDef.name, type, config, resolvers[type.typeDef.name]))) 20 | return schemaComposer.buildSchema() 21 | } 22 | 23 | function isOptional(target: string, type: AnyType, config: ConverterConfig) { 24 | return type.typeDef.isRequired ? `${target}!` : type.typeDef.isOptional ? `${target}` : config.defaultNullability ? `${target}` : `${target}!` 25 | } 26 | 27 | export function getFieldType(schemaComposer: SchemaComposer, type: AnyType, config: ConverterConfig) { 28 | switch (type.typeDef.type) { 29 | case 'String': 30 | return isOptional('String', type, config) 31 | case 'Int': 32 | return isOptional('Int', type, config) 33 | case 'Float': 34 | return isOptional('Float', type, config) 35 | case 'Boolean': 36 | return isOptional('Boolean', type, config) 37 | case 'ID': 38 | return isOptional('ID', type, config) 39 | case 'List': 40 | return isOptional(`[${getFieldType(schemaComposer, type.typeDef.shape, config)}]`, type, config) 41 | case 'PaginatedList': 42 | schemaComposer.createObjectTC({ 43 | name: `${type.typeDef.shape.typeDef.shape.typeDef.name}Edge`, 44 | fields: { 45 | node: { 46 | type: type.typeDef.shape.typeDef.shape.typeDef.name, 47 | }, 48 | cursor: { 49 | type: 'String!', 50 | } 51 | } 52 | }) 53 | 54 | schemaComposer.createObjectTC({ 55 | name: `${type.typeDef.shape.typeDef.shape.typeDef.name}Connection`, 56 | fields: { 57 | edges: { 58 | type: `[${type.typeDef.shape.typeDef.shape.typeDef.name}Edge]`, 59 | }, 60 | pageInfo: { 61 | type: 'PageInfo!', 62 | } 63 | } 64 | }) 65 | 66 | return isOptional(`${type.typeDef.shape.typeDef.shape.typeDef.name}Connection`, type, config) 67 | case 'Ref': 68 | if (!typeof type.typeDef.shape) { 69 | throw new Error('Ref type must be a function or a valid Garph Type') 70 | } 71 | 72 | let shape 73 | if (typeof type.typeDef.shape === 'function') { 74 | shape = type.typeDef.shape() 75 | } else { 76 | shape = type.typeDef.shape 77 | } 78 | 79 | return isOptional(shape.typeDef.name, type, config) 80 | default: 81 | return isOptional(type.typeDef.name, type, config) 82 | } 83 | } 84 | 85 | export function convertToGraphqlType(schemaComposer: SchemaComposer, name: string, type: AnyType, config: ConverterConfig, resolvers?: any) { 86 | switch (type.typeDef.type) { 87 | case 'ObjectType': 88 | const objType = schemaComposer.createObjectTC({ 89 | name, 90 | description: type.typeDef.description, 91 | fields: parseFields(schemaComposer, name, type.typeDef.shape, config, resolvers), 92 | }) 93 | 94 | if (type.typeDef.interfaces) { 95 | type.typeDef.interfaces.forEach(i => { 96 | objType.addFields(parseFields(schemaComposer, name, i.typeDef.shape, config, resolvers)) 97 | objType.addInterface(i.typeDef.name) 98 | }) 99 | } 100 | 101 | if (type.typeDef.extend) { 102 | type.typeDef.extend.forEach(i => { 103 | objType.addFields(parseFields(schemaComposer, name, i as any, config, resolvers)) 104 | }) 105 | } 106 | 107 | return objType 108 | case 'Enum': 109 | return schemaComposer.createEnumTC({ 110 | name, 111 | description: type.typeDef.description, 112 | values: type.typeDef.shape.reduce((acc, val) => { 113 | acc[val] = {} 114 | return acc 115 | }, {}) 116 | }) 117 | case 'Union': 118 | return schemaComposer.createUnionTC({ 119 | name, 120 | description: type.typeDef.description, 121 | types: Object.values(type.typeDef.shape).map((t: AnyType) => t.typeDef.name), 122 | resolveType: resolvers?.resolveType 123 | }) 124 | case 'InputType': 125 | const inputType = schemaComposer.createInputTC({ 126 | name, 127 | description: type.typeDef.description, 128 | fields: parseFields(schemaComposer, name, type.typeDef.shape, config), 129 | }) 130 | 131 | if (type.typeDef.extend) { 132 | type.typeDef.extend.forEach(i => { 133 | inputType.addFields(parseFields(schemaComposer, name, i as any, config, resolvers)) 134 | }) 135 | } 136 | 137 | return inputType 138 | case 'Scalar': 139 | return schemaComposer.createScalarTC({ 140 | name, 141 | description: type.typeDef.description, 142 | serialize: type.typeDef.scalarOptions?.serialize, 143 | parseValue: type.typeDef.scalarOptions?.parseValue, 144 | parseLiteral: type.typeDef.scalarOptions?.parseLiteral, 145 | specifiedByURL: type.typeDef.scalarOptions?.specifiedByUrl 146 | }) 147 | case 'InterfaceType': 148 | const interfaceType = schemaComposer.createInterfaceTC({ 149 | name, 150 | description: type.typeDef.description, 151 | fields: parseFields(schemaComposer, name, type.typeDef.shape, config), 152 | resolveType: resolvers?.resolveType 153 | }) 154 | 155 | if (type.typeDef.interfaces) { 156 | type.typeDef.interfaces.forEach(i => { 157 | interfaceType.addFields(parseFields(schemaComposer, name, i.typeDef.shape, config)) 158 | interfaceType.addInterface(i.typeDef.name) 159 | }) 160 | } 161 | 162 | if (type.typeDef.extend) { 163 | type.typeDef.extend.forEach(i => { 164 | interfaceType.addFields(parseFields(schemaComposer, name, i as any, config, resolvers)) 165 | }) 166 | } 167 | 168 | return interfaceType 169 | } 170 | } 171 | 172 | export function parseFields(schemaComposer: SchemaComposer, name: string, fields: AnyType, config: ConverterConfig, resolvers?: any) { 173 | const fieldsObj = {} 174 | Object.keys(fields).forEach(fieldName => { 175 | const field = fields[fieldName] 176 | 177 | fieldsObj[fieldName] = { 178 | type: getFieldType(schemaComposer, field, config), 179 | args: parseArgs(schemaComposer, field.typeDef.args, config), 180 | defaultValue: field.typeDef.defaultValue, 181 | deprecationReason: field.typeDef.deprecated, 182 | description: field.typeDef.description 183 | } 184 | 185 | if (resolvers?.[fieldName]) { 186 | Object.assign(fieldsObj[fieldName], addResolver(resolvers[fieldName], `${name}.${fieldName}`)) 187 | } 188 | }) 189 | 190 | return fieldsObj 191 | } 192 | 193 | function addResolver (resolver, cacheKey: string) { 194 | if (!resolver) return 195 | if (resolver.resolve || resolver.subscribe) return resolver 196 | 197 | // Loader 198 | if (resolver.load) { 199 | factory.add(cacheKey, { cache: true }, async (queries) => resolver.load(queries)) 200 | 201 | return { 202 | resolve: (parent, args, context, info) => { 203 | return dataLoader[cacheKey]({ parent, args, context, info }) 204 | } 205 | } 206 | } 207 | 208 | // Loader (no cache) 209 | if (resolver.loadBatch) { 210 | factory.add(cacheKey, { cache: false }, async (queries) => resolver.loadBatch(queries)) 211 | 212 | return { 213 | resolve: (parent, args, context, info) => { 214 | return dataLoader[cacheKey]({ parent, args, context, info }) 215 | } 216 | } 217 | } 218 | 219 | return { resolve: resolver } 220 | } 221 | 222 | export function parseArgs(schemaComposer: SchemaComposer, anyArgs: Args, config) { 223 | if (!anyArgs) return 224 | 225 | const args = {} 226 | 227 | Object.keys(anyArgs).forEach(argName => { 228 | const arg = anyArgs[argName] 229 | args[argName] = { 230 | type: getFieldType(schemaComposer, arg, config), 231 | defaultValue: arg.typeDef.defaultValue, 232 | deprecationReason: arg.typeDef.deprecated, 233 | description: arg.typeDef.description 234 | } 235 | }) 236 | 237 | return args 238 | } 239 | -------------------------------------------------------------------------------- /www/api/classes/GarphSchema.md: -------------------------------------------------------------------------------- 1 | [garph](../index.md) / GarphSchema 2 | 3 | # Class: GarphSchema 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new GarphSchema**(`«destructured»?`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `«destructured»` | `Object` | 16 | | › `types` | [`AnyType`](../index.md#anytype)[] | 17 | 18 | #### Defined in 19 | 20 | [index.ts:625](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L625) 21 | 22 | ## Properties 23 | 24 | ### nodeType 25 | 26 | • **nodeType**: `GInterface`<``"Node"``, { `id`: `GString`<``"ID"``\> }\> 27 | 28 | #### Defined in 29 | 30 | [index.ts:601](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L601) 31 | 32 | ___ 33 | 34 | ### pageInfoArgs 35 | 36 | • **pageInfoArgs**: `Object` 37 | 38 | #### Type declaration 39 | 40 | | Name | Type | 41 | | :------ | :------ | 42 | | `after` | `GOptional`<`GString`<``"ID"``\>\> | 43 | | `before` | `GOptional`<`GString`<``"ID"``\>\> | 44 | | `first` | `GOptional`<`GNumber`<``"Int"``\>\> | 45 | | `last` | `GOptional`<`GNumber`<``"Int"``\>\> | 46 | 47 | #### Defined in 48 | 49 | [index.ts:612](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L612) 50 | 51 | ___ 52 | 53 | ### pageInfoType 54 | 55 | • **pageInfoType**: `GType`<``"PageInfo"``, { `endCursor`: `GOptional`<`GString`<``"String"``\>\> ; `hasNextPage`: `GBoolean` ; `hasPreviousPage`: `GBoolean` ; `startCursor`: `GOptional`<`GString`<``"String"``\>\> }\> 56 | 57 | #### Defined in 58 | 59 | [index.ts:605](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L605) 60 | 61 | ___ 62 | 63 | ### types 64 | 65 | • **types**: `Map`<`string`, [`AnyType`](../index.md#anytype)\> 66 | 67 | #### Defined in 68 | 69 | [index.ts:599](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L599) 70 | 71 | ## Methods 72 | 73 | ### boolean 74 | 75 | ▸ **boolean**(): `GBoolean` 76 | 77 | #### Returns 78 | 79 | `GBoolean` 80 | 81 | #### Defined in 82 | 83 | [index.ts:710](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L710) 84 | 85 | ___ 86 | 87 | ### connection 88 | 89 | ▸ **connection**<`N`, `T`\>(`name`, `shape`): `GType`<`string`, { `edges`: `GList`<`T`\> ; `pageInfo`: `GType`<``"PageInfo"``, { `endCursor`: `GOptional`<`GString`<``"String"``\>\> ; `hasNextPage`: `GBoolean` ; `hasPreviousPage`: `GBoolean` ; `startCursor`: `GOptional`<`GString`<``"String"``\>\> }\> }\> 90 | 91 | #### Type parameters 92 | 93 | | Name | Type | 94 | | :------ | :------ | 95 | | `N` | extends `string` | 96 | | `T` | extends [`Type`](Type.md)<`any`, ``"Ref"``, `T`\> | 97 | 98 | #### Parameters 99 | 100 | | Name | Type | 101 | | :------ | :------ | 102 | | `name` | `N` | 103 | | `shape` | `T` | 104 | 105 | #### Returns 106 | 107 | `GType`<`string`, { `edges`: `GList`<`T`\> ; `pageInfo`: `GType`<``"PageInfo"``, { `endCursor`: `GOptional`<`GString`<``"String"``\>\> ; `hasNextPage`: `GBoolean` ; `hasPreviousPage`: `GBoolean` ; `startCursor`: `GOptional`<`GString`<``"String"``\>\> }\> }\> 108 | 109 | #### Defined in 110 | 111 | [index.ts:641](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L641) 112 | 113 | ___ 114 | 115 | ### edge 116 | 117 | ▸ **edge**<`N`, `T`\>(`name`, `shape`): `GType`<`N`, { `cursor`: [`AnyString`](../index.md#anystring) ; `node`: `T` }\> 118 | 119 | #### Type parameters 120 | 121 | | Name | Type | 122 | | :------ | :------ | 123 | | `N` | extends `string` | 124 | | `T` | extends [`Type`](Type.md)<`any`, ``"Ref"``, `T`\> | 125 | 126 | #### Parameters 127 | 128 | | Name | Type | 129 | | :------ | :------ | 130 | | `name` | `N` | 131 | | `shape` | `T` | 132 | 133 | #### Returns 134 | 135 | `GType`<`N`, { `cursor`: [`AnyString`](../index.md#anystring) ; `node`: `T` }\> 136 | 137 | #### Defined in 138 | 139 | [index.ts:651](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L651) 140 | 141 | ___ 142 | 143 | ### enumType 144 | 145 | ▸ **enumType**<`N`, `T`\>(`name`, `args`): `GEnum`<`N`, `T`\> 146 | 147 | #### Type parameters 148 | 149 | | Name | Type | 150 | | :------ | :------ | 151 | | `N` | extends `string` | 152 | | `T` | extends readonly `string`[] \| `TSEnumType` | 153 | 154 | #### Parameters 155 | 156 | | Name | Type | 157 | | :------ | :------ | 158 | | `name` | `N` | 159 | | `args` | `T` | 160 | 161 | #### Returns 162 | 163 | `GEnum`<`N`, `T`\> 164 | 165 | #### Defined in 166 | 167 | [index.ts:670](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L670) 168 | 169 | ___ 170 | 171 | ### float 172 | 173 | ▸ **float**(): `GNumber`<``"Float"``\> 174 | 175 | #### Returns 176 | 177 | `GNumber`<``"Float"``\> 178 | 179 | #### Defined in 180 | 181 | [index.ts:706](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L706) 182 | 183 | ___ 184 | 185 | ### id 186 | 187 | ▸ **id**(): `GString`<``"ID"``\> 188 | 189 | #### Returns 190 | 191 | `GString`<``"ID"``\> 192 | 193 | #### Defined in 194 | 195 | [index.ts:698](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L698) 196 | 197 | ___ 198 | 199 | ### inputType 200 | 201 | ▸ **inputType**<`N`, `T`\>(`name`, `shape`): `GInput`<`N`, `T`\> 202 | 203 | #### Type parameters 204 | 205 | | Name | Type | 206 | | :------ | :------ | 207 | | `N` | extends `string` | 208 | | `T` | extends [`AnyTypes`](../index.md#anytypes) | 209 | 210 | #### Parameters 211 | 212 | | Name | Type | 213 | | :------ | :------ | 214 | | `name` | `N` | 215 | | `shape` | `T` | 216 | 217 | #### Returns 218 | 219 | `GInput`<`N`, `T`\> 220 | 221 | #### Defined in 222 | 223 | [index.ts:664](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L664) 224 | 225 | ___ 226 | 227 | ### int 228 | 229 | ▸ **int**(): `GNumber`<``"Int"``\> 230 | 231 | #### Returns 232 | 233 | `GNumber`<``"Int"``\> 234 | 235 | #### Defined in 236 | 237 | [index.ts:702](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L702) 238 | 239 | ___ 240 | 241 | ### interface 242 | 243 | ▸ **interface**<`N`, `T`\>(`name`, `shape`): `GInterface`<`N`, `T`\> 244 | 245 | #### Type parameters 246 | 247 | | Name | Type | 248 | | :------ | :------ | 249 | | `N` | extends `string` | 250 | | `T` | extends [`AnyTypes`](../index.md#anytypes) | 251 | 252 | #### Parameters 253 | 254 | | Name | Type | 255 | | :------ | :------ | 256 | | `name` | `N` | 257 | | `shape` | `T` | 258 | 259 | #### Returns 260 | 261 | `GInterface`<`N`, `T`\> 262 | 263 | #### Defined in 264 | 265 | [index.ts:688](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L688) 266 | 267 | ___ 268 | 269 | ### node 270 | 271 | ▸ **node**<`N`, `T`\>(`name`, `shape`): `GType`<`N`, `T` & { `id`: `GString`<``"ID"``\> }\> 272 | 273 | #### Type parameters 274 | 275 | | Name | Type | 276 | | :------ | :------ | 277 | | `N` | extends `string` | 278 | | `T` | extends [`AnyTypes`](../index.md#anytypes) | 279 | 280 | #### Parameters 281 | 282 | | Name | Type | 283 | | :------ | :------ | 284 | | `name` | `N` | 285 | | `shape` | `T` | 286 | 287 | #### Returns 288 | 289 | `GType`<`N`, `T` & { `id`: `GString`<``"ID"``\> }\> 290 | 291 | #### Defined in 292 | 293 | [index.ts:635](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L635) 294 | 295 | ___ 296 | 297 | ### ref 298 | 299 | ▸ **ref**<`T`\>(`ref`): `GRef`<`T`\> 300 | 301 | #### Type parameters 302 | 303 | | Name | 304 | | :------ | 305 | | `T` | 306 | 307 | #### Parameters 308 | 309 | | Name | Type | 310 | | :------ | :------ | 311 | | `ref` | `T` | 312 | 313 | #### Returns 314 | 315 | `GRef`<`T`\> 316 | 317 | #### Defined in 318 | 319 | [index.ts:716](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L716) 320 | 321 | ___ 322 | 323 | ### registerType 324 | 325 | ▸ **registerType**(`type`): `void` 326 | 327 | #### Parameters 328 | 329 | | Name | Type | 330 | | :------ | :------ | 331 | | `type` | [`AnyType`](../index.md#anytype) | 332 | 333 | #### Returns 334 | 335 | `void` 336 | 337 | #### Defined in 338 | 339 | [index.ts:619](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L619) 340 | 341 | ___ 342 | 343 | ### scalarType 344 | 345 | ▸ **scalarType**<`I`, `O`\>(`name`, `options?`): `GScalar`<`I`, `O`\> 346 | 347 | #### Type parameters 348 | 349 | | Name | 350 | | :------ | 351 | | `I` | 352 | | `O` | 353 | 354 | #### Parameters 355 | 356 | | Name | Type | 357 | | :------ | :------ | 358 | | `name` | `string` | 359 | | `options?` | `ScalarOptions`<`I`, `O`\> | 360 | 361 | #### Returns 362 | 363 | `GScalar`<`I`, `O`\> 364 | 365 | #### Defined in 366 | 367 | [index.ts:682](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L682) 368 | 369 | ___ 370 | 371 | ### string 372 | 373 | ▸ **string**(): `GString`<``"String"``\> 374 | 375 | #### Returns 376 | 377 | `GString`<``"String"``\> 378 | 379 | #### Defined in 380 | 381 | [index.ts:694](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L694) 382 | 383 | ___ 384 | 385 | ### type 386 | 387 | ▸ **type**<`N`, `T`\>(`name`, `shape`): `GType`<`N`, `T`\> 388 | 389 | #### Type parameters 390 | 391 | | Name | Type | 392 | | :------ | :------ | 393 | | `N` | extends `string` | 394 | | `T` | extends [`AnyTypes`](../index.md#anytypes) | 395 | 396 | #### Parameters 397 | 398 | | Name | Type | 399 | | :------ | :------ | 400 | | `name` | `N` | 401 | | `shape` | `T` | 402 | 403 | #### Returns 404 | 405 | `GType`<`N`, `T`\> 406 | 407 | #### Defined in 408 | 409 | [index.ts:629](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L629) 410 | 411 | ___ 412 | 413 | ### unionType 414 | 415 | ▸ **unionType**<`N`, `T`\>(`name`, `args`): `GUnion`<`N`, `T`\> 416 | 417 | #### Type parameters 418 | 419 | | Name | Type | 420 | | :------ | :------ | 421 | | `N` | extends `string` | 422 | | `T` | extends [`AnyObjects`](../index.md#anyobjects) | 423 | 424 | #### Parameters 425 | 426 | | Name | Type | 427 | | :------ | :------ | 428 | | `name` | `N` | 429 | | `args` | `T` | 430 | 431 | #### Returns 432 | 433 | `GUnion`<`N`, `T`\> 434 | 435 | #### Defined in 436 | 437 | [index.ts:676](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L676) 438 | -------------------------------------------------------------------------------- /www/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Garph - GraphQL. Reimagined. 3 | description: Garph provides fullstack TypeScript experience for building GraphQL-APIs without codegen 4 | aside: false 5 | sidebar: false 6 | layout: page 7 | --- 8 | 9 | 272 | 273 |
274 |
275 |
276 |

277 | GraphQL. 278 | Reimagined. 279 |

280 |

281 | Garph provides fullstack TypeScript experience for building GraphQL-APIs without codegen 282 |

283 | 293 | 294 | 301 | 304 | 305 |
306 |
307 |
308 |
309 | 310 |
311 |
312 |
313 | 314 |
315 |
316 | TypeScript 317 | End-to-end type-safety 318 |
319 |
320 |
321 |
322 | 323 |
324 |
325 | GraphQL + Relay 326 | Spec-compatible API 327 |
328 |
329 |
330 |
331 | 332 |
333 |
334 | Framework-ready 335 | Backend and Frontend 336 |
337 |
338 |
339 |
340 | 341 |
342 |
343 | Batteries-included 344 | Everything you need 345 |
346 |
347 |
348 |
349 |
350 |
351 | 352 |
353 |
354 | “The ideal GQL stack imo looks like this” 355 |
356 |
— Nate Wienert (Tamagui, Vercel)
357 |
358 |
359 | 362 |
363 | 364 |
365 |
366 | -------------------------------------------------------------------------------- /www/docs/guide/schemas.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: [2, 3] 3 | --- 4 | 5 | # Schemas 6 | 7 | In GraphQL, a schema is a collection of types that define the structure of data that can be queried or mutated in a GraphQL API. The schema acts as a contract between the client and server, specifying what types of data can be queried, what types of mutations can be performed, and how the data will be structured and returned. 8 | 9 | ## Constructors 10 | 11 | Create new Garph schema 12 | 13 | ```ts 14 | import { GarphSchema } from 'garph' 15 | 16 | const g = new GarphSchema() 17 | ``` 18 | 19 | Create new Garph schema with types 20 | 21 | ```ts 22 | import { GarphSchema } from 'garph' 23 | 24 | const g = new GarphSchema({ types: [type1, type2, type3] }) 25 | ``` 26 | 27 | ## Types 28 | 29 | ### Object Type 30 | 31 | An object type is a type in GraphQL that represents an object with a set of named fields and their corresponding values. Object types are used to define the structure and shape of data that can be queried and retrieved from a GraphQL API. They can also be used as inputs for mutations. 32 | 33 | ```ts 34 | g.type('User', { 35 | name: g.string() 36 | }) 37 | ``` 38 | 39 | GraphQL Type: 40 | 41 | ```graphql 42 | type User { 43 | name: String! 44 | } 45 | ``` 46 | 47 | ### String 48 | 49 | A string is a type in GraphQL that represents a sequence of characters, such as text or a message. It is used to represent textual data. 50 | 51 | ```ts 52 | g.string() 53 | ``` 54 | 55 | GraphQL Type: 56 | 57 | ```graphql 58 | String! 59 | ``` 60 | 61 | ### Int 62 | 63 | An integer is a type in GraphQL that represents a whole number without any decimal points. It is used to represent numeric data. 64 | 65 | ```ts 66 | g.int() 67 | ``` 68 | 69 | GraphQL Type: 70 | 71 | ```graphql 72 | Int! 73 | ``` 74 | 75 | ### Float 76 | 77 | A float is a type in GraphQL that represents a floating-point number with decimal points. It is used to represent numeric data that may have decimal points. 78 | 79 | ```ts 80 | g.float() 81 | ``` 82 | 83 | GraphQL Type: 84 | 85 | ```graphql 86 | Float! 87 | ``` 88 | 89 | ### Boolean 90 | 91 | A boolean is a type in GraphQL that represents a value of either true or false. It is used to represent logical data. 92 | 93 | ```ts 94 | g.boolean() 95 | ``` 96 | 97 | GraphQL Type: 98 | 99 | ```graphql 100 | Boolean! 101 | ``` 102 | 103 | ### ID 104 | 105 | An ID is a type in GraphQL that represents a unique identifier, typically used to identify a specific object or record in a database. It is a string that can be serialized and deserialized. 106 | 107 | ```ts 108 | g.id() 109 | ``` 110 | 111 | GraphQL Type: 112 | 113 | ```graphql 114 | ID! 115 | ``` 116 | 117 | ### Enum 118 | 119 | An enum is a type in GraphQL that represents a set of predefined values. It is used to restrict the possible values of a field or an argument to a specific set of values. 120 | 121 | Plain: 122 | 123 | ```ts 124 | g.enumType('Fruits', ['Apples', 'Oranges'] as const) 125 | ``` 126 | 127 | (We need `as const` for proper type inference) 128 | 129 | From TypeScript Enum: 130 | 131 | ```ts 132 | enum Fruits { 133 | Apples, 134 | Oranges 135 | } 136 | 137 | g.enumType('Fruits', Fruits) 138 | ``` 139 | 140 | GraphQL Type: 141 | 142 | ```graphql 143 | enum Fruits { 144 | Apples 145 | Oranges 146 | } 147 | ``` 148 | 149 | ### Union 150 | 151 | A union is a type in GraphQL that represents a set of types. It is used to represent a type that can be one of several possible types. 152 | 153 | ```ts 154 | g.unionType('Name', { a, b }) 155 | ``` 156 | 157 | GraphQL Type: 158 | 159 | ```graphql 160 | union Name = A | B 161 | ``` 162 | 163 | ### Ref 164 | 165 | Reference: 166 | 167 | ```ts 168 | const name = g.type('Name', { 169 | greet: g.ref(otherType) 170 | }) 171 | ``` 172 | 173 | Circular Reference: 174 | 175 | ```ts 176 | const name = g.type('Name', { 177 | greet: g.ref(() => name) 178 | }) 179 | ``` 180 | 181 | ### Interface 182 | 183 | An interface is a type in GraphQL that defines a set of fields that can be implemented by other object types. It is used to define a common set of fields and their corresponding types that can be used by multiple object types. 184 | 185 | ```ts 186 | g.interface('Name', { 187 | greet: g.string() 188 | }) 189 | ``` 190 | 191 | GraphQL Type: 192 | 193 | ```graphql 194 | interface Name { 195 | greet: String! 196 | } 197 | ``` 198 | 199 | Implementing an interface 200 | 201 | ```ts 202 | const test = g.type('Test', {}).implements(interface) 203 | ``` 204 | 205 | Or a set of interfaces: 206 | 207 | ```ts 208 | const test = g.type('Test', {}).implements([interface, interface]) 209 | ``` 210 | 211 | > **Note**: Inherited fields will be added to the schema automatically, you don't need to re-specify them 212 | 213 | ### Input 214 | 215 | An input type is a type in GraphQL that represents the input data for a mutation or a query argument. It is used to define the shape and structure of the data that can be passed as input to a GraphQL API. 216 | 217 | ```ts 218 | g.inputType('Name', { 219 | greet: g.string() 220 | }) 221 | ``` 222 | 223 | GraphQL Type: 224 | 225 | ```graphql 226 | input Name { 227 | greet: String! 228 | } 229 | ``` 230 | 231 | ### Scalar 232 | 233 | Custom scalars can be useful for handling specific data types that are not natively supported by GraphQL or you want a version of an existing type that does some validation. For example, you might define a custom scalar for handling dates: 234 | 235 | ```ts 236 | g.scalarType('Date', { 237 | serialize: (value) => value.getTime(), 238 | parseValue: (value) => new Date(value) 239 | }) 240 | ``` 241 | 242 | GraphQL Type: 243 | 244 | ```graphql 245 | scalar Date 246 | ``` 247 | 248 | ### Directive 249 | 250 | A directive is a special kind of declaration in GraphQL that can be used to modify the behavior of a query or a schema. It is used to annotate parts of a query with metadata or to specify additional constraints on the execution of a query. 251 | 252 | Currently not supported. 253 | 254 | See: https://github.com/stepci/garph/issues/40 255 | 256 | ## Relay Types 257 | 258 | ### Node 259 | 260 | ```ts 261 | g.node('User', { 262 | name: g.string() 263 | }) 264 | ``` 265 | 266 | GraphQL Type: 267 | 268 | ```graphql 269 | type User { 270 | id: ID! 271 | name: String! 272 | } 273 | ``` 274 | 275 | ### Edge 276 | 277 | ```ts 278 | g.edge('UserEdge', g.ref(userNode)) 279 | ``` 280 | 281 | GraphQL Type: 282 | 283 | ```graphql 284 | type UserEdge { 285 | cursor: String! 286 | node: User 287 | } 288 | ``` 289 | 290 | ### Connection 291 | 292 | ```ts 293 | g.connection('UserConnection', g.ref(userEdge)) 294 | ``` 295 | 296 | GraphQL Type: 297 | 298 | ```graphql 299 | type UserConnection { 300 | edges: [UserEdge] 301 | pageInfo: PageInfo! 302 | } 303 | ``` 304 | 305 | ### PageInfo 306 | 307 | ```ts 308 | g.pageInfoType 309 | ``` 310 | 311 | GraphQL Type: 312 | 313 | ```graphql 314 | type PageInfo { 315 | hasNextPage: Boolean! 316 | hasPreviousPage: Boolean! 317 | startCursor: String 318 | endCursor: String 319 | } 320 | ``` 321 | 322 | ### PageInfoArgs 323 | 324 | ``` 325 | g.pageInfoArgs 326 | ``` 327 | 328 | GraphQL Type: 329 | 330 | ```graphql 331 | (first: Int, last: Int, before: ID, after: ID) 332 | ``` 333 | 334 | ## Modifiers 335 | 336 | Modifiers can be chained together to produce desired type 337 | 338 | ### Args 339 | 340 | Used to pass additional parameters to a query or a mutation. They can be used to filter, sort, or modify the data returned by a query 341 | 342 | ```ts 343 | g.type('Query', { 344 | greet: g.string().args({ name: g.string() }) 345 | }) 346 | ``` 347 | 348 | GraphQL Type: 349 | 350 | ```graphql 351 | type Query { 352 | greet(name: String!): String! 353 | } 354 | ``` 355 | 356 | ### Implements 357 | 358 | Define a set of interfaces that an object type implements 359 | 360 | ```ts 361 | const test = g.type('Test', {}).implements(interface) 362 | ``` 363 | 364 | Or array of interfaces: 365 | 366 | ```ts 367 | const test = g.type('Test', {}).implements([interface, interface]) 368 | ``` 369 | 370 | GraphQL Type: 371 | 372 | ```graphql 373 | type Test implements interface { 374 | ... 375 | } 376 | ``` 377 | 378 | ### Extend 379 | 380 | Define a set of properties that extends an input, interface or object type 381 | 382 | ```ts 383 | const name = { 384 | name: g.string() 385 | } 386 | 387 | const test = g.type('Test', {}).extend(name) 388 | ``` 389 | 390 | Or array of properties: 391 | 392 | ```ts 393 | const test = g.type('Test', {}).extend([firstname, lastname]) 394 | ``` 395 | 396 | GraphQL Type: 397 | 398 | ```graphql 399 | type Test { 400 | name: String! 401 | } 402 | ``` 403 | 404 | ### List 405 | 406 | Turns a particular type into a list 407 | 408 | ```ts 409 | g.string().list() 410 | ``` 411 | 412 | ### Paginated List 413 | 414 | Turns a particular type into a cursor-paginated list 415 | 416 | ```ts 417 | g.ref(user).paginatedList() 418 | ``` 419 | 420 | ### Optional (nullable) 421 | 422 | Specifies whether a field can return a null value if the requested data is not available 423 | 424 | ```ts 425 | g.string().optional() 426 | ``` 427 | 428 | ### Required 429 | 430 | Specifies whether a field must return a value 431 | 432 | ```ts 433 | g.string().required() 434 | ``` 435 | 436 | ### Default 437 | 438 | Default values can be used to provide a fallback value if a requested value is not available. They can be specified for arguments, query variables, and input objects 439 | 440 | ```ts 441 | g.string().default("Default string") 442 | ``` 443 | 444 | ### Description 445 | 446 | A string that can be used to describe a schema element such as a field or an argument 447 | 448 | ```ts 449 | g.string().description("Description") 450 | ``` 451 | 452 | ### Deprecated 453 | 454 | Mark a field as deprecated. It is used to indicate that a schema element is no longer supported or recommended and should not be used in new code. When a deprecated schema element is used, a warning is generated to alert developers 455 | 456 | ```ts 457 | g.string().deprecated("Deprecation reason") 458 | ``` 459 | 460 | ### Omit Resolver 461 | 462 | Omit returning a field in the resolver. Used when you want to skip returning a value in case the value is resolved elsewhere 463 | 464 | ```ts 465 | g.string().omitResolver() 466 | ``` 467 | 468 | ## Utilities 469 | 470 | [→ `buildSchema`](/api/index.md#buildschema) 471 | 472 | [→ `printSchema`](/api/index.md#printschema) 473 | -------------------------------------------------------------------------------- /www/api/index.md: -------------------------------------------------------------------------------- 1 | garph 2 | 3 | # garph 4 | 5 | ## Classes 6 | 7 | - [GarphSchema](classes/GarphSchema.md) 8 | - [Type](classes/Type.md) 9 | 10 | ## Type Aliases 11 | 12 | ### AnyArgs 13 | 14 | Ƭ **AnyArgs**: [`Type`](classes/Type.md)<`any`, ``"Args"``\> 15 | 16 | #### Defined in 17 | 18 | [index.ts:58](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L58) 19 | 20 | ___ 21 | 22 | ### AnyBoolean 23 | 24 | Ƭ **AnyBoolean**: [`Type`](classes/Type.md)<`boolean`, ``"Boolean"``\> 25 | 26 | #### Defined in 27 | 28 | [index.ts:46](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L46) 29 | 30 | ___ 31 | 32 | ### AnyEnum 33 | 34 | Ƭ **AnyEnum**: [`Type`](classes/Type.md)<`any`, ``"Enum"``\> 35 | 36 | #### Defined in 37 | 38 | [index.ts:54](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L54) 39 | 40 | ___ 41 | 42 | ### AnyFloat 43 | 44 | Ƭ **AnyFloat**: [`Type`](classes/Type.md)<`number`, ``"Float"``\> 45 | 46 | #### Defined in 47 | 48 | [index.ts:49](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L49) 49 | 50 | ___ 51 | 52 | ### AnyID 53 | 54 | Ƭ **AnyID**: [`Type`](classes/Type.md)<`string`, ``"ID"``\> 55 | 56 | #### Defined in 57 | 58 | [index.ts:45](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L45) 59 | 60 | ___ 61 | 62 | ### AnyInput 63 | 64 | Ƭ **AnyInput**: [`Type`](classes/Type.md)<`any`, ``"InputType"``\> 65 | 66 | #### Defined in 67 | 68 | [index.ts:56](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L56) 69 | 70 | ___ 71 | 72 | ### AnyInt 73 | 74 | Ƭ **AnyInt**: [`Type`](classes/Type.md)<`number`, ``"Int"``\> 75 | 76 | #### Defined in 77 | 78 | [index.ts:48](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L48) 79 | 80 | ___ 81 | 82 | ### AnyInterface 83 | 84 | Ƭ **AnyInterface**: [`Type`](classes/Type.md)<`any`, ``"InterfaceType"``\> 85 | 86 | #### Defined in 87 | 88 | [index.ts:57](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L57) 89 | 90 | ___ 91 | 92 | ### AnyList 93 | 94 | Ƭ **AnyList**: [`Type`](classes/Type.md)<`any`, ``"List"``\> 95 | 96 | #### Defined in 97 | 98 | [index.ts:51](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L51) 99 | 100 | ___ 101 | 102 | ### AnyNumber 103 | 104 | Ƭ **AnyNumber**: [`Type`](classes/Type.md)<`number`, `any`\> 105 | 106 | #### Defined in 107 | 108 | [index.ts:47](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L47) 109 | 110 | ___ 111 | 112 | ### AnyObject 113 | 114 | Ƭ **AnyObject**: [`Type`](classes/Type.md)<`any`, ``"ObjectType"``\> 115 | 116 | #### Defined in 117 | 118 | [index.ts:60](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L60) 119 | 120 | ___ 121 | 122 | ### AnyObjects 123 | 124 | Ƭ **AnyObjects**: `Object` 125 | 126 | #### Index signature 127 | 128 | ▪ [key: `string`]: [`AnyObject`](index.md#anyobject) 129 | 130 | #### Defined in 131 | 132 | [index.ts:71](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L71) 133 | 134 | ___ 135 | 136 | ### AnyOmitResolver 137 | 138 | Ƭ **AnyOmitResolver**: [`Type`](classes/Type.md)<`any`, ``"OmitResolver"``\> 139 | 140 | #### Defined in 141 | 142 | [index.ts:61](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L61) 143 | 144 | ___ 145 | 146 | ### AnyOptional 147 | 148 | Ƭ **AnyOptional**: [`Type`](classes/Type.md)<`any`, ``"Optional"``\> 149 | 150 | #### Defined in 151 | 152 | [index.ts:59](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L59) 153 | 154 | ___ 155 | 156 | ### AnyPaginatedList 157 | 158 | Ƭ **AnyPaginatedList**: [`Type`](classes/Type.md)<`any`, ``"PaginatedList"``\> 159 | 160 | #### Defined in 161 | 162 | [index.ts:52](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L52) 163 | 164 | ___ 165 | 166 | ### AnyRef 167 | 168 | Ƭ **AnyRef**: [`Type`](classes/Type.md)<`any`, ``"Ref"``\> 169 | 170 | #### Defined in 171 | 172 | [index.ts:50](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L50) 173 | 174 | ___ 175 | 176 | ### AnyScalar 177 | 178 | Ƭ **AnyScalar**: [`Type`](classes/Type.md)<`any`, ``"Scalar"``\> 179 | 180 | #### Defined in 181 | 182 | [index.ts:55](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L55) 183 | 184 | ___ 185 | 186 | ### AnyString 187 | 188 | Ƭ **AnyString**: [`Type`](classes/Type.md)<`string`, ``"String"``\> 189 | 190 | #### Defined in 191 | 192 | [index.ts:44](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L44) 193 | 194 | ___ 195 | 196 | ### AnyType 197 | 198 | Ƭ **AnyType**: [`Type`](classes/Type.md)<`any`, `any`\> 199 | 200 | #### Defined in 201 | 202 | [index.ts:43](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L43) 203 | 204 | ___ 205 | 206 | ### AnyTypes 207 | 208 | Ƭ **AnyTypes**: `Object` 209 | 210 | #### Index signature 211 | 212 | ▪ [key: `string`]: [`AnyType`](index.md#anytype) 213 | 214 | #### Defined in 215 | 216 | [index.ts:67](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L67) 217 | 218 | ___ 219 | 220 | ### AnyUnion 221 | 222 | Ƭ **AnyUnion**: [`Type`](classes/Type.md)<`any`, ``"Union"``\> 223 | 224 | #### Defined in 225 | 226 | [index.ts:53](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L53) 227 | 228 | ___ 229 | 230 | ### Args 231 | 232 | Ƭ **Args**: `Object` 233 | 234 | #### Index signature 235 | 236 | ▪ [key: `string`]: [`AnyType`](index.md#anytype) 237 | 238 | #### Defined in 239 | 240 | [index.ts:63](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L63) 241 | 242 | ___ 243 | 244 | ### Infer 245 | 246 | Ƭ **Infer**<`T`, `options`\>: `ExpandRecursively`<[`InferRaw`](index.md#inferraw)<`T`, `options`\>\> 247 | 248 | #### Type parameters 249 | 250 | | Name | Type | 251 | | :------ | :------ | 252 | | `T` | `T` | 253 | | `options` | extends `InferOptions` = { `omitResolver`: `never` } | 254 | 255 | #### Defined in 256 | 257 | [index.ts:93](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L93) 258 | 259 | ___ 260 | 261 | ### InferArg 262 | 263 | Ƭ **InferArg**<`T`\>: `ExpandRecursively`<[`InferArgRaw`](index.md#inferargraw)<`T`\>\> 264 | 265 | #### Type parameters 266 | 267 | | Name | 268 | | :------ | 269 | | `T` | 270 | 271 | #### Defined in 272 | 273 | [index.ts:125](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L125) 274 | 275 | ___ 276 | 277 | ### InferArgRaw 278 | 279 | Ƭ **InferArgRaw**<`T`\>: `T` extends [`AnyArgs`](index.md#anyargs) ? { [K in keyof T["\_args"] as T["\_args"][K] extends AnyOptional ? never : K]: InferRaw } & { [K in keyof T["\_args"] as T["\_args"][K] extends AnyOptional ? K : never]?: InferRaw } : `never` 280 | 281 | #### Type parameters 282 | 283 | | Name | 284 | | :------ | 285 | | `T` | 286 | 287 | #### Defined in 288 | 289 | [index.ts:126](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L126) 290 | 291 | ___ 292 | 293 | ### InferArgs 294 | 295 | Ƭ **InferArgs**<`T`\>: `ExpandRecursively`<[`InferArgsRaw`](index.md#inferargsraw)<`T`\>\> 296 | 297 | #### Type parameters 298 | 299 | | Name | Type | 300 | | :------ | :------ | 301 | | `T` | extends [`AnyType`](index.md#anytype) | 302 | 303 | #### Defined in 304 | 305 | [index.ts:120](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L120) 306 | 307 | ___ 308 | 309 | ### InferArgsRaw 310 | 311 | Ƭ **InferArgsRaw**<`T`\>: `T` extends [`AnyObject`](index.md#anyobject) \| [`AnyInterface`](index.md#anyinterface) ? { [K in keyof T["\_shape"]]: InferArgRaw } : `never` 312 | 313 | #### Type parameters 314 | 315 | | Name | Type | 316 | | :------ | :------ | 317 | | `T` | extends [`AnyType`](index.md#anytype) | 318 | 319 | #### Defined in 320 | 321 | [index.ts:121](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L121) 322 | 323 | ___ 324 | 325 | ### InferRaw 326 | 327 | Ƭ **InferRaw**<`T`, `options`\>: `T` extends [`AnyInput`](index.md#anyinput) \| [`AnyObject`](index.md#anyobject) \| [`AnyInterface`](index.md#anyinterface) ? { `__typename?`: `T`[``"_name"``] } & { [K in keyof T["\_shape"] as T["\_shape"][K] extends AnyOptional \| options["omitResolver"] ? never : T["\_shape"][K] extends AnyArgs ? T["\_shape"][K]["\_shape"] extends AnyOptional \| options["omitResolver"] ? never : K : K]: InferRaw } & { [K in keyof T["\_shape"] as T["\_shape"][K] extends AnyOptional \| options["omitResolver"] ? K : T["\_shape"][K] extends AnyArgs ? T["\_shape"][K]["\_shape"] extends AnyOptional \| options["omitResolver"] ? K : never : never]?: InferRaw } : [`InferShallow`](index.md#infershallow)<`T`, `options`\> 328 | 329 | #### Type parameters 330 | 331 | | Name | Type | 332 | | :------ | :------ | 333 | | `T` | `T` | 334 | | `options` | extends `InferOptions` = { `omitResolver`: `never` } | 335 | 336 | #### Defined in 337 | 338 | [index.ts:94](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L94) 339 | 340 | ___ 341 | 342 | ### InferResolvers 343 | 344 | Ƭ **InferResolvers**<`T`, `X`\>: { [K in keyof T]: K extends "Subscription" ? { [G in keyof T[K]["\_shape"]]?: Object } : { [G in keyof T[K]["\_shape"]]?: Function } \| { [G in keyof T[K]["\_shape"]]?: Object \| Object \| Object } & Object } 345 | 346 | #### Type parameters 347 | 348 | | Name | Type | 349 | | :------ | :------ | 350 | | `T` | extends [`AnyTypes`](index.md#anytypes) | 351 | | `X` | extends `InferResolverConfig` | 352 | 353 | #### Defined in 354 | 355 | [index.ts:134](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L134) 356 | 357 | ___ 358 | 359 | ### InferResolversStrict 360 | 361 | Ƭ **InferResolversStrict**<`T`, `X`\>: { [K in keyof T]: K extends "Subscription" ? { [G in keyof T[K]["\_shape"]]: Object } : { [G in keyof T[K]["\_shape"]]: Function } \| { [G in keyof T[K]["\_shape"]]: Object \| Object \| Object } & Object } 362 | 363 | #### Type parameters 364 | 365 | | Name | Type | 366 | | :------ | :------ | 367 | | `T` | extends [`AnyTypes`](index.md#anytypes) | 368 | | `X` | extends `InferResolverConfig` | 369 | 370 | #### Defined in 371 | 372 | [index.ts:156](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L156) 373 | 374 | ___ 375 | 376 | ### InferShallow 377 | 378 | Ƭ **InferShallow**<`T`, `options`\>: `T` extends [`AnyString`](index.md#anystring) \| [`AnyID`](index.md#anyid) \| [`AnyScalar`](index.md#anyscalar) \| [`AnyNumber`](index.md#anynumber) \| [`AnyBoolean`](index.md#anyboolean) ? `T`[``"_shape"``] : `T` extends [`AnyEnum`](index.md#anyenum) ? `T`[``"_inner"``] : `T` extends [`AnyUnion`](index.md#anyunion) ? [`InferRaw`](index.md#inferraw)<`ObjectToUnion`<`T`[``"_inner"``]\>, `options`\> : `T` extends [`AnyList`](index.md#anylist) ? [`InferRaw`](index.md#inferraw)<`T`[``"_shape"``], `options`\>[] : `T` extends [`AnyPaginatedList`](index.md#anypaginatedlist) ? `T`[``"_inner"``] : `T` extends [`AnyOptional`](index.md#anyoptional) ? [`InferRaw`](index.md#inferraw)<`T`[``"_shape"``], `options`\> \| ``null`` \| `undefined` : `T` extends [`AnyOmitResolver`](index.md#anyomitresolver) ? [`InferRaw`](index.md#inferraw)<`T`[``"_shape"``], `options`\> : `T` extends [`AnyArgs`](index.md#anyargs) ? [`InferRaw`](index.md#inferraw)<`T`[``"_shape"``], `options`\> : `T` extends [`AnyRef`](index.md#anyref) ? [`InferRaw`](index.md#inferraw)<`T`[``"_inner"``], `options`\> : `T` 379 | 380 | #### Type parameters 381 | 382 | | Name | Type | 383 | | :------ | :------ | 384 | | `T` | `T` | 385 | | `options` | extends `InferOptions` = { `omitResolver`: `never` } | 386 | 387 | #### Defined in 388 | 389 | [index.ts:108](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L108) 390 | 391 | ___ 392 | 393 | ### InferUnionNames 394 | 395 | Ƭ **InferUnionNames**<`T`\>: `T` extends [`AnyUnion`](index.md#anyunion) ? `ObjectToUnion`<`T`[``"_inner"``]\>[``"_name"``] : `never` 396 | 397 | #### Type parameters 398 | 399 | | Name | 400 | | :------ | 401 | | `T` | 402 | 403 | #### Defined in 404 | 405 | [index.ts:132](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L132) 406 | 407 | ___ 408 | 409 | ### TypeDefinition 410 | 411 | Ƭ **TypeDefinition**<`T`\>: `Object` 412 | 413 | #### Type parameters 414 | 415 | | Name | 416 | | :------ | 417 | | `T` | 418 | 419 | #### Type declaration 420 | 421 | | Name | Type | 422 | | :------ | :------ | 423 | | `args?` | [`Args`](index.md#args) | 424 | | `defaultValue?` | `any` | 425 | | `deprecated?` | `string` | 426 | | `description?` | `string` | 427 | | `extend?` | [`AnyTypes`](index.md#anytypes)[] | 428 | | `interfaces?` | [`AnyInterface`](index.md#anyinterface)[] | 429 | | `isOptional?` | `boolean` | 430 | | `isRequired?` | `boolean` | 431 | | `name?` | `string` | 432 | | `scalarOptions?` | `ScalarOptions`<`any`, `any`\> | 433 | | `shape?` | `T` | 434 | | `type` | `GarphType` | 435 | 436 | #### Defined in 437 | 438 | [index.ts:28](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L28) 439 | 440 | ## Variables 441 | 442 | ### g 443 | 444 | • `Const` **g**: [`GarphSchema`](classes/GarphSchema.md) 445 | 446 | #### Defined in 447 | 448 | [index.ts:729](https://github.com/stepci/garph/blob/bc1d582/src/index.ts#L729) 449 | 450 | ## Functions 451 | 452 | ### buildSchema 453 | 454 | ▸ **buildSchema**(`«destructured»`, `config?`): `GraphQLSchema` 455 | 456 | #### Parameters 457 | 458 | | Name | Type | 459 | | :------ | :------ | 460 | | `«destructured»` | `Object` | 461 | | › `g` | [`GarphSchema`](classes/GarphSchema.md) | 462 | | › `resolvers?` | `any` | 463 | | `config` | `ConverterConfig` | 464 | 465 | #### Returns 466 | 467 | `GraphQLSchema` 468 | 469 | #### Defined in 470 | 471 | [schema.ts:17](https://github.com/stepci/garph/blob/bc1d582/src/schema.ts#L17) 472 | 473 | ___ 474 | 475 | ### printSchema 476 | 477 | ▸ **printSchema**(`g`, `config?`): `string` 478 | 479 | #### Parameters 480 | 481 | | Name | Type | 482 | | :------ | :------ | 483 | | `g` | [`GarphSchema`](classes/GarphSchema.md) | 484 | | `config` | `ConverterConfig` | 485 | 486 | #### Returns 487 | 488 | `string` 489 | 490 | #### Defined in 491 | 492 | [schema.ts:11](https://github.com/stepci/garph/blob/bc1d582/src/schema.ts#L11) 493 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLResolveInfo } from 'graphql' 2 | import { TSEnumType, UnionToIntersection, getEnumProperties, ObjectToUnion, ExpandRecursively, MaybePromise, MaybeFunction } from './utils' 3 | import { buildSchema, printSchema } from './schema' 4 | 5 | type GraphQLRootType = 'Query' | 'Mutation' | 'Subscription' 6 | type GarphType = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID' | 'ObjectType' | 'InterfaceType' | 'InputType' | 'Scalar' | 'Enum' | 'List' | 'PaginatedList' | 'Union' | 'Ref' | 'Optional' | 'Args' | 'OmitResolver' 7 | 8 | export abstract class Type { 9 | _name?: string 10 | _is: X 11 | _inner?: any 12 | _output?: any 13 | _args?: Args 14 | _shape: T 15 | typeDef: TypeDefinition 16 | 17 | description(text: string) { 18 | this.typeDef.description = text 19 | return this 20 | } 21 | 22 | deprecated(reason: string) { 23 | this.typeDef.deprecated = reason 24 | return this 25 | } 26 | } 27 | 28 | export type TypeDefinition = { 29 | name?: string 30 | type: GarphType 31 | shape?: T 32 | args?: Args 33 | description?: string 34 | isOptional?: boolean 35 | isRequired?: boolean 36 | deprecated?: string 37 | scalarOptions?: ScalarOptions 38 | defaultValue?: any 39 | interfaces?: AnyInterface[] 40 | extend?: AnyTypes[] 41 | } 42 | 43 | export type AnyType = Type 44 | export type AnyString = Type 45 | export type AnyID = Type 46 | export type AnyBoolean = Type 47 | export type AnyNumber = Type 48 | export type AnyInt = Type 49 | export type AnyFloat = Type 50 | export type AnyRef = Type 51 | export type AnyList = Type 52 | export type AnyPaginatedList = Type 53 | export type AnyUnion = Type 54 | export type AnyEnum = Type 55 | export type AnyScalar = Type 56 | export type AnyInput = Type 57 | export type AnyInterface = Type 58 | export type AnyArgs = Type 59 | export type AnyOptional = Type 60 | export type AnyObject = Type 61 | export type AnyOmitResolver = Type 62 | 63 | export type Args = { 64 | [key: string]: AnyType 65 | } 66 | 67 | export type AnyTypes = { 68 | [key: string]: AnyType 69 | } 70 | 71 | export type AnyObjects = { 72 | [key: string]: AnyObject 73 | } 74 | 75 | type ScalarOptions = { 76 | serialize: (value: I) => O 77 | parseValue: (value: O) => I 78 | parseLiteral?: (ast: any) => I 79 | specifiedByUrl?: string 80 | } 81 | 82 | type InferResolverConfig = { 83 | context?: any 84 | } 85 | 86 | type InferOptions = { 87 | omitResolver?: AnyOmitResolver | never 88 | } 89 | 90 | type RefType = () => AnyType 91 | 92 | // TODO: Refactor Args to get rid of this mess 93 | export type Infer = ExpandRecursively> 94 | export type InferRaw = T extends AnyInput | AnyObject | AnyInterface ? { 95 | __typename?: T['_name'] 96 | } & { 97 | [K in keyof T['_shape'] as T['_shape'][K] extends AnyOptional | options['omitResolver'] ? never : 98 | T['_shape'][K] extends AnyArgs ? 99 | T['_shape'][K]['_shape'] extends AnyOptional | options['omitResolver'] ? never : K : 100 | K]: InferRaw 101 | } & { 102 | [K in keyof T['_shape'] as T['_shape'][K] extends AnyOptional | options['omitResolver'] ? K : 103 | T['_shape'][K] extends AnyArgs ? 104 | T['_shape'][K]['_shape'] extends AnyOptional | options['omitResolver'] ? K : never : 105 | never]?: InferRaw 106 | } : InferShallow 107 | 108 | export type InferShallow = 109 | T extends AnyString | AnyID | AnyScalar | AnyNumber | AnyBoolean ? T['_shape'] : 110 | T extends AnyEnum ? T['_inner'] : 111 | T extends AnyUnion ? InferRaw, options> : 112 | T extends AnyList ? InferRaw[] : 113 | T extends AnyPaginatedList ? T['_inner'] : 114 | T extends AnyOptional ? InferRaw | null | undefined : 115 | T extends AnyOmitResolver ? InferRaw : 116 | T extends AnyArgs ? InferRaw : 117 | T extends AnyRef ? InferRaw : 118 | T 119 | 120 | export type InferArgs = ExpandRecursively> 121 | export type InferArgsRaw = T extends AnyObject | AnyInterface ? { 122 | [K in keyof T['_shape']]: InferArgRaw 123 | } : never 124 | 125 | export type InferArg = ExpandRecursively> 126 | export type InferArgRaw = T extends AnyArgs ? { 127 | [K in keyof T['_args'] as T['_args'][K] extends AnyOptional ? never : K]: InferRaw 128 | } & { 129 | [K in keyof T['_args'] as T['_args'][K] extends AnyOptional ? K : never]?: InferRaw 130 | }: never 131 | 132 | export type InferUnionNames = T extends AnyUnion ? ObjectToUnion['_name'] : never 133 | 134 | export type InferResolvers = { 135 | [K in keyof T]: K extends 'Subscription' ? { 136 | [G in keyof T[K]['_shape']]?: { 137 | subscribe: (parent: {}, args: InferArg, context: X['context'], info: GraphQLResolveInfo) => MaybePromise}>> 138 | resolve?: (value: Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo) => MaybePromise> 139 | } 140 | } : { 141 | [G in keyof T[K]['_shape']]?: (parent: K extends GraphQLRootType ? {} : Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo) => MaybePromise>> | AsyncGenerator> 142 | } | { 143 | [G in keyof T[K]['_shape']]?: { 144 | resolve: (parent: K extends GraphQLRootType ? {} : Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo) => MaybePromise> | AsyncGenerator> 145 | } | { 146 | load: (queries: { parent: K extends GraphQLRootType ? {} : Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo } []) => MaybePromise[]> 147 | } | { 148 | loadBatch: (queries: { parent: K extends GraphQLRootType ? {} : Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo } []) => MaybePromise[]> 149 | } 150 | } & { 151 | __isTypeOf?: (parent: Infer, context: X['context'], info: GraphQLResolveInfo) => MaybePromise 152 | __resolveType?: (parent: Infer, context: X['context'], info: GraphQLResolveInfo) => MaybePromise> 153 | } 154 | } 155 | 156 | export type InferResolversStrict = { 157 | [K in keyof T]: K extends 'Subscription' ? { 158 | [G in keyof T[K]['_shape']]: { 159 | subscribe: (parent: {}, args: InferArg, context: X['context'], info: GraphQLResolveInfo) => MaybePromise}>> 160 | resolve?: (value: Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo) => MaybePromise> 161 | } 162 | } : { 163 | [G in keyof T[K]['_shape']]: (parent: K extends GraphQLRootType ? {} : Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo) => MaybePromise>> | AsyncGenerator> 164 | } | { 165 | [G in keyof T[K]['_shape']]: { 166 | resolve: (parent: K extends GraphQLRootType ? {} : Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo) => MaybePromise> | AsyncGenerator> 167 | } | { 168 | load: (queries: { parent: K extends GraphQLRootType ? {} : Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo } []) => MaybePromise>[] 169 | } | { 170 | loadBatch: (queries: { parent: K extends GraphQLRootType ? {} : Infer, args: InferArg, context: X['context'], info: GraphQLResolveInfo } []) => MaybePromise>[] 171 | } 172 | } & { 173 | __isTypeOf?: (parent: Infer, context: X['context'], info: GraphQLResolveInfo) => MaybePromise 174 | __resolveType?: (parent: Infer, context: X['context'], info: GraphQLResolveInfo) => MaybePromise> 175 | } 176 | } 177 | 178 | class GType extends Type { 179 | declare _name: N 180 | 181 | constructor(name: string, shape: T, interfaces?: AnyInterface[], extend?: AnyTypes[]) { 182 | super() 183 | this.typeDef = { 184 | name, 185 | type: 'ObjectType', 186 | shape, 187 | interfaces, 188 | extend 189 | } 190 | } 191 | 192 | implements(ref: D | D[]) { 193 | // This is temporary construct, until we figure out how to properly manage to shared schema 194 | this.typeDef.interfaces = Array.isArray(ref) ? ref : [ref] 195 | return new GType>(this.typeDef.name, this.typeDef.shape as any, Array.isArray(ref) ? ref : [ref], this.typeDef.extend) 196 | } 197 | 198 | extend(ref: D | D[]) { 199 | // This is temporary construct, until we figure out how to properly manage to shared schema 200 | this.typeDef.extend = Array.isArray(ref) ? ref : [ref] 201 | return new GType>(this.typeDef.name, this.typeDef.shape as any, this.typeDef.interfaces, Array.isArray(ref) ? ref : [ref]) 202 | } 203 | } 204 | 205 | class GInput extends Type { 206 | declare _name: N 207 | 208 | constructor(name: string, shape: T, extend?: AnyTypes[]) { 209 | super() 210 | this.typeDef = { 211 | name, 212 | type: 'InputType', 213 | shape, 214 | extend 215 | } 216 | } 217 | 218 | extend(ref: D | D[]) { 219 | // This is temporary construct, until we figure out how to properly manage to shared schema 220 | this.typeDef.extend = Array.isArray(ref) ? ref : [ref] 221 | return new GInput>(this.typeDef.name, this.typeDef.shape as any, Array.isArray(ref) ? ref : [ref]) 222 | } 223 | } 224 | 225 | class GInterface extends Type { 226 | declare _name: N 227 | 228 | constructor(name: string, shape: T, interfaces?: AnyInterface[], extend?: AnyTypes[]) { 229 | super() 230 | this.typeDef = { 231 | name, 232 | type: 'InterfaceType', 233 | shape, 234 | interfaces, 235 | extend 236 | } 237 | } 238 | 239 | implements(ref: D | D[]) { 240 | // This is temporary construct, until we figure out how to properly manage to shared schema 241 | this.typeDef.interfaces = Array.isArray(ref) ? ref : [ref] 242 | return new GInterface>(this.typeDef.name, this.typeDef.shape as any, Array.isArray(ref) ? ref : [ref], this.typeDef.extend) 243 | } 244 | 245 | extend(ref: D | D[]) { 246 | // This is temporary construct, until we figure out how to properly manage to shared schema 247 | this.typeDef.extend = Array.isArray(ref) ? ref : [ref] 248 | return new GInterface>(this.typeDef.name, this.typeDef.shape as any, this.typeDef.interfaces, Array.isArray(ref) ? ref : [ref]) 249 | } 250 | } 251 | 252 | class GString extends Type { 253 | constructor(type: 'String' | 'ID' = 'String') { 254 | super() 255 | this.typeDef = { 256 | type 257 | } 258 | } 259 | 260 | optional() { 261 | return new GOptional(this) 262 | } 263 | 264 | required() { 265 | this.typeDef.isRequired = true 266 | return this 267 | } 268 | 269 | list() { 270 | return new GList(this) 271 | } 272 | 273 | default(value: string) { 274 | this.typeDef.defaultValue = value 275 | return this 276 | } 277 | 278 | omitResolver () { 279 | return new GOmitResolver(this) 280 | } 281 | 282 | args(args: X) { 283 | return new GArgs(this, args) 284 | } 285 | } 286 | 287 | class GNumber extends Type { 288 | constructor(type: 'Int' | 'Float' = 'Int') { 289 | super() 290 | this.typeDef = { 291 | type 292 | } 293 | } 294 | 295 | optional() { 296 | return new GOptional(this) 297 | } 298 | 299 | required() { 300 | this.typeDef.isRequired = true 301 | return this 302 | } 303 | 304 | list() { 305 | return new GList(this) 306 | } 307 | 308 | default(value: number) { 309 | this.typeDef.defaultValue = value 310 | return this 311 | } 312 | 313 | omitResolver () { 314 | return new GOmitResolver(this) 315 | } 316 | 317 | args(args: X) { 318 | return new GArgs(this, args) 319 | } 320 | } 321 | 322 | class GBoolean extends Type { 323 | constructor() { 324 | super() 325 | this.typeDef = { 326 | type: 'Boolean' 327 | } 328 | } 329 | 330 | optional() { 331 | return new GOptional(this) 332 | } 333 | 334 | required() { 335 | this.typeDef.isRequired = true 336 | return this 337 | } 338 | 339 | list() { 340 | return new GList(this) 341 | } 342 | 343 | default(value: boolean) { 344 | this.typeDef.defaultValue = value 345 | return this 346 | } 347 | 348 | omitResolver () { 349 | return new GOmitResolver(this) 350 | } 351 | 352 | args(args: X) { 353 | return new GArgs(this, args) 354 | } 355 | } 356 | 357 | class GEnum extends Type { 358 | declare _name: N 359 | declare _inner: T extends readonly string[] ? T[number] : keyof T 360 | 361 | constructor(name: string, shape: T) { 362 | super() 363 | 364 | let enumShape: readonly string[] 365 | if (Array.isArray(shape)) { 366 | enumShape = shape 367 | } else { 368 | enumShape = getEnumProperties(shape as TSEnumType) 369 | } 370 | 371 | this.typeDef = { 372 | name, 373 | type: 'Enum', 374 | shape: enumShape 375 | } 376 | } 377 | } 378 | 379 | class GUnion extends Type { 380 | declare _name: N 381 | declare _inner: T 382 | 383 | constructor(name: string, shape: T) { 384 | super() 385 | this.typeDef = { 386 | name, 387 | type: 'Union', 388 | shape 389 | } 390 | } 391 | } 392 | 393 | class GRef extends Type { 394 | declare _inner: T extends RefType ? ReturnType : T 395 | 396 | constructor(ref: T) { 397 | super() 398 | this.typeDef = { 399 | shape: ref, 400 | type: 'Ref' 401 | } 402 | } 403 | 404 | optional() { 405 | return new GOptional(this) 406 | } 407 | 408 | required() { 409 | this.typeDef.isRequired = true 410 | return this 411 | } 412 | 413 | list() { 414 | return new GList(this) 415 | } 416 | 417 | paginatedList() { 418 | return new GPaginatedList(this) 419 | } 420 | 421 | default(value: InferRaw) { 422 | this.typeDef.defaultValue = value 423 | return this 424 | } 425 | 426 | omitResolver () { 427 | return new GOmitResolver(this) 428 | } 429 | 430 | args(x: X) { 431 | return new GArgs(this, x) 432 | } 433 | } 434 | 435 | class GScalar extends Type { 436 | declare _output: O 437 | 438 | constructor(name: string, scalarOptions?: ScalarOptions) { 439 | super() 440 | this.typeDef = { 441 | name, 442 | type: 'Scalar', 443 | scalarOptions 444 | } 445 | } 446 | 447 | specifiedByUrl(url: string) { 448 | this.typeDef.scalarOptions.specifiedByUrl = url 449 | return this 450 | } 451 | } 452 | 453 | class GList extends Type { 454 | constructor(shape: T) { 455 | super() 456 | this.typeDef = { 457 | type: 'List', 458 | shape: shape 459 | } 460 | } 461 | 462 | optional() { 463 | return new GOptional(this) 464 | } 465 | 466 | required() { 467 | this.typeDef.isRequired = true 468 | return this 469 | } 470 | 471 | default(value: typeof this._inner) { 472 | this.typeDef.defaultValue = value 473 | return this 474 | } 475 | 476 | omitResolver () { 477 | return new GOmitResolver(this) 478 | } 479 | 480 | args(args: X) { 481 | return new GArgs(this, args) 482 | } 483 | 484 | list() { 485 | return new GList(this) 486 | } 487 | } 488 | 489 | class GPaginatedList extends Type { 490 | declare _inner: { 491 | edges: { 492 | node: InferRaw 493 | cursor: string 494 | }[] 495 | pageInfo: { 496 | hasNextPage: boolean, 497 | hasPreviousPage: boolean, 498 | startCursor?: string, 499 | endCursor?: string 500 | } 501 | } 502 | 503 | constructor(shape: T) { 504 | super() 505 | this.typeDef = { 506 | type: 'PaginatedList', 507 | shape: shape 508 | } 509 | } 510 | 511 | optional() { 512 | return new GOptional(this) 513 | } 514 | 515 | required() { 516 | this.typeDef.isRequired = true 517 | return this 518 | } 519 | 520 | omitResolver () { 521 | return new GOmitResolver(this) 522 | } 523 | 524 | args(args: X) { 525 | return new GArgs(this, args) 526 | } 527 | 528 | // list() { 529 | // return new GList(this) 530 | // } 531 | } 532 | 533 | class GOptional extends Type { 534 | constructor(shape: T) { 535 | super() 536 | this.typeDef = shape.typeDef 537 | this.typeDef.isOptional = true 538 | this.typeDef.isRequired = false 539 | } 540 | 541 | list() { 542 | return new GList(this) 543 | } 544 | 545 | default(value: InferRaw) { 546 | this.typeDef.defaultValue = value 547 | return this 548 | } 549 | 550 | omitResolver () { 551 | return new GOmitResolver(this) 552 | } 553 | 554 | args(args: X) { 555 | return new GArgs(this, args) 556 | } 557 | } 558 | 559 | class GOmitResolver extends Type { 560 | constructor(shape: T) { 561 | super() 562 | this.typeDef = shape.typeDef 563 | } 564 | 565 | optional() { 566 | return new GOptional(this) 567 | } 568 | 569 | required() { 570 | this.typeDef.isRequired = true 571 | return this 572 | } 573 | 574 | default(value: InferRaw) { 575 | this.typeDef.defaultValue = value 576 | return this 577 | } 578 | 579 | args(args: X) { 580 | return new GArgs(this, args) 581 | } 582 | 583 | list() { 584 | return new GList(this) 585 | } 586 | } 587 | 588 | class GArgs extends Type { 589 | declare _args: X 590 | 591 | constructor(shape: T, args: X) { 592 | super() 593 | this.typeDef = shape.typeDef 594 | this.typeDef.args = args 595 | } 596 | } 597 | 598 | export class GarphSchema { 599 | types: Map = new Map() 600 | 601 | nodeType = this.interface('Node', { 602 | id: this.id() 603 | }) 604 | 605 | pageInfoType = this.type('PageInfo', { 606 | hasNextPage: this.boolean(), 607 | hasPreviousPage: this.boolean(), 608 | startCursor: this.string().optional(), 609 | endCursor: this.string().optional() 610 | }) 611 | 612 | pageInfoArgs = { 613 | first: this.int().optional(), 614 | last: this.int().optional(), 615 | before: this.id().optional(), 616 | after: this.id().optional() 617 | } 618 | 619 | registerType(type: AnyType) { 620 | const name = type.typeDef.name 621 | if (!['Node', 'PageInfo'].includes(name) && this.types.has(name)) throw new Error(`Type with name "${name}" already exists`) 622 | this.types.set(name, type) 623 | } 624 | 625 | constructor ({ types }: { types: AnyType[] } = { types: [] }) { 626 | types.forEach(t => this.registerType(t)) 627 | } 628 | 629 | type(name: N, shape: T) { 630 | const t = new GType(name, shape) 631 | this.registerType(t) 632 | return t 633 | } 634 | 635 | node(name: N, shape: T) { 636 | const t = new GType(name, shape).implements(this.nodeType) 637 | this.registerType(t) 638 | return t 639 | } 640 | 641 | connection(name: N, shape: T) { 642 | const t = new GType(name, { 643 | edges: new GList(shape), 644 | pageInfo: this.pageInfoType 645 | }) 646 | 647 | this.registerType(t) 648 | return t 649 | } 650 | 651 | edge(name: N, shape: T) { 652 | const t = new GType(name, { 656 | node: shape, 657 | cursor: g.string() 658 | }) 659 | 660 | this.registerType(t) 661 | return t 662 | } 663 | 664 | inputType(name: N, shape: T) { 665 | const t = new GInput(name, shape) 666 | this.registerType(t) 667 | return t 668 | } 669 | 670 | enumType(name: N, args: T) { 671 | const t = new GEnum(name, args) 672 | this.registerType(t) 673 | return t 674 | } 675 | 676 | unionType(name: N, args: T) { 677 | const t = new GUnion(name, args) 678 | this.registerType(t) 679 | return t 680 | } 681 | 682 | scalarType(name: string, options?: ScalarOptions) { 683 | const t = new GScalar(name, options) 684 | this.registerType(t) 685 | return t 686 | } 687 | 688 | interface(name: N, shape: T) { 689 | const t = new GInterface(name, shape) 690 | this.registerType(t) 691 | return t 692 | } 693 | 694 | string() { 695 | return new GString<'String'>() 696 | } 697 | 698 | id() { 699 | return new GString<'ID'>('ID') 700 | } 701 | 702 | int() { 703 | return new GNumber<'Int'>('Int') 704 | } 705 | 706 | float() { 707 | return new GNumber<'Float'>('Float') 708 | } 709 | 710 | boolean() { 711 | return new GBoolean() 712 | } 713 | 714 | // The generic has to be any, because constraints cannot be applied to recursive types 715 | // This is a limitation of TypeScript 716 | ref(ref: T) { 717 | return new GRef(ref) 718 | } 719 | 720 | // enum(args: T[]) { 721 | // return new GEnum('', args) 722 | // } 723 | 724 | // union(args: T[]) { 725 | // return new GUnion('', args) 726 | // } 727 | } 728 | 729 | export const g = new GarphSchema() 730 | 731 | export { buildSchema, printSchema } 732 | --------------------------------------------------------------------------------