├── .prettierignore ├── packages ├── create-graphqlgen │ ├── .gitignore │ ├── README.md │ ├── tslint.json │ ├── tsconfig.json │ ├── src │ │ ├── templates.ts │ │ ├── index.ts │ │ └── loader.ts │ └── package.json ├── graphqlgen-json-schema │ ├── README.md │ ├── jest.config.js │ ├── tsconfig.json │ ├── src │ │ ├── tests │ │ │ ├── __snapshots__ │ │ │ │ └── basic.test.ts.snap │ │ │ ├── basic.test.ts │ │ │ └── fixtures │ │ │ │ └── basic.yml │ │ ├── definition.ts │ │ └── schema.json │ └── package.json ├── graphqlgen │ ├── .gitignore │ ├── tests │ │ ├── path-helpers │ │ │ ├── mocks │ │ │ │ ├── types.ts │ │ │ │ ├── dir-1 │ │ │ │ │ └── index.ts │ │ │ │ └── dir-2 │ │ │ │ │ └── dir-3.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.ts.snap │ │ │ └── index.test.ts │ │ ├── glob │ │ │ ├── mocks │ │ │ │ ├── dir-1 │ │ │ │ │ ├── file-11.ts │ │ │ │ │ └── file-12.ts │ │ │ │ └── dir-2 │ │ │ │ │ ├── file-21.ts │ │ │ │ │ └── file-22.ts │ │ │ ├── extract-glob-pattern.test.ts │ │ │ └── handle-glob-pattern.test.ts │ │ ├── validation │ │ │ ├── mocks │ │ │ │ ├── gqlSchema │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── model.ts │ │ │ │ │ └── schema.ts │ │ │ │ ├── missingModels │ │ │ │ │ ├── index.ts │ │ │ │ │ └── schema.graphql │ │ │ │ ├── overridenModel │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── model.ts │ │ │ │ │ └── schema.graphql │ │ │ │ ├── tsSchemaConst │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── model.ts │ │ │ │ │ └── schema.ts │ │ │ │ └── tsSchemaDefault │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── model.ts │ │ │ │ │ └── schema.ts │ │ │ ├── validateDefinition.test.ts │ │ │ └── validateModels.test.ts │ │ ├── fixtures │ │ │ ├── subscription │ │ │ │ ├── types.ts │ │ │ │ ├── flow-types.js │ │ │ │ └── schema.graphql │ │ │ ├── basic │ │ │ │ ├── index.ts │ │ │ │ ├── types-flow.js │ │ │ │ └── schema.graphql │ │ │ ├── defaultName │ │ │ │ ├── index.ts │ │ │ │ ├── flow-types.js │ │ │ │ └── schema.graphql │ │ │ ├── scalar │ │ │ │ ├── types.ts │ │ │ │ ├── flow-types.js │ │ │ │ └── schema.graphql │ │ │ ├── context │ │ │ │ ├── schema.graphql │ │ │ │ ├── types.ts │ │ │ │ └── flow-types.js │ │ │ ├── input │ │ │ │ ├── types.ts │ │ │ │ └── schema.graphql │ │ │ ├── union │ │ │ │ ├── types.ts │ │ │ │ ├── flow-types.js │ │ │ │ └── schema.graphql │ │ │ ├── enum │ │ │ │ ├── types-flow.js │ │ │ │ ├── types.ts │ │ │ │ └── schema.graphql │ │ │ └── interface │ │ │ │ ├── types.ts │ │ │ │ └── schema.graphql │ │ ├── introspection │ │ │ ├── mocks │ │ │ │ ├── flow-types.js │ │ │ │ └── types.ts │ │ │ ├── flow.test.ts │ │ │ └── typescript.test.ts │ │ ├── flow │ │ │ ├── large-schema.test.ts │ │ │ └── basic.test.ts │ │ ├── typescript │ │ │ ├── large-schema.test.ts │ │ │ └── basic.test.ts │ │ ├── replaceVariables.test.ts │ │ └── generation.ts │ ├── benchmarks │ │ ├── integration │ │ │ ├── trivial │ │ │ │ ├── models.ts │ │ │ │ ├── models.js │ │ │ │ └── schema.graphql │ │ │ └── index.ts │ │ ├── lib │ │ │ ├── helpers.ts │ │ │ └── benchmark.ts │ │ ├── index.ts │ │ ├── README.md │ │ └── micro │ │ │ └── index.ts │ ├── src │ │ ├── generators │ │ │ ├── reason │ │ │ │ ├── reason.d.ts │ │ │ │ ├── scaffolder.ts │ │ │ │ └── generator.ts │ │ │ ├── common.spec.ts │ │ │ ├── flow │ │ │ │ └── scaffolder.ts │ │ │ └── typescript │ │ │ │ └── scaffolder.ts │ │ ├── glob.ts │ │ ├── types.ts │ │ ├── introspection │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utils.ts │ │ │ ├── factory.ts │ │ │ └── flow-ast.ts │ │ ├── path-helpers.ts │ │ ├── project-output.ts │ │ ├── utils.ts │ │ ├── output.ts │ │ └── index.ts │ ├── jest.config.js │ ├── tsconfig.json │ └── package.json └── graphqlgen-templates │ ├── flow-yoga │ ├── README.md │ ├── src │ │ ├── .babelrc │ │ ├── generated │ │ │ ├── tmp-resolvers │ │ │ │ ├── Post.js │ │ │ │ ├── User.js │ │ │ │ ├── Query.js │ │ │ │ ├── index.js │ │ │ │ └── Mutation.js │ │ │ └── graphqlgen.js │ │ ├── resolvers │ │ │ ├── index.js │ │ │ ├── Post.js │ │ │ ├── User.js │ │ │ ├── Query.js │ │ │ └── Mutation.js │ │ ├── index.js │ │ ├── types.js │ │ ├── schema.graphql │ │ └── data.js │ ├── .flowconfig │ ├── package.json │ └── graphqlgen.yml │ └── typescript-yoga │ ├── README.md │ ├── tsconfig.json │ ├── src │ ├── resolvers │ │ ├── Post.ts │ │ ├── User.ts │ │ ├── index.ts │ │ ├── Query.ts │ │ └── Mutation.ts │ ├── index.ts │ ├── types.ts │ ├── generated │ │ ├── tmp-resolvers │ │ │ ├── User.ts │ │ │ ├── Post.ts │ │ │ ├── index.ts │ │ │ ├── Query.ts │ │ │ └── Mutation.ts │ │ └── graphqlgen.ts │ ├── schema.graphql │ └── data.ts │ ├── package.json │ └── graphqlgen.yml ├── docs ├── assets │ └── view-on-github.png ├── SUMMARY.md ├── book.json ├── package.json ├── 04-bootstrapping.md ├── README.md ├── 01-configuration.md ├── 03-scaffolding-resolvers.md └── 02-generation.md ├── .github ├── make-release-notes.sh ├── release-template.ejs ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── RELEASE.md ├── .gitignore ├── .circleci └── config.yml ├── renovate.json ├── package.json ├── LICENSE ├── CONTRIBUTING.md └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .github 3 | -------------------------------------------------------------------------------- /packages/create-graphqlgen/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/create-graphqlgen/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/graphqlgen/.gitignore: -------------------------------------------------------------------------------- 1 | src/**/generated -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/path-helpers/mocks/types.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/path-helpers/mocks/dir-1/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/path-helpers/mocks/dir-2/dir-3.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/glob/mocks/dir-1/file-11.ts: -------------------------------------------------------------------------------- 1 | // Empty 2 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/glob/mocks/dir-1/file-12.ts: -------------------------------------------------------------------------------- 1 | // Empty 2 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/glob/mocks/dir-2/file-21.ts: -------------------------------------------------------------------------------- 1 | // Empty 2 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/glob/mocks/dir-2/file-22.ts: -------------------------------------------------------------------------------- 1 | // Empty 2 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/gqlSchema/types.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/subscription/types.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | name: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/gqlSchema/model.ts: -------------------------------------------------------------------------------- 1 | interface PostModel { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/missingModels/index.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/overridenModel/types.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/tsSchemaConst/types.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/tsSchemaDefault/types.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /docs/assets/view-on-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prisma-labs/graphqlgen/HEAD/docs/assets/view-on-github.png -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/subscription/flow-types.js: -------------------------------------------------------------------------------- 1 | export interface User { 2 | name: string; 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/overridenModel/model.ts: -------------------------------------------------------------------------------- 1 | interface PostModel { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/tsSchemaConst/model.ts: -------------------------------------------------------------------------------- 1 | interface PostModel { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/tsSchemaDefault/model.ts: -------------------------------------------------------------------------------- 1 | interface PostModel { 2 | id: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/basic/index.ts: -------------------------------------------------------------------------------- 1 | export interface Number { 2 | id: string 3 | value: number 4 | } 5 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-flow"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/make-release-notes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git-release-notes 330b5098552..ab41c39c917 ./.github/release-template.ejs | pbcopy 3 | -------------------------------------------------------------------------------- /.github/release-template.ejs: -------------------------------------------------------------------------------- 1 | <% commits.forEach(function (commit) { %> 2 | * <%= commit.sha1 %> <%= commit.title %><% }) %> 3 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/defaultName/index.ts: -------------------------------------------------------------------------------- 1 | export interface NumberNode { 2 | id: string 3 | value: number 4 | } 5 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/scalar/types.ts: -------------------------------------------------------------------------------- 1 | export interface AddMemberPayload { 2 | json: Json 3 | } 4 | 5 | type Json = any 6 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/basic/types-flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export interface Number { 4 | id: string; 5 | value: number; 6 | } 7 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/context/schema.graphql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: ID! 3 | } 4 | 5 | type Query { 6 | createUser: User 7 | } 8 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/integration/trivial/models.ts: -------------------------------------------------------------------------------- 1 | type A = { 2 | a: string 3 | b: number 4 | c: boolean 5 | } 6 | 7 | export { A } 8 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/integration/trivial/models.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type A = { 4 | a: string, 5 | b: number, 6 | c: boolean, 7 | } 8 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/defaultName/flow-types.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | export interface NumberNode { 4 | id: string; 5 | value: number; 6 | } 7 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/scalar/flow-types.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | export interface AddMemberPayload { 4 | json: Json; 5 | } 6 | 7 | type Json = any 8 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | 11 | [strict] 12 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/subscription/schema.graphql: -------------------------------------------------------------------------------- 1 | type Subscription { 2 | subscribeToUser: User! 3 | } 4 | 5 | type User { 6 | name: String! 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | node_modules/ 4 | dist/ 5 | tmp/ 6 | package-lock.json 7 | .idea/ 8 | *.log 9 | # needed for gitbook 10 | .grunt 11 | _book -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/missingModels/schema.graphql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: String! 3 | posts: [Post!]! 4 | } 5 | 6 | type Post { 7 | id: String! 8 | } 9 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/integration/trivial/schema.graphql: -------------------------------------------------------------------------------- 1 | type A { 2 | a: String 3 | b: Int 4 | c: Boolean 5 | } 6 | 7 | type Query { 8 | a: A 9 | } 10 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/overridenModel/schema.graphql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: String! 3 | posts: [Post!]! 4 | } 5 | 6 | type Post { 7 | id: String! 8 | } 9 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/generators/reason/reason.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'reason' { 2 | export function parseRE(code: string): any 3 | export function printRE(ast: any): string 4 | } 5 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "outDir": "dist", 5 | "strict": true, 6 | "lib": ["esnext", "dom"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/tsSchemaDefault/schema.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | type User { 3 | id: String! 4 | posts: [Post!]! 5 | } 6 | 7 | type Post { 8 | id: String! 9 | } 10 | ` 11 | -------------------------------------------------------------------------------- /packages/create-graphqlgen/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-standard", "tslint-config-prettier"], 3 | "rules": { 4 | "no-use-before-declare": false, 5 | "space-before-function-paren": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/context/types.ts: -------------------------------------------------------------------------------- 1 | export interface Data { 2 | users: User[] 3 | } 4 | 5 | export interface Context { 6 | data: Data 7 | } 8 | 9 | export interface User { 10 | id: string 11 | } 12 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/input/types.ts: -------------------------------------------------------------------------------- 1 | export interface AddMemberPayload { 2 | newUserId: string 3 | existingUserrInviteSent: boolean 4 | } 5 | 6 | export interface DeactivateMemberPayload { 7 | ok: boolean 8 | } 9 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/tsSchemaConst/schema.ts: -------------------------------------------------------------------------------- 1 | export const typeDefs = ` 2 | type User { 3 | id: String! 4 | posts: [Post!]! 5 | } 6 | 7 | type Post { 8 | id: String! 9 | } 10 | ` 11 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:10 6 | steps: 7 | - checkout 8 | - run: yarn install 9 | - run: yarn test:ci 10 | # - run: npx semantic-release 11 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/union/types.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: string 3 | name: string 4 | } 5 | 6 | export interface Student { 7 | age: number 8 | } 9 | 10 | export interface Professor { 11 | degree: string 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/context/flow-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export interface Data { 4 | users: User[]; 5 | } 6 | 7 | export interface Context { 8 | data: Data; 9 | } 10 | 11 | export interface User { 12 | id: string; 13 | } 14 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | - [Overview](./README.md) 4 | - [Configuration](./01-configuration.md) 5 | - [Generation](./02-generation.md) 6 | - [Scaffolding Resolvers](./03-scaffolding-resolvers.md) 7 | - [Bootstrapping](./04-bootstrapping.md) 8 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/mocks/gqlSchema/schema.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | export const typeDefs = gql` 4 | type User { 5 | id: String! 6 | posts: [Post!]! 7 | } 8 | 9 | type Post { 10 | id: String! 11 | } 12 | ` 13 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/union/flow-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export interface User { 4 | id: string; 5 | name: string; 6 | } 7 | 8 | export interface Student { 9 | age: number; 10 | } 11 | 12 | export interface Professor { 13 | degree: string; 14 | } 15 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/scalar/schema.graphql: -------------------------------------------------------------------------------- 1 | scalar Json 2 | 3 | type AddMemberPayload { 4 | json: Json 5 | } 6 | 7 | input AddMemberData { 8 | email: String! 9 | projects: [ID!]! 10 | } 11 | 12 | type Mutation { 13 | addMember(data: AddMemberData!): AddMemberPayload! 14 | } 15 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/union/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | users(first: Int): [UserType!]! 3 | } 4 | 5 | type User { 6 | id: ID! 7 | name: String! 8 | } 9 | 10 | union UserType = Student | Professor 11 | 12 | type Student { 13 | age: Int! 14 | } 15 | 16 | type Professor { 17 | degree: String 18 | } 19 | -------------------------------------------------------------------------------- /packages/graphqlgen/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/tests', '/src'], 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest', 6 | }, 7 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | } 10 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/generators/reason/scaffolder.ts: -------------------------------------------------------------------------------- 1 | import { GenerateArgs } from '../../types' 2 | 3 | export { format } from './generator' 4 | 5 | const noop = (s: string) => s 6 | 7 | export function generate(args: GenerateArgs) { 8 | noop(JSON.stringify(args)) 9 | return 'Reason code scaffolder is not yet implmented' 10 | } 11 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/enum/types-flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export interface User { 4 | id: string; 5 | name: string; 6 | enumAnnotation: EnumAnnotation; 7 | enumAsUnionType: EnumAsUnionType; 8 | } 9 | 10 | type EnumAnnotation = 'ADMIN' | 'EDITOR' | 'COLLABORATOR' 11 | type EnumAsUnionType = 'RED' | 'GREEN' | 'BLUE' 12 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/enum/types.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: string 3 | name: string 4 | enumAnnotation: EnumAnnotation 5 | enumAsUnionType: EnumAsUnionType 6 | } 7 | 8 | enum EnumAnnotation { 9 | ADMIN, 10 | EDITOR, 11 | COLLABORATOR, 12 | } 13 | 14 | type EnumAsUnionType = 'RED' | 'GREEN' | 'BLUE' 15 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/resolvers/Post.ts: -------------------------------------------------------------------------------- 1 | import { PostResolvers } from '../generated/graphqlgen' 2 | 3 | export const Post: PostResolvers.Type = { 4 | ...PostResolvers.defaultResolvers, 5 | 6 | author: (parent, args, ctx) => { 7 | return ctx.data.users.find(user => user.id === parent.authorId)! 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest', 6 | }, 7 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | verbose: false, 10 | } 11 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/resolvers/User.ts: -------------------------------------------------------------------------------- 1 | import { UserResolvers } from '../generated/graphqlgen' 2 | 3 | export const User: UserResolvers.Type = { 4 | ...UserResolvers.defaultResolvers, 5 | 6 | posts: ({ postIDs }, args, ctx) => { 7 | return ctx.data.posts.filter(post => postIDs.includes(post.id)) 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/resolvers/index.ts: -------------------------------------------------------------------------------- 1 | import { Resolvers } from '../generated/graphqlgen' 2 | import { Query } from './Query' 3 | import { Mutation } from './Mutation' 4 | import { Post } from './Post' 5 | import { User } from './User' 6 | 7 | export const resolvers: Resolvers = { 8 | Query, 9 | Mutation, 10 | Post, 11 | User, 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/interface/types.ts: -------------------------------------------------------------------------------- 1 | export interface Media { 2 | id: string 3 | url: string 4 | } 5 | 6 | export interface Dimensions { 7 | width: number 8 | height: number 9 | } 10 | 11 | export interface Image extends Media { 12 | dimensions: Dimensions 13 | } 14 | 15 | export interface Video extends Media { 16 | dimensions: number 17 | } 18 | -------------------------------------------------------------------------------- /packages/create-graphqlgen/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "noUnusedLocals": true, 7 | "rootDir": "src", 8 | "outDir": "dist", 9 | "sourceMap": true, 10 | "lib": ["dom", "es2017", "esnext.asynciterable"] 11 | }, 12 | "include": ["src/index.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/introspection/mocks/flow-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | interface Interface { 4 | field: string; 5 | optionalField?: string; 6 | fieldUnionNull: string | null; 7 | } 8 | 9 | export interface ExportedInterface { 10 | field: string; 11 | } 12 | 13 | type Type = { 14 | field: string, 15 | } 16 | 17 | export type ExportedType = { 18 | field: string, 19 | } 20 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es5", 5 | "outDir": "dist", 6 | // TODO remove all libs (somehow needed for yarn workspaces) 7 | "lib": ["es2015", "esnext.asynciterable"], 8 | "resolveJsonModule": true 9 | }, 10 | "include": ["src/*.ts"], 11 | "exclude": ["node_modules", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/generated/tmp-resolvers/Post.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { Post_defaultResolvers } from '../graphqlgen' 3 | import type { Post_Resolvers } from '../graphqlgen' 4 | 5 | export const Post: Post_Resolvers = { 6 | ...Post_defaultResolvers, 7 | 8 | author: (parent, args, ctx, info) => { 9 | throw new Error('Resolver not implemented') 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/generated/tmp-resolvers/User.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { User_defaultResolvers } from '../graphqlgen' 3 | import type { User_Resolvers } from '../graphqlgen' 4 | 5 | export const User: User_Resolvers = { 6 | ...User_defaultResolvers, 7 | 8 | posts: (parent, args, ctx, info) => { 9 | throw new Error('Resolver not implemented') 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/resolvers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { Resolvers } from '../generated/graphqlgen' 3 | 4 | import { Query } from './Query' 5 | import { Post } from './Post' 6 | import { User } from './User' 7 | import { Mutation } from './Mutation' 8 | 9 | export const resolvers: Resolvers = { 10 | Query, 11 | Post, 12 | User, 13 | Mutation, 14 | } 15 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/resolvers/Post.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Post_defaultResolvers } from '../generated/graphqlgen' 3 | import type { Post_Resolvers } from '../generated/graphqlgen' 4 | 5 | export const Post: Post_Resolvers = { 6 | ...Post_defaultResolvers, 7 | 8 | author: (parent, args, ctx, info) => { 9 | throw new Error('Resolver not implemented') 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLServer } from 'graphql-yoga' 2 | import { resolvers } from './resolvers' 3 | import { data } from './data' 4 | 5 | const server = new GraphQLServer({ 6 | typeDefs: './src/schema.graphql', 7 | resolvers, 8 | context: { data }, 9 | } as any) 10 | 11 | server.start(() => console.log('Server is running on http://localhost:4000')) 12 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { GraphQLServer } from 'graphql-yoga' 4 | import { resolvers } from './resolvers' 5 | import { data } from './data' 6 | 7 | const server = new GraphQLServer({ 8 | typeDefs: './src/schema.graphql', 9 | resolvers, 10 | context: { data }, 11 | }) 12 | 13 | server.start(() => console.log('Server is running on http://localhost:4000')) 14 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/resolvers/User.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { User_defaultResolvers } from '../generated/graphqlgen' 3 | import type { User_Resolvers } from '../generated/graphqlgen' 4 | 5 | export const User: User_Resolvers = { 6 | ...User_defaultResolvers, 7 | posts: ({ postIDs }, args, ctx, info) => { 8 | return ctx.data.posts.filter(post => postIDs.includes(post.id)) 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/enum/schema.graphql: -------------------------------------------------------------------------------- 1 | type User { 2 | id: ID! 3 | name: String! 4 | enumAnnotation: EnumAnnotation! 5 | enumAsUnionType: EnumAsUnionType! 6 | } 7 | 8 | enum EnumAnnotation { 9 | EDITOR 10 | COLLABORATOR 11 | } 12 | 13 | enum EnumAsUnionType { 14 | RED 15 | GREEN 16 | BLUE 17 | } 18 | 19 | type Query { 20 | createUser(name: String!, type: EnumAnnotation!): User 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | 8 | 9 | ## Description 10 | 11 | 12 | ## Additional context 13 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/introspection/mocks/types.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | interface Interface { 3 | field: string 4 | optionalField?: string 5 | fieldUnionNull: string | null 6 | } 7 | 8 | export interface ExportedInterface { 9 | field: string 10 | } 11 | 12 | // @ts-ignore 13 | type Type = { 14 | field: string 15 | } 16 | 17 | export type ExportedType = { 18 | field: string 19 | } 20 | 21 | // @ts-ignore 22 | enum Enum { 23 | A, 24 | B, 25 | C, 26 | } 27 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Context { 2 | data: Data 3 | } 4 | 5 | export interface User { 6 | id: string 7 | name: string | null 8 | postIDs: string[] 9 | } 10 | 11 | export interface Post { 12 | id: string 13 | title: string 14 | content: string 15 | published: boolean 16 | authorId: string 17 | } 18 | 19 | export interface Data { 20 | posts: Post[] 21 | users: User[] 22 | idProvider: () => string 23 | } 24 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", "docker:disable"], 3 | "packageRules": [ 4 | { 5 | "groupName": "patch & minor dev dependencies", 6 | "depTypeList": ["devDependencies"], 7 | "packagePatterns": [".*"], 8 | "updateTypes": ["patch", "minor"] 9 | }, 10 | { 11 | "groupName": "patch prod dependencies", 12 | "depTypeList": ["dependencies"], 13 | "packagePatterns": [".*"], 14 | "updateTypes": ["patch"] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export interface Context { 4 | data: Data; 5 | } 6 | 7 | export interface User { 8 | id: string; 9 | name: string; 10 | postIDs: string[]; 11 | } 12 | 13 | export interface Post { 14 | id: string; 15 | title: string; 16 | content: string; 17 | published: boolean; 18 | authorId: string; 19 | } 20 | 21 | export interface Data { 22 | posts: Array; 23 | users: Array; 24 | idProvider: () => string; 25 | } 26 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/generated/tmp-resolvers/User.ts: -------------------------------------------------------------------------------- 1 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 2 | // Please do not import this file directly but copy & paste to your application code. 3 | 4 | import { UserResolvers } from '../graphqlgen' 5 | 6 | export const User: UserResolvers.Type = { 7 | ...UserResolvers.defaultResolvers, 8 | 9 | posts: (parent, args, ctx) => { 10 | throw new Error('Resolver not implemented') 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/generated/tmp-resolvers/Query.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { Query_Resolvers } from '../graphqlgen' 3 | 4 | export const Query: Query_Resolvers = { 5 | feed: (parent, args, ctx, info) => { 6 | throw new Error('Resolver not implemented') 7 | }, 8 | drafts: (parent, args, ctx, info) => { 9 | throw new Error('Resolver not implemented') 10 | }, 11 | post: (parent, args, ctx, info) => { 12 | throw new Error('Resolver not implemented') 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/generated/tmp-resolvers/Post.ts: -------------------------------------------------------------------------------- 1 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 2 | // Please do not import this file directly but copy & paste to your application code. 3 | 4 | import { PostResolvers } from '../graphqlgen' 5 | 6 | export const Post: PostResolvers.Type = { 7 | ...PostResolvers.defaultResolvers, 8 | 9 | author: (parent, args, ctx) => { 10 | throw new Error('Resolver not implemented') 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/resolvers/Query.ts: -------------------------------------------------------------------------------- 1 | import { QueryResolvers } from '../generated/graphqlgen' 2 | 3 | export const Query: QueryResolvers.Type = { 4 | feed: (parent, args, ctx) => { 5 | return ctx.data.posts.filter(post => post.published) 6 | }, 7 | 8 | drafts: (parent, args, ctx) => { 9 | return ctx.data.posts.filter(post => !post.published) 10 | }, 11 | 12 | post: (parent, args, ctx) => { 13 | return ctx.data.posts.find(post => post.id === args.id)! 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphqlgen-template-yoga", 3 | "version": "0.0.0", 4 | "main": "src/index.ts", 5 | "scripts": { 6 | "start": "nodemon -x ts-node --no-cache src/index.ts", 7 | "generate": "graphqlgen" 8 | }, 9 | "dependencies": { 10 | "graphql-yoga": "^1.16.7" 11 | }, 12 | "devDependencies": { 13 | "graphqlgen": "0.5.1", 14 | "nodemon": "1.18.9", 15 | "ts-node": "8.0.2", 16 | "typescript": "3.3.3" 17 | }, 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/path-helpers/__snapshots__/index.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`valid directory path 1`] = ` 4 | Array [ 5 | "tests", 6 | "fixtures", 7 | "basic", 8 | "index.ts", 9 | ] 10 | `; 11 | 12 | exports[`valid directory path without slash 1`] = ` 13 | Array [ 14 | "tests", 15 | "fixtures", 16 | "basic", 17 | "index.ts", 18 | ] 19 | `; 20 | 21 | exports[`valid path 1`] = ` 22 | Array [ 23 | "tests", 24 | "fixtures", 25 | "basic", 26 | "index.ts", 27 | ] 28 | `; 29 | -------------------------------------------------------------------------------- /packages/graphqlgen/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "rootDir": "./src", 7 | "outDir": "dist", 8 | "sourceMap": true, 9 | "lib": ["es2015", "esnext.asynciterable", "es2017"], 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "strict": true, 13 | "resolveJsonModule": true 14 | }, 15 | "include": ["./src/**/*.ts"], 16 | "exclude": ["node_modules", "generated", "example", "examples"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/interface/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | media(first: Int): [Media] 3 | mediaItem(id: Int!): Media 4 | } 5 | 6 | interface Unused { 7 | foo: Int 8 | } 9 | 10 | interface Media { 11 | id: ID! 12 | url: String! 13 | } 14 | 15 | type Dimensions { 16 | width: Int! 17 | height: Int! 18 | } 19 | 20 | type Image implements Media { 21 | id: ID! 22 | url: String! 23 | dimensions: Dimensions! 24 | } 25 | 26 | type Video implements Media { 27 | id: ID! 28 | url: String! 29 | duration: Int! 30 | } 31 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/generated/tmp-resolvers/index.ts: -------------------------------------------------------------------------------- 1 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 2 | // Please do not import this file directly but copy & paste to your application code. 3 | 4 | import { Resolvers } from '../graphqlgen' 5 | 6 | import { Query } from './Query' 7 | import { Post } from './Post' 8 | import { User } from './User' 9 | import { Mutation } from './Mutation' 10 | 11 | export const resolvers: Resolvers = { 12 | Query, 13 | Post, 14 | User, 15 | Mutation, 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ## Description 8 | 9 | 10 | ## Steps to reproduce 11 | 12 | 13 | ## Expected results 14 | 15 | 16 | ## Actual results 17 | 18 | 19 | ## Versions 20 | 24 | - graphqlgen: 25 | - OS name and version: 26 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/generated/tmp-resolvers/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 3 | // Please do not import this file directly but copy & paste to your application code. 4 | 5 | import type { Resolvers } from '../graphqlgen' 6 | 7 | import { Query } from './Query' 8 | import { Post } from './Post' 9 | import { User } from './User' 10 | import { Mutation } from './Mutation' 11 | 12 | export const resolvers: Resolvers = { 13 | Query, 14 | Post, 15 | User, 16 | Mutation, 17 | } 18 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | feed: [Post!]! 3 | drafts: [Post!]! 4 | post(id: ID!): Post 5 | } 6 | 7 | type Mutation { 8 | createUser(name: String!): User! 9 | createDraft(title: String!, content: String!, authorId: ID!): Post! 10 | deletePost(id: ID!): Post 11 | publish(id: ID!): Post 12 | } 13 | 14 | type Post { 15 | id: ID! 16 | title: String! 17 | content: String! 18 | published: Boolean! 19 | author: User! 20 | } 21 | 22 | type User { 23 | id: ID! 24 | name: String 25 | posts: [Post!]! 26 | } 27 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/src/tests/__snapshots__/basic.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`basic config 1`] = ` 4 | Object { 5 | "context": "./src/types.ts:Context", 6 | "language": "typescript", 7 | "models": Object { 8 | "files": Array [ 9 | "./src/types.ts", 10 | ], 11 | }, 12 | "output": "./src/generated/graphqlgen.ts", 13 | "resolver-scaffolding": Object { 14 | "layout": "file-per-type", 15 | "output": "./src/generated/tmp-resolvers/", 16 | }, 17 | "schema": "./src/schema.graphql", 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | feed: [Post!]! 3 | drafts: [Post!]! 4 | post(id: ID!): Post 5 | } 6 | 7 | type Mutation { 8 | createUser(name: String = null): User! 9 | createDraft(title: String!, content: String!, authorId: ID!): Post! 10 | deletePost(id: ID!): Post 11 | publish(id: ID!): Post 12 | } 13 | 14 | type Post { 15 | id: ID! 16 | title: String! 17 | content: String! 18 | published: Boolean! 19 | author: User! 20 | } 21 | 22 | type User { 23 | id: ID! 24 | name: String 25 | posts: [Post!]! 26 | } 27 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/introspection/flow.test.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import { addFileToTypesMap } from '../../src/introspection' 3 | 4 | const relative = (p: string) => join(__dirname, p) 5 | const language = 'flow' 6 | 7 | describe('flow file introspection', () => { 8 | test('find all types in file', () => { 9 | const typesNames = Object.keys( 10 | addFileToTypesMap(relative('./mocks/flow-types.js'), language), 11 | ) 12 | 13 | expect(typesNames).toEqual([ 14 | 'Interface', 15 | 'Type', 16 | 'ExportedInterface', 17 | 'ExportedType', 18 | ]) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/generated/tmp-resolvers/Mutation.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { Mutation_Resolvers } from '../graphqlgen' 3 | 4 | export const Mutation: Mutation_Resolvers = { 5 | createUser: (parent, args, ctx, info) => { 6 | throw new Error('Resolver not implemented') 7 | }, 8 | createDraft: (parent, args, ctx, info) => { 9 | throw new Error('Resolver not implemented') 10 | }, 11 | deletePost: (parent, args, ctx, info) => { 12 | throw new Error('Resolver not implemented') 13 | }, 14 | publish: (parent, args, ctx, info) => { 15 | throw new Error('Resolver not implemented') 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/resolvers/Query.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Query_defaultResolvers } from '../generated/graphqlgen' 3 | import type { Query_Resolvers } from '../generated/graphqlgen' 4 | 5 | export const Query: Query_Resolvers = { 6 | ...Query_defaultResolvers, 7 | 8 | feed: (parent, args, ctx) => { 9 | return ctx.data.posts.filter(post => post.published) 10 | }, 11 | 12 | drafts: (parent, args, ctx) => { 13 | return ctx.data.posts.filter(post => !post.published) 14 | }, 15 | 16 | post: (parent, args, ctx) => { 17 | return ctx.data.posts.find(post => post.id === args.id) || null 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/basic/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | id: ID! 3 | custom_required: Number! 4 | custom_nullable: Number 5 | custom_array_nullable: [Number] 6 | custom_array_required: [Number]! 7 | custom_with_arg(id: Int!): Number! 8 | custom_with_custom_arg(id: Number!): Number! 9 | scalar_required: Boolean! 10 | scalar_nullable: Boolean 11 | scalar_array_nullable: [Boolean] 12 | scalar_array_required: [Boolean]! 13 | scalar_with_arg(id: Int!): Boolean! 14 | scalar_with_custom_arg(id: Number!): Boolean! 15 | # commented_field: Boolean! 16 | } 17 | 18 | type Number { 19 | id: ID 20 | value: Float 21 | } 22 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/defaultName/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | id: ID! 3 | custom_required: Number! 4 | custom_nullable: Number 5 | custom_array_nullable: [Number] 6 | custom_array_required: [Number]! 7 | custom_with_arg(id: Int!): Number! 8 | custom_with_custom_arg(id: Number!): Number! 9 | scalar_required: Boolean! 10 | scalar_nullable: Boolean 11 | scalar_array_nullable: [Boolean] 12 | scalar_array_required: [Boolean]! 13 | scalar_with_arg(id: Int!): Boolean! 14 | scalar_with_custom_arg(id: Number!): Boolean! 15 | # commented_field: Boolean! 16 | } 17 | 18 | type Number { 19 | id: ID 20 | value: Float 21 | } 22 | -------------------------------------------------------------------------------- /docs/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "graphqlgen", 3 | "plugins": ["edit-link", "github", "anchorjs", "custom-favicon", "base"], 4 | "links": { 5 | "sidebar": false 6 | }, 7 | "pluginsConfig": { 8 | "favicon": "favicon.png", 9 | "base": { 10 | "base": "/graphqlgen/" 11 | }, 12 | "edit-link": { 13 | "base": "https://github.com/prisma/graphqlgen/tree/master/docs", 14 | "label": "Edit This Page" 15 | }, 16 | "github": { 17 | "url": "https://github.com/prisma/graphqlgen/" 18 | }, 19 | "theme-default": { 20 | "styles": { 21 | "website": "build/gitbook.css" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "prepare": "gitbook install && cp -a node_modules/highlight.js/. ~/.gitbook/versions/3.2.3/node_modules/highlight.js/", 4 | "build": "yarn prepare && gitbook build && mv _book tmp && mkdir _book && mv tmp _book/graphqlgen", 5 | "start": "yarn prepare && gitbook serve" 6 | }, 7 | "devDependencies": { 8 | "gitbook-cli": "2.3.2" 9 | }, 10 | "dependencies": { 11 | "gitbook-plugin-anchorjs": "2.1.0", 12 | "gitbook-plugin-base": "1.2.0", 13 | "gitbook-plugin-custom-favicon": "0.0.4", 14 | "gitbook-plugin-edit-link": "2.0.2", 15 | "gitbook-plugin-github": "3.0.0", 16 | "highlight.js": "9.14.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphqlgen-template-flow-yoga", 3 | "version": "0.0.0", 4 | "main": "src/index.js", 5 | "scripts": { 6 | "start": "npm run flow && babel-node src/index.js", 7 | "generate": "graphqlgen", 8 | "flow": "flow" 9 | }, 10 | "dependencies": { 11 | "graphql": "^14.0.2", 12 | "graphql-yoga": "^1.16.2" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "7.2.2", 16 | "@babel/node": "7.2.2", 17 | "@babel/preset-env": "7.3.1", 18 | "@babel/preset-flow": "7.0.0", 19 | "flow-bin": "0.89.0", 20 | "flow-typed": "2.5.1", 21 | "graphqlgen": "0.5.1" 22 | }, 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/generated/tmp-resolvers/Query.ts: -------------------------------------------------------------------------------- 1 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 2 | // Please do not import this file directly but copy & paste to your application code. 3 | 4 | import { QueryResolvers } from '../graphqlgen' 5 | 6 | export const Query: QueryResolvers.Type = { 7 | ...QueryResolvers.defaultResolvers, 8 | feed: (parent, args, ctx) => { 9 | throw new Error('Resolver not implemented') 10 | }, 11 | drafts: (parent, args, ctx) => { 12 | throw new Error('Resolver not implemented') 13 | }, 14 | post: (parent, args, ctx) => { 15 | throw new Error('Resolver not implemented') 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/src/definition.ts: -------------------------------------------------------------------------------- 1 | export interface GraphQLGenDefinition { 2 | language: Language 3 | schema: string 4 | context?: string 5 | models: Models 6 | output: string 7 | ['resolver-scaffolding']?: ResolverScaffolding 8 | ['default-resolvers']?: boolean 9 | ['iresolvers-augmentation']?: boolean 10 | } 11 | 12 | export interface Models { 13 | files?: File[] 14 | override?: { [typeName: string]: string } 15 | } 16 | 17 | export type File = 18 | | string 19 | | { 20 | path: string 21 | defaultName?: string 22 | } 23 | 24 | export type Language = 'typescript' | 'flow' 25 | 26 | export interface ResolverScaffolding { 27 | output: string 28 | layout: string 29 | } 30 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/lib/helpers.ts: -------------------------------------------------------------------------------- 1 | import * as FS from 'fs' 2 | import * as CP from 'child_process' 3 | 4 | /** 5 | * Return the latest git revision id. 6 | */ 7 | const getGitHeadSha = (): string => { 8 | return CP.execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim() 9 | } 10 | 11 | /** 12 | * Helper to update a json file. User needs only update the passed object. 13 | */ 14 | const updateJSONFile = ( 15 | path: string, 16 | updater: (data: T) => T, 17 | ): void => { 18 | const data: T = JSON.parse(FS.readFileSync(path, 'utf8')) 19 | const updatedData = updater(data) 20 | FS.writeFileSync(path, JSON.stringify(updatedData, null, 2)) 21 | } 22 | 23 | export { getGitHeadSha, updateJSONFile } 24 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/graphqlgen.yml: -------------------------------------------------------------------------------- 1 | # The target programming language for the generated code 2 | language: flow 3 | 4 | # The file path pointing to your GraphQL schema 5 | schema: ./src/schema.graphql 6 | 7 | # Type definition for the resolver context object 8 | context: ./src/types.js:Context 9 | 10 | # Map SDL types from the GraphQL schema to TS models 11 | models: 12 | files: 13 | - ./src/types.js 14 | 15 | # Generated typings for resolvers and default resolver implementations 16 | # DO NOT EDIT THIS FILE 17 | output: ./src/generated/graphqlgen.js 18 | 19 | # Temporary scaffolded resolvers to copy and paste in your application 20 | resolver-scaffolding: 21 | output: ./src/generated/tmp-resolvers/ 22 | layout: file-per-type 23 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/flow/large-schema.test.ts: -------------------------------------------------------------------------------- 1 | import { testGeneration } from '../generation' 2 | import { join } from 'path' 3 | 4 | const relative = (p: string) => join(__dirname, p) 5 | const typesDir = relative('./generated-large/graphqlgen.js') 6 | const resolversDir = relative('./generated-large/tmp-resolvers/') 7 | const language = 'flow' 8 | 9 | test('large schema', async () => { 10 | return testGeneration({ 11 | language, 12 | schema: relative('../fixtures/prisma/schema.graphql'), 13 | models: { 14 | files: [relative('../fixtures/prisma/flow-types.js')], 15 | }, 16 | output: typesDir, 17 | ['resolver-scaffolding']: { 18 | output: resolversDir, 19 | layout: 'file-per-type', 20 | }, 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphqlgen-json-schema", 3 | "version": "0.6.0-rc8", 4 | "main": "dist/definition.js", 5 | "typings": "dist/definition.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "prepare": "yarn build", 11 | "build": "rm -rf dist && tsc -d; cp src/schema.json dist", 12 | "test": "yarn build", 13 | "t": "jest" 14 | }, 15 | "devDependencies": { 16 | "@types/jest": "24.0.5", 17 | "ajv": "6.9.0", 18 | "jest": "23.6.0", 19 | "js-yaml": "3.12.1", 20 | "ts-jest": "24.0.0", 21 | "typescript": "3.3.3" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/prisma/graphqlgen.git" 26 | }, 27 | "license": "MIT" 28 | } 29 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/graphqlgen.yml: -------------------------------------------------------------------------------- 1 | # The target programming language for the generated code 2 | language: typescript 3 | 4 | # The file path pointing to your GraphQL schema 5 | schema: ./src/schema.graphql 6 | 7 | # Type definition for the resolver context object 8 | context: ./src/types.ts:Context 9 | 10 | # Map SDL types from the GraphQL schema to TS models 11 | models: 12 | files: 13 | - ./src/types.ts 14 | 15 | # Generated typings for resolvers and default resolver implementations 16 | # DO NOT EDIT THIS FILE 17 | output: ./src/generated/graphqlgen.ts 18 | 19 | # Temporary scaffolded resolvers to copy and paste in your application 20 | resolver-scaffolding: 21 | output: ./src/generated/tmp-resolvers/ 22 | layout: file-per-type 23 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/data.ts: -------------------------------------------------------------------------------- 1 | import { Data } from './types' 2 | 3 | const users = [ 4 | { id: '1', name: 'Alice', postIDs: ['3', '4'] }, 5 | { id: '2', name: 'Bob', postIDs: [] }, 6 | ] 7 | 8 | const posts = [ 9 | { 10 | id: '3', 11 | title: 'GraphQL Conf 2019', 12 | content: 'An awesome GraphQL conference in Berlin.', 13 | published: true, 14 | authorId: '1', 15 | }, 16 | { 17 | id: '4', 18 | title: 'GraphQL Weekly', 19 | content: 'Weekly news about the Grap[hQL ecosystem and community.', 20 | published: false, 21 | authorId: '1', 22 | }, 23 | ] 24 | 25 | let idCount = 4 26 | function idProvider(): string { 27 | return `${idCount++}` 28 | } 29 | 30 | export const data: Data = { posts, users, idProvider } 31 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/fixtures/input/schema.graphql: -------------------------------------------------------------------------------- 1 | type AddMemberPayload { 2 | newUserId: ID 3 | existingUserInviteSent: Boolean 4 | } 5 | 6 | input AddMemberData { 7 | email: String! 8 | projects: [ID!]! 9 | sideProjects: [ID] 10 | profile: ProfileData = null 11 | phones: [PhoneData] = [] 12 | isVIP: Boolean = false 13 | } 14 | 15 | input ProfileData { 16 | firstName: String 17 | lastName: String 18 | photo: Photo 19 | } 20 | 21 | input Photo { 22 | title: String! 23 | url: String! 24 | } 25 | 26 | input PhoneData { 27 | number: String! 28 | } 29 | 30 | type Mutation { 31 | addMember( 32 | data: AddMemberData! 33 | upsert: Boolean = false 34 | note: String = null 35 | ): AddMemberPayload! 36 | addMembers(data: [AddMemberData!]!): AddMemberPayload! 37 | } 38 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/data.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Data, Post } from './types' 3 | 4 | const users = [ 5 | { id: '1', name: 'Alice', postIDs: ['3', '4'] }, 6 | { id: '2', name: 'Bob', postIDs: [] }, 7 | ] 8 | 9 | const posts: Post[] = [ 10 | { 11 | id: '3', 12 | title: 'GraphQL Conf 2019', 13 | content: 'An awesome GraphQL conference in Berlin.', 14 | published: true, 15 | authorId: '1', 16 | }, 17 | { 18 | id: '4', 19 | title: 'GraphQL Weekly', 20 | content: 'Weekly news about the Grap[hQL ecosystem and community.', 21 | published: false, 22 | authorId: '1', 23 | }, 24 | ] 25 | 26 | let idCount = 4 27 | function idProvider(): string { 28 | return `${idCount++}` 29 | } 30 | 31 | export const data: Data = { posts, users, idProvider } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "cd packages/graphqlgen-json-schema && yarn build && cd ../graphqlgen && yarn build", 5 | "test": "yarn build && cd packages/graphqlgen && yarn test", 6 | "test:ci": "yarn build && cd packages/graphqlgen && yarn test:ci", 7 | "fix:prettier": "yarn prettier --write './**/*.{md,ts,js,graphql,yaml}'" 8 | }, 9 | "workspaces": [ 10 | "packages/*" 11 | ], 12 | "devDependencies": { 13 | "husky": "1.3.1", 14 | "prettier": "1.16.4", 15 | "pretty-quick": "1.10.0" 16 | }, 17 | "prettier": { 18 | "semi": false, 19 | "trailingComma": "all", 20 | "singleQuote": true 21 | }, 22 | "husky": { 23 | "hooks": { 24 | "pre-commit": "pretty-quick --staged" 25 | } 26 | }, 27 | "license": "MIT" 28 | } 29 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/src/tests/basic.test.ts: -------------------------------------------------------------------------------- 1 | import * as Ajv from 'ajv' 2 | import * as yaml from 'js-yaml' 3 | import schema = require('../schema.json') 4 | import * as fs from 'fs' 5 | import * as path from 'path' 6 | 7 | const ajv = new Ajv().addMetaSchema( 8 | require('ajv/lib/refs/json-schema-draft-06.json'), 9 | ) 10 | const validateYaml = ajv.compile(schema) 11 | 12 | export function parseConfig(file: string) { 13 | const config = yaml.safeLoad(file) 14 | 15 | if (!validateYaml(config)) { 16 | throw new Error(JSON.stringify(validateYaml.errors!, null, 2)) 17 | } 18 | 19 | return config 20 | } 21 | 22 | test('basic config', () => { 23 | const file = fs.readFileSync( 24 | path.join(__dirname, 'fixtures/basic.yml'), 25 | 'utf-8', 26 | ) 27 | expect(parseConfig(file)).toMatchSnapshot() 28 | }) 29 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/generated/tmp-resolvers/Mutation.ts: -------------------------------------------------------------------------------- 1 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 2 | // Please do not import this file directly but copy & paste to your application code. 3 | 4 | import { MutationResolvers } from '../graphqlgen' 5 | 6 | export const Mutation: MutationResolvers.Type = { 7 | ...MutationResolvers.defaultResolvers, 8 | createUser: (parent, args, ctx) => { 9 | throw new Error('Resolver not implemented') 10 | }, 11 | createDraft: (parent, args, ctx) => { 12 | throw new Error('Resolver not implemented') 13 | }, 14 | deletePost: (parent, args, ctx) => { 15 | throw new Error('Resolver not implemented') 16 | }, 17 | publish: (parent, args, ctx) => { 18 | throw new Error('Resolver not implemented') 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/typescript/large-schema.test.ts: -------------------------------------------------------------------------------- 1 | import { testGeneration } from '../generation' 2 | import { join } from 'path' 3 | 4 | const relative = (p: string) => join(__dirname, p) 5 | 6 | const typesDir = relative('./generated-large/graphqlgen.ts') 7 | const resolversDir = relative('./generated-large/tmp-resolvers/') 8 | const language = 'typescript' 9 | 10 | describe('large schema tests', () => { 11 | test('large schema', async () => { 12 | return testGeneration({ 13 | language, 14 | schema: relative('../fixtures/prisma/schema.graphql'), 15 | models: { 16 | files: [relative('../fixtures/prisma/types.ts')], 17 | }, 18 | output: typesDir, 19 | ['resolver-scaffolding']: { 20 | output: resolversDir, 21 | layout: 'file-per-type', 22 | }, 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/src/tests/fixtures/basic.yml: -------------------------------------------------------------------------------- 1 | # The target programming language for the generated code 2 | language: typescript 3 | 4 | # The file path pointing to your GraphQL schema 5 | schema: ./src/schema.graphql 6 | 7 | # Type definition for the resolver context object 8 | context: ./src/types.ts:Context 9 | 10 | # Map SDL types from the GraphQL schema to TS models 11 | models: 12 | files: 13 | - ./src/types.ts 14 | - path: ./src/generated/client.ts 15 | defaultName: ${typeName}Node 16 | 17 | # Generated typings for resolvers and default resolver implementations 18 | # Please don't edit this file but just import from here 19 | output: ./src/generated/graphqlgen.ts 20 | 21 | # Temporary scaffolded resolvers to copy and paste in your application 22 | resolver-scaffolding: 23 | output: ./src/generated/tmp-resolvers/ 24 | layout: file-per-type 25 | -------------------------------------------------------------------------------- /docs/04-bootstrapping.md: -------------------------------------------------------------------------------- 1 | # Bootstrapping 2 | 3 | You can bootstrap an entire GraphQL server based on one of the available [templates](https://github.com/prisma/graphqlgen/tree/master/packages/graphqlgen-templates) using [`npm init`](https://docs.npmjs.com/cli/init): 4 | 5 | ``` 6 | npm init graphqlgen ./my-graphql-server 7 | ``` 8 | 9 | You'll be prompted to choose the server template you want to bootstrap 10 | 11 | ``` 12 | ? What GraphQL server template do you want to bootstrap? (Use arrow keys) 13 | 14 | ❯ yoga (GraphQL Yoga template with typescript) 15 | flow-yoga (GraphQL Yoga template with flow) 16 | ``` 17 | 18 | Or provide the `--template` option to configure the template you want to bootstrap 19 | 20 | ``` 21 | npm init graphqlgen ./my-graphql-server --template yoga 22 | ``` 23 | 24 | Then start the server: 25 | 26 | ``` 27 | cd my-graphql-server 28 | yarn start 29 | ``` 30 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/replaceVariables.test.ts: -------------------------------------------------------------------------------- 1 | import { replaceVariablesInString } from '../src/parse' 2 | 3 | describe('replaceVariablesInString', () => { 4 | test('basic usage', () => { 5 | const replacements = { 6 | typeName: 'User', 7 | } 8 | expect(replaceVariablesInString('${typeName}Node', replacements)).toBe( 9 | 'UserNode', 10 | ) 11 | }) 12 | test('allow repititions of variables', () => { 13 | const replacements = { 14 | typeName: 'User', 15 | } 16 | expect( 17 | replaceVariablesInString('${typeName}Node${typeName}', replacements), 18 | ).toBe('UserNodeUser') 19 | }) 20 | test('throw for missing variable', () => { 21 | const replacements = { 22 | typeNa: 'User', 23 | } 24 | expect(() => 25 | replaceVariablesInString('${typeName}Node', replacements), 26 | ).toThrow() 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/glob.ts: -------------------------------------------------------------------------------- 1 | import * as glob from 'glob' 2 | import { File } from 'graphqlgen-json-schema' 3 | import { getPath } from './parse' 4 | 5 | /** 6 | * Returns the path array from glob patterns 7 | */ 8 | export const extractGlobPattern = (file: File) => { 9 | return glob.sync(getPath(file)) 10 | } 11 | 12 | /** 13 | * Handles the glob pattern of models.files 14 | */ 15 | export const handleGlobPattern = (files?: File[]): File[] => { 16 | if (!files) { 17 | return [] 18 | } 19 | 20 | return files.reduce((acc, file) => { 21 | const globedPaths = extractGlobPattern(file) 22 | 23 | if (globedPaths.length === 0) { 24 | return [...acc, file] 25 | } 26 | 27 | const globedFiles: File[] = globedPaths.map(path => { 28 | if (typeof file === 'string') { 29 | return path 30 | } 31 | 32 | return { 33 | path, 34 | defaultName: file.defaultName, 35 | } 36 | }) 37 | 38 | return [...acc, ...globedFiles] 39 | }, []) 40 | } 41 | -------------------------------------------------------------------------------- /.github/RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | ## 1. Create Release Notes 4 | 1. [Draft a new release on Github](https://github.com/prisma/graphqlgen/releases/new) 5 | 2. `yarn global add git-release-notes` 6 | 3. Get the release notes from executing `.github/make-release-notes.sh` 7 | 4. Separate them like [here](https://github.com/prisma/graphqlgen/releases/tag/0.5.0) by Features and Fixes and add them to the draft 8 | 5. Get feedback for the draft 9 | 10 | ## 2. Publish `graphqlgen-json-schema`, if there was a change 11 | ```sh 12 | cd ../graphqlgen-json-schema/ 13 | yarn publish --no-git-tag-version 14 | ``` 15 | 16 | ## 3. Publish `create-graphqlgen`, if there was a change 17 | ```sh 18 | cd ../create-graphqlgen 19 | yarn publish --no-git-tag-version 20 | ``` 21 | 22 | ## 4. Publish `graphqlgen` 23 | ```sh 24 | cd ../graphqlgen/ 25 | yarn add graphqlgen-json-schema # if there was a change to graphqlgen-json-schema, add it to graphqlgen 26 | yarn publish --no-git-tag-version 27 | ``` 28 | ## 5. Push the bumped versions with `Bump to NEW_VERSION` to github 29 | 30 | ## 6. Publish the release draft on Github 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Prisma 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 | -------------------------------------------------------------------------------- /packages/create-graphqlgen/src/templates.ts: -------------------------------------------------------------------------------- 1 | interface Template { 2 | name: string 3 | description: string 4 | repo: TemplateRepository 5 | } 6 | 7 | interface TemplateRepository { 8 | uri: string 9 | branch: string 10 | path: string 11 | } 12 | 13 | const defaultTemplate: Template = { 14 | name: 'typescript-yoga', 15 | description: 'GraphQL Yoga template with TypeScript', 16 | repo: { 17 | uri: 'https://github.com/prisma/graphqlgen', 18 | branch: 'master', 19 | path: '/packages/graphqlgen-templates/typescript-yoga', 20 | }, 21 | } 22 | 23 | const availableTemplates: Template[] = [ 24 | defaultTemplate, 25 | { 26 | name: 'flow-yoga', 27 | description: 'GraphQL Yoga template with Flow', 28 | repo: { 29 | uri: 'https://github.com/prisma/graphqlgen', 30 | branch: 'master', 31 | path: '/packages/graphqlgen-templates/flow-yoga', 32 | }, 33 | }, 34 | ] 35 | 36 | const templatesNames = availableTemplates.map(t => `\`${t.name}\``).join(', ') 37 | 38 | export { 39 | Template, 40 | TemplateRepository, 41 | defaultTemplate, 42 | availableTemplates, 43 | templatesNames, 44 | } 45 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/introspection/typescript.test.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import { addFileToTypesMap } from '../../src/introspection' 3 | 4 | const relative = (p: string) => join(__dirname, p) 5 | const language = 'typescript' 6 | 7 | describe('typescript file introspection', () => { 8 | test('find all types in file', () => { 9 | const typesNames = Object.keys( 10 | addFileToTypesMap(relative('./mocks/types.ts'), language), 11 | ) 12 | 13 | expect(typesNames).toEqual([ 14 | 'Interface', 15 | 'Type', 16 | 'Enum', 17 | 'ExportedInterface', 18 | 'ExportedType', 19 | ]) 20 | }) 21 | 22 | // TODO: Update test 23 | // test('extract fields from typescript type', () => { 24 | // const model: Model = { modelTypeName: 'Interface', absoluteFilePath: relative('./mocks/types.ts'), importPathRelativeToOutput: 'not_used' } 25 | // const fields = extractFieldsFromTypescriptType(model) 26 | // 27 | // expect(fields).toEqual([ 28 | // { fieldName: 'field', fieldOptional: false }, 29 | // { fieldName: 'optionalField', fieldOptional: true }, 30 | // { fieldName: 'fieldUnionNull', fieldOptional: true }, 31 | // ]) 32 | // }) 33 | }) 34 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/types.ts: -------------------------------------------------------------------------------- 1 | import * as prettier from 'prettier' 2 | import { 3 | GraphQLTypeObject, 4 | GraphQLEnumObject, 5 | GraphQLUnionObject, 6 | GraphQLInterfaceObject, 7 | } from './source-helper' 8 | import { TypeDefinition } from './introspection/types' 9 | 10 | export interface GenerateArgs { 11 | interfaces: GraphQLInterfaceObject[] 12 | types: GraphQLTypeObject[] 13 | enums: GraphQLEnumObject[] 14 | unions: GraphQLUnionObject[] 15 | context?: ContextDefinition 16 | modelMap: ModelMap 17 | defaultResolversEnabled: boolean 18 | iResolversAugmentationEnabled: boolean 19 | delegatedParentResolversEnabled: boolean 20 | } 21 | 22 | export interface ModelMap { 23 | [schemaTypeName: string]: Model 24 | } 25 | 26 | export interface Model { 27 | absoluteFilePath: string 28 | importPathRelativeToOutput: string 29 | definition: TypeDefinition 30 | } 31 | 32 | export interface ContextDefinition { 33 | contextPath: string 34 | interfaceName: string 35 | } 36 | 37 | export interface CodeFileLike { 38 | path: string 39 | force: boolean 40 | code: string 41 | } 42 | 43 | export interface IGenerator { 44 | generate: (args: GenerateArgs) => string | CodeFileLike[] 45 | format: (code: string, options?: prettier.Options) => string 46 | } 47 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/glob/extract-glob-pattern.test.ts: -------------------------------------------------------------------------------- 1 | import { extractGlobPattern } from '../../src/glob' 2 | 3 | /** 4 | * Walk through all files under a directory 5 | */ 6 | test('Basic Walk', () => { 7 | const mockPath = './tests/glob/mocks/dir-1/*.ts' 8 | expect(extractGlobPattern(mockPath)).toMatchObject([ 9 | './tests/glob/mocks/dir-1/file-11.ts', 10 | './tests/glob/mocks/dir-1/file-12.ts', 11 | ]) 12 | }) 13 | 14 | /** 15 | * Walk through all folders under a dir using pattern 16 | */ 17 | test('Wild Walk', () => { 18 | const mockPath = './tests/glob/mocks/**/*.ts' 19 | expect(extractGlobPattern(mockPath)).toMatchObject([ 20 | './tests/glob/mocks/dir-1/file-11.ts', 21 | './tests/glob/mocks/dir-1/file-12.ts', 22 | './tests/glob/mocks/dir-2/file-21.ts', 23 | './tests/glob/mocks/dir-2/file-22.ts', 24 | ]) 25 | }) 26 | 27 | /** 28 | * If no glob pattern is mentioned, Return the input array 29 | */ 30 | test('No Walk', () => { 31 | const mockPath = './tests/glob/mocks/dir-1/file-11.ts' 32 | expect(extractGlobPattern(mockPath)).toMatchObject([mockPath]) 33 | }) 34 | 35 | /** 36 | * Unknown Path, Returns an empty array 37 | */ 38 | test('Unknown Walk', () => { 39 | const mockPath = './tests/glob/mocks/dir-3/*.ts' 40 | 41 | expect(extractGlobPattern(mockPath)).toMatchObject([]) 42 | }) 43 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/resolvers/Mutation.ts: -------------------------------------------------------------------------------- 1 | import { MutationResolvers } from '../generated/graphqlgen' 2 | 3 | export const Mutation: MutationResolvers.Type = { 4 | createUser: (parent, { name }, ctx) => { 5 | const id = ctx.data.idProvider() 6 | const newUser = { id, name, postIDs: [] } 7 | ctx.data.users.push(newUser) 8 | return newUser 9 | }, 10 | 11 | createDraft: (parent, { title, content, authorId }, ctx) => { 12 | const author = ctx.data.users.find(user => user.id === authorId) 13 | if (author === null) { 14 | throw new Error(`User with ID '${authorId}' does not exist.`) 15 | } 16 | const id = ctx.data.idProvider() 17 | const newDraft = { id, title, content, authorId, published: false } 18 | ctx.data.posts.push(newDraft) 19 | author!.postIDs.push(id) 20 | return newDraft 21 | }, 22 | 23 | deletePost: (parent, { id }, ctx) => { 24 | const postIndex = ctx.data.posts.findIndex(post => post.id === id) 25 | if (postIndex < 0) { 26 | throw new Error(`Post with ID '${id}' does not exist.`) 27 | } 28 | const deleted = ctx.data.posts.splice(postIndex, 1) 29 | return deleted[0] 30 | }, 31 | 32 | publish: (parent, { id }, ctx) => { 33 | const post = ctx.data.posts.find(post => post.id === id) 34 | post!.published = true 35 | return post! 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/index.ts: -------------------------------------------------------------------------------- 1 | import * as Benchmark from './lib/benchmark' 2 | import * as IntegrationBenchmarks from './integration' 3 | import * as MicroBenchmarks from './micro' 4 | import * as yargs from 'yargs' 5 | import * as Path from 'path' 6 | 7 | const argv = yargs 8 | .usage('bench [args]') 9 | .option('save', { 10 | alias: 's', 11 | default: false, 12 | describe: 'append benchmark results to benchmark history file', 13 | type: 'boolean', 14 | }) 15 | .options('filter', { 16 | alias: 'f', 17 | default: '', 18 | describe: 'only run matching benchmarks', 19 | type: 'string', 20 | }) 21 | .version(false) 22 | .help().argv 23 | 24 | const pattern = argv.filter ? new RegExp(argv.filter) : null 25 | 26 | const filterer = pattern 27 | ? (b: Benchmark.Benchmark) => { 28 | return b.name.match(pattern) 29 | } 30 | : () => true 31 | 32 | const reports = [ 33 | ...MicroBenchmarks.collect(), 34 | ...IntegrationBenchmarks.collect(), 35 | ] 36 | .filter(filterer) 37 | .reduce((acc, benchmark) => { 38 | acc.push(benchmark.run()) 39 | return acc 40 | }, []) 41 | 42 | if (argv.save) { 43 | Benchmark.saveReports(Path.join(__dirname, './history.json'), reports) 44 | reports.forEach(report => console.log(report.summary)) 45 | } else { 46 | reports.forEach(report => console.log(report.summary)) 47 | } 48 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/resolvers/Mutation.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Mutation_Resolvers } from '../generated/graphqlgen' 3 | 4 | export const Mutation: Mutation_Resolvers = { 5 | createUser: (parent, { name }, ctx, info) => { 6 | const id = ctx.data.idProvider() 7 | const newUser = { id, name, postIDs: [] } 8 | ctx.data.users.push(newUser) 9 | return newUser 10 | }, 11 | 12 | createDraft: (parent, { title, content, authorId }, ctx, info) => { 13 | const author = ctx.data.users.find(user => user.id === authorId) 14 | if (!author) { 15 | throw new Error(`User with ID '${authorId}' does not exist.`) 16 | } 17 | const id = ctx.data.idProvider() 18 | const newDraft = { id, title, content, authorId, published: false } 19 | ctx.data.posts.push(newDraft) 20 | author.postIDs.push(id) 21 | return newDraft 22 | }, 23 | 24 | deletePost: (parent, { id }, ctx, info) => { 25 | const postIndex = ctx.data.posts.findIndex(post => post.id === id) 26 | if (postIndex < 0) { 27 | throw new Error(`Post with ID '${id}' does not exist.`) 28 | } 29 | const deleted = ctx.data.posts.splice(postIndex, 1) 30 | return deleted[0] 31 | }, 32 | 33 | publish: (parent, { id }, ctx, info) => { 34 | const post = ctx.data.posts.find(post => post.id === id) 35 | if (!post) { 36 | throw new Error(`Post with ID '${id}' does not exist.`) 37 | } 38 | post.published = true 39 | return post 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /packages/create-graphqlgen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-graphqlgen", 3 | "version": "0.6.0-rc3", 4 | "main": "dist/index.js", 5 | "scripts": { 6 | "prepare": "yarn build", 7 | "clean": "rimraf dist", 8 | "build": "yarn clean && tsc --declaration", 9 | "build:watch": "yarn clean && tsc --declaration --watch", 10 | "lint": "tslint {src,test}/**/*.ts", 11 | "start": "ts-node src/index.ts" 12 | }, 13 | "bin": { 14 | "create-graphqlgen": "dist/index.js" 15 | }, 16 | "files": [ 17 | "dist", 18 | "README.md" 19 | ], 20 | "dependencies": { 21 | "chalk": "^2.4.1", 22 | "execa": "^1.0.0", 23 | "inquirer": "^6.2.0", 24 | "meow": "^5.0.0", 25 | "ora": "^3.0.0", 26 | "parse-github-url": "^1.0.2", 27 | "request": "^2.88.0", 28 | "tar": "^4.4.7", 29 | "update-notifier": "^2.5.0" 30 | }, 31 | "devDependencies": { 32 | "@types/execa": "0.9.0", 33 | "@types/inquirer": "0.0.43", 34 | "@types/meow": "5.0.0", 35 | "@types/node": "10.12.25", 36 | "@types/ora": "3.0.0", 37 | "@types/parse-github-url": "1.0.0", 38 | "@types/request": "2.48.1", 39 | "@types/tar": "4.0.0", 40 | "@types/tmp": "0.0.33", 41 | "@types/update-notifier": "2.5.0", 42 | "prettier": "1.16.4", 43 | "rimraf": "2.6.3", 44 | "ts-node": "8.0.2", 45 | "tslint": "5.12.1", 46 | "tslint-config-prettier": "1.18.0", 47 | "tslint-config-standard": "8.0.1", 48 | "typescript": "3.3.3" 49 | }, 50 | "license": "MIT" 51 | } 52 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/introspection/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import { Language } from 'graphqlgen-json-schema' 3 | 4 | import { FilesToTypesMap, TypeDefinition, TypesMap } from './types' 5 | import { buildTSTypesMap } from './ts-ast' 6 | import { buildFlowTypesMap } from './flow-ast' 7 | import { NormalizedFile } from '../parse' 8 | 9 | export const filesToTypesMap: { [filePath: string]: TypesMap } = {} 10 | 11 | function buildTypesMapByLanguage( 12 | fileContent: string, 13 | filePath: string, 14 | language: Language, 15 | ): TypesMap { 16 | switch (language) { 17 | case 'typescript': 18 | return buildTSTypesMap(fileContent, filePath) 19 | case 'flow': 20 | return buildFlowTypesMap(fileContent, filePath) 21 | } 22 | } 23 | 24 | export function addFileToTypesMap( 25 | filePath: string, 26 | language: Language, 27 | ): TypesMap { 28 | if (filesToTypesMap[filePath] !== undefined) { 29 | return filesToTypesMap[filePath] 30 | } 31 | 32 | const fileContent = fs.readFileSync(filePath).toString() 33 | 34 | const typesMap = buildTypesMapByLanguage(fileContent, filePath, language) 35 | 36 | filesToTypesMap[filePath] = typesMap 37 | 38 | return typesMap 39 | } 40 | 41 | export function findTypeInFile( 42 | filePath: string, 43 | typeName: string, 44 | language: Language, 45 | ): TypeDefinition | undefined { 46 | addFileToTypesMap(filePath, language) 47 | 48 | return filesToTypesMap[filePath][typeName] 49 | } 50 | 51 | export function addFilesToTypesMap( 52 | files: NormalizedFile[], 53 | language: Language, 54 | ): FilesToTypesMap { 55 | files.forEach(file => { 56 | addFileToTypesMap(file.path, language) 57 | }) 58 | 59 | return filesToTypesMap 60 | } 61 | 62 | export function getFilesToTypesMap() { 63 | return filesToTypesMap 64 | } 65 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/path-helpers/index.test.ts: -------------------------------------------------------------------------------- 1 | import { getAbsoluteFilePath } from '../../src/path-helpers' 2 | import { join, resolve } from 'path' 3 | import { normalizeFilePath } from '../../src/utils' 4 | 5 | const relative = (p: string) => join(__dirname, p) 6 | const language = 'typescript' 7 | 8 | test('invalid path', () => { 9 | expect(() => 10 | getAbsoluteFilePath(join('../fixtures/type.ts'), language), 11 | ).toThrow() 12 | }) 13 | 14 | test('valid path', () => { 15 | expect( 16 | getAbsoluteFilePath(join(__dirname, '../fixtures/basic/index.ts'), language) 17 | .split('/') 18 | .slice(-4), 19 | ).toMatchSnapshot() 20 | }) 21 | 22 | test('valid directory path', () => { 23 | expect( 24 | getAbsoluteFilePath(relative('../fixtures/basic/'), language) 25 | .split('/') 26 | .slice(-4), 27 | ).toMatchSnapshot() 28 | }) 29 | 30 | test('valid directory path without slash', () => { 31 | expect( 32 | getAbsoluteFilePath(relative('../fixtures/basic'), language) 33 | .split('/') 34 | .slice(-4), 35 | ).toMatchSnapshot() 36 | }) 37 | 38 | test('normalizeFilePath()', () => { 39 | // '/path/to/file.ts' => '/path/to/file.ts' 40 | expect(normalizeFilePath(relative('./mocks/types.ts'), language)).toEqual( 41 | resolve(relative('./mocks/types.ts')), 42 | ) 43 | 44 | // '/path/to/file' => '/path/to/file.ts' 45 | expect(normalizeFilePath(relative('./mocks/types'), language)).toEqual( 46 | resolve(relative('./mocks/types.ts')), 47 | ) 48 | 49 | // '/path/to/file' => '/path/to/file/index.ts' 50 | expect(normalizeFilePath(relative('./mocks/dir-1'), language)).toEqual( 51 | resolve(relative('./mocks/dir-1/index.ts')), 52 | ) 53 | 54 | // '/path/to/file' => '/path/to/file.ts' 55 | // Even though there is a folder of the same name 56 | expect(normalizeFilePath(relative('./mocks/dir-2/dir-3'), language)).toEqual( 57 | resolve(relative('./mocks/dir-2/dir-3.ts')), 58 | ) 59 | }) 60 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/path-helpers.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { Language } from 'graphqlgen-json-schema' 4 | 5 | export function getExtNameFromLanguage(language: Language) { 6 | const extNames = { 7 | typescript: '.ts', 8 | flow: '.js', 9 | } 10 | 11 | return extNames[language] 12 | } 13 | 14 | export function getAbsoluteFilePath( 15 | modelPath: string, 16 | language: Language, 17 | ): string { 18 | let absolutePath = path.resolve(modelPath) 19 | const extName = getExtNameFromLanguage(language) 20 | 21 | if ( 22 | !fs.existsSync(absolutePath) && 23 | fs.existsSync(`${absolutePath}${extName}`) 24 | ) { 25 | absolutePath += extName 26 | } 27 | 28 | if (!fs.existsSync(absolutePath)) { 29 | throw new Error(`${absolutePath} not found`) 30 | } 31 | 32 | if (!fs.lstatSync(absolutePath).isDirectory()) { 33 | if (path.extname(absolutePath) !== extName) { 34 | throw new Error(`${absolutePath} has to be a ${extName} file`) 35 | } 36 | 37 | return absolutePath.replace(/\\/g, '/') 38 | } 39 | 40 | const indexPath = path.join(absolutePath, 'index' + extName) 41 | if (!fs.existsSync(indexPath)) { 42 | throw new Error( 43 | `No index${extName} file found in directory: ${absolutePath}`, 44 | ) 45 | } 46 | 47 | return indexPath.replace(/\\/g, '/') 48 | } 49 | 50 | export function getImportPathRelativeToOutput( 51 | absolutePath: string, 52 | outputDir: string, 53 | ): string { 54 | let relativePath = path.relative(path.dirname(outputDir), absolutePath) 55 | 56 | if (!relativePath.startsWith('.')) { 57 | relativePath = './' + relativePath 58 | } 59 | 60 | // remove .ts or .js file extension 61 | relativePath = relativePath.replace(/\.(ts|js)$/, '') 62 | 63 | // remove /index 64 | relativePath = relativePath.replace(/\/index$/, '') 65 | 66 | // replace \ with / 67 | relativePath = relativePath.replace(/\\/g, '/') 68 | 69 | return relativePath 70 | } 71 | -------------------------------------------------------------------------------- /packages/graphqlgen-json-schema/src/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-06/schema#", 3 | "title": "JSON schema for graphqlgen.yml files", 4 | "properties": { 5 | "language": { 6 | "type": "string", 7 | "oneOf": [{ "enum": ["typescript", "flow"] }] 8 | }, 9 | "schema": { 10 | "type": "string" 11 | }, 12 | "context": { 13 | "type": "string" 14 | }, 15 | "models": { 16 | "type": "object", 17 | "properties": { 18 | "files": { 19 | "type": "array", 20 | "items": { 21 | "anyOf": [ 22 | { 23 | "type": "string" 24 | }, 25 | { 26 | "type": "object", 27 | "properties": { 28 | "path": { 29 | "type": "string" 30 | }, 31 | "defaultName": { 32 | "type": "string" 33 | } 34 | }, 35 | "required": ["path"] 36 | } 37 | ] 38 | } 39 | }, 40 | "override": { 41 | "type": "object", 42 | "patternProperties": { 43 | "^[a-zA-Z0-9]*$": { 44 | "type": "string" 45 | } 46 | } 47 | } 48 | }, 49 | "additionalProperties": false 50 | }, 51 | "output": { 52 | "type": "string", 53 | "description": "Path to main output file" 54 | }, 55 | "resolver-scaffolding": { 56 | "description": "All output fields", 57 | "type": "object", 58 | "properties": { 59 | "output": { 60 | "type": "string", 61 | "description": "Path to scaffolded file for missing model definitions" 62 | }, 63 | "layout": { 64 | "type": "string", 65 | "oneOf": [{ "enum": ["file-per-type"] }] 66 | } 67 | }, 68 | "required": ["output", "layout"], 69 | "additionalProperties": false 70 | } 71 | }, 72 | "required": ["language", "schema", "models", "output"], 73 | "additionalProperties": false 74 | } 75 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/validateDefinition.test.ts: -------------------------------------------------------------------------------- 1 | import { validateDefinition } from '../../src/validation' 2 | import { join } from 'path' 3 | 4 | const relative = (p: string) => join(__dirname, p) 5 | 6 | const language = 'typescript' 7 | 8 | describe('test validateDefinition()', () => { 9 | test('invalid syntax', () => { 10 | const definition = './src/toto.ts::ModelName' 11 | const validation = validateDefinition('User', definition, language) 12 | 13 | expect(validation).toMatchObject({ 14 | validSyntax: false, 15 | fileExists: false, 16 | interfaceExists: false, 17 | definition: { 18 | rawDefinition: definition, 19 | }, 20 | }) 21 | }) 22 | 23 | test('invalid path', () => { 24 | const definition = './src/toto.ts:ModelName' 25 | const validation = validateDefinition('User', definition, language) 26 | 27 | expect(validation).toMatchObject({ 28 | validSyntax: true, 29 | fileExists: false, 30 | interfaceExists: false, 31 | definition: { 32 | rawDefinition: definition, 33 | }, 34 | }) 35 | }) 36 | 37 | test('invalid interface name', () => { 38 | const filePath = relative('../fixtures/basic/index.ts') 39 | const definition = `${filePath}:ModelName` 40 | const validation = validateDefinition('User', definition, language) 41 | 42 | expect(validation).toMatchObject({ 43 | validSyntax: true, 44 | fileExists: true, 45 | interfaceExists: false, 46 | definition: { 47 | rawDefinition: definition, 48 | }, 49 | }) 50 | }) 51 | 52 | test('invalid interface name', () => { 53 | const filePath = relative('../fixtures/basic/index.ts') 54 | const definition = `${filePath}:Number` 55 | const validation = validateDefinition('User', definition, language) 56 | 57 | expect(validation).toMatchObject({ 58 | validSyntax: true, 59 | fileExists: true, 60 | interfaceExists: true, 61 | definition: { 62 | rawDefinition: definition, 63 | filePath, 64 | typeName: 'User', 65 | modelName: 'Number', 66 | }, 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/introspection/types.ts: -------------------------------------------------------------------------------- 1 | import { File } from 'graphqlgen-json-schema' 2 | 3 | export type InnerType = 4 | | ScalarTypeAnnotation 5 | | UnionTypeAnnotation 6 | | AnonymousInterfaceAnnotation 7 | | LiteralTypeAnnotation 8 | 9 | export type InternalInnerType = InnerType | TypeReferenceAnnotation 10 | 11 | export type UnknownType = '_UNKNOWN_TYPE_' 12 | export type Scalar = 'string' | 'number' | 'boolean' | null 13 | 14 | export type TypeDefinition = InterfaceDefinition | TypeAliasDefinition 15 | 16 | export type InnerAndTypeDefinition = InnerType | TypeDefinition 17 | 18 | type Defer = () => T 19 | 20 | interface BaseTypeDefinition { 21 | name: string 22 | } 23 | 24 | export interface InterfaceDefinition extends BaseTypeDefinition { 25 | kind: 'InterfaceDefinition' 26 | fields: FieldDefinition[] 27 | } 28 | 29 | export interface FieldDefinition { 30 | name: string 31 | getType: Defer 32 | optional: boolean 33 | } 34 | 35 | export interface TypeAliasDefinition extends BaseTypeDefinition { 36 | kind: 'TypeAliasDefinition' 37 | getType: Defer 38 | isEnum: Defer //If type is UnionType && `types` are scalar strings 39 | } 40 | 41 | export interface UnionTypeAnnotation { 42 | kind: 'UnionTypeAnnotation' 43 | getTypes: Defer 44 | isArray: boolean 45 | isEnum: Defer 46 | } 47 | 48 | export interface ScalarTypeAnnotation { 49 | kind: 'ScalarTypeAnnotation' 50 | type: Scalar | UnknownType 51 | isArray: boolean 52 | } 53 | 54 | export interface AnonymousInterfaceAnnotation { 55 | kind: 'AnonymousInterfaceAnnotation' 56 | fields: FieldDefinition[] 57 | isArray: boolean 58 | } 59 | 60 | export interface TypeReferenceAnnotation { 61 | kind: 'TypeReferenceAnnotation' 62 | referenceType: string 63 | } 64 | 65 | export interface LiteralTypeAnnotation { 66 | kind: 'LiteralTypeAnnotation' 67 | type: string 68 | value: string | number | boolean 69 | isArray: boolean 70 | } 71 | 72 | export interface TypesMap { 73 | [typeName: string]: TypeDefinition 74 | } 75 | 76 | export interface FilesToTypesMap { 77 | [filePath: string]: TypesMap 78 | } 79 | 80 | export interface InterfaceNamesToFile { 81 | [interfaceName: string]: File 82 | } 83 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/lib/benchmark.ts: -------------------------------------------------------------------------------- 1 | import * as Bench from 'benchmark' 2 | import * as H from '../lib/helpers' 3 | 4 | type History = Record 5 | 6 | /** 7 | * Benchmark performance results 8 | * 9 | * * hz: The number of executions per second. 10 | * * count: The number of times a test was executed. 11 | * * cycles: The number of cycles performed while benchmarking. 12 | * * stats.deviation: The sample standard deviation. 13 | * * stats.mean: The sample arithmetic mean (secs). 14 | * * stats.moe: The margin of error. 15 | * * stats.rme: The relative margin of error (expressed as a percentage of the mean). 16 | * * stats.sample: The array of sampled periods. 17 | * * stats.sem: The standard error of the mean. 18 | * * stats.variance: The sample variance. 19 | * 20 | * To get more detail refer to: 21 | * 22 | * * https://stackoverflow.com/questions/32629779/define-number-of-cycles-benchmark-js 23 | * * http://monsur.hossa.in/2012/12/11/benchmarkjs.html 24 | */ 25 | type Report = { 26 | name: string 27 | summary: string 28 | hz: number 29 | count: number 30 | cycles: number 31 | stats: Bench.Stats 32 | } 33 | 34 | /** 35 | * Function that will collect benchmarks of a benchmark type. 36 | */ 37 | type Collect = () => Benchmark[] 38 | 39 | class Benchmark { 40 | runner: Bench 41 | name: string 42 | 43 | constructor({ name, test }: { name: string; test: Function }) { 44 | this.runner = new Bench({ name, fn: test }) 45 | this.name = name 46 | } 47 | 48 | run = (): Report => { 49 | this.runner.run() 50 | 51 | const report = { 52 | name: this.name, 53 | summary: this.runner.toString(), 54 | hz: this.runner.hz, 55 | count: this.runner.count, 56 | cycles: this.runner.cycles, 57 | stats: this.runner.stats, 58 | } 59 | 60 | return report 61 | } 62 | } 63 | 64 | /** 65 | * Save results of benchmarks to a json file. Provided path must point to a 66 | * JSON file. File must at least contain the seed. 67 | */ 68 | const saveReports = (path: string, reports: Report[]): void => { 69 | H.updateJSONFile(path, history => { 70 | history[H.getGitHeadSha()] = reports 71 | return history 72 | }) 73 | } 74 | 75 | export { Report, Benchmark, saveReports, Collect } 76 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 | 5 | > Generate & scaffold type-safe resolvers based on your GraphQL Schema in TypeScript, Flow & Reason 6 | 7 |
8 | 9 | Prisma 10 | 11 | ## Motivation 12 | 13 | Programming in type-safe environments provides a lot of benefits and gives you confidence about your code. `graphqlgen` leverages the strongly typed GraphQL schema with the goal of making your backend type-safe while reducing the need to write boilerplate through code generation. 14 | 15 | #### Supported languages 16 | 17 | - `TypeScript` 18 | - `Flow` 19 | - `Reason` ([coming soon](https://github.com/prisma/graphqlgen/issues/130)) 20 | 21 | ## Install 22 | 23 | You can install the `graphqlgen` CLI with the following command: 24 | 25 | ```bash 26 | npm install -g graphqlgen 27 | ``` 28 | 29 | ## Usage 30 | 31 |
Note: Using graphqlgen in production 32 |
33 | 34 | While `graphqlgen` is ready to be used in production, it's still in active development and there might be breaking changes before it hits 1.0. Most changes will just affect the configuration and generated code layout but not the behaviour of the code itself. 35 | 36 |
37 | 38 | --- 39 | 40 | Once installed, you can invoke the CLI as follows: 41 | 42 | ``` 43 | graphqlgen 44 | ``` 45 | 46 | The invocation of the command depends on a configuration file called `graphqlgen.yml` which **must be located in the directory where `graphqlgen` is invoked**. Here is an example: 47 | 48 | ```yml 49 | language: typescript 50 | 51 | schema: ./src/schema.graphql 52 | context: ./src/types.ts:Context 53 | models: 54 | files: 55 | - ./src/generated/prisma-client/index.ts 56 | 57 | output: ./src/generated/graphqlgen.ts 58 | 59 | resolver-scaffolding: 60 | output: ./src/generated/tmp-resolvers/ 61 | layout: file-per-type 62 | ``` 63 | 64 | ### Community 65 | 66 | Learn more about the awesome Prisma and GraphQL communities, including various _events_, _newsletters_, _podcasts_ and a lot more on the [**Prisma Community**](https://prisma.io/community/) page. 67 | 68 |

Prisma

69 | -------------------------------------------------------------------------------- /packages/graphqlgen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphqlgen", 3 | "version": "0.6.0-rc9", 4 | "description": "Generate resolver types based on a GraphQL Schema", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist", 8 | "README.md" 9 | ], 10 | "bin": { 11 | "graphql-resolver-codegen": "dist/index.js", 12 | "graphqlgen": "dist/index.js", 13 | "gg": "dist/index.js" 14 | }, 15 | "scripts": { 16 | "clean": "rm -rf tests/**/tmp && rm -rf tests/**/generated && rm -rf dist", 17 | "prepublishOnly": "rm -rf example/node_modules && yarn checks && yarn test && yarn build", 18 | "postpublish": "yarn clean", 19 | "benchmarks": "ts-node benchmarks", 20 | "build": "yarn clean && tsc --declaration", 21 | "watch": "tsc --watch", 22 | "check:types": "yarn tsc --noEmit", 23 | "check:lint": "tslint --project tsconfig.json {src,test}/**/*.ts", 24 | "checks": "yarn check:types && yarn check:lint", 25 | "test": "jest", 26 | "test:watch": "jest --watch", 27 | "test:ci": "yarn check:lint && jest --maxWorkers 4", 28 | "gen": "ts-node --files src/index.ts" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/prisma/graphqlgen.git" 33 | }, 34 | "author": "Prisma", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/prisma/graphqlgen/issues" 38 | }, 39 | "homepage": "https://github.com/prisma/graphqlgen#readme", 40 | "dependencies": { 41 | "@babel/parser": "^7.1.5", 42 | "@babel/types": "7.3.0", 43 | "ajv": "^6.5.5", 44 | "camelcase": "5.0.0", 45 | "chalk": "2.4.2", 46 | "glob": "^7.1.3", 47 | "graphql": "^0.13.0 || ^14.0.0", 48 | "graphql-import": "0.7.1", 49 | "graphqlgen-json-schema": "0.6.0-rc8", 50 | "js-yaml": "3.12.1", 51 | "mkdirp": "0.5.1", 52 | "prettier": "1.16.4", 53 | "reason": "3.3.4", 54 | "rimraf": "2.6.3", 55 | "ts-node": "8.0.2", 56 | "typescript": "3.3.3", 57 | "yargs": "12.0.5" 58 | }, 59 | "devDependencies": { 60 | "@types/benchmark": "1.0.31", 61 | "@types/camelcase": "4.1.0", 62 | "@types/graphql": "14.0.5", 63 | "@types/jest": "24.0.5", 64 | "@types/js-yaml": "3.12.0", 65 | "@types/mkdirp": "0.5.2", 66 | "@types/node": "10.12.25", 67 | "@types/prettier": "1.15.2", 68 | "@types/rimraf": "2.0.2", 69 | "@types/yargs": "12.0.8", 70 | "benchmark": "2.1.4", 71 | "flow-bin": "0.86.0", 72 | "graphql-tag": "2.10.1", 73 | "jest": "23.6.0", 74 | "ts-jest": "24.0.0", 75 | "tslint": "5.12.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | - Results can be reviewed in [history.json](https://github.com/prisma/graphqlgen/blob/master/packages/graphqlgen/benchmarks/history.json) 4 | - Run benchmarks within that package via `yarn run benchmarks` 5 | - Save results via `yarn run benchmarks --save` 6 | 7 | ### Folder Structure 8 | 9 | ``` 10 | /benchmarks 11 | history.json <-- file keeping results of past benchmark runs 12 | index.ts <-- benchmark execution entrypoint 13 | /lib/* <-- base tools/types/logic for benchmark system 14 | /integration <-- integration-type benchmarks testing how quickly code-generation runs 15 | index.ts <-- integration-type benchmarks entrypoint (creates & collects benchmarks) 16 | /a <-- integration-type benchmark for a particular set of fixtures 17 | model.ts 18 | schema.graphql 19 | /b/* 20 | /c/* 21 | ``` 22 | 23 | ### Developer Guide 24 | 25 | #### Adding a new integration-type benchmark 26 | 27 | 1. Create a new folder for your benchmark case: 28 | 29 | ``` 30 | /benchmarks 31 | /integration 32 | / 33 | ``` 34 | 35 | 2. Add fixtures containing whatever data case/pattern you're interested in benching: 36 | 37 | ``` 38 | model.ts 39 | schema.graphql 40 | ``` 41 | 42 | #### Adding a new type of benchmark 43 | 44 | 1. Create a new folder for your benchmark type: 45 | 46 | ``` 47 | /benchmarks 48 | / 49 | ``` 50 | 51 | 2. Implement an `index.ts` that exports a `collect` function: 52 | 53 | ``` 54 | /benchmarks 55 | / 56 | index.ts 57 | ``` 58 | 59 | ```ts 60 | import * as Benchmark from '../lib/benchmark' 61 | 62 | const collect: Benchmark.Collect = () => { 63 | // TODO 64 | } 65 | 66 | export { collect } 67 | ``` 68 | 69 | The collect function is responsible for returning benchmarks from your benchmark type to run. Some guidelines to keep in mind: 70 | 71 | 1. Adding new benchmarks to this type should be trivial, therefore, should only require the addition of fixtures and/or benchmark-specific code. For example the benchmark type should be prepared to pick up new folders automatically. 72 | 73 | 2. Support all types of languages supported by graphqlgen 74 | 75 | With your system in place, add benchmarks as needed, in the format your collector dictates: 76 | 77 | ``` 78 | /benchmarks 79 | / 80 | index.ts 81 | ... <-- dictated by your collector 82 | ``` 83 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/introspection/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InnerAndTypeDefinition, 3 | InternalInnerType, 4 | LiteralTypeAnnotation, 5 | UnionTypeAnnotation, 6 | } from './types' 7 | 8 | import { filesToTypesMap } from './index' 9 | 10 | export function buildTypeGetter( 11 | type: InternalInnerType, 12 | filePath: string, 13 | ): () => InnerAndTypeDefinition { 14 | if (type.kind === 'TypeReferenceAnnotation') { 15 | return () => filesToTypesMap[filePath][type.referenceType] 16 | } else { 17 | return () => type 18 | } 19 | } 20 | 21 | export function isFieldDefinitionEnumOrLiteral( 22 | modelFieldType: InnerAndTypeDefinition, 23 | ): boolean { 24 | // If type is: 'value' 25 | if (isLiteralString(modelFieldType)) { 26 | return true 27 | } 28 | 29 | if ( 30 | modelFieldType.kind === 'UnionTypeAnnotation' && 31 | modelFieldType.isEnum() 32 | ) { 33 | return true 34 | } 35 | 36 | // If type is: type X = 'value' 37 | if ( 38 | modelFieldType.kind === 'TypeAliasDefinition' && 39 | isLiteralString(modelFieldType.getType()) 40 | ) { 41 | return true 42 | } 43 | 44 | // If type is: Type X = 'value' | 'value2' 45 | return ( 46 | modelFieldType.kind === 'TypeAliasDefinition' && modelFieldType.isEnum() 47 | ) 48 | } 49 | 50 | export function isLiteralString(type: InnerAndTypeDefinition) { 51 | return type.kind === 'LiteralTypeAnnotation' && type.type === 'string' 52 | } 53 | 54 | export function getEnumValues(type: InnerAndTypeDefinition): string[] { 55 | // If type is: 'value' 56 | if (isLiteralString(type)) { 57 | return [(type as LiteralTypeAnnotation).value as string] 58 | } 59 | 60 | if (type.kind === 'TypeAliasDefinition' && isLiteralString(type.getType())) { 61 | return [(type.getType() as LiteralTypeAnnotation).value as string] 62 | } 63 | 64 | let unionTypes: InnerAndTypeDefinition[] = [] 65 | 66 | if (type.kind === 'TypeAliasDefinition' && type.isEnum()) { 67 | unionTypes = (type.getType() as UnionTypeAnnotation).getTypes() 68 | } else if (type.kind === 'UnionTypeAnnotation' && type.isEnum) { 69 | unionTypes = type.getTypes() 70 | } else { 71 | return [] 72 | } 73 | 74 | return unionTypes.map(unionType => { 75 | return (unionType as LiteralTypeAnnotation).value 76 | }) as string[] 77 | } 78 | 79 | export function isEnumUnion(unionTypes: InnerAndTypeDefinition[]) { 80 | return unionTypes.every(unionType => { 81 | return ( 82 | unionType.kind === 'LiteralTypeAnnotation' && 83 | unionType.isArray === false && 84 | unionType.type === 'string' 85 | ) 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/glob/handle-glob-pattern.test.ts: -------------------------------------------------------------------------------- 1 | import { handleGlobPattern } from '../../src/glob' 2 | 3 | /** 4 | * Test the glob pattern handling of Files[] : Basic 5 | */ 6 | test('Handle Basic Walk', () => { 7 | expect( 8 | handleGlobPattern([ 9 | './tests/glob/mocks/dir-1/*.ts', 10 | { path: 'somepath', defaultName: undefined }, 11 | { path: 'anotherPath', defaultName: undefined }, 12 | ]), 13 | ).toMatchObject([ 14 | './tests/glob/mocks/dir-1/file-11.ts', 15 | './tests/glob/mocks/dir-1/file-12.ts', 16 | { path: 'somepath', defaultName: undefined }, 17 | { path: 'anotherPath', defaultName: undefined }, 18 | ]) 19 | }) 20 | 21 | /** 22 | * Test the glob pattern handling of Files[] : Complex 23 | */ 24 | test('Handle Complex Walk', () => { 25 | expect( 26 | handleGlobPattern([ 27 | { path: './tests/glob/mocks/**/*.ts', defaultName: '{typeName}Node' }, 28 | { path: 'anotherPath', defaultName: undefined }, 29 | ]), 30 | ).toMatchObject([ 31 | { 32 | path: './tests/glob/mocks/dir-1/file-11.ts', 33 | defaultName: '{typeName}Node', 34 | }, 35 | { 36 | path: './tests/glob/mocks/dir-1/file-12.ts', 37 | defaultName: '{typeName}Node', 38 | }, 39 | { 40 | path: './tests/glob/mocks/dir-2/file-21.ts', 41 | defaultName: '{typeName}Node', 42 | }, 43 | { 44 | path: './tests/glob/mocks/dir-2/file-22.ts', 45 | defaultName: '{typeName}Node', 46 | }, 47 | { path: 'anotherPath', defaultName: undefined }, 48 | ]) 49 | }) 50 | 51 | /** 52 | * Test the glob pattern handling of Files[] with duplicates 53 | */ 54 | test('Handle Complex Walk', () => { 55 | expect( 56 | handleGlobPattern([ 57 | { path: './tests/glob/mocks/**/*.ts', defaultName: '{typeName}Node' }, 58 | { 59 | path: './tests/glob/mocks/dir-1/file-11.ts', 60 | defaultName: undefined, 61 | }, 62 | { path: 'anotherPath', defaultName: undefined }, 63 | ]), 64 | ).toMatchObject([ 65 | { 66 | path: './tests/glob/mocks/dir-1/file-11.ts', 67 | defaultName: '{typeName}Node', 68 | }, 69 | { 70 | path: './tests/glob/mocks/dir-1/file-12.ts', 71 | defaultName: '{typeName}Node', 72 | }, 73 | { 74 | path: './tests/glob/mocks/dir-2/file-21.ts', 75 | defaultName: '{typeName}Node', 76 | }, 77 | { 78 | path: './tests/glob/mocks/dir-2/file-22.ts', 79 | defaultName: '{typeName}Node', 80 | }, 81 | { path: './tests/glob/mocks/dir-1/file-11.ts', defaultName: undefined }, 82 | { path: 'anotherPath', defaultName: undefined }, 83 | ]) 84 | }) 85 | -------------------------------------------------------------------------------- /docs/01-configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration: `graphqlgen.yml` 2 | 3 | ### Name 4 | 5 | The configuration file must be called **`graphqlgen.yml`**. 6 | 7 | ### Reference 8 | 9 | - `language`: The target programming language for the generated code. Possible values: `typescript`, `flow`. 10 | - `schema`: The file path pointing to your GraphQL schema file. 11 | - `context`: Points to the definition of the `context` object that's passed through your GraphQL resolver chain. 12 | - `models`: A mapping from types in your GraphQL schema to the models defined in your programming language. Learn more about [_models_](#models). 13 | - `output`: Specifies where the generated type definitions and _default_ resolver implementations should be located. Must point to a **single file**. 14 | - `resolver-scaffolding`: An object with two properties 15 | - `output`: Specifies where the scaffolded resolvers should be located. Must point to a **directory**. 16 | - `layout`: Specifies the [_layout_](#layouts) for the generated files. Possible values: `file-per-type` (more layouts [coming soon](https://github.com/prisma/graphqlgen/issues/106): `single-file`, `file-per-type-classes`, `single-file-classes`). 17 | - `default-resolvers`: A boolean dictating if default resolvers will be generated or not. Defaults to `true`. 18 | - `iresolvers-augmentation`: A boolean dictating if Apollo Server IResolvers type should be augmented so that it is compatible with graphqlgen `Resolvers` type. Defaults to `true`. 19 | - `delegated-parent-resolvers`: A boolean dictating if the resolver signatures generated should include the signature for [DelegatedParentResolver](https://github.com/prisma/graphql-middleware#middleware-fragments)s. Defaults to `false`. 20 | 21 | Whether a property is required or not depends on whether you're doing [Generation](#generation) or [Scaffolding](#scaffolding). 22 | 23 | ### Models 24 | 25 | Models represent domain objects in the target `language: 26 | 27 | - Models are **not necessarily** 1-to-1 mappings to your database structures, **but can be**. 28 | - Models are **not necessarily** the types from your GraphQL schema, **but can be**. 29 | 30 | When starting a new project, it is often the case that models look _very_ similar to to the SDL types in your GraphQL schema. Only as a project grows, it is often useful to decouple the TypeScript representation of an object from the way it's exposed through the API. 31 | 32 | Consider an example where you have a `User` model with a `password` field. The `password` field most likely should not be exposed through the API, but it's still required within your code. In that case, the model differs from the SDL type representation in the GraphQL schema. 33 | 34 | ### Layouts 35 | 36 | There are four layouts that can be applied when scaffolding resolver skeletons: 37 | 38 | - `file-per-type`: Generates one file per SDL type and puts the corresponding resolvers into it. 39 | - `single-file` (coming soon): Generates _all_ resolvers in a single file. 40 | - `file-per-type-classes` (coming soon): Same as `file-per-type` but generates resolvers as classes instead of plain objects. 41 | - `single-file-classes` (coming soon): Same as `single-file` but generates resolvers as classes instead of plain objects. 42 | 43 | See [this](https://github.com/prisma/graphqlgen/issues/106) issue to learn more about the upcoming layouts. 44 | -------------------------------------------------------------------------------- /packages/create-graphqlgen/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as path from 'path' 4 | import * as fs from 'fs' 5 | import * as meow from 'meow' 6 | import * as inquirer from 'inquirer' 7 | import * as Loader from './loader' 8 | import * as Templates from './templates' 9 | 10 | const cli = meow( 11 | ` 12 | create-graphqlgen [dir] 13 | 14 | > Scaffolds the initial files of your project. 15 | 16 | Options: 17 | -t, --template Select a template. (${Templates.templatesNames}) 18 | --no-install Skips dependency installation. 19 | --no-generate Skips model generation. 20 | --force (-f) Overwrites existing files. 21 | `, 22 | { 23 | flags: { 24 | 'no-install': { 25 | type: 'boolean', 26 | default: false, 27 | }, 28 | 'no-generate': { 29 | type: 'boolean', 30 | default: false, 31 | }, 32 | template: { 33 | type: 'string', 34 | alias: 't', 35 | default: false, 36 | }, 37 | force: { 38 | type: 'boolean', 39 | default: false, 40 | alias: 'f', 41 | }, 42 | }, 43 | }, 44 | ) 45 | 46 | const main = async (cli: meow.Result) => { 47 | let template = Templates.defaultTemplate 48 | 49 | if (cli.flags['template']) { 50 | const selectedTemplate = Templates.availableTemplates.find( 51 | t => t.name === cli.flags['template'], 52 | ) 53 | 54 | if (selectedTemplate) { 55 | template = selectedTemplate 56 | } else { 57 | console.log( 58 | `Unknown template. Available templates: ${Templates.templatesNames}`, 59 | ) 60 | return 61 | } 62 | } else { 63 | const res = await inquirer.prompt<{ templateName: string }>([ 64 | { 65 | name: 'templateName', 66 | message: 'Choose a GraphQL server template?', 67 | type: 'list', 68 | choices: Templates.availableTemplates.map(t => ({ 69 | name: `${t.name} (${t.description})`, 70 | value: t.name, 71 | })), 72 | }, 73 | ]) 74 | 75 | template = Templates.availableTemplates.find( 76 | t => t.name === res.templateName, 77 | ) 78 | } 79 | 80 | let [output] = cli.input 81 | 82 | interface PathResponse { 83 | path: string 84 | } 85 | 86 | if (!output) { 87 | const res = await inquirer.prompt([ 88 | { 89 | name: 'path', 90 | message: 'Where should we scaffold graphql server?', 91 | type: 'input', 92 | default: '.', 93 | }, 94 | ]) 95 | 96 | output = res.path 97 | } 98 | 99 | if (fs.existsSync(output)) { 100 | const allowedFiles = ['.git', '.gitignore'] 101 | const conflictingFiles = fs 102 | .readdirSync(output) 103 | .filter(f => !allowedFiles.includes(f)) 104 | 105 | if (conflictingFiles.length > 0 && !cli.flags.force) { 106 | console.log(`Directory ${output} must be empty.`) 107 | return 108 | } 109 | } else { 110 | fs.mkdirSync(output) 111 | } 112 | 113 | Loader.loadGraphQLGenStarter(template, path.resolve(output), { 114 | installDependencies: !cli.flags['no-install'], 115 | generateModels: !cli.flags['no-generate'], 116 | }) 117 | } 118 | 119 | main(cli) 120 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Please read this guide if you're interested in contributing to graphqlgen. 4 | 5 | **We welcome any form of contribution, especially from new members of our community** 💚 6 | 7 | ## Discussions 8 | 9 | **Our community is a safe and friendly environment, where we support and treat each other with respect**. 10 | 11 | We invite you to actively participate in discussions on Github, [the Forum](https://www.prisma.io/forum/) and [Slack](https://slack.prisma.io/). 12 | 13 | You'll see many discussions about usage or design questions, but any topic is welcome. 14 | They are a great foundation to find potential issues, feature requests or documentation improvements. 15 | 16 | ## Design principles 17 | 18 | 1. **Embrace code redundancy**. Opposing to the generally desired DRY principle, in the case of generated code, it's more important to be readable and to provide as much context as possible without the need to navigate a lot through the code first. (This also allows for more helpful auto-completion/intellisense.) 19 | 20 | 2. **Graphqlgen should be as unopiniated as possible**. Every design decisions matter and should be carefully thought. 21 | 22 | ## Issues 23 | 24 | To report a bug, you can use [this template](https://github.com/prisma/graphqlgen/issues/new?template=bug_report.md). 25 | 26 | When you're starting to look into fixing a bug, create a WIP PR that you mention in the original issue. This way, we ensure that everyone interested can share their thoughts, and duplicate work is prevented. 27 | 28 | Adding tests is a great way to help preventing future bugs. 29 | 30 | ## Documentation 31 | 32 | You can either improve existing content or add new resources. If you miss a particular information in [the reference documentation](https://oss.prisma.io/graphqlgen/), feel free to either create an issue or PR. 33 | 34 | ## Features 35 | 36 | To request a new feature, you can use [this template](https://github.com/prisma/graphqlgen/issues/new?template=feature_request.md). 37 | 38 | To contribute features or API changes, it's best to start a discussion in an issue first, ideally with a proposal. This allows everyone to participate and ensures that no potential implementation work is in vain. 39 | 40 | ## Submitting Changes 41 | 42 | After getting some feedback, push to your fork and submit a pull request. We 43 | may suggest some changes or improvements or alternatives, but for small changes 44 | your pull request should be accepted quickly. 45 | 46 | ## Logistics 47 | 48 | Below are a series of steps to help you from a more "practical" perspective. If you need additional help concerning Git, Github has some great guides that you may want to check out! Check them out [here](https://guides.github.com/) 49 | 50 | **Fork the repo and then clone your fork** 51 | 52 | SSH: 53 | 54 | ```sh 55 | git clone git@github.com:YOUR_USERNAME/graphqlgen.git 56 | ``` 57 | 58 | HTTPS: 59 | 60 | ```sh 61 | git clone https://github.com/YOUR_USERNAME/graphqlgen.git 62 | ``` 63 | 64 | **Add the remote upstream** 65 | 66 | ```sh 67 | git remote add upstream git://github.com/prisma/graphqlgen.git 68 | ``` 69 | 70 | **Fetch changes from upstream** 71 | 72 | ```sh 73 | git fetch upstream 74 | ``` 75 | 76 | **Pull changes locally** 77 | 78 | ```sh 79 | git pull upstream master 80 | ``` 81 | 82 | **Push changes up** 83 | 84 | ```sh 85 | git push 86 | ``` 87 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/flow/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { testGeneration } from '../generation' 2 | import { join } from 'path' 3 | 4 | const relative = (p: string) => join(__dirname, p) 5 | const typesPath = relative('./generated-basic/graphqlgen.js') 6 | const resolversDir = relative('./generated-basic/tmp-resolvers/') 7 | const language = 'flow' 8 | 9 | test('basic schema', async () => { 10 | return testGeneration({ 11 | language, 12 | schema: relative('../fixtures/basic/schema.graphql'), 13 | models: { 14 | files: [relative('../fixtures/basic/types-flow.js')], 15 | }, 16 | output: typesPath, 17 | ['resolver-scaffolding']: { 18 | output: resolversDir, 19 | layout: 'file-per-type', 20 | }, 21 | }) 22 | }) 23 | 24 | test('basic enum', async () => { 25 | return testGeneration({ 26 | language, 27 | schema: relative('../fixtures/enum/schema.graphql'), 28 | models: { 29 | files: [relative('../fixtures/enum/types-flow.js')], 30 | }, 31 | output: typesPath, 32 | ['resolver-scaffolding']: { 33 | output: resolversDir, 34 | layout: 'file-per-type', 35 | }, 36 | }) 37 | }) 38 | 39 | test('basic union', async () => { 40 | return testGeneration({ 41 | language, 42 | schema: relative('../fixtures/union/schema.graphql'), 43 | models: { 44 | files: [relative('../fixtures/union/flow-types.js')], 45 | }, 46 | output: typesPath, 47 | ['resolver-scaffolding']: { 48 | output: resolversDir, 49 | layout: 'file-per-type', 50 | }, 51 | }) 52 | }) 53 | 54 | test('defaultName', async () => { 55 | return testGeneration({ 56 | language, 57 | schema: relative('../fixtures/defaultName/schema.graphql'), 58 | models: { 59 | files: [ 60 | { 61 | path: relative('../fixtures/defaultName/flow-types.js'), 62 | defaultName: '${typeName}Node', 63 | }, 64 | ], 65 | }, 66 | output: typesPath, 67 | ['resolver-scaffolding']: { 68 | output: resolversDir, 69 | layout: 'file-per-type', 70 | }, 71 | }) 72 | }) 73 | 74 | test('basic scalar', async () => { 75 | return testGeneration({ 76 | language, 77 | schema: relative('../fixtures/scalar/schema.graphql'), 78 | models: { 79 | files: [relative('../fixtures/scalar/flow-types.js')], 80 | }, 81 | output: typesPath, 82 | ['resolver-scaffolding']: { 83 | output: resolversDir, 84 | layout: 'file-per-type', 85 | }, 86 | }) 87 | }) 88 | 89 | test('context', async () => { 90 | return testGeneration({ 91 | language, 92 | schema: relative('../fixtures/context/schema.graphql'), 93 | context: relative('../fixtures/context/flow-types.js:Context'), 94 | models: { 95 | files: [relative('../fixtures/context/flow-types.js')], 96 | }, 97 | output: typesPath, 98 | ['resolver-scaffolding']: { 99 | output: resolversDir, 100 | layout: 'file-per-type', 101 | }, 102 | }) 103 | }) 104 | 105 | test('subscription', () => { 106 | return testGeneration({ 107 | language, 108 | schema: relative('../fixtures/subscription/schema.graphql'), 109 | models: { 110 | files: [relative('../fixtures/subscription/flow-types.js')], 111 | }, 112 | output: typesPath, 113 | ['resolver-scaffolding']: { 114 | output: resolversDir, 115 | layout: 'file-per-type', 116 | }, 117 | }) 118 | }) 119 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/introspection/factory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InternalInnerType, 3 | TypeAliasDefinition, 4 | FieldDefinition, 5 | InterfaceDefinition, 6 | Scalar, 7 | ScalarTypeAnnotation, 8 | UnionTypeAnnotation, 9 | AnonymousInterfaceAnnotation, 10 | LiteralTypeAnnotation, 11 | TypeReferenceAnnotation, 12 | UnknownType, 13 | } from './types' 14 | import { buildTypeGetter, isEnumUnion } from './utils' 15 | import { filesToTypesMap } from './index' 16 | 17 | export function createTypeAlias( 18 | name: string, 19 | type: InternalInnerType, 20 | filePath: string, 21 | ): TypeAliasDefinition { 22 | return { 23 | kind: 'TypeAliasDefinition', 24 | name, 25 | getType: buildTypeGetter(type, filePath), 26 | isEnum: () => { 27 | return type.kind === 'UnionTypeAnnotation' && isEnumUnion(type.getTypes()) 28 | }, 29 | } 30 | } 31 | export function createInterfaceField( 32 | name: string, 33 | type: InternalInnerType, 34 | filePath: string, 35 | optional: boolean, 36 | ): FieldDefinition { 37 | return { 38 | name, 39 | getType: buildTypeGetter(type, filePath), 40 | optional, 41 | } 42 | } 43 | export function createInterface( 44 | name: string, 45 | fields: FieldDefinition[], 46 | ): InterfaceDefinition { 47 | return { 48 | kind: 'InterfaceDefinition', 49 | name, 50 | fields, 51 | } 52 | } 53 | 54 | interface TypeAnnotationOpts { 55 | isArray?: boolean 56 | isTypeRef?: boolean 57 | isAny?: boolean 58 | } 59 | export function createTypeAnnotation( 60 | type: Scalar | UnknownType, 61 | options?: TypeAnnotationOpts, 62 | ): ScalarTypeAnnotation { 63 | let opts: TypeAnnotationOpts = {} 64 | if (options === undefined) { 65 | opts = { isArray: false, isTypeRef: false, isAny: false } 66 | } else { 67 | opts = { 68 | isArray: options.isArray === undefined ? false : options.isArray, 69 | isAny: options.isAny === undefined ? false : options.isAny, 70 | } 71 | } 72 | 73 | const isArray = opts.isArray === undefined ? false : opts.isArray 74 | 75 | return { 76 | kind: 'ScalarTypeAnnotation', 77 | type, 78 | isArray, 79 | } 80 | } 81 | export function createUnionTypeAnnotation( 82 | types: InternalInnerType[], 83 | filePath: string, 84 | ): UnionTypeAnnotation { 85 | const getTypes = () => { 86 | return types.map(unionType => { 87 | return unionType.kind === 'TypeReferenceAnnotation' 88 | ? filesToTypesMap[filePath][unionType.referenceType] 89 | : unionType 90 | }) 91 | } 92 | 93 | return { 94 | kind: 'UnionTypeAnnotation', 95 | getTypes, 96 | isArray: false, 97 | isEnum: () => isEnumUnion(getTypes()), 98 | } 99 | } 100 | export function createAnonymousInterfaceAnnotation( 101 | fields: FieldDefinition[], 102 | isArray: boolean = false, 103 | ): AnonymousInterfaceAnnotation { 104 | return { 105 | kind: 'AnonymousInterfaceAnnotation', 106 | fields, 107 | isArray, 108 | } 109 | } 110 | export function createLiteralTypeAnnotation( 111 | type: string, 112 | value: string | number | boolean, 113 | isArray: boolean = false, 114 | ): LiteralTypeAnnotation { 115 | return { 116 | kind: 'LiteralTypeAnnotation', 117 | type, 118 | value, 119 | isArray, 120 | } 121 | } 122 | export function createTypeReferenceAnnotation( 123 | referenceType: string, 124 | ): TypeReferenceAnnotation { 125 | return { kind: 'TypeReferenceAnnotation', referenceType } 126 | } 127 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/validation/validateModels.test.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import { validateModels } from '../../src/validation' 3 | import { parseSchema } from '../../src/parse' 4 | import { Models } from 'graphqlgen-json-schema' 5 | 6 | interface TestConfig { 7 | schema: string 8 | models: Models 9 | } 10 | 11 | const relative = (p: string) => join(__dirname, p) 12 | const language = 'typescript' 13 | 14 | function testValidateModels(config: TestConfig, expectedResult: boolean) { 15 | const schema = parseSchema(config.schema) 16 | console.log = jest.fn() 17 | expect(validateModels(config.models, schema, language)).toBe(expectedResult) 18 | if (!expectedResult) { 19 | expect(console.log).toHaveBeenCalled() 20 | } 21 | } 22 | 23 | describe('test validateModels()', () => { 24 | test('missing models', () => { 25 | testValidateModels( 26 | { 27 | schema: relative('./mocks/missingModels/schema.graphql'), 28 | models: { 29 | files: [relative('./mocks/missingModels/index.ts')], 30 | }, 31 | }, 32 | false, 33 | ) 34 | }) 35 | 36 | test('invalid files', () => { 37 | testValidateModels( 38 | { 39 | schema: relative('./mocks/missingModels/schema.graphql'), 40 | models: { 41 | files: [relative('./mocks/missingModels/typesB.ts')], 42 | }, 43 | }, 44 | false, 45 | ) 46 | }) 47 | 48 | test('evaluate overriden model', () => { 49 | testValidateModels( 50 | { 51 | schema: relative('./mocks/overridenModel/schema.graphql'), 52 | models: { 53 | files: [relative('./mocks/overridenModel/types.ts')], 54 | override: { 55 | Post: relative('./mocks/overridenModel/model.ts:PostModel'), 56 | }, 57 | }, 58 | }, 59 | true, 60 | ) 61 | }) 62 | 63 | test('invalid overriden models', () => { 64 | testValidateModels( 65 | { 66 | schema: relative('./mocks/overridenModel/schema.graphql'), 67 | models: { 68 | files: [relative('./mocks/overridenModel/types.ts')], 69 | override: { 70 | Post: relative('./mocks/overridenModel/model.ts:Post'), 71 | }, 72 | }, 73 | }, 74 | false, 75 | ) 76 | }) 77 | 78 | test('import schema ts default', () => { 79 | testValidateModels( 80 | { 81 | schema: relative('./mocks/tsSchemaDefault/schema.ts'), 82 | models: { 83 | files: [relative('./mocks/tsSchemaDefault/types.ts')], 84 | override: { 85 | Post: relative('./mocks/tsSchemaDefault/model.ts:PostModel'), 86 | }, 87 | }, 88 | }, 89 | true, 90 | ) 91 | }) 92 | 93 | test('import schema ts const', () => { 94 | testValidateModels( 95 | { 96 | schema: relative('./mocks/tsSchemaConst/schema.ts:typeDefs'), 97 | models: { 98 | files: [relative('./mocks/tsSchemaConst/types.ts')], 99 | override: { 100 | Post: relative('./mocks/tsSchemaConst/model.ts:PostModel'), 101 | }, 102 | }, 103 | }, 104 | true, 105 | ) 106 | }) 107 | 108 | test('import schema gql default', () => { 109 | testValidateModels( 110 | { 111 | schema: relative('./mocks/gqlSchema/schema.ts:typeDefs'), 112 | models: { 113 | files: [relative('./mocks/gqlSchema/types.ts')], 114 | override: { 115 | Post: relative('./mocks/gqlSchema/model.ts:PostModel'), 116 | }, 117 | }, 118 | }, 119 | true, 120 | ) 121 | }) 122 | }) 123 | -------------------------------------------------------------------------------- /docs/03-scaffolding-resolvers.md: -------------------------------------------------------------------------------- 1 | ## Scaffolding Resolvers 2 | 3 | > **IMPORTANT**: Scaffolded resolvers are typically generated into a _temporary_ directory and manually copied over into your actual source files. After the generated resolver skeletons have been copied over, the generated files can be deleted. 4 | 5 | This feature increases your productivity by generating the boilerplate resolver skeletons for those fields that are not [default resolvers](#default-resolvers). A great example for this are the resolvers for your [_root types_](https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e/): `Query`, `Mutation` and `Subscription`. 6 | 7 | For fields on these types, the resolver implementation needs to call out to some data source (e.g. a database, a REST API or a Prisma service) and therefore can not be automatically generated by `graphqlgen`. However, `graphqlgen` is able to reduce the amount of boilerplate you need to write by generating resolver "skeletons". 8 | 9 | Consider the following `Query` type: 10 | 11 | ```graphql 12 | type Query { 13 | user(id: ID!): User 14 | } 15 | ``` 16 | 17 | The resolver skeleton for the `user` field will look similar to this: 18 | 19 | ```ts 20 | export const Query: QueryResolvers.Type = { 21 | user: (parent, args) => null, 22 | } 23 | ``` 24 | 25 | With that boilerplate in place, all that's left to do for the developer is implement fetching the requested `User` object from some data source (guided by the generated typings for resolver arguments and return values). 26 | 27 | The relevant properties from `graphqlgen.yml` for the Scaffolding feature are: 28 | 29 | - `language` (required) 30 | - `schema` (required) 31 | - `models` (required) 32 | - `context` (optional) 33 | - `output` (required) 34 | - `resolver-scaffolding` (required) 35 | 36 | ### Example 37 | 38 | #### Setup 39 | 40 | Assume you have the following minimal setup with three files: 41 | 42 | **`./src/schema.graphql`** 43 | 44 | ```graphql 45 | type Query { 46 | user(id: ID!): User 47 | } 48 | 49 | type Mutation { 50 | createUser(name: String): User! 51 | } 52 | 53 | type User { 54 | id: ID! 55 | name: String 56 | } 57 | ``` 58 | 59 | **`./src/models.ts`** 60 | 61 | ```ts 62 | export interface User { 63 | id: string 64 | name: string | null 65 | password: string 66 | } 67 | ``` 68 | 69 | **`./graphqlgen.yml`** 70 | 71 | ```yml 72 | language: typescript 73 | schema: ./src/schema.graphql 74 | models: 75 | files: 76 | - ./src/models.ts 77 | output: ./src/generated/graphqlgen.ts 78 | resolver-scaffolding: 79 | output: ./src/tmp/ 80 | layout: file-per-type 81 | ``` 82 | 83 | #### Generated code 84 | 85 | After running `$ graphqlgen` in your terminal, the following code will be generated into **`./src/tmp/`**: 86 | 87 | **`./tmp/User.ts`** 88 | 89 | ```ts 90 | import { UserResolvers } from '../generated/graphqlgen' 91 | 92 | export const User: UserResolvers.Type = { 93 | ...UserResolvers.defaultResolvers, 94 | } 95 | ``` 96 | 97 | **`./tmp/Query.ts`** 98 | 99 | ```ts 100 | import { QueryResolvers } from '../generated/graphqlgen' 101 | 102 | export const Query: QueryResolvers.Type = { 103 | ...QueryResolvers.defaultResolvers, 104 | user: (parent, args) => null, 105 | } 106 | ``` 107 | 108 | **`./tmp/Mutation.ts`** 109 | 110 | ```ts 111 | import { MutationResolvers } from '../generated/graphqlgen' 112 | 113 | export const Mutation: MutationResolvers.Type = { 114 | ...MutationResolvers.defaultResolvers, 115 | createUser: (parent, args) => { 116 | throw new Error('Resolver not implemented') 117 | }, 118 | } 119 | ``` 120 | 121 | **`./tmp/index.ts`** 122 | 123 | ```ts 124 | import { Resolvers } from '../generated/graphqlgen' 125 | 126 | import { Query } from './Query' 127 | import { Mutation } from './Mutation' 128 | import { User } from './User' 129 | 130 | export const resolvers: Resolvers = { 131 | Query, 132 | Mutation, 133 | User, 134 | } 135 | ``` 136 | 137 | Note the following: 138 | 139 | - The paths in the `import` statements will likely need to be adjusted depending on your file structure. 140 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/generators/reason/generator.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os' 2 | import * as camelCase from 'camelcase' 3 | import * as refmt from 'reason' 4 | import { GraphQLScalarType, GraphQLTypeField } from '../../source-helper' 5 | 6 | import { GenerateArgs } from '../../types' 7 | import { upperFirst } from '../../utils' 8 | 9 | type SpecificGraphQLScalarType = 10 | | 'bool' 11 | | 'int' 12 | | 'float' 13 | | 'string' 14 | | 'nonScalar' 15 | 16 | function getTypeFromGraphQLType( 17 | type: GraphQLScalarType, 18 | ): SpecificGraphQLScalarType { 19 | if (type === 'Int') { 20 | return 'int' 21 | } 22 | if (type === 'Float') { 23 | return 'float' 24 | } 25 | if (type === 'Boolean') { 26 | return 'bool' 27 | } 28 | if (type === 'String' || type === 'ID' || type === 'DateTime') { 29 | return 'string' 30 | } 31 | return 'nonScalar' 32 | } 33 | 34 | export function format(code: string) { 35 | try { 36 | return refmt.printRE(refmt.parseRE(code)) 37 | } catch (e) { 38 | console.log( 39 | `There is a syntax error in generated code, unformatted code printed, error: ${JSON.stringify( 40 | e, 41 | )}`, 42 | ) 43 | return code 44 | } 45 | } 46 | 47 | function printFieldLikeType(field: GraphQLTypeField) { 48 | if ( 49 | getTypeFromGraphQLType(field.type.name as GraphQLScalarType) !== 'nonScalar' 50 | ) { 51 | return `${getTypeFromGraphQLType(field.type.name as GraphQLScalarType)},` 52 | } 53 | 54 | if (field.type.isArray) { 55 | return `Js.Array.t(Data.${camelCase(field.type.name)}),` 56 | } 57 | 58 | return `Data.${camelCase(field.type.name)},` 59 | } 60 | 61 | export function generate(args: GenerateArgs) { 62 | console.log(`Reason binding is experimental`) 63 | return ` 64 | module Data = { 65 | ${args.types 66 | .map( 67 | type => ` 68 | type ${camelCase(type.name)} = { 69 | . 70 | ${type.fields 71 | .filter( 72 | field => 73 | getTypeFromGraphQLType(field.type.name as GraphQLScalarType) !== 74 | 'nonScalar', 75 | ) 76 | .map( 77 | field => ` 78 | "${field.name}": ${printFieldLikeType(field)} 79 | `, 80 | ) 81 | .join(os.EOL)} 82 | } 83 | `, 84 | ) 85 | .join(os.EOL)} 86 | 87 | ${args.unions 88 | .map( 89 | union => ` 90 | type ${camelCase(union.name)} = 91 | ${union.types.map(t => `| ${t.name}`).join(os.EOL)} 92 | `, 93 | ) 94 | .join(os.EOL)} 95 | 96 | ${args.enums 97 | .map( 98 | e => ` 99 | type ${camelCase(e.name)} = 100 | ${e.values.map(v => `| ${v}`).join(os.EOL)} 101 | `, 102 | ) 103 | .join(os.EOL)} 104 | }; 105 | 106 | 107 | ${args.types 108 | .map( 109 | type => ` 110 | module ${upperFirst(type.name)} = { 111 | 112 | ${type.fields 113 | .filter(field => field.arguments.length > 0) 114 | .map( 115 | field => ` 116 | type ${field.name}Argument = { 117 | . 118 | ${field.arguments 119 | .map( 120 | arg => ` 121 | "${arg.name}": ${printFieldLikeType(field)} 122 | `, 123 | ) 124 | .join(os.EOL)} 125 | } 126 | `, 127 | ) 128 | .join(os.EOL)} 129 | 130 | type parent; 131 | type args; 132 | type context; 133 | type info; 134 | 135 | type resolvers = { 136 | . 137 | ${type.fields 138 | .filter( 139 | field => 140 | getTypeFromGraphQLType(field.type.name as GraphQLScalarType) === 141 | 'nonScalar', 142 | ) 143 | .map( 144 | field => ` 145 | "${field.name}": (parent, ${ 146 | field.arguments.length > 0 ? `${field.name}Argument` : `args` 147 | }, context, info) => ${printFieldLikeType(field)} 148 | `, 149 | ) 150 | .join(os.EOL)} 151 | } 152 | } 153 | `, 154 | ) 155 | .join(os.EOL)} 156 | 157 | ` 158 | } 159 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/micro/index.ts: -------------------------------------------------------------------------------- 1 | import * as Core from '../../src/generators/common' 2 | import * as Benchmark from '../lib/benchmark' 3 | 4 | const type = { 5 | name: 'Z', 6 | implements: null, 7 | type: { 8 | name: 'Z', 9 | isInput: false, 10 | isEnum: false, 11 | isInterface: false, 12 | isObject: true, 13 | isScalar: false, 14 | isUnion: false, 15 | }, 16 | fields: [], 17 | } 18 | 19 | const typeMap: Core.InputTypesMap = { 20 | A: { 21 | name: 'A', 22 | implements: null, 23 | type: { 24 | name: 'A', 25 | isInput: true, 26 | isEnum: false, 27 | isInterface: false, 28 | isObject: false, 29 | isScalar: false, 30 | isUnion: false, 31 | }, 32 | fields: [ 33 | { 34 | name: 'b', 35 | arguments: [], 36 | type: { 37 | name: 'B', 38 | isInput: true, 39 | isEnum: false, 40 | isInterface: false, 41 | isObject: false, 42 | isScalar: false, 43 | isUnion: false, 44 | isRequired: false, 45 | isArray: false, 46 | isArrayRequired: false, 47 | }, 48 | }, 49 | { 50 | name: 'c', 51 | arguments: [], 52 | type: { 53 | name: 'C', 54 | isInput: true, 55 | isEnum: false, 56 | isInterface: false, 57 | isObject: false, 58 | isScalar: false, 59 | isUnion: false, 60 | isRequired: false, 61 | isArray: false, 62 | isArrayRequired: false, 63 | }, 64 | }, 65 | ], 66 | }, 67 | B: { 68 | name: 'B', 69 | implements: null, 70 | type: { 71 | name: 'B', 72 | isInput: true, 73 | isEnum: false, 74 | isInterface: false, 75 | isObject: false, 76 | isScalar: false, 77 | isUnion: false, 78 | }, 79 | fields: [ 80 | { 81 | name: 'd', 82 | arguments: [], 83 | type: { 84 | name: 'D', 85 | isInput: true, 86 | isEnum: false, 87 | isInterface: false, 88 | isObject: false, 89 | isScalar: false, 90 | isUnion: false, 91 | isRequired: false, 92 | isArray: false, 93 | isArrayRequired: false, 94 | }, 95 | }, 96 | ], 97 | }, 98 | C: { 99 | name: 'C', 100 | implements: null, 101 | type: { 102 | name: 'C', 103 | isInput: true, 104 | isEnum: false, 105 | isInterface: false, 106 | isObject: false, 107 | isScalar: false, 108 | isUnion: false, 109 | }, 110 | fields: [ 111 | { 112 | name: 'd', 113 | arguments: [], 114 | type: { 115 | name: 'D', 116 | isInput: true, 117 | isEnum: false, 118 | isInterface: false, 119 | isObject: false, 120 | isScalar: false, 121 | isUnion: false, 122 | isRequired: false, 123 | isArray: false, 124 | isArrayRequired: false, 125 | }, 126 | }, 127 | ], 128 | }, 129 | D: { 130 | name: 'D', 131 | implements: null, 132 | type: { 133 | name: 'D', 134 | isInput: true, 135 | isEnum: false, 136 | isInterface: false, 137 | isObject: false, 138 | isScalar: false, 139 | isUnion: false, 140 | }, 141 | fields: [ 142 | { 143 | name: 'foo', 144 | arguments: [], 145 | type: { 146 | name: 'String', 147 | isInput: false, 148 | isEnum: false, 149 | isInterface: false, 150 | isObject: false, 151 | isScalar: true, 152 | isUnion: false, 153 | isRequired: false, 154 | isArray: false, 155 | isArrayRequired: false, 156 | }, 157 | }, 158 | ], 159 | }, 160 | } 161 | 162 | const benchGetDistinctInputTypes = new Benchmark.Benchmark({ 163 | name: 'getDistinctInputTypes', 164 | test: () => { 165 | Core.getDistinctInputTypes(type, { Z: ['A'] }, typeMap) 166 | }, 167 | }) 168 | 169 | const collect: Benchmark.Collect = () => { 170 | return [benchGetDistinctInputTypes] 171 | } 172 | 173 | export { collect } 174 | -------------------------------------------------------------------------------- /packages/create-graphqlgen/src/loader.ts: -------------------------------------------------------------------------------- 1 | import * as tar from 'tar' 2 | import * as tmp from 'tmp' 3 | import * as github from 'parse-github-url' 4 | import * as fs from 'fs' 5 | import * as ora from 'ora' 6 | import * as request from 'request' 7 | import * as execa from 'execa' 8 | import chalk from 'chalk' 9 | import { Template } from './templates' 10 | 11 | interface LoadOptions { 12 | installDependencies: boolean 13 | generateModels: boolean 14 | } 15 | 16 | const loadGraphQLGenStarter = async ( 17 | template: Template, 18 | output: string, 19 | options: LoadOptions, 20 | ): Promise => { 21 | const tar = getGraphQLGenTemplateRepositoryTarInformation(template) 22 | const tmp = await downloadRepository(tar) 23 | 24 | await extractGraphQLGenStarterFromRepository(tmp, tar, output) 25 | 26 | if (options.installDependencies) { 27 | await installGraphQLGenStarter(output) 28 | } 29 | 30 | if (options.generateModels) { 31 | await generateGraphQLGenStarterModels(output) 32 | } 33 | 34 | printHelpMessage() 35 | } 36 | 37 | interface TemplateRepositoryTarInformation { 38 | uri: string 39 | files: string 40 | } 41 | 42 | const getGraphQLGenTemplateRepositoryTarInformation = ( 43 | template: Template, 44 | ): TemplateRepositoryTarInformation => { 45 | const meta = github(template.repo.uri) 46 | 47 | const uri = [ 48 | `https://api.github.com/repos`, 49 | meta.repo, 50 | 'tarball', 51 | template.repo.branch, 52 | ].join('/') 53 | 54 | return { uri, files: template.repo.path } 55 | } 56 | 57 | const downloadRepository = async ( 58 | tar: TemplateRepositoryTarInformation, 59 | ): Promise => { 60 | const spinner = ora(`Downloading starter from ${chalk.cyan(tar.uri)}`).start() 61 | 62 | const tmpPath = tmp.fileSync({ 63 | postfix: '.tar.gz', 64 | }) 65 | 66 | await new Promise(resolve => { 67 | request(tar.uri, { 68 | headers: { 69 | 'User-Agent': 'prisma/create-graphqlgen', 70 | }, 71 | }) 72 | .pipe(fs.createWriteStream(tmpPath.name)) 73 | .on('close', resolve) 74 | }) 75 | 76 | spinner.succeed() 77 | 78 | return tmpPath.name 79 | } 80 | 81 | const extractGraphQLGenStarterFromRepository = async ( 82 | tmp: string, 83 | repo: TemplateRepositoryTarInformation, 84 | output: string, 85 | ): Promise => { 86 | const spinner = ora(`Extracting content to ${chalk.cyan(output)}`) 87 | 88 | await tar.extract({ 89 | file: tmp, 90 | cwd: output, 91 | filter: path => RegExp(repo.files).test(path), 92 | strip: repo.files.split('/').length, 93 | }) 94 | 95 | spinner.succeed() 96 | 97 | return 98 | } 99 | 100 | const installGraphQLGenStarter = async (path: string): Promise => { 101 | const spinner = ora(`Installing dependencies 👩‍🚀`).start() 102 | 103 | process.chdir(path) 104 | 105 | try { 106 | if (await isYarnInstalled()) { 107 | await execa.shellSync('yarnpkg install', { stdio: `ignore` }) 108 | } else { 109 | await execa.shellSync('npm install', { stdio: `ignore` }) 110 | } 111 | 112 | spinner.succeed() 113 | } catch (err) { 114 | spinner.fail() 115 | } 116 | } 117 | 118 | const generateGraphQLGenStarterModels = async (path: string): Promise => { 119 | const spinner = ora(`Generating models 👷‍`).start() 120 | 121 | process.chdir(path) 122 | 123 | try { 124 | if (await isYarnInstalled()) { 125 | await execa.shellSync('yarn generate', { stdio: `ignore` }) 126 | } else { 127 | await execa.shellSync('npm run generate', { stdio: `ignore` }) 128 | } 129 | 130 | spinner.succeed() 131 | } catch (err) { 132 | spinner.fail() 133 | } 134 | } 135 | 136 | const isYarnInstalled = async (): Promise => { 137 | try { 138 | await execa.shell(`yarnpkg --version`, { stdio: `ignore` }) 139 | return true 140 | } catch (err) { 141 | return false 142 | } 143 | } 144 | 145 | const printHelpMessage = (): void => { 146 | const message = ` 147 | Your GraphQL server has been successfully set up! 148 | 149 | Try running the following commands: 150 | - ${chalk.yellow(`yarn start`)} 151 | Starts the GraphQL server. 152 | 153 | - ${chalk.greenBright(`yarn generate`)} 154 | Generates type safe interfaces from your schema. 155 | ` 156 | 157 | console.log(message) 158 | } 159 | 160 | export { LoadOptions, loadGraphQLGenStarter } 161 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/generators/common.spec.ts: -------------------------------------------------------------------------------- 1 | import * as Source from '../source-helper' 2 | import * as Common from './common' 3 | 4 | it('getDistinctInputTypes', () => { 5 | const Z: Source.GraphQLTypeObject = { 6 | name: 'Z', 7 | type: { 8 | name: 'Z', 9 | isInput: false, 10 | isEnum: false, 11 | isInterface: false, 12 | isObject: true, 13 | isScalar: false, 14 | isUnion: false, 15 | }, 16 | fields: [], 17 | implements: null, 18 | } 19 | 20 | const typeMap: Common.InputTypesMap = { 21 | A: { 22 | name: 'A', 23 | implements: null, 24 | type: { 25 | name: 'A', 26 | isInput: true, 27 | isEnum: false, 28 | isInterface: false, 29 | isObject: false, 30 | isScalar: false, 31 | isUnion: false, 32 | }, 33 | fields: [ 34 | { 35 | name: 'b', 36 | arguments: [], 37 | type: { 38 | name: 'B', 39 | isInput: true, 40 | isEnum: false, 41 | isInterface: false, 42 | isObject: false, 43 | isScalar: false, 44 | isUnion: false, 45 | isRequired: false, 46 | isArray: false, 47 | isArrayRequired: false, 48 | }, 49 | }, 50 | { 51 | name: 'c', 52 | arguments: [], 53 | type: { 54 | name: 'C', 55 | isInput: true, 56 | isEnum: false, 57 | isInterface: false, 58 | isObject: false, 59 | isScalar: false, 60 | isUnion: false, 61 | isRequired: false, 62 | isArray: false, 63 | isArrayRequired: false, 64 | }, 65 | }, 66 | ], 67 | }, 68 | B: { 69 | name: 'B', 70 | implements: null, 71 | type: { 72 | name: 'B', 73 | isInput: true, 74 | isEnum: false, 75 | isInterface: false, 76 | isObject: false, 77 | isScalar: false, 78 | isUnion: false, 79 | }, 80 | fields: [ 81 | { 82 | name: 'd', 83 | arguments: [], 84 | type: { 85 | name: 'D', 86 | isInput: true, 87 | isEnum: false, 88 | isInterface: false, 89 | isObject: false, 90 | isScalar: false, 91 | isUnion: false, 92 | isRequired: false, 93 | isArray: false, 94 | isArrayRequired: false, 95 | }, 96 | }, 97 | ], 98 | }, 99 | C: { 100 | name: 'C', 101 | implements: null, 102 | type: { 103 | name: 'C', 104 | isInput: true, 105 | isEnum: false, 106 | isInterface: false, 107 | isObject: false, 108 | isScalar: false, 109 | isUnion: false, 110 | }, 111 | fields: [ 112 | { 113 | name: 'd', 114 | arguments: [], 115 | type: { 116 | name: 'D', 117 | isInput: true, 118 | isEnum: false, 119 | isInterface: false, 120 | isObject: false, 121 | isScalar: false, 122 | isUnion: false, 123 | isRequired: false, 124 | isArray: false, 125 | isArrayRequired: false, 126 | }, 127 | }, 128 | ], 129 | }, 130 | D: { 131 | name: 'D', 132 | implements: null, 133 | type: { 134 | name: 'D', 135 | isInput: true, 136 | isEnum: false, 137 | isInterface: false, 138 | isObject: false, 139 | isScalar: false, 140 | isUnion: false, 141 | }, 142 | fields: [ 143 | { 144 | name: 'foo', 145 | arguments: [], 146 | type: { 147 | name: 'String', 148 | isInput: false, 149 | isEnum: false, 150 | isInterface: false, 151 | isObject: false, 152 | isScalar: true, 153 | isUnion: false, 154 | isRequired: false, 155 | isArray: false, 156 | isArrayRequired: false, 157 | }, 158 | }, 159 | ], 160 | }, 161 | } 162 | 163 | expect(Common.getDistinctInputTypes(Z, { Z: ['A'] }, typeMap)) 164 | .toMatchInlineSnapshot(` 165 | Array [ 166 | "A", 167 | "B", 168 | "C", 169 | "D", 170 | ] 171 | `) 172 | }) 173 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/generators/flow/scaffolder.ts: -------------------------------------------------------------------------------- 1 | import { GenerateArgs, CodeFileLike } from '../../types' 2 | import { upperFirst } from '../../utils' 3 | import { 4 | GraphQLTypeObject, 5 | GraphQLInterfaceObject, 6 | GraphQLUnionObject, 7 | } from '../../source-helper' 8 | import { 9 | fieldsFromModelDefinition, 10 | shouldScaffoldFieldResolver, 11 | isParentType, 12 | } from '../common' 13 | 14 | export { format } from './generator' 15 | 16 | function renderParentResolvers(type: GraphQLTypeObject): CodeFileLike { 17 | const upperTypeName = upperFirst(type.name) 18 | const code = `/* @flow */ 19 | import type { ${upperTypeName}_Resolvers } from '[TEMPLATE-INTERFACES-PATH]' 20 | 21 | export const ${type.name}: ${upperTypeName}_Resolvers = { 22 | ${type.fields.map(field => { 23 | if (type.name === 'Subscription') { 24 | return `${field.name}: { 25 | subscribe: (parent, args, ctx, info) => { 26 | throw new Error('Resolver not implemented') 27 | } 28 | }` 29 | } 30 | 31 | return `${field.name}: (parent, args, ctx, info) => { 32 | throw new Error('Resolver not implemented') 33 | }` 34 | })} 35 | } 36 | ` 37 | return { 38 | path: `${type.name}.js`, 39 | force: false, 40 | code, 41 | } 42 | } 43 | function renderExports(types: GraphQLTypeObject[]): string { 44 | return `\ 45 | // @flow 46 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 47 | // Please do not import this file directly but copy & paste to your application code. 48 | 49 | import type { Resolvers } from '[TEMPLATE-INTERFACES-PATH]' 50 | ${types 51 | .filter(type => type.type.isObject) 52 | .map( 53 | type => ` 54 | import { ${type.name} } from './${type.name}' 55 | `, 56 | ) 57 | .join(';')} 58 | 59 | export const resolvers: Resolvers = { 60 | ${types 61 | .filter(type => type.type.isObject) 62 | .map(type => `${type.name}`) 63 | .join(',')} 64 | }` 65 | } 66 | 67 | function renderPolyResolvers( 68 | type: GraphQLInterfaceObject | GraphQLUnionObject, 69 | ): CodeFileLike { 70 | const upperTypeName = upperFirst(type.name) 71 | 72 | const code = `\ 73 | // @flow 74 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 75 | // Please do not import this file directly but copy & paste to your application code. 76 | 77 | import { ${upperTypeName}_Resolvers } from '[TEMPLATE-INTERFACES-PATH]' 78 | 79 | export const ${type.name}: ${upperTypeName}_Resolvers = { 80 | __resolveType: (parent, ctx, info) => { 81 | throw new Error('Resolver not implemented') 82 | } 83 | }` 84 | return { path: `${type.name}.ts`, force: false, code } 85 | } 86 | 87 | function renderResolvers( 88 | type: GraphQLTypeObject, 89 | args: GenerateArgs, 90 | ): CodeFileLike { 91 | const model = args.modelMap[type.name] 92 | const modelFields = fieldsFromModelDefinition(model.definition) 93 | const upperTypeName = upperFirst(type.name) 94 | const code = `/* @flow */ 95 | ${ 96 | args.defaultResolversEnabled 97 | ? `import { ${upperTypeName}_defaultResolvers } from '[TEMPLATE-INTERFACES-PATH]'` 98 | : '' 99 | } 100 | import type { ${upperTypeName}_Resolvers } from '[TEMPLATE-INTERFACES-PATH]' 101 | 102 | export const ${type.name}: ${upperTypeName}_Resolvers = { 103 | ${args.defaultResolversEnabled ? `...${upperTypeName}_defaultResolvers,` : ''} 104 | ${type.fields 105 | .filter(graphQLField => 106 | shouldScaffoldFieldResolver(graphQLField, modelFields, args), 107 | ) 108 | .map( 109 | field => ` 110 | ${field.name}: (parent, args, ctx, info) => { 111 | throw new Error('Resolver not implemented') 112 | } 113 | `, 114 | )} 115 | } 116 | ` 117 | return { 118 | path: `${type.name}.js`, 119 | force: false, 120 | code, 121 | } 122 | } 123 | 124 | export function generate(args: GenerateArgs): CodeFileLike[] { 125 | let files: CodeFileLike[] = args.types 126 | .filter(type => type.type.isObject) 127 | .filter(type => !isParentType(type.name)) 128 | .map(type => renderResolvers(type, args)) 129 | 130 | files = files.concat( 131 | args.interfaces.map(type => renderPolyResolvers(type)), 132 | args.unions.map(type => renderPolyResolvers(type)), 133 | ) 134 | 135 | files = files.concat( 136 | args.types 137 | .filter(type => isParentType(type.name)) 138 | .map(renderParentResolvers), 139 | ) 140 | 141 | files.push({ 142 | path: 'index.js', 143 | force: false, 144 | code: renderExports(args.types), 145 | }) 146 | 147 | return files 148 | } 149 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/project-output.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path' 2 | import * as FS from 'fs' 3 | import * as mkdirp from 'mkdirp' 4 | import { GraphQLGenDefinition } from 'graphqlgen-json-schema' 5 | import chalk from 'chalk' 6 | import { CodeFileLike } from './types' 7 | import { 8 | getImportPathRelativeToOutput, 9 | getAbsoluteFilePath, 10 | } from './path-helpers' 11 | import { replaceAll } from './utils' 12 | 13 | function writeChangesOnly(filename: string, content: string) { 14 | if ( 15 | !FS.existsSync(filename) || 16 | FS.readFileSync(filename, { encoding: 'utf-8' }) !== content 17 | ) { 18 | console.log(chalk.green(`Overriding ${filename} with new model`)) 19 | FS.writeFileSync(filename, content) 20 | } else { 21 | console.log(chalk.gray(`File ${filename} is the same`)) 22 | } 23 | } 24 | 25 | /** 26 | * Bootstrap a graphqlgen config for the user to finish configuring. 27 | */ 28 | const writeConfigScaffolding = () => { 29 | const yaml = `\ 30 | # The target programming language for the generated code 31 | language: typescript 32 | 33 | # The file path pointing to your GraphQL schema 34 | schema: .graphql 35 | 36 | # Type definition for the resolver context object 37 | context: : 38 | 39 | # Map SDL types from the GraphQL schema to TS models 40 | models: 41 | files: 42 | - .ts 43 | 44 | # Generated typings for resolvers and default resolver implementations 45 | # Please don't edit this file but just import from here 46 | output: /graphqlgen.ts 47 | 48 | # Temporary scaffolded resolvers to copy and paste in your application 49 | resolver-scaffolding: 50 | output: 51 | layout: file-per-type 52 | ` 53 | const outputPath = Path.join(process.cwd(), 'graphqlgen.yml') 54 | 55 | if (FS.existsSync(outputPath)) { 56 | return console.log(chalk.red('graphqlgen.yml file already exists')) 57 | } 58 | 59 | try { 60 | FS.writeFileSync(outputPath, yaml, { encoding: 'utf-8' }) 61 | } catch (e) { 62 | return console.error( 63 | chalk.red(`Failed to write the graphqlgen.yml file, error: ${e}`), 64 | ) 65 | } 66 | 67 | console.log(chalk.green('graphqlgen.yml file created')) 68 | } 69 | 70 | /** 71 | * Output the generated resolver types. 72 | */ 73 | const writeTypes = (types: string, config: GraphQLGenDefinition): void => { 74 | // Create generation target folder, if it does not exist 75 | // TODO: Error handling around this 76 | mkdirp.sync(Path.dirname(config.output)) 77 | 78 | try { 79 | writeChangesOnly(config.output, types) 80 | } catch (e) { 81 | console.error( 82 | chalk.red(`Failed to write the file at ${config.output}, error: ${e}`), 83 | ) 84 | process.exit(1) 85 | } 86 | 87 | console.log( 88 | chalk.green( 89 | `Type definitions for your resolvers generated at ${config.output}`, 90 | ), 91 | ) 92 | } 93 | 94 | /** 95 | * Output scaffolded resolvers. 96 | */ 97 | const writeResolversScaffolding = ( 98 | resolvers: CodeFileLike[], 99 | config: GraphQLGenDefinition, 100 | ) => { 101 | if (!config['resolver-scaffolding']) { 102 | return 103 | } 104 | 105 | const outputResolversDir = config['resolver-scaffolding'].output 106 | 107 | const toBeCreatedFiles = resolvers.map(f => 108 | Path.join(outputResolversDir, f.path), 109 | ) 110 | 111 | if (FS.existsSync(outputResolversDir)) { 112 | FS.readdirSync(outputResolversDir) 113 | .map(f => Path.join(outputResolversDir, f)) 114 | .filter(f => !toBeCreatedFiles.includes(f)) 115 | .forEach(f => { 116 | FS.unlinkSync(f) 117 | console.log( 118 | chalk.yellow(`Deleting file ${f} - model scaffold no long available`), 119 | ) 120 | }) 121 | } 122 | 123 | resolvers.forEach(f => { 124 | const writePath = Path.join(outputResolversDir, f.path) 125 | mkdirp.sync(Path.dirname(writePath)) 126 | try { 127 | writeChangesOnly( 128 | writePath, 129 | replaceAll( 130 | f.code, 131 | '[TEMPLATE-INTERFACES-PATH]', 132 | getImportPathRelativeToOutput( 133 | getAbsoluteFilePath(config.output, config.language), 134 | writePath, 135 | ), 136 | ), 137 | ) 138 | } catch (e) { 139 | console.error( 140 | chalk.red( 141 | `Failed to write the file at ${outputResolversDir}, error: ${e}`, 142 | ), 143 | ) 144 | process.exit(1) 145 | } 146 | }) 147 | 148 | console.log( 149 | chalk.green(`Resolvers scaffolded for you at ${outputResolversDir}`), 150 | ) 151 | } 152 | 153 | export { writeTypes, writeConfigScaffolding, writeResolversScaffolding } 154 | -------------------------------------------------------------------------------- /packages/graphqlgen/benchmarks/integration/index.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path' 2 | import * as Glob from 'glob' 3 | import * as Parse from '../../src/parse' 4 | import * as ConfigTypes from 'graphqlgen-json-schema' 5 | import * as Validation from '../../src/validation' 6 | import * as GGen from '../../src' 7 | import * as Util from '../../src/utils' 8 | import * as Benchmark from '../lib/benchmark' 9 | 10 | const collect: Benchmark.Collect = () => { 11 | const paths = Glob.sync(Path.join(__dirname, './*')) 12 | const benchmarks: Benchmark.Benchmark[] = [] 13 | 14 | for (const path of paths) { 15 | if (Util.isFile(path)) continue 16 | 17 | const errors = validateFixtures(path) 18 | 19 | if (errors) { 20 | for (const error of errors) { 21 | console.log(error.message) 22 | } 23 | process.exit(100) 24 | } 25 | 26 | const modelPaths = Glob.sync(Path.join(path, './models.*')) 27 | 28 | for (const modelPath of modelPaths) { 29 | // 1. We know there will be an extension because of isFile 30 | // filter above. 31 | // 2. We know it will be a support language extension 32 | // because of the set filter below. 33 | const ext = Util.getExt(modelPath) as Util.LanguageExtension 34 | 35 | if (!Util.languageExtensions.includes(ext)) continue 36 | 37 | const benchmark = create({ 38 | language: Util.getLangFromExt(ext), 39 | rootPath: path, 40 | }) 41 | 42 | benchmarks.push(benchmark) 43 | } 44 | } 45 | 46 | return benchmarks 47 | } 48 | 49 | type Options = { 50 | language: ConfigTypes.GraphQLGenDefinition['language'] 51 | rootPath: string 52 | } 53 | 54 | /** 55 | * Create a benchmark instance for testing the performance 56 | * of the whole GraphqlGen pipeline (except for initial 57 | * config parsing, file loading, and model map creation). 58 | */ 59 | const create = (config: Options): Benchmark.Benchmark => { 60 | const codeGenConfig = createCodeGenConfig({ 61 | language: config.language, 62 | rootPath: config.rootPath, 63 | }) 64 | 65 | const benchmark = new Benchmark.Benchmark({ 66 | name: `generateCode (${Path.basename(config.rootPath)} schema, ${ 67 | config.language 68 | })`, 69 | test: () => { 70 | GGen.generateCode(codeGenConfig) 71 | }, 72 | }) 73 | 74 | return benchmark 75 | } 76 | 77 | type CodeGenConfigOptions = { 78 | language: ConfigTypes.GraphQLGenDefinition['language'] 79 | rootPath: string 80 | } 81 | 82 | /** 83 | * Create a configuration ready for consumption by the 84 | * main code gen function. This utility function is needed 85 | * because of the current complexity of assembling the config. 86 | */ 87 | const createCodeGenConfig = ( 88 | config: CodeGenConfigOptions, 89 | ): GGen.GenerateCodeArgs => { 90 | const ext = Util.getExtFromLang(config.language) 91 | const sdlFilePath = Path.join(config.rootPath, 'schema.graphql') 92 | 93 | const schema = Parse.parseSchema(sdlFilePath) 94 | 95 | const models = { 96 | files: [Path.join(config.rootPath, `./models.${ext}`)], 97 | } 98 | 99 | const graphqlGenConfig: ConfigTypes.GraphQLGenDefinition = { 100 | language: config.language, 101 | schema: sdlFilePath, 102 | output: Path.join(config.rootPath, './'), 103 | models, 104 | } 105 | 106 | // Needed to initialize singleton data in Parse module 107 | Validation.validateConfig(graphqlGenConfig, schema) 108 | 109 | const modelMap = Parse.parseModels( 110 | models, 111 | schema, 112 | Path.join(config.rootPath, './'), 113 | graphqlGenConfig.language, 114 | ) 115 | 116 | return { 117 | language: graphqlGenConfig.language, 118 | schema, 119 | config: graphqlGenConfig, 120 | modelMap, 121 | } 122 | } 123 | 124 | /** 125 | * Function that checks for correctness of folder/file layout of benchmarks. 126 | * The returned validation failure, if any, contains a message to help developers 127 | * fix the problem and so should be printed to them nicely. 128 | */ 129 | const validateFixtures = (scenarioFolder: string): null | Error[] => { 130 | const name = Path.basename(scenarioFolder) 131 | const files = Glob.sync(Path.join(scenarioFolder, './*')).map(path => 132 | Path.basename(path), 133 | ) 134 | 135 | const errors = [] 136 | 137 | if (!files.includes('schema.graphql')) { 138 | errors.push( 139 | new Error(`benchmark "${name}" missing the "schema.graphql" file`), 140 | ) 141 | } 142 | 143 | if (!files.includes('models.ts')) { 144 | errors.push( 145 | new Error( 146 | `benchmark "${name}" missing a mdodel file such as "models.ts"`, 147 | ), 148 | ) 149 | } 150 | 151 | return errors.length ? errors : null 152 | } 153 | 154 | export { collect } 155 | -------------------------------------------------------------------------------- /docs/02-generation.md: -------------------------------------------------------------------------------- 1 | # Generation 2 | 3 | > **IMPORTANT**: The generated typings and default resolver implementations are all stored in a single file which should never be edited! 4 | 5 | The goal of this feature is to make your resolvers type-safe! Without a tool like `graphqlgen`, type-safe resolvers would require you to write huge amounts of boilerplate to keep your GraphQL schema in sync with your TypeScript type definitions, which is a cumbersome and error-prone process. 6 | 7 | For each model, `graphqlgen` generates the following: 8 | 9 | - Type definitions for resolver arguments and return value 10 | - Default resolver implementations 11 | 12 | The relevant properties from `graphqlgen.yml` for the Generation feature are: 13 | 14 | - `language` (required) 15 | - `schema` (required) 16 | - `models` (required) 17 | - `context` (optional) 18 | - `output` (required) 19 | 20 | ### Example 21 | 22 | #### Setup 23 | 24 | Assume you have the following minimal setup with three files: 25 | 26 | **`./src/schema.graphql`** 27 | 28 | ```graphql 29 | type Query { 30 | user(id: ID!): User 31 | } 32 | 33 | type User { 34 | id: ID! 35 | name: String 36 | } 37 | ``` 38 | 39 | **`./src/models.ts`** 40 | 41 | ```ts 42 | export interface User { 43 | id: string 44 | name: string | null 45 | password: string 46 | } 47 | ``` 48 | 49 | **`./graphqlgen.yml`** 50 | 51 | ```yml 52 | language: typescript 53 | schema: ./src/schema.graphql 54 | models: 55 | files: 56 | - ./src/models.ts 57 | output: ./src/generated/graphqlgen.ts 58 | ``` 59 | 60 | #### Generated code 61 | 62 | After running `$ graphqlgen` in your terminal, the following: 63 | 64 | **`./src/generated/graphqlgen.ts`** 65 | 66 | ```ts 67 | import { GraphQLResolveInfo } from 'graphql' 68 | type Context = any 69 | import { User } from '../models' 70 | 71 | export namespace QueryResolvers { 72 | export const defaultResolvers = {} 73 | 74 | export interface ArgsUser { 75 | id: string 76 | } 77 | 78 | export type UserResolver = ( 79 | parent: {}, 80 | args: ArgsUser, 81 | ctx: Context, 82 | info: GraphQLResolveInfo, 83 | ) => User | null | Promise 84 | 85 | export interface Type { 86 | user: ( 87 | parent: {}, 88 | args: ArgsUser, 89 | ctx: Context, 90 | info: GraphQLResolveInfo, 91 | ) => User | null | Promise 92 | } 93 | } 94 | 95 | export namespace UserResolvers { 96 | export const defaultResolvers = { 97 | id: (parent: User) => parent.id, 98 | name: (parent: User) => parent.name, 99 | } 100 | 101 | export type IdResolver = ( 102 | parent: User, 103 | args: {}, 104 | ctx: Context, 105 | info: GraphQLResolveInfo, 106 | ) => string | Promise 107 | 108 | export type NameResolver = ( 109 | parent: User, 110 | args: {}, 111 | ctx: Context, 112 | info: GraphQLResolveInfo, 113 | ) => string | null | Promise 114 | 115 | export interface Type { 116 | id: ( 117 | parent: User, 118 | args: {}, 119 | ctx: Context, 120 | info: GraphQLResolveInfo, 121 | ) => string | Promise 122 | 123 | name: ( 124 | parent: User, 125 | args: {}, 126 | ctx: Context, 127 | info: GraphQLResolveInfo, 128 | ) => string | null | Promise 129 | } 130 | } 131 | 132 | export interface Resolvers { 133 | Query: QueryResolvers.Type 134 | User: UserResolvers.Type 135 | } 136 | ``` 137 | 138 | ### Type Definitions 139 | 140 | This is required to make your resolvers type safe. Type definitions are generated for the resolvers' return values as well as for the first three resolver arguments: 141 | 142 | 1. `parent`: The return value of the previous resolver execution level. [Learn more](https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e/). 143 | 1. `args`: The query parameters provided by the client who submitted the query. 144 | 1. `context`: An object to be passed through the GraphQL resolver chain. 145 | 146 | ### Default resolvers 147 | 148 | Default resolvers are trivial resolver implementations where the fields from the `parent` arguments are immediately returned. Consider for the example the following `User` type in a GraphQL schema: 149 | 150 | ```graphql 151 | type User { 152 | id: ID! 153 | name: String 154 | } 155 | ``` 156 | 157 | The default resolvers for that type look as follows: 158 | 159 | ```ts 160 | export const defaultResolvers = { 161 | id: (parent: User) => parent.id, 162 | name: (parent: User) => parent.name, 163 | } 164 | ``` 165 | 166 | Note that the default resolvers can be omitted in the vanilla JavaScript version of GraphQL, they're only required when using TypeScript! [Learn more](https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e/). 167 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/generators/typescript/scaffolder.ts: -------------------------------------------------------------------------------- 1 | import { GenerateArgs, CodeFileLike } from '../../types' 2 | import { 3 | GraphQLTypeObject, 4 | GraphQLInterfaceObject, 5 | GraphQLUnionObject, 6 | } from '../../source-helper' 7 | import { 8 | fieldsFromModelDefinition, 9 | shouldScaffoldFieldResolver, 10 | isParentType, 11 | } from '../common' 12 | 13 | export { format } from './generator' 14 | 15 | function renderResolvers( 16 | type: GraphQLTypeObject, 17 | args: GenerateArgs, 18 | ): CodeFileLike { 19 | const model = args.modelMap[type.name] 20 | const modelFields = fieldsFromModelDefinition(model.definition) 21 | 22 | const code = `\ 23 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 24 | // Please do not import this file directly but copy & paste to your application code. 25 | 26 | import { ${type.name}Resolvers } from '[TEMPLATE-INTERFACES-PATH]' 27 | 28 | export const ${type.name}: ${type.name}Resolvers.Type = { 29 | ${ 30 | args.defaultResolversEnabled 31 | ? `...${type.name}Resolvers.defaultResolvers,` 32 | : '' 33 | } 34 | ${type.fields 35 | .filter(field => shouldScaffoldFieldResolver(field, modelFields, args)) 36 | .map( 37 | field => ` 38 | ${field.name}: (parent, args, ctx) => { 39 | throw new Error('Resolver not implemented') 40 | } 41 | `, 42 | )} 43 | }` 44 | return { path: `${type.name}.ts`, force: false, code } 45 | } 46 | 47 | function renderPolyResolvers( 48 | type: GraphQLInterfaceObject | GraphQLUnionObject, 49 | ): CodeFileLike { 50 | const code = `\ 51 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 52 | // Please do not import this file directly but copy & paste to your application code. 53 | 54 | import { ${type.name}Resolvers } from '[TEMPLATE-INTERFACES-PATH]' 55 | 56 | export const ${type.name}: ${type.name}Resolvers.Type = { 57 | __resolveType: (parent, ctx) => { 58 | throw new Error('Resolver not implemented') 59 | } 60 | }` 61 | return { path: `${type.name}.ts`, force: false, code } 62 | } 63 | 64 | function renderParentResolvers( 65 | type: GraphQLTypeObject, 66 | args: GenerateArgs, 67 | ): CodeFileLike { 68 | const code = `\ 69 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 70 | // Please do not import this file directly but copy & paste to your application code. 71 | 72 | import { ${type.name}Resolvers } from '[TEMPLATE-INTERFACES-PATH]' 73 | 74 | export const ${type.name}: ${type.name}Resolvers.Type = { 75 | ${ 76 | args.defaultResolversEnabled 77 | ? `...${type.name}Resolvers.defaultResolvers,` 78 | : '' 79 | } 80 | ${type.fields.map(field => { 81 | if (type.name === 'Subscription') { 82 | return `${field.name}: { 83 | subscribe: (parent, args, ctx) => { 84 | throw new Error('Resolver not implemented') 85 | } 86 | }` 87 | } 88 | 89 | return `${field.name}: (parent, args, ctx) => { 90 | throw new Error('Resolver not implemented') 91 | }` 92 | })} 93 | } 94 | ` 95 | return { 96 | path: `${type.name}.ts`, 97 | force: false, 98 | code, 99 | } 100 | } 101 | 102 | function renderExports(types: GraphQLTypeObject[]): string { 103 | return `\ 104 | // This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT. 105 | // Please do not import this file directly but copy & paste to your application code. 106 | 107 | import { Resolvers } from '[TEMPLATE-INTERFACES-PATH]' 108 | ${types 109 | .filter(type => type.type.isObject) 110 | .map( 111 | type => ` 112 | import { ${type.name} } from './${type.name}' 113 | `, 114 | ) 115 | .join(';')} 116 | 117 | export const resolvers: Resolvers = { 118 | ${types 119 | .filter(type => type.type.isObject) 120 | .map(type => `${type.name}`) 121 | .join(',')} 122 | }` 123 | } 124 | 125 | export function generate(args: GenerateArgs): CodeFileLike[] { 126 | let files: CodeFileLike[] = args.types 127 | .filter(type => type.type.isObject) 128 | .filter(type => !isParentType(type.name)) 129 | .map(type => renderResolvers(type, args)) 130 | 131 | files = files.concat( 132 | args.interfaces.map(type => renderPolyResolvers(type)), 133 | args.unions.map(type => renderPolyResolvers(type)), 134 | ) 135 | 136 | files = files.concat( 137 | args.types 138 | .filter(type => type.type.isObject) 139 | .filter(type => isParentType(type.name)) 140 | .map(type => renderParentResolvers(type, args)), 141 | ) 142 | 143 | files.push({ 144 | path: 'index.ts', 145 | force: false, 146 | code: renderExports(args.types), 147 | }) 148 | 149 | return files 150 | } 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # graphqlgen 4 | 5 | [![CircleCI](https://circleci.com/gh/prisma/graphqlgen.svg?style=shield)](https://circleci.com/gh/prisma/graphqlgen) [![npm version](https://badge.fury.io/js/graphqlgen.svg)](https://badge.fury.io/js/graphqlgen) 6 | 7 | Generate & scaffold type-safe resolvers based on your GraphQL Schema in TypeScript, Flow & Reason 8 | 9 | ## Deprecation note 10 | 11 | `graphqlgen` has been officially deprecated in favor of the [The Guild](http://the-guild.dev)'s project [GraphQL Code Generator](https://graphql-code-generator.com/). Learn more about the collaboration of Prisma and the Guild in [this]( https://www.prisma.io/blog/the-guild-takes-over-oss-libraries-vvluy2i4uevs) blog post. 12 | 13 | --- 14 | 15 | - [About](#about) 16 | - [Highlights](#highlights) 17 | - [Motivation](#motivation) 18 | - [Supported languages](#supported-languages) 19 | - [Getting started](#getting-started) 20 | - [Try out a project initializer](#try-out-a-project-initializer) 21 | - [Add to existing project](#add-to-existing-project) 22 | - [Documentation](#documentation) 23 | - [Addendum](#addendum) 24 | - [Community](#community) 25 | - [Project Status](#project-status) 26 | - [Prior Art](#prior-art) 27 | 28 | ## About 29 | 30 | ### Highlights 31 | 32 | - **Schema-first** Design in SDL to derive ideal types 33 | - **Type-safety** Resolvers with precise signatures including `parent`, `args` and return type 34 | - **DX** Precise resolver types puts your editor intellisense to work 35 | - **Ecosystem Interop** codegen suitable for Yoga 1 or Apollo Server and supports [prettier](https://github.com/prettier/prettier) and [graphql-import](https://github.com/prisma/graphql-import) out of the box 36 | 37 | ### Motivation 38 | 39 | Programming in type-safe environments can contribute toward great confidence in your code's integrity. `graphqlgen` aims to leverage the GraphQL type system to make your resolvers completely type-safe. This is important because resolvers are the heart of any graphql service and yet the hardest to statically type due to their dynaminism. 40 | 41 | ### Supported languages 42 | 43 | - `TypeScript` 44 | - `Flow` 45 | 46 | Others under discussion: 47 | 48 | - [`reason`](https://github.com/prisma/graphqlgen/issues/253) 49 | 50 | ## Getting started 51 | 52 | ### Try out a project initializer 53 | 54 | 1. Run initializer 55 | 56 | ```bash 57 | yarn create graphqlgen my-app # npm init graphqlgen my-app 58 | cd my-app 59 | yarn start # npm run start 60 | ``` 61 | 62 | 2. Edit `./my-app/src/schema.graphql` to your heart's content. 63 | 64 | 3. Generate types: 65 | 66 | ``` 67 | yarn graphqlgen 68 | ``` 69 | 70 | ### Add to existing project 71 | 72 | ```bash 73 | yarn add --dev graphqlgen # npm install --save-dev graphqlgen 74 | ``` 75 | 76 | Then you will have access to the cli (`gg` or `graphqlgen`): 77 | 78 | ```bash 79 | yarn -s gg --help # npm run gg --help 80 | ``` 81 | 82 | ``` 83 | Usage: graphqlgen or gg 84 | 85 | Options: 86 | -i, --init Initialize a graphqlgen.yml file 87 | -v, --version Show version number [boolean] 88 | -h, --help Show help [boolean] 89 | ``` 90 | 91 | `gg` depends on the presence of a `graphqlgen.yml` config **located in the directory where `gg` is invoked**. Here is an example: 92 | 93 | ```yml 94 | language: typescript 95 | schema: ./src/schema.graphql 96 | context: ./src/context.ts:Context 97 | output: ./src/generated/graphqlgen.ts 98 | models: 99 | files: 100 | - ./src/generated/prisma-client/index.ts 101 | ``` 102 | 103 | ### Documentation 104 | 105 | https://oss.prisma.io/graphqlgen 106 | 107 | ## Addendum 108 | 109 | ### Community 110 | 111 | Join us at `#graphqlgen` in our [Slack group](https://slack.prisma.io) and if you have more fleshed out ideas, bug reports etc. create a Github issue: 112 | 113 | - [feature request](https://github.com/prisma/graphqlgen/issues/new?template=feature_request.md&labels=enhancement) 114 | - [bug report](https://github.com/prisma/graphqlgen/issues/new?template=bug_report.md&labels=bug) 115 | 116 | ### Project Status 117 | 118 | `graphqlgen` is still in early stage development where breaking changes and tool design are a fluid matter. Feedback is deeply appreciated. You may feel comfortable giving it a try on production systems since there is no runtime aspect and hence quite safe to do so (save for a few optional default resolvers). 119 | 120 | ### Prior Art 121 | 122 | - [**gqlgen**](https://github.com/99designs/gqlgen) is the Golang equivalent of `graphqlgen` and served as a source of inspiration 123 | - [**graphql-code-generator**](https://github.com/dotansimha/graphql-code-generator) is a similar tool based on templates support both frontend & backend 124 | 125 |

Prisma

126 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path' 2 | import * as FS from 'fs' 3 | import { Language } from 'graphqlgen-json-schema' 4 | 5 | import { getExtNameFromLanguage } from './path-helpers' 6 | import { NormalizedFile } from './parse' 7 | import { FilesToTypesMap, InterfaceNamesToFile } from './introspection/types' 8 | 9 | /** 10 | * Uppercase the first letter of a string. Useful when generating type names. 11 | */ 12 | const upperFirst = (s: string): string => { 13 | return s.replace(/^\w/, c => c.toUpperCase()) 14 | } 15 | 16 | /** 17 | * Append a file extension to a file name. Leading dots in given 18 | * file extension are gracefully dropped. 19 | */ 20 | const appendExt = (ext: string, filePath: string): string => { 21 | const normalizedExt = ext.replace(/^\.+/, '') 22 | return filePath + '.' + normalizedExt 23 | } 24 | 25 | /** 26 | * Normalize different kinds of path notation. Will do synchronous 27 | * file IO to accomplish task. 28 | * 29 | * Examples: 30 | * 31 | * ./path/to/index.ts => `pwd`/path/to/index.ts 32 | * ./path/to => `pwd`/path/to/to.ts 33 | * ./path/to => `pwd`/path/to/index.ts 34 | * ./path/to/ => `pwd`/path/to/index.ts 35 | */ 36 | const normalizeFilePath = (filePath: string, language: Language): string => { 37 | const ext = getExtNameFromLanguage(language) 38 | 39 | // If the filepath is set against a file then just return that. 40 | if (Path.extname(filePath) === ext) { 41 | return Path.resolve(filePath) 42 | } 43 | 44 | // If there is no file extension then infer it (from given language) and return if 45 | // exists on file system. Otherwise consider file path as being to a folder that 46 | // mnust have an index file. 47 | 48 | const filePathWithExt = Path.resolve(filePath) + ext 49 | 50 | if (FS.existsSync(filePathWithExt)) { 51 | return filePathWithExt 52 | } 53 | 54 | return Path.join(Path.resolve(filePath), appendExt(ext, 'index')) 55 | } 56 | 57 | /** 58 | * Create a map of interface names to the path of the file in which they're defined 59 | * The first evaluated interfaces are always the chosen ones 60 | */ 61 | const getTypeToFileMapping = ( 62 | files: NormalizedFile[], 63 | filesToTypesMap: FilesToTypesMap, 64 | ): InterfaceNamesToFile => { 65 | // REFACTOR: This function basically just takes an index and flips it. Make generic. 66 | const mapping: InterfaceNamesToFile = {} 67 | 68 | for (const file of files) { 69 | // WARNING: typesMap is not typesafe since the lookup could fail. 70 | const typesMap = filesToTypesMap[file.path] 71 | const interfaceNames = Object.keys(typesMap) 72 | 73 | for (const interfaceName of interfaceNames) { 74 | if (!mapping[interfaceName]) { 75 | mapping[interfaceName] = file 76 | } 77 | } 78 | } 79 | 80 | return mapping 81 | } 82 | 83 | /** 84 | * Replace all occurances of given search string in a given 85 | * string with another string. 86 | */ 87 | const replaceAll = ( 88 | str: string, 89 | search: string, 90 | replacement: string, 91 | ): string => { 92 | return str.split(search).join(replacement) 93 | } 94 | 95 | /** 96 | * Return a new array whose items are a merger of the given two arrays. 97 | */ 98 | const concat = (a: T[], b: T[]): T[] => { 99 | return a.concat(b) 100 | } 101 | 102 | // TODO Refactor; confusing; only one callsite 103 | const uniq = (value: T, index: number, array: T[]): boolean => { 104 | return array.indexOf(value) === index 105 | } 106 | 107 | type LanguageExtension = 'ts' | 'js' 108 | const languageExtensions: LanguageExtension[] = ['ts', 'js'] 109 | 110 | const extToLangIndex: Record<'ts' | 'js', Language> = { 111 | ts: 'typescript', 112 | js: 'flow', 113 | } 114 | 115 | const langToExtIndex: Record = { 116 | typescript: 'ts', 117 | flow: 'js', 118 | } 119 | 120 | const getExtFromLang = (lang: Language): LanguageExtension => { 121 | return langToExtIndex[lang] 122 | } 123 | 124 | const getLangFromExt = (ext: LanguageExtension): Language => { 125 | return extToLangIndex[ext] 126 | } 127 | 128 | /** 129 | * Get the extension from a file name (or path with file name). 130 | * Unlike Path.extname this returns null if no ext can be extracted. 131 | */ 132 | const getExt = (path: string): null | string => { 133 | const ext = Path.extname(path) 134 | if (ext === '') return null 135 | const extWithoutDot = ext.slice(1) 136 | return extWithoutDot 137 | } 138 | 139 | /** 140 | * Predicate function for checking if a path is to file. 141 | * Relies on convention that a dot being present in last 142 | * path item is a file. 143 | * 144 | * Examples: 145 | * 146 | * /a/b/c -> false 147 | * /a/b/c.foo -> true 148 | */ 149 | const isFile = (path: string): boolean => { 150 | return Path.extname(path) !== '' 151 | } 152 | 153 | export { 154 | LanguageExtension, 155 | languageExtensions, 156 | getLangFromExt, 157 | getExtFromLang, 158 | getTypeToFileMapping, 159 | uniq, 160 | concat, 161 | replaceAll, 162 | upperFirst, 163 | normalizeFilePath, 164 | getExt, 165 | isFile, 166 | } 167 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/typescript/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { testGeneration } from '../generation' 2 | import { join } from 'path' 3 | 4 | const relative = (p: string) => join(__dirname, p) 5 | 6 | const typesPath = relative('./generated-basic/graphqlgen.ts') 7 | const resolversDir = relative('./generated-basic/tmp-resolvers/') 8 | const language = 'typescript' 9 | 10 | test('basic schema', async () => { 11 | return testGeneration({ 12 | language, 13 | schema: relative('../fixtures/basic/schema.graphql'), 14 | models: { 15 | files: [relative('../fixtures/basic/index.ts')], 16 | }, 17 | output: typesPath, 18 | ['resolver-scaffolding']: { 19 | output: resolversDir, 20 | layout: 'file-per-type', 21 | }, 22 | }) 23 | }) 24 | 25 | test('basic enum', async () => { 26 | return testGeneration({ 27 | language, 28 | schema: relative('../fixtures/enum/schema.graphql'), 29 | models: { 30 | files: [relative('../fixtures/enum/types.ts')], 31 | }, 32 | output: typesPath, 33 | ['resolver-scaffolding']: { 34 | output: resolversDir, 35 | layout: 'file-per-type', 36 | }, 37 | }) 38 | }) 39 | 40 | test('basic union', async () => { 41 | return testGeneration({ 42 | language, 43 | schema: relative('../fixtures/union/schema.graphql'), 44 | models: { 45 | files: [relative('../fixtures/union/types.ts')], 46 | }, 47 | output: typesPath, 48 | ['resolver-scaffolding']: { 49 | output: resolversDir, 50 | layout: 'file-per-type', 51 | }, 52 | }) 53 | }) 54 | 55 | test('defaultName', async () => { 56 | return testGeneration({ 57 | language, 58 | schema: relative('../fixtures/defaultName/schema.graphql'), 59 | models: { 60 | files: [ 61 | { 62 | path: relative('../fixtures/defaultName/index.ts'), 63 | defaultName: '${typeName}Node', 64 | }, 65 | ], 66 | }, 67 | output: typesPath, 68 | ['resolver-scaffolding']: { 69 | output: resolversDir, 70 | layout: 'file-per-type', 71 | }, 72 | }) 73 | }) 74 | 75 | test('basic scalar', async () => { 76 | return testGeneration({ 77 | language, 78 | schema: relative('../fixtures/scalar/schema.graphql'), 79 | models: { 80 | files: [relative('../fixtures/scalar/types.ts')], 81 | }, 82 | output: typesPath, 83 | ['resolver-scaffolding']: { 84 | output: resolversDir, 85 | layout: 'file-per-type', 86 | }, 87 | }) 88 | }) 89 | 90 | test('basic input', async () => { 91 | return testGeneration({ 92 | language, 93 | schema: relative('../fixtures/input/schema.graphql'), 94 | models: { 95 | files: [relative('../fixtures/input/types.ts')], 96 | }, 97 | output: typesPath, 98 | ['resolver-scaffolding']: { 99 | output: resolversDir, 100 | layout: 'file-per-type', 101 | }, 102 | }) 103 | }) 104 | 105 | test('context', async () => { 106 | return testGeneration({ 107 | language, 108 | schema: relative('../fixtures/context/schema.graphql'), 109 | context: relative('../fixtures/context/types.ts:Context'), 110 | models: { 111 | files: [relative('../fixtures/context/types.ts')], 112 | }, 113 | output: typesPath, 114 | ['resolver-scaffolding']: { 115 | output: resolversDir, 116 | layout: 'file-per-type', 117 | }, 118 | }) 119 | }) 120 | 121 | test('subscription', () => { 122 | return testGeneration({ 123 | language, 124 | schema: relative('../fixtures/subscription/schema.graphql'), 125 | models: { 126 | files: [relative('../fixtures/subscription/types.ts')], 127 | }, 128 | output: typesPath, 129 | ['resolver-scaffolding']: { 130 | output: resolversDir, 131 | layout: 'file-per-type', 132 | }, 133 | }) 134 | }) 135 | 136 | test('override model', async () => { 137 | testGeneration({ 138 | language, 139 | schema: relative('../fixtures/basic/schema.graphql'), 140 | models: { 141 | override: { 142 | Number: `${relative('../fixtures/basic')}:Number`, 143 | }, 144 | }, 145 | output: relative('./generated/basic/graphqlgen.ts'), 146 | ['resolver-scaffolding']: { 147 | output: relative('./tmp/basic/'), 148 | layout: 'file-per-type', 149 | }, 150 | }) 151 | }) 152 | 153 | test('basic interface', async () => { 154 | testGeneration({ 155 | language, 156 | schema: relative('../fixtures/interface/schema.graphql'), 157 | models: { 158 | files: [relative('../fixtures/interface/types.ts')], 159 | }, 160 | output: relative('./generated/interface/graphqlgen.ts'), 161 | ['resolver-scaffolding']: { 162 | output: relative('./tmp/interface/'), 163 | layout: 'file-per-type', 164 | }, 165 | }) 166 | }) 167 | 168 | test('basic schema with delegated parent resolvers', async () => { 169 | return testGeneration({ 170 | language, 171 | schema: relative('../fixtures/basic/schema.graphql'), 172 | models: { 173 | files: [relative('../fixtures/basic/index.ts')], 174 | }, 175 | output: typesPath, 176 | ['resolver-scaffolding']: { 177 | output: resolversDir, 178 | layout: 'file-per-type', 179 | }, 180 | 'delegated-parent-resolvers': true, 181 | }) 182 | }) 183 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/output.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import * as os from 'os' 3 | import { graphQLToTypecriptFlowType, GraphQLTypeObject } from './source-helper' 4 | import { maybeReplaceDefaultName, ValidatedDefinition } from './validation' 5 | import { Language } from 'graphqlgen-json-schema' 6 | import { getExtNameFromLanguage } from './path-helpers' 7 | 8 | export function outputDefinitionFilesNotFound( 9 | validatedDefinitions: ValidatedDefinition[], 10 | ) { 11 | const invalidDefinitions = validatedDefinitions.filter( 12 | validation => !validation.fileExists, 13 | ) 14 | 15 | console.log(`❌ Some path to model definitions defined in your graphqlgen.yml were not found 16 | 17 | ${chalk.bold( 18 | 'Step 1', 19 | )}: Make sure each of these model definitions point to an existing file 20 | 21 | models: 22 | override: 23 | ${invalidDefinitions 24 | .map( 25 | def => 26 | ` ${def.definition.typeName}: ${chalk.redBright( 27 | def.definition.filePath!, 28 | )}:${def.definition.modelName}`, 29 | ) 30 | .join(os.EOL)} 31 | 32 | ${chalk.bold('Step 2')}: Re-run ${chalk.bold('`graphqlgen`')}`) 33 | } 34 | 35 | export function outputWrongSyntaxFiles( 36 | validatedDefinitions: ValidatedDefinition[], 37 | ) { 38 | const invalidDefinitions = validatedDefinitions.filter( 39 | validation => !validation.validSyntax, 40 | ) 41 | 42 | console.log(`❌ Some model definitions defined in your graphqlgen.yml have syntax errors 43 | 44 | ${chalk.bold( 45 | 'Step 1', 46 | )}: Make sure each of these model definitions follow the correct syntax 47 | 48 | ${chalk.cyan( 49 | `(Correct syntax: ${chalk.bold('')}: ${chalk.bold( 50 | '', 51 | )}:${chalk.bold('')})`, 52 | )} 53 | 54 | models: 55 | override: 56 | ${invalidDefinitions 57 | .map(def => 58 | chalk.redBright( 59 | ` ${def.definition.typeName}: ${def.definition.rawDefinition}`, 60 | ), 61 | ) 62 | .join(os.EOL)} 63 | 64 | ${chalk.bold('Step 2')}: Re-run ${chalk.bold('`graphqlgen`')}`) 65 | } 66 | 67 | export function outputInterfaceDefinitionsNotFound( 68 | validatedDefinitions: ValidatedDefinition[], 69 | ) { 70 | const invalidDefinitions = validatedDefinitions.filter( 71 | validation => !validation.interfaceExists, 72 | ) 73 | 74 | console.log(`❌ Some model definitions defined in your graphqlgen.yml were not found 75 | 76 | ${chalk.bold( 77 | 'Step 1', 78 | )}: Make sure each of these model definitions are defined in the following files 79 | 80 | models: 81 | override: 82 | ${invalidDefinitions 83 | .map( 84 | def => 85 | ` ${def.definition.typeName}: ${ 86 | def.definition.filePath 87 | }:${chalk.redBright(def.definition.modelName!)}`, 88 | ) 89 | .join(os.EOL)} 90 | 91 | ${chalk.bold('Step 2')}: Re-run ${chalk.bold('`graphqlgen`')}`) 92 | } 93 | 94 | export function outputMissingModels( 95 | missingModels: GraphQLTypeObject[], 96 | language: Language, 97 | defaultName: string | null, 98 | ) { 99 | console.log(`❌ Some types from your application schema have model definitions that are not defined yet 100 | 101 | ${chalk.bold( 102 | 'Step 1', 103 | )}: Copy/paste the model definitions below to your application 104 | 105 | ${missingModels 106 | .map(type => renderModelFromType(type, language, defaultName)) 107 | .join(os.EOL)} 108 | 109 | 110 | ${chalk.bold('Step 2')}: Reference the model definitions in your ${chalk.bold( 111 | 'graphqlgen.yml', 112 | )} file 113 | 114 | models: 115 | files: 116 | - ./path/to/your/file${getExtNameFromLanguage(language)} 117 | 118 | ${chalk.bold('Step 3')}: Re-run ${chalk.bold('`graphqlgen`')}`) 119 | } 120 | 121 | export function outputModelFilesNotFound(filesNotFound: string[]) { 122 | console.log(`❌ Some path to model definitions defined in your graphqlgen.yml were not found 123 | 124 | ${chalk.bold('Step 1')}: Make sure each of these files exist 125 | 126 | models: 127 | files: 128 | ${filesNotFound.map(file => ` - ${chalk.redBright(file)}`).join(os.EOL)} 129 | 130 | ${chalk.bold('Step 2')}: Re-run ${chalk.bold('`graphqlgen`')}`) 131 | } 132 | 133 | function renderModelFromType( 134 | type: GraphQLTypeObject, 135 | language: Language, 136 | defaultName: string | null, 137 | ): string { 138 | switch (language) { 139 | case 'typescript': 140 | return renderTypescriptModelFromType(type, defaultName) 141 | case 'flow': 142 | return renderFlowModelFromType(type, defaultName) 143 | } 144 | } 145 | 146 | function renderTypescriptModelFromType( 147 | type: GraphQLTypeObject, 148 | defaultName: string | null, 149 | ): string { 150 | const name = maybeReplaceDefaultName(type.name, defaultName) 151 | return `\ 152 | export interface ${chalk.bold(name)} { 153 | ${type.fields 154 | .filter(field => field.type.isScalar) 155 | .map(field => ` ${field.name}: ${graphQLToTypecriptFlowType(field.type)}`) 156 | .join(os.EOL)} 157 | }` 158 | } 159 | 160 | function renderFlowModelFromType( 161 | type: GraphQLTypeObject, 162 | defaultName: string | null, 163 | ): string { 164 | const name = maybeReplaceDefaultName(type.name, defaultName) 165 | return `\ 166 | export interface ${chalk.bold(name)} { 167 | ${type.fields 168 | .map(field => ` ${field.name}: ${graphQLToTypecriptFlowType(field.type)}`) 169 | .join(',' + os.EOL)} 170 | }` 171 | } 172 | -------------------------------------------------------------------------------- /packages/graphqlgen/tests/generation.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript' 2 | import { EOL } from 'os' 3 | import * as rimraf from 'rimraf' 4 | import * as path from 'path' 5 | import { execFile } from 'child_process' 6 | import { writeFileSync } from 'fs' 7 | import chalk from 'chalk' 8 | import { File, GraphQLGenDefinition } from 'graphqlgen-json-schema' 9 | import { getPath, parseModels, parseSchema } from '../src/parse' 10 | import { validateConfig } from '../src/validation' 11 | import { writeResolversScaffolding, writeTypes } from '../src/project-output' 12 | import { generateCode } from '../src/index' 13 | const flow = require('flow-bin') 14 | 15 | class ExecError extends Error { 16 | constructor( 17 | public message: string, 18 | public stdout: string, 19 | public stderr: string, 20 | ) { 21 | super(message) 22 | // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work 23 | Object.setPrototypeOf(this, new.target.prototype) 24 | } 25 | } 26 | 27 | const exec = (command: string, args: string[]): Promise => { 28 | return new Promise(resolve => { 29 | return execFile(command, args, (err, stdout, stderr) => { 30 | if (err) { 31 | resolve(new ExecError(err.message, stdout, stderr)) 32 | } else { 33 | resolve(stdout) 34 | } 35 | }) 36 | }) 37 | } 38 | 39 | function printTypescriptErrors(diagnotics: ReadonlyArray) { 40 | diagnotics.forEach(diagnostic => { 41 | if (diagnostic.file) { 42 | const { line, character } = diagnostic.file.getLineAndCharacterOfPosition( 43 | diagnostic.start!, 44 | ) 45 | const message = ts.flattenDiagnosticMessageText( 46 | diagnostic.messageText, 47 | '\n', 48 | ) 49 | console.log( 50 | `${diagnostic.file.fileName} (${line + 1},${character + 51 | 1}): ${message}`, 52 | ) 53 | } else { 54 | console.log( 55 | `${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`, 56 | ) 57 | } 58 | }) 59 | } 60 | 61 | function compileTypescript(fileNames: string[], compiledOutputDir: string) { 62 | const errors = ts 63 | .createProgram(fileNames, { 64 | sourceMap: false, 65 | noEmitOnError: true, 66 | target: ts.ScriptTarget.ESNext, 67 | module: ts.ModuleKind.CommonJS, 68 | outDir: compiledOutputDir, 69 | }) 70 | .emit().diagnostics 71 | 72 | if (errors.length > 0) { 73 | printTypescriptErrors(errors) 74 | } 75 | 76 | expect(errors.length).toEqual(0) 77 | } 78 | 79 | async function compileFlow(includeFiles: File[], typesPath: string) { 80 | const flowConfig = ` 81 | [ignore] 82 | 83 | [include] 84 | ${includeFiles.map(file => getPath(file)).join(EOL)} 85 | 86 | [libs] 87 | 88 | [lints] 89 | 90 | [options] 91 | 92 | [strict] 93 | ` 94 | 95 | const flowConfigName = `.flowconfig-${Math.random()}` 96 | const flowConfigPath = path.join(path.dirname(typesPath), flowConfigName) 97 | 98 | writeFileSync(flowConfigPath, flowConfig) 99 | 100 | const result = await exec(flow, [ 101 | 'check', 102 | '--flowconfig-name', 103 | flowConfigName, 104 | path.resolve(path.dirname(typesPath)), 105 | ]) 106 | 107 | if (result instanceof ExecError) { 108 | const errorDelimiter = 109 | 'Error ----------------------------------------------------------------' 110 | 111 | const errors = result.stdout 112 | .split(errorDelimiter) 113 | // Do not take into account error from 'import type { GraphQLResolveInfo } from "graphql"' 114 | .filter( 115 | error => 116 | error.length !== 0 && 117 | !error.includes('Cannot resolve module `graphql`'), 118 | ) 119 | 120 | if (errors.length > 0) { 121 | const message = errors 122 | .map(error => `${chalk.red(errorDelimiter) + EOL}${error}`) 123 | .join(EOL) 124 | throw new Error(message) 125 | } 126 | } 127 | } 128 | 129 | export async function testGeneration(config: GraphQLGenDefinition) { 130 | const schema = parseSchema(config.schema) 131 | 132 | expect(validateConfig(config, schema)).toBe(true) 133 | 134 | const modelMap = parseModels( 135 | config.models, 136 | schema, 137 | config.output, 138 | config.language, 139 | ) 140 | const { generatedTypes, generatedResolvers = [] } = generateCode({ 141 | schema, 142 | language: config.language, 143 | config, 144 | modelMap, 145 | prettify: true, 146 | }) 147 | 148 | expect(generatedTypes).toMatchSnapshot() 149 | expect(generatedResolvers).toMatchSnapshot() 150 | 151 | const restoreLog = console.log 152 | 153 | // Mock console.log 154 | console.log = jest.fn() 155 | 156 | writeTypes(generatedTypes, config) 157 | writeResolversScaffolding(generatedResolvers, config) 158 | 159 | // Restore console log to print errors if there are any 160 | console.log = restoreLog 161 | 162 | const outputResolversDir = config['resolver-scaffolding']!.output 163 | 164 | const fileNames = [ 165 | ...generatedResolvers.map(resolver => 166 | path.join(outputResolversDir, resolver.path), 167 | ), 168 | config.output, 169 | ] 170 | 171 | const compiledOutputDir = path.join( 172 | path.dirname(config.output), 173 | String(Math.random()), 174 | ) 175 | 176 | if (config.language === 'typescript') { 177 | compileTypescript(fileNames, compiledOutputDir) 178 | } 179 | 180 | if (config.language === 'flow') { 181 | await compileFlow(config.models.files!, config.output) 182 | } 183 | 184 | rimraf.sync(path.dirname(config.output)) 185 | } 186 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import chalk from 'chalk' 4 | import * as prettier from 'prettier' 5 | import * as yargs from 'yargs' 6 | import { GraphQLGenDefinition, Language } from 'graphqlgen-json-schema' 7 | import { GraphQLTypes } from './source-helper' 8 | import { IGenerator, GenerateArgs, CodeFileLike, ModelMap } from './types' 9 | import { 10 | generate as generateTS, 11 | format as formatTS, 12 | } from './generators/typescript/generator' 13 | import { 14 | generate as generateFlow, 15 | format as formatFlow, 16 | } from './generators/flow/generator' 17 | import { generate as scaffoldTS } from './generators/typescript/scaffolder' 18 | import { generate as scaffoldFlow } from './generators/flow/scaffolder' 19 | import { parseConfig, parseContext, parseSchema, parseModels } from './parse' 20 | import { validateConfig } from './validation' 21 | import { handleGlobPattern } from './glob' 22 | import * as Project from './project-output' 23 | 24 | export type CodeGenArgs = { 25 | schema: GraphQLTypes 26 | config: GraphQLGenDefinition 27 | modelMap: ModelMap 28 | prettify?: boolean 29 | prettifyOptions?: prettier.Options 30 | language: Language 31 | } 32 | 33 | function getTypesGenerator(language: Language): IGenerator { 34 | switch (language) { 35 | case 'typescript': 36 | return { generate: generateTS, format: formatTS } 37 | case 'flow': 38 | return { generate: generateFlow, format: formatFlow } 39 | } 40 | } 41 | 42 | function getResolversGenerator(language: Language): IGenerator { 43 | switch (language) { 44 | case 'typescript': 45 | return { generate: scaffoldTS, format: formatTS } 46 | case 'flow': 47 | return { generate: scaffoldFlow, format: formatFlow } 48 | } 49 | } 50 | 51 | function generateTypes( 52 | generateArgs: GenerateArgs, 53 | generateCodeArgs: CodeGenArgs, 54 | ): string { 55 | const generatorFn: IGenerator = getTypesGenerator(generateCodeArgs.language!) 56 | const generatedTypes = generatorFn.generate(generateArgs) 57 | 58 | return generateCodeArgs.prettify 59 | ? generatorFn.format( 60 | generatedTypes as string, 61 | generateCodeArgs.prettifyOptions, 62 | ) 63 | : (generatedTypes as string) 64 | } 65 | 66 | function generateResolvers( 67 | generateArgs: GenerateArgs, 68 | generateCodeArgs: CodeGenArgs, 69 | ): CodeFileLike[] { 70 | const generatorFn: IGenerator = getResolversGenerator( 71 | generateCodeArgs.language!, 72 | ) 73 | const generatedResolvers = generatorFn.generate( 74 | generateArgs, 75 | ) as CodeFileLike[] 76 | 77 | return generatedResolvers.map(r => { 78 | return { 79 | path: r.path, 80 | force: r.force, 81 | code: generateCodeArgs.prettify 82 | ? generatorFn.format(r.code, generateCodeArgs.prettifyOptions) 83 | : r.code, 84 | } 85 | }) 86 | } 87 | 88 | type CodeGenResult = { 89 | generatedTypes: string 90 | generatedResolvers?: CodeFileLike[] 91 | } 92 | 93 | export function generateCode(codeGenArgs: CodeGenArgs): CodeGenResult { 94 | const generateArgs: GenerateArgs = { 95 | enums: codeGenArgs.schema.enums, 96 | interfaces: codeGenArgs.schema.interfaces, 97 | types: codeGenArgs.schema.types, 98 | unions: codeGenArgs.schema.unions, 99 | modelMap: codeGenArgs.modelMap!, 100 | context: parseContext( 101 | codeGenArgs.config.context, 102 | codeGenArgs.config.output, 103 | ), 104 | defaultResolversEnabled: 105 | typeof codeGenArgs.config['default-resolvers'] === 'boolean' 106 | ? codeGenArgs.config['default-resolvers'] 107 | : true, 108 | iResolversAugmentationEnabled: 109 | typeof codeGenArgs.config['iresolvers-augmentation'] === 'boolean' 110 | ? codeGenArgs.config['iresolvers-augmentation'] 111 | : true, 112 | delegatedParentResolversEnabled: 113 | typeof codeGenArgs.config['delegated-parent-resolvers'] === 'boolean' 114 | ? codeGenArgs.config['delegated-parent-resolvers'] 115 | : false, 116 | } 117 | 118 | const generatedTypes = generateTypes(generateArgs, codeGenArgs) 119 | const generatedResolvers = codeGenArgs.config['resolver-scaffolding'] 120 | ? generateResolvers(generateArgs, codeGenArgs) 121 | : undefined 122 | 123 | // const generatedModels = generateModels(generateArgs, {schema, prettify, prettifyOptions, language}) 124 | 125 | return { generatedTypes, generatedResolvers } 126 | } 127 | 128 | /** 129 | * The CLI interface 130 | */ 131 | async function run() { 132 | const argv = yargs 133 | .usage('Usage: graphqlgen or gg') 134 | .alias('i', 'init') 135 | .describe('i', 'Initialize a graphqlgen.yml file') 136 | .alias('v', 'version') 137 | .describe('v', 'Print the version of graphqlgen') 138 | .version() 139 | .strict() 140 | .help('h') 141 | .alias('h', 'help').argv 142 | 143 | if (argv.i) { 144 | Project.writeConfigScaffolding() 145 | return true 146 | } 147 | 148 | const config = parseConfig() 149 | const parsedSchema = parseSchema(config.schema) 150 | 151 | // Override the config.models.files using handleGlobPattern 152 | config.models = { 153 | ...config.models, 154 | files: handleGlobPattern(config.models.files), 155 | } 156 | 157 | if (!validateConfig(config, parsedSchema)) { 158 | return false 159 | } 160 | 161 | const modelMap = parseModels( 162 | config.models, 163 | parsedSchema, 164 | config.output, 165 | config.language, 166 | ) 167 | 168 | const options = (await prettier.resolveConfig(process.cwd())) || {} // TODO: Abstract this TS specific behavior better 169 | 170 | if (JSON.stringify(options) !== '{}') { 171 | console.log(chalk.blue(`Found a prettier configuration to use`)) 172 | } 173 | 174 | const { generatedTypes, generatedResolvers } = generateCode({ 175 | schema: parsedSchema!, 176 | language: config.language, 177 | prettify: true, 178 | prettifyOptions: options, 179 | config, 180 | modelMap, 181 | }) 182 | 183 | Project.writeTypes(generatedTypes, config) 184 | 185 | if (config['resolver-scaffolding']) { 186 | Project.writeResolversScaffolding(generatedResolvers!, config) 187 | } 188 | } 189 | 190 | // Only call run when running from CLI, not when included for tests 191 | if (require.main === module) { 192 | run() 193 | } 194 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/flow-yoga/src/generated/graphqlgen.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | // Code generated by github.com/prisma/graphqlgen, DO NOT EDIT. 3 | 4 | import type { GraphQLResolveInfo } from 'graphql' 5 | import type { Post, User } from '../types' 6 | import type { Context } from '../types' 7 | 8 | // Types for Query 9 | export const Query_defaultResolvers = {} 10 | 11 | export interface Query_Args_Post { 12 | id: string; 13 | } 14 | 15 | export type Query_Feed_Resolver = ( 16 | parent: {}, 17 | args: {}, 18 | ctx: Context, 19 | info: GraphQLResolveInfo, 20 | ) => Post[] | Promise 21 | 22 | export type Query_Drafts_Resolver = ( 23 | parent: {}, 24 | args: {}, 25 | ctx: Context, 26 | info: GraphQLResolveInfo, 27 | ) => Post[] | Promise 28 | 29 | export type Query_Post_Resolver = ( 30 | parent: {}, 31 | args: Query_Args_Post, 32 | ctx: Context, 33 | info: GraphQLResolveInfo, 34 | ) => Post | null | Promise 35 | 36 | export interface Query_Resolvers { 37 | feed: ( 38 | parent: {}, 39 | args: {}, 40 | ctx: Context, 41 | info: GraphQLResolveInfo, 42 | ) => Post[] | Promise; 43 | 44 | drafts: ( 45 | parent: {}, 46 | args: {}, 47 | ctx: Context, 48 | info: GraphQLResolveInfo, 49 | ) => Post[] | Promise; 50 | 51 | post: ( 52 | parent: {}, 53 | args: Query_Args_Post, 54 | ctx: Context, 55 | info: GraphQLResolveInfo, 56 | ) => Post | null | Promise; 57 | } 58 | 59 | // Types for Post 60 | export const Post_defaultResolvers = { 61 | id: (parent: Post) => parent.id, 62 | title: (parent: Post) => parent.title, 63 | content: (parent: Post) => parent.content, 64 | published: (parent: Post) => parent.published, 65 | } 66 | 67 | export type Post_Id_Resolver = ( 68 | parent: Post, 69 | args: {}, 70 | ctx: Context, 71 | info: GraphQLResolveInfo, 72 | ) => string | Promise 73 | 74 | export type Post_Title_Resolver = ( 75 | parent: Post, 76 | args: {}, 77 | ctx: Context, 78 | info: GraphQLResolveInfo, 79 | ) => string | Promise 80 | 81 | export type Post_Content_Resolver = ( 82 | parent: Post, 83 | args: {}, 84 | ctx: Context, 85 | info: GraphQLResolveInfo, 86 | ) => string | Promise 87 | 88 | export type Post_Published_Resolver = ( 89 | parent: Post, 90 | args: {}, 91 | ctx: Context, 92 | info: GraphQLResolveInfo, 93 | ) => boolean | Promise 94 | 95 | export type Post_Author_Resolver = ( 96 | parent: Post, 97 | args: {}, 98 | ctx: Context, 99 | info: GraphQLResolveInfo, 100 | ) => User | Promise 101 | 102 | export interface Post_Resolvers { 103 | id: ( 104 | parent: Post, 105 | args: {}, 106 | ctx: Context, 107 | info: GraphQLResolveInfo, 108 | ) => string | Promise; 109 | 110 | title: ( 111 | parent: Post, 112 | args: {}, 113 | ctx: Context, 114 | info: GraphQLResolveInfo, 115 | ) => string | Promise; 116 | 117 | content: ( 118 | parent: Post, 119 | args: {}, 120 | ctx: Context, 121 | info: GraphQLResolveInfo, 122 | ) => string | Promise; 123 | 124 | published: ( 125 | parent: Post, 126 | args: {}, 127 | ctx: Context, 128 | info: GraphQLResolveInfo, 129 | ) => boolean | Promise; 130 | 131 | author: ( 132 | parent: Post, 133 | args: {}, 134 | ctx: Context, 135 | info: GraphQLResolveInfo, 136 | ) => User | Promise; 137 | } 138 | 139 | // Types for User 140 | export const User_defaultResolvers = { 141 | id: (parent: User) => parent.id, 142 | name: (parent: User) => parent.name, 143 | } 144 | 145 | export type User_Id_Resolver = ( 146 | parent: User, 147 | args: {}, 148 | ctx: Context, 149 | info: GraphQLResolveInfo, 150 | ) => string | Promise 151 | 152 | export type User_Name_Resolver = ( 153 | parent: User, 154 | args: {}, 155 | ctx: Context, 156 | info: GraphQLResolveInfo, 157 | ) => string | null | Promise 158 | 159 | export type User_Posts_Resolver = ( 160 | parent: User, 161 | args: {}, 162 | ctx: Context, 163 | info: GraphQLResolveInfo, 164 | ) => Post[] | Promise 165 | 166 | export interface User_Resolvers { 167 | id: ( 168 | parent: User, 169 | args: {}, 170 | ctx: Context, 171 | info: GraphQLResolveInfo, 172 | ) => string | Promise; 173 | 174 | name: ( 175 | parent: User, 176 | args: {}, 177 | ctx: Context, 178 | info: GraphQLResolveInfo, 179 | ) => string | null | Promise; 180 | 181 | posts: ( 182 | parent: User, 183 | args: {}, 184 | ctx: Context, 185 | info: GraphQLResolveInfo, 186 | ) => Post[] | Promise; 187 | } 188 | 189 | // Types for Mutation 190 | export const Mutation_defaultResolvers = {} 191 | 192 | export interface Mutation_Args_CreateUser { 193 | name: string; 194 | } 195 | 196 | export interface Mutation_Args_CreateDraft { 197 | title: string; 198 | content: string; 199 | authorId: string; 200 | } 201 | 202 | export interface Mutation_Args_DeletePost { 203 | id: string; 204 | } 205 | 206 | export interface Mutation_Args_Publish { 207 | id: string; 208 | } 209 | 210 | export type Mutation_CreateUser_Resolver = ( 211 | parent: {}, 212 | args: Mutation_Args_CreateUser, 213 | ctx: Context, 214 | info: GraphQLResolveInfo, 215 | ) => User | Promise 216 | 217 | export type Mutation_CreateDraft_Resolver = ( 218 | parent: {}, 219 | args: Mutation_Args_CreateDraft, 220 | ctx: Context, 221 | info: GraphQLResolveInfo, 222 | ) => Post | Promise 223 | 224 | export type Mutation_DeletePost_Resolver = ( 225 | parent: {}, 226 | args: Mutation_Args_DeletePost, 227 | ctx: Context, 228 | info: GraphQLResolveInfo, 229 | ) => Post | null | Promise 230 | 231 | export type Mutation_Publish_Resolver = ( 232 | parent: {}, 233 | args: Mutation_Args_Publish, 234 | ctx: Context, 235 | info: GraphQLResolveInfo, 236 | ) => Post | null | Promise 237 | 238 | export interface Mutation_Resolvers { 239 | createUser: ( 240 | parent: {}, 241 | args: Mutation_Args_CreateUser, 242 | ctx: Context, 243 | info: GraphQLResolveInfo, 244 | ) => User | Promise; 245 | 246 | createDraft: ( 247 | parent: {}, 248 | args: Mutation_Args_CreateDraft, 249 | ctx: Context, 250 | info: GraphQLResolveInfo, 251 | ) => Post | Promise; 252 | 253 | deletePost: ( 254 | parent: {}, 255 | args: Mutation_Args_DeletePost, 256 | ctx: Context, 257 | info: GraphQLResolveInfo, 258 | ) => Post | null | Promise; 259 | 260 | publish: ( 261 | parent: {}, 262 | args: Mutation_Args_Publish, 263 | ctx: Context, 264 | info: GraphQLResolveInfo, 265 | ) => Post | null | Promise; 266 | } 267 | 268 | export interface Resolvers { 269 | Query: Query_Resolvers; 270 | Post: Post_Resolvers; 271 | User: User_Resolvers; 272 | Mutation: Mutation_Resolvers; 273 | } 274 | -------------------------------------------------------------------------------- /packages/graphqlgen-templates/typescript-yoga/src/generated/graphqlgen.ts: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/prisma/graphqlgen, DO NOT EDIT. 2 | 3 | import { GraphQLResolveInfo } from 'graphql' 4 | import { Post, User, Context } from '../types' 5 | 6 | export namespace QueryResolvers { 7 | export const defaultResolvers = {} 8 | 9 | export interface ArgsPost { 10 | id: string 11 | } 12 | 13 | export type FeedResolver = ( 14 | parent: undefined, 15 | args: {}, 16 | ctx: Context, 17 | info: GraphQLResolveInfo, 18 | ) => Post[] | Promise 19 | 20 | export type DraftsResolver = ( 21 | parent: undefined, 22 | args: {}, 23 | ctx: Context, 24 | info: GraphQLResolveInfo, 25 | ) => Post[] | Promise 26 | 27 | export type PostResolver = ( 28 | parent: undefined, 29 | args: ArgsPost, 30 | ctx: Context, 31 | info: GraphQLResolveInfo, 32 | ) => Post | null | Promise 33 | 34 | export interface Type { 35 | feed: ( 36 | parent: undefined, 37 | args: {}, 38 | ctx: Context, 39 | info: GraphQLResolveInfo, 40 | ) => Post[] | Promise 41 | 42 | drafts: ( 43 | parent: undefined, 44 | args: {}, 45 | ctx: Context, 46 | info: GraphQLResolveInfo, 47 | ) => Post[] | Promise 48 | 49 | post: ( 50 | parent: undefined, 51 | args: ArgsPost, 52 | ctx: Context, 53 | info: GraphQLResolveInfo, 54 | ) => Post | null | Promise 55 | } 56 | } 57 | 58 | export namespace PostResolvers { 59 | export const defaultResolvers = { 60 | id: (parent: Post) => parent.id, 61 | title: (parent: Post) => parent.title, 62 | content: (parent: Post) => parent.content, 63 | published: (parent: Post) => parent.published, 64 | } 65 | 66 | export type IdResolver = ( 67 | parent: Post, 68 | args: {}, 69 | ctx: Context, 70 | info: GraphQLResolveInfo, 71 | ) => string | Promise 72 | 73 | export type TitleResolver = ( 74 | parent: Post, 75 | args: {}, 76 | ctx: Context, 77 | info: GraphQLResolveInfo, 78 | ) => string | Promise 79 | 80 | export type ContentResolver = ( 81 | parent: Post, 82 | args: {}, 83 | ctx: Context, 84 | info: GraphQLResolveInfo, 85 | ) => string | Promise 86 | 87 | export type PublishedResolver = ( 88 | parent: Post, 89 | args: {}, 90 | ctx: Context, 91 | info: GraphQLResolveInfo, 92 | ) => boolean | Promise 93 | 94 | export type AuthorResolver = ( 95 | parent: Post, 96 | args: {}, 97 | ctx: Context, 98 | info: GraphQLResolveInfo, 99 | ) => User | Promise 100 | 101 | export interface Type { 102 | id: ( 103 | parent: Post, 104 | args: {}, 105 | ctx: Context, 106 | info: GraphQLResolveInfo, 107 | ) => string | Promise 108 | 109 | title: ( 110 | parent: Post, 111 | args: {}, 112 | ctx: Context, 113 | info: GraphQLResolveInfo, 114 | ) => string | Promise 115 | 116 | content: ( 117 | parent: Post, 118 | args: {}, 119 | ctx: Context, 120 | info: GraphQLResolveInfo, 121 | ) => string | Promise 122 | 123 | published: ( 124 | parent: Post, 125 | args: {}, 126 | ctx: Context, 127 | info: GraphQLResolveInfo, 128 | ) => boolean | Promise 129 | 130 | author: ( 131 | parent: Post, 132 | args: {}, 133 | ctx: Context, 134 | info: GraphQLResolveInfo, 135 | ) => User | Promise 136 | } 137 | } 138 | 139 | export namespace UserResolvers { 140 | export const defaultResolvers = { 141 | id: (parent: User) => parent.id, 142 | name: (parent: User) => (parent.name === undefined ? null : parent.name), 143 | } 144 | 145 | export type IdResolver = ( 146 | parent: User, 147 | args: {}, 148 | ctx: Context, 149 | info: GraphQLResolveInfo, 150 | ) => string | Promise 151 | 152 | export type NameResolver = ( 153 | parent: User, 154 | args: {}, 155 | ctx: Context, 156 | info: GraphQLResolveInfo, 157 | ) => string | null | Promise 158 | 159 | export type PostsResolver = ( 160 | parent: User, 161 | args: {}, 162 | ctx: Context, 163 | info: GraphQLResolveInfo, 164 | ) => Post[] | Promise 165 | 166 | export interface Type { 167 | id: ( 168 | parent: User, 169 | args: {}, 170 | ctx: Context, 171 | info: GraphQLResolveInfo, 172 | ) => string | Promise 173 | 174 | name: ( 175 | parent: User, 176 | args: {}, 177 | ctx: Context, 178 | info: GraphQLResolveInfo, 179 | ) => string | null | Promise 180 | 181 | posts: ( 182 | parent: User, 183 | args: {}, 184 | ctx: Context, 185 | info: GraphQLResolveInfo, 186 | ) => Post[] | Promise 187 | } 188 | } 189 | 190 | export namespace MutationResolvers { 191 | export const defaultResolvers = {} 192 | 193 | export interface ArgsCreateUser { 194 | name: string | null 195 | } 196 | 197 | export interface ArgsCreateDraft { 198 | title: string 199 | content: string 200 | authorId: string 201 | } 202 | 203 | export interface ArgsDeletePost { 204 | id: string 205 | } 206 | 207 | export interface ArgsPublish { 208 | id: string 209 | } 210 | 211 | export type CreateUserResolver = ( 212 | parent: undefined, 213 | args: ArgsCreateUser, 214 | ctx: Context, 215 | info: GraphQLResolveInfo, 216 | ) => User | Promise 217 | 218 | export type CreateDraftResolver = ( 219 | parent: undefined, 220 | args: ArgsCreateDraft, 221 | ctx: Context, 222 | info: GraphQLResolveInfo, 223 | ) => Post | Promise 224 | 225 | export type DeletePostResolver = ( 226 | parent: undefined, 227 | args: ArgsDeletePost, 228 | ctx: Context, 229 | info: GraphQLResolveInfo, 230 | ) => Post | null | Promise 231 | 232 | export type PublishResolver = ( 233 | parent: undefined, 234 | args: ArgsPublish, 235 | ctx: Context, 236 | info: GraphQLResolveInfo, 237 | ) => Post | null | Promise 238 | 239 | export interface Type { 240 | createUser: ( 241 | parent: undefined, 242 | args: ArgsCreateUser, 243 | ctx: Context, 244 | info: GraphQLResolveInfo, 245 | ) => User | Promise 246 | 247 | createDraft: ( 248 | parent: undefined, 249 | args: ArgsCreateDraft, 250 | ctx: Context, 251 | info: GraphQLResolveInfo, 252 | ) => Post | Promise 253 | 254 | deletePost: ( 255 | parent: undefined, 256 | args: ArgsDeletePost, 257 | ctx: Context, 258 | info: GraphQLResolveInfo, 259 | ) => Post | null | Promise 260 | 261 | publish: ( 262 | parent: undefined, 263 | args: ArgsPublish, 264 | ctx: Context, 265 | info: GraphQLResolveInfo, 266 | ) => Post | null | Promise 267 | } 268 | } 269 | 270 | export interface Resolvers { 271 | Query: QueryResolvers.Type 272 | Post: PostResolvers.Type 273 | User: UserResolvers.Type 274 | Mutation: MutationResolvers.Type 275 | } 276 | -------------------------------------------------------------------------------- /packages/graphqlgen/src/introspection/flow-ast.ts: -------------------------------------------------------------------------------- 1 | import { parse as parseFlow } from '@babel/parser' 2 | import { 3 | ExportNamedDeclaration, 4 | File as FlowFile, 5 | Statement, 6 | TypeAlias, 7 | InterfaceDeclaration, 8 | ObjectTypeProperty, 9 | UnionTypeAnnotation, 10 | isTypeAlias, 11 | ObjectTypeSpreadProperty, 12 | isObjectTypeProperty, 13 | Identifier, 14 | StringLiteral, 15 | isObjectTypeAnnotation, 16 | FlowType, 17 | isStringTypeAnnotation, 18 | isNumberTypeAnnotation, 19 | isBooleanTypeAnnotation, 20 | isAnyTypeAnnotation, 21 | isGenericTypeAnnotation, 22 | isArrayTypeAnnotation, 23 | isStringLiteralTypeAnnotation, 24 | isNumberLiteralTypeAnnotation, 25 | isBooleanLiteralTypeAnnotation, 26 | isUnionTypeAnnotation, 27 | isNullLiteralTypeAnnotation, 28 | } from '@babel/types' 29 | 30 | import { 31 | TypeAliasDefinition, 32 | InterfaceDefinition, 33 | TypesMap, 34 | InternalInnerType, 35 | } from './types' 36 | import { 37 | createInterface, 38 | createInterfaceField, 39 | createTypeAlias, 40 | createTypeAnnotation, 41 | createTypeReferenceAnnotation, 42 | createLiteralTypeAnnotation, 43 | createAnonymousInterfaceAnnotation, 44 | createUnionTypeAnnotation, 45 | } from './factory' 46 | 47 | type ExtractableType = TypeAlias | InterfaceDeclaration 48 | 49 | function shouldExtractType(node: Statement) { 50 | return node.type === 'TypeAlias' || node.type === 'InterfaceDeclaration' 51 | } 52 | 53 | function findFlowTypes(sourceFile: FlowFile): ExtractableType[] { 54 | const statements = sourceFile.program.body 55 | 56 | const types = statements.filter(shouldExtractType) 57 | 58 | const typesFromNamedExport = statements 59 | .filter( 60 | node => 61 | node.type === 'ExportNamedDeclaration' && 62 | node.declaration !== null && 63 | shouldExtractType(node.declaration), 64 | ) 65 | .map(node => { 66 | return (node as ExportNamedDeclaration).declaration 67 | }) 68 | 69 | return [...types, ...typesFromNamedExport] as ExtractableType[] 70 | } 71 | 72 | export function isFieldOptional(node: ObjectTypeProperty) { 73 | if (!!node.optional) { 74 | return true 75 | } 76 | 77 | if (node.value.type === 'NullLiteralTypeAnnotation') { 78 | return true 79 | } 80 | 81 | if (node.value.type === 'UnionTypeAnnotation') { 82 | return (node.value as UnionTypeAnnotation).types.some( 83 | unionType => unionType.type === 'NullLiteralTypeAnnotation', 84 | ) 85 | } 86 | 87 | return false 88 | } 89 | 90 | export function computeType( 91 | node: FlowType, 92 | filePath: string, 93 | ): InternalInnerType { 94 | if (isStringTypeAnnotation(node)) { 95 | return createTypeAnnotation('string') 96 | } 97 | if (isNumberTypeAnnotation(node)) { 98 | return createTypeAnnotation('number') 99 | } 100 | if (isBooleanTypeAnnotation(node)) { 101 | return createTypeAnnotation('boolean') 102 | } 103 | if (isAnyTypeAnnotation(node)) { 104 | return createTypeAnnotation(null, { isAny: true }) 105 | } 106 | if ( 107 | (isGenericTypeAnnotation(node) && node.id.name === 'undefined') || 108 | isNullLiteralTypeAnnotation(node) 109 | ) { 110 | return createTypeAnnotation(null) 111 | } 112 | if (isGenericTypeAnnotation(node)) { 113 | const referenceTypeName = node.id.name 114 | 115 | return createTypeReferenceAnnotation(referenceTypeName) 116 | } 117 | if (isArrayTypeAnnotation(node)) { 118 | const computedType = computeType(node.elementType, filePath) 119 | 120 | if (computedType.kind !== 'TypeReferenceAnnotation') { 121 | computedType.isArray = true 122 | } 123 | 124 | return computedType 125 | } 126 | if ( 127 | isStringLiteralTypeAnnotation(node) || 128 | isNumberLiteralTypeAnnotation(node) || 129 | isBooleanLiteralTypeAnnotation(node) 130 | ) { 131 | const literalValue = node.value 132 | 133 | return createLiteralTypeAnnotation(typeof literalValue, literalValue) 134 | } 135 | 136 | if (isObjectTypeAnnotation(node)) { 137 | const interfaceFields = extractInterfaceFields(node.properties, filePath) 138 | 139 | return createAnonymousInterfaceAnnotation(interfaceFields) 140 | } 141 | 142 | if (isUnionTypeAnnotation(node)) { 143 | const unionTypes = node.types.map(unionType => 144 | computeType(unionType, filePath), 145 | ) 146 | 147 | return createUnionTypeAnnotation(unionTypes, filePath) 148 | } 149 | 150 | return createTypeAnnotation('_UNKNOWN_TYPE_') 151 | } 152 | 153 | function extractTypeAlias( 154 | typeName: string, 155 | typeAlias: TypeAlias, 156 | filePath: string, 157 | ): TypeAliasDefinition | InterfaceDefinition { 158 | if (isObjectTypeAnnotation(typeAlias.right)) { 159 | return extractInterface(typeName, typeAlias.right.properties, filePath) 160 | } else { 161 | const typeAliasType = computeType(typeAlias.right, filePath) 162 | 163 | return createTypeAlias(typeName, typeAliasType, filePath) 164 | } 165 | } 166 | 167 | function isSupportedTypeOfField( 168 | field: ObjectTypeProperty | ObjectTypeSpreadProperty, 169 | ) { 170 | return isObjectTypeProperty(field) 171 | } 172 | 173 | function extractInterfaceFieldName( 174 | field: ObjectTypeProperty | ObjectTypeSpreadProperty, 175 | ): string { 176 | if (isObjectTypeProperty(field)) { 177 | return field.key.type === 'Identifier' 178 | ? (field.key as Identifier).name 179 | : (field.key as StringLiteral).value 180 | } 181 | 182 | return '' 183 | } 184 | 185 | function extractInterfaceFields( 186 | fields: (ObjectTypeProperty | ObjectTypeSpreadProperty)[], 187 | filePath: string, 188 | ) { 189 | return fields.map(field => { 190 | const fieldName = extractInterfaceFieldName(field) 191 | 192 | if (!isSupportedTypeOfField(field)) { 193 | return createInterfaceField( 194 | '', 195 | createTypeAnnotation('_UNKNOWN_TYPE_'), 196 | filePath, 197 | false, 198 | ) 199 | } 200 | 201 | const fieldAsObjectTypeProperty = field as ObjectTypeProperty 202 | const fieldType = computeType(fieldAsObjectTypeProperty.value, filePath) 203 | const isOptional = isFieldOptional(fieldAsObjectTypeProperty) 204 | 205 | return createInterfaceField(fieldName, fieldType, filePath, isOptional) 206 | }) 207 | } 208 | 209 | function extractInterface( 210 | typeName: string, 211 | fields: (ObjectTypeProperty | ObjectTypeSpreadProperty)[], 212 | filePath: string, 213 | ): InterfaceDefinition { 214 | const interfaceFields = extractInterfaceFields(fields, filePath) 215 | 216 | return createInterface(typeName, interfaceFields) 217 | } 218 | 219 | export function buildFlowTypesMap(fileContent: string, filePath: string) { 220 | const ast = parseFlow(fileContent, { 221 | plugins: ['flow'], 222 | sourceType: 'module', 223 | }) 224 | 225 | const typesMap = findFlowTypes(ast).reduce( 226 | (acc, type) => { 227 | const typeName = type.id.name 228 | 229 | if (isTypeAlias(type)) { 230 | return { 231 | ...acc, 232 | [typeName]: extractTypeAlias(typeName, type, filePath), 233 | } 234 | } 235 | 236 | return { 237 | ...acc, 238 | [typeName]: extractInterface(typeName, type.body.properties, filePath), 239 | } 240 | }, 241 | {} as TypesMap, 242 | ) 243 | 244 | return typesMap 245 | } 246 | --------------------------------------------------------------------------------