Skip to content
This repository was archived by the owner on May 10, 2018. It is now read-only.

Commit 3ac90eb

Browse files
committed
Typescript and Javascript generators
0 parents  commit 3ac90eb

File tree

10 files changed

+438
-0
lines changed

10 files changed

+438
-0
lines changed

.circleci/config.yml

Whitespace-only changes.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
dist
3+
package-lock.json
4+
yarn.lock
5+
.vscode

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
src
2+
.circleci

package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "graphql-static-binding",
3+
"version": "0.1.0",
4+
"description": "Generate static binding files for a GraphQL schema",
5+
"main": "dist/index.js",
6+
"typings": "dist/index.d.ts",
7+
"scripts": {
8+
"prepublish": "npm run build",
9+
"start": "ts-node --inspect src/index.ts",
10+
"build": "tsc -d"
11+
},
12+
"keywords": [
13+
"graphql"
14+
],
15+
"author": "Kim Brandwijk <[email protected]>",
16+
"license": "MIT",
17+
"dependencies": {
18+
"graphql": "^0.12.3"
19+
},
20+
"devDependencies": {
21+
"@types/node": "^8.5.1",
22+
"ts-node": "^4.0.2",
23+
"typescript": "^2.6.2"
24+
}
25+
}

src/generators/javascript.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
GraphQLUnionType,
3+
GraphQLWrappingType,
4+
GraphQLObjectType,
5+
GraphQLInputObjectType,
6+
GraphQLInputField,
7+
GraphQLField,
8+
GraphQLInputType,
9+
GraphQLOutputType,
10+
GraphQLScalarType,
11+
GraphQLNamedType,
12+
13+
isNonNullType,
14+
isListType,
15+
GraphQLFieldMap,
16+
GraphQLEnumType,
17+
GraphQLType,
18+
GraphQLInterfaceType,
19+
} from 'graphql'
20+
21+
import { Generator } from '../types'
22+
23+
export const generator: Generator = {
24+
Main: renderMainMethod,
25+
}
26+
27+
function renderMainMethod(queryType: GraphQLObjectType, mutationType?: GraphQLObjectType | null, subscriptionType?: GraphQLObjectType | null) {
28+
return `exports.binding = {
29+
query: {
30+
${renderMainMethodFields(queryType.getFields())}
31+
}${mutationType ? `,
32+
mutation: {
33+
${renderMainMethodFields(mutationType.getFields())}
34+
}`: ''}${subscriptionType ? `,
35+
subscription: {
36+
${renderMainMethodFields(subscriptionType.getFields())}
37+
}`: ''}
38+
}`
39+
}
40+
41+
function renderMainMethodFields(fields: GraphQLFieldMap<any, any>): string {
42+
return Object.keys(fields).map(f => {
43+
const field = fields[f]
44+
return ` ${field.name}(args, info) {
45+
return /* TODO: Get actual implementation here from graphql-binding */
46+
}`
47+
}).join(',\n')
48+
}

src/generators/typescript.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import {
2+
GraphQLUnionType,
3+
GraphQLWrappingType,
4+
GraphQLObjectType,
5+
GraphQLInputObjectType,
6+
GraphQLInputField,
7+
GraphQLField,
8+
GraphQLInputType,
9+
GraphQLOutputType,
10+
GraphQLScalarType,
11+
GraphQLNamedType,
12+
13+
isNonNullType,
14+
isListType,
15+
GraphQLFieldMap,
16+
GraphQLEnumType,
17+
GraphQLType,
18+
GraphQLInterfaceType,
19+
} from 'graphql'
20+
21+
import { Generator } from '../types'
22+
23+
export const generator: Generator = {
24+
GraphQLUnionType: renderUnionType,
25+
GraphQLObjectType: renderObjectType,
26+
GraphQLInputObjectType: renderObjectType,
27+
GraphQLScalarType: renderScalarType,
28+
GraphQLEnumType: renderEnumType,
29+
GraphQLInterfaceType: renderObjectType,
30+
RootType: renderRootType,
31+
SchemaType: renderSchemaInterface,
32+
Main: renderMainMethod,
33+
}
34+
35+
const scalarMapping = {
36+
Int: 'number',
37+
String: 'string',
38+
ID: 'string | number',
39+
Float: 'number',
40+
Boolean: 'boolean'
41+
}
42+
43+
function renderMainMethod(queryType: GraphQLObjectType, mutationType?: GraphQLObjectType | null, subscriptionType?: GraphQLObjectType | null) {
44+
return `export const binding: Schema = {
45+
query: {
46+
${renderMainMethodFields(queryType.getFields())}
47+
}${mutationType ? `,
48+
mutation: {
49+
${renderMainMethodFields(mutationType.getFields())}
50+
}`: ''}${subscriptionType ? `,
51+
subscription: {
52+
${renderMainMethodFields(subscriptionType.getFields())}
53+
}`: ''}
54+
}`
55+
}
56+
57+
function renderMainMethodFields(fields: GraphQLFieldMap<any, any>): string {
58+
return Object.keys(fields).map(f => {
59+
const field = fields[f]
60+
return ` ${field.name}: (args, info): ${renderFieldType(field.type)}${!isNonNullType(field.type) ? ' | null' : ''} => { return /* TODO: Get actual implementation here from graphql-binding */ }`
61+
}).join(',\n')
62+
}
63+
64+
function renderScalarType(type: GraphQLScalarType): string {
65+
return `${type.description ? `/*
66+
${type.description}
67+
*/
68+
`: ''}export type ${type.name} = ${scalarMapping[type.name] || 'string'}`
69+
}
70+
71+
function renderEnumType(type: GraphQLEnumType): string {
72+
return `${renderDescription(type.description)}export enum ${type.name} {
73+
${type.getValues().map(e => ` ${e.name} = '${e.value}'`).join(',\n')}
74+
}`
75+
}
76+
77+
function renderRootType(type: GraphQLObjectType): string {
78+
const fieldDefinition = Object.keys(type.getFields()).map(f => {
79+
const field = type.getFields()[f]
80+
return ` ${field.name}: (args: {${field.args.length > 0 ? ' ': ''}${field.args.map(f => `${renderFieldName(f)}: ${renderFieldType(f.type)}`).join(', ')}${field.args.length > 0 ? ' ': ''}}, info: any) => ${renderFieldType(field.type)}${!isNonNullType(field.type) ? ' | null' : ''}`
81+
}).join('\n')
82+
83+
return renderInterfaceWrapper(type.name, type.description, type.getInterfaces(), fieldDefinition)
84+
}
85+
86+
function renderUnionType(type: GraphQLUnionType): string {
87+
return `${renderDescription(type.description)}export type ${type.name} = ${type.getTypes().map(t => t.name).join(' | ')}`
88+
}
89+
90+
function renderObjectType(type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType): string {
91+
const fieldDefinition = Object.keys(type.getFields()).map(f => {
92+
const field = type.getFields()[f]
93+
return ` ${renderFieldName(field)}: ${renderFieldType(field.type)}`
94+
}).join('\n')
95+
96+
let interfaces: GraphQLInterfaceType[] = []
97+
if (type instanceof GraphQLObjectType) {
98+
interfaces = type.getInterfaces()
99+
}
100+
101+
return renderInterfaceWrapper(type.name, type.description, interfaces, fieldDefinition)
102+
}
103+
104+
105+
106+
function renderFieldName(field: GraphQLInputField | GraphQLField<any, any>) {
107+
return `${field.name}${isNonNullType(field.type) ? '' : '?'}`
108+
}
109+
110+
function renderFieldType(type: GraphQLInputType | GraphQLOutputType) {
111+
if (isNonNullType(type)) {
112+
return renderFieldType((type as GraphQLWrappingType).ofType)
113+
}
114+
if (isListType(type)) {
115+
return `Array<${renderFieldType((type as GraphQLWrappingType).ofType)}>`
116+
}
117+
return (type as GraphQLNamedType).name
118+
}
119+
120+
function renderSchemaInterface(queryType: GraphQLObjectType, mutationType?: GraphQLObjectType | null, subscriptionType?: GraphQLObjectType | null) {
121+
return `export interface Schema {
122+
query: ${queryType.name}
123+
${mutationType ? ` mutation: ${mutationType.name}
124+
` : ''}${subscriptionType ? ` subscription: ${subscriptionType.name}
125+
` : ''}}`
126+
}
127+
128+
function renderInterfaceWrapper(typeName: string, typeDescription: string, interfaces: GraphQLInterfaceType[],fieldDefinition: string): string {
129+
return `${renderDescription(typeDescription)}export interface ${typeName}${interfaces.length > 0 ? ` extends ${interfaces.map(i => i.name).join(', ')}`: ''} {
130+
${fieldDefinition}
131+
}`
132+
}
133+
134+
function renderDescription(description?: string) {
135+
return `${description ? `/*
136+
${description}
137+
*/
138+
`: ''}`
139+
}

src/index.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { DocumentNode, Kind, ObjectTypeDefinitionNode, OperationTypeDefinitionNode, parse } from 'graphql';
2+
import { GraphQLSchema } from 'graphql/type/schema';
3+
import { buildASTSchema } from 'graphql/utilities/buildASTSchema';
4+
import { Generator } from './types'
5+
6+
export { Generator } from './types'
7+
8+
export function generateCode(schema: string, generator: Generator): string {
9+
const document: DocumentNode = parse(schema, { noLocation: true })
10+
const ast: GraphQLSchema = buildASTSchema(document)
11+
12+
// Create types
13+
const typeNames = Object
14+
.keys(ast.getTypeMap())
15+
.filter(typeName => !typeName.startsWith('__'))
16+
.filter(typeName => typeName !== ast.getQueryType().name)
17+
.filter(typeName => ast.getMutationType() ? typeName !== ast.getMutationType()!.name : true)
18+
.filter(typeName => ast.getSubscriptionType() ? typeName !== ast.getSubscriptionType()!.name : true)
19+
.sort((a,b) => ast.getType(a).constructor.name < ast.getType(b).constructor.name ? -1 : 1)
20+
21+
// Process all types
22+
const generatedClass = typeNames.map(typeName => {
23+
const type = ast.getTypeMap()[typeName]
24+
return generator[type.constructor.name] ? generator[type.constructor.name](type) : null
25+
})
26+
27+
// Special case 1: generate schema interface
28+
if (generator.SchemaType) {
29+
generatedClass.push(generator.SchemaType(ast.getQueryType(), ast.getMutationType(), ast.getSubscriptionType()))
30+
}
31+
32+
// Special case 2: generate root field interfaces
33+
if (generator.RootType) {
34+
generatedClass.push(generator.RootType(ast.getQueryType()))
35+
if (ast.getMutationType()) { generatedClass.push(generator.RootType(ast.getMutationType()!)) }
36+
if (ast.getSubscriptionType()) { generatedClass.push(generator.RootType(ast.getSubscriptionType()!)) }
37+
}
38+
39+
// Special case 3: the main method
40+
generatedClass.push(generator.Main(ast.getQueryType(), ast.getMutationType(), ast.getSubscriptionType()))
41+
42+
return generatedClass.filter(r => r).join('\n\n')
43+
}

0 commit comments

Comments
 (0)