Skip to content

Commit

Permalink
feat: importing root fields (Query.xxx) is implemented
Browse files Browse the repository at this point in the history
Closes #8
  • Loading branch information
kbrandwijk authored Jan 4, 2018
1 parent 67ae175 commit 09f4319
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 8 deletions.
15 changes: 14 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@ jobs:
- checkout
- run: sudo npm install -g yarn semantic-release@12.0.0
- run: yarn install
- run: yarn test
- run:
name: test
command: |
mkdir -p ~/reports
yarn test
- store_test_results:
path: ~/reports
prefix: tests
- store_artifacts:
path: ~/reports
prefix: tests
- store_artifacts:
path: coverage
prefix: coverage
- run: semantic-release
- run: yarn docs
# needs proper NOW setup first
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ dist
package-lock.json
*.log
yarn.lock
docs/now.json
docs/now.json
reports
.nyc_output
coverage
9 changes: 9 additions & 0 deletions fixtures/merged-root-fields/a.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# import Query.* from 'b.graphql'

type Query {
helloA: String
}

type Dummy {
field: String
}
12 changes: 12 additions & 0 deletions fixtures/merged-root-fields/b.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type Query {
posts(filter: PostFilter): [Post]
hello: String
}

type Post {
field1: String
}

input PostFilter {
field3: Int
}
5 changes: 5 additions & 0 deletions fixtures/root-fields/a.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# import Query.posts from 'b.graphql'

type Dummy {
field: String
}
12 changes: 12 additions & 0 deletions fixtures/root-fields/b.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type Query {
posts(filter: PostFilter): [Post]
hello: String
}

type Post {
field1: String
}

input PostFilter {
field3: Int
}
3 changes: 3 additions & 0 deletions fixtures/type-not-found/a.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type Dummy {
test: Post
}
3 changes: 3 additions & 0 deletions fixtures/type-not-found/b.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface Boo {
test: Post
}
3 changes: 3 additions & 0 deletions fixtures/type-not-found/c.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type Test {
myfield(post: Post): String
}
3 changes: 3 additions & 0 deletions fixtures/type-not-found/d.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type Test implements MyInterface {
test: String
}
5 changes: 5 additions & 0 deletions fixtures/type-not-found/e.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
union A = B | C

type B {
field: String
}
3 changes: 3 additions & 0 deletions fixtures/type-not-found/f.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
input Test {
myfield: Post
}
25 changes: 24 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,40 @@
"typescript": {
"definition": "dist/index.d.ts"
},
"nyc": {
"extension": [
".ts"
],
"require": [
"ts-node/register"
],
"include": [
"src/**/*.ts"
],
"exclude": [
"**/*.d.ts",
"**/*.test.ts"
],
"all": true,
"sourceMap": true,
"instrument": true
},
"scripts": {
"prepare": "npm run build",
"build": "rm -rf dist && tsc -d",
"test-only": "npm run build && ava --verbose dist/**/*.test.js",
"testlocal": "npm run build && nyc --reporter lcov --reporter text ava-ts --verbose src/**/*.test.ts",
"test-only": "npm run build && nyc --reporter lcov ava-ts --verbose src/**/*.test.ts --tap | tap-xunit > ~/reports/ava.xml",
"test": "tslint src/**/*.ts && npm run test-only",
"docs": "typedoc --out docs src/index.ts --hideGenerator --exclude **/*.test.ts",
"docs:publish": "cp ./now.json ./docs && cd docs && now --public -f && now alias && now rm --yes --safe graphql-import & cd .."
},
"devDependencies": {
"@types/node": "8.5.5",
"ava": "0.24.0",
"ava-ts": "0.23.0",
"nyc": "11.4.1",
"tap-xunit": "2.2.0",
"ts-node": "4.1.0",
"tslint": "5.8.0",
"tslint-config-standard": "7.0.0",
"typedoc": "0.9.0",
Expand Down
84 changes: 84 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ test('parseImportLine: parse single import', t => {
})
})

test('parseImportLine: invalid', t => {
t.throws(() => parseImportLine(`import from "schema.graphql"`), Error)
})

test('parseImportLine: invalid 2', t => {
t.throws(() => parseImportLine(`import A from ""`), Error)
})

test('parseImportLine: parse multi import', t => {
t.deepEqual(parseImportLine(`import A, B from "schema.graphql"`), {
imports: ['A', 'B'],
Expand Down Expand Up @@ -271,3 +279,79 @@ interface Node {
const actualSDL = importSchema('fixtures/relative-paths/src/schema.graphql')
t.is(actualSDL, expectedSDL)
})

test('root field imports', t => {
const expectedSDL = `\
type Dummy {
field: String
}
type Query {
posts(filter: PostFilter): [Post]
}
input PostFilter {
field3: Int
}
type Post {
field1: String
}
`
const actualSDL = importSchema('fixtures/root-fields/a.graphql')
t.is(actualSDL, expectedSDL)
})

test('merged root field imports', t => {
const expectedSDL = `\
type Dummy {
field: String
}
type Query {
helloA: String
posts(filter: PostFilter): [Post]
hello: String
}
input PostFilter {
field3: Int
}
type Post {
field1: String
}
`
const actualSDL = importSchema('fixtures/merged-root-fields/a.graphql')
t.is(actualSDL, expectedSDL)
})

test('missing type on type', t => {
const err = t.throws(() => importSchema('fixtures/type-not-found/a.graphql'), Error)
t.is(err.message, `Field test: Couldn't find type Post in any of the schemas.`)
})

test('missing type on interface', t => {
const err = t.throws(() => importSchema('fixtures/type-not-found/b.graphql'), Error)
t.is(err.message, `Field test: Couldn't find type Post in any of the schemas.`)
})

test('missing type on input type', t => {
const err = t.throws(() => importSchema('fixtures/type-not-found/c.graphql'), Error)
t.is(err.message, `Field myfield: Couldn't find type Post in any of the schemas.`)
})

test('missing interface type', t => {
const err = t.throws(() => importSchema('fixtures/type-not-found/d.graphql'), Error)
t.is(err.message, `Couldn't find interface MyInterface in any of the schemas.`)
})

test('missing union type', t => {
const err = t.throws(() => importSchema('fixtures/type-not-found/e.graphql'), Error)
t.is(err.message, `Couldn't find type C in any of the schemas.`)
})

test('missing type on input type', t => {
const err = t.throws(() => importSchema('fixtures/type-not-found/f.graphql'), Error)
t.is(err.message, `Field myfield: Couldn't find type Post in any of the schemas.`)
})
45 changes: 40 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as fs from 'fs'
import { DefinitionNode, parse, print, TypeDefinitionNode } from 'graphql'
import { flatten } from 'lodash'
import { DefinitionNode, parse, print, TypeDefinitionNode, GraphQLObjectType, ObjectTypeDefinitionNode } from 'graphql'
import { flatten, groupBy } from 'lodash'
import * as path from 'path'

import { completeDefinitionPool } from './definition'
Expand Down Expand Up @@ -32,7 +32,7 @@ const read: (f: string) => string =
export function parseImportLine(importLine: string): RawModule {
// Apply regex to import line
const matches = importLine.match(/^import (\*|(.*)) from ('|")(.*)('|")$/)
if (matches.length !== 6) {
if (!matches || matches.length !== 6 || !matches[4]) {
throw new Error(`Too few regex matches: ${matches}`)
}

Expand Down Expand Up @@ -80,9 +80,28 @@ export function importSchema(filePath: string): string {
)

// Post processing of the final schema (missing types, unused types, etc.)
// Query, Mutation and Subscription should be merged
// And should always be in the first set, to make sure they
// are not filtered out.
const typesToFilter = ['Query', 'Mutation', 'Subscription']
const firstTypes = flatten(typeDefinitions).filter(d => typesToFilter.includes(d.name.value))
const otherFirstTypes = typeDefinitions[0].filter(d => !typesToFilter.includes(d.name.value))
const firstSet = otherFirstTypes.concat(firstTypes)
const processedTypeNames = []
const mergedFirstTypes = []
for (const type of firstSet) {
if (!processedTypeNames.includes(type.name.value)) {
processedTypeNames.push(type.name.value)
mergedFirstTypes.push(type)
} else {
const existingType = mergedFirstTypes.find(t => t.name.value === type.name.value)
existingType.fields = existingType.fields.concat((type as ObjectTypeDefinitionNode).fields)
}
}

document.definitions = completeDefinitionPool(
flatten(allDefinitions),
typeDefinitions[0],
firstSet,
flatten(typeDefinitions),
)

Expand Down Expand Up @@ -169,11 +188,27 @@ function filterImportedDefinitions(
imports: string[],
typeDefinitions: DefinitionNode[]
): TypeDefinitionNode[] {

// This should do something smart with fields

const filteredDefinitions = filterTypeDefinitions(typeDefinitions)

if (imports.includes('*')) {
return filteredDefinitions
} else {
return filteredDefinitions.filter(d => imports.includes(d.name.value))
const result = filteredDefinitions.filter(d => imports.map(i => i.split('.')[0]).includes(d.name.value))
const fieldImports = imports
.filter(i => i.split('.').length > 1)
const groupedFieldImports = groupBy(fieldImports, x => x.split('.')[0])

for (const rootType in groupedFieldImports) {
const fields = groupedFieldImports[rootType].map(x => x.split('.')[1]);
(filteredDefinitions.find(def => def.name.value === rootType) as ObjectTypeDefinitionNode).fields =
(filteredDefinitions.find(def => def.name.value === rootType) as ObjectTypeDefinitionNode).fields
.filter(f => fields.includes(f.name.value) || fields.includes('*'))
}

return result
}
}

Expand Down

0 comments on commit 09f4319

Please sign in to comment.