Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ardatan/graphql-import
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.5.2
Choose a base ref
...
head repository: ardatan/graphql-import
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.5.3
Choose a head ref

Commits on Apr 29, 2018

  1. Add docs for importing root fields

    Fixes #61
    SpaceK33z authored Apr 29, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    46679c3 View commit details
  2. Copy the full SHA
    faaff94 View commit details

Commits on Apr 30, 2018

  1. Merge pull request #155 from SpaceK33z/patch-1

    Add docs for importing root fields
    schickling authored Apr 30, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    425ec6e View commit details

Commits on May 1, 2018

  1. Copy the full SHA
    fdf38bb View commit details
  2. Copy the full SHA
    c05a1da View commit details

Commits on May 2, 2018

  1. Copy the full SHA
    fdb7cd0 View commit details

Commits on May 3, 2018

  1. Copy the full SHA
    7d2a6e0 View commit details
  2. Copy the full SHA
    662ef4b View commit details

Commits on May 4, 2018

  1. Copy the full SHA
    ad9f9dc View commit details

Commits on May 5, 2018

  1. Copy the full SHA
    19e4740 View commit details

Commits on May 6, 2018

  1. Copy the full SHA
    824378f View commit details

Commits on May 8, 2018

  1. Copy the full SHA
    3a923dc View commit details
  2. Copy the full SHA
    201712a View commit details

Commits on May 9, 2018

  1. Copy the full SHA
    cf60eb6 View commit details

Commits on May 10, 2018

  1. Copy the full SHA
    78fb618 View commit details

Commits on May 14, 2018

  1. Copy the full SHA
    2c6c422 View commit details

Commits on May 16, 2018

  1. Copy the full SHA
    07348b4 View commit details

Commits on May 17, 2018

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    28d9a22 View commit details
  2. Copy the full SHA
    74f1f46 View commit details

Commits on May 18, 2018

  1. Copy the full SHA
    51c46c7 View commit details

Commits on May 19, 2018

  1. Copy the full SHA
    25d9975 View commit details

Commits on May 22, 2018

  1. Update all.graphql

    schickling authored May 22, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    09b6ed5 View commit details
  2. Update a.graphql

    schickling authored May 22, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    00f55b4 View commit details
  3. Copy the full SHA
    fc9a12c View commit details
  4. Copy the full SHA
    5739a25 View commit details
  5. fix: node 4

    timsuchanek committed May 22, 2018
    Copy the full SHA
    b954114 View commit details
  6. Merge pull request #176 from prismagraphql/wildcard-import

    WIP: Exclude root types (Query, Mutation & Subscription) from wildcard import
    timsuchanek authored May 22, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    13f49c9 View commit details
Showing with 288 additions and 98 deletions.
  1. +49 −3 README.md
  2. +1 −1 fixtures/imports-only/all.graphql
  3. +2 −2 fixtures/root-fields/a.graphql
  4. +19 −26 package.json
  5. +138 −30 src/index.test.ts
  6. +79 −36 src/index.ts
52 changes: 49 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# graphql-import

[![CircleCI](https://circleci.com/gh/graphcool/graphql-import.svg?style=shield)](https://circleci.com/gh/graphcool/graphql-import) [![npm version](https://badge.fury.io/js/graphql-import.svg)](https://badge.fury.io/js/graphql-import)
[![CircleCI](https://circleci.com/gh/prismagraphql/graphql-import.svg?style=shield)](https://circleci.com/gh/prismagraphql/graphql-import) [![npm version](https://badge.fury.io/js/graphql-import.svg)](https://badge.fury.io/js/graphql-import)

Import & export definitions in GraphQL SDL (also refered to as GraphQL modules)

@@ -24,7 +24,9 @@ const resolvers = {}
const schema = makeExecutableSchema({ typeDefs, resolvers })
```

## Example
## Examples

### Importing specific fields

Assume the following directory structure:

@@ -67,7 +69,7 @@ type C {
}
```

Running `console.log(importSchema('a.graphql'))` procudes the following output:
Running `console.log(importSchema('a.graphql'))` produces the following output:

```graphql
type A {
@@ -86,6 +88,48 @@ type C {
}
```

### Extending root fields

It is possible to import from a root field like `Query`;

`queries.graphql`

```graphql
type Query {
feed: [Post!]!
drafts: [Post!]!
}
```

`mutations.graphql`

```graphql
type Mutation {
publish(id: ID!): Post!
deletePost(id: ID!): Post!
}
```

`schema.graphql`

```graphql
# import Query.* from "queries.graphql"
# import Mutation.publish from "mutations.graphql"
```

Running `console.log(importSchema('schema.graphql'))` produces the following output:

```graphql
type Query {
feed: [Post!]!
drafts: [Post!]!
}

type Mutation {
publish(id: ID!): Post!
}
```

Please refer to [`src/index.test.ts`](https://github.com/graphcool/graphql-import/blob/master/src/index.test.ts) for more examples.

## Development
@@ -98,3 +142,5 @@ The [implementation documentation](https://graphql-import.now.sh/) documents how
- Namespaces
- Support importing from HTTP endpoints (or [Links](https://github.com/apollographql/apollo-link))
- Create RFC to add import syntax to GraphQL spec

<p align="center"><a href="https://oss.prisma.io"><img src="https://imgur.com/IMU2ERq.png" alt="Prisma" height="170px"></a></p>
2 changes: 1 addition & 1 deletion fixtures/imports-only/all.graphql
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# import Query.* from "a.graphql"
#import Query.* from "b.graphql"
# import Query.* from "b.graphql"
4 changes: 2 additions & 2 deletions fixtures/root-fields/a.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# import Query.posts from 'b.graphql'

type Dummy {
field: String
}
field: String
}
45 changes: 19 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
@@ -6,55 +6,48 @@
},
"license": "MIT",
"repository": "git@github.com:graphcool/graphql-import.git",
"files": [
"dist"
],
"files": ["dist"],
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"typescript": {
"definition": "dist/index.d.ts"
},
"nyc": {
"extension": [
".ts"
],
"require": [
"ts-node/register"
],
"include": [
"src/**/*.ts"
],
"exclude": [
"**/*.d.ts",
"**/*.test.ts"
],
"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",
"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",
"testlocal":
"npm run build && nyc --reporter lcov --reporter text ava-ts -u --verbose src/**/*.test.ts",
"test-only":
"npm run build && mkdir -p ~/reports && 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 .."
"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 .."
},
"peerDependencies": {
"graphql": "^0.11.0 || ^0.12.0 || ^0.13.0"
},
"devDependencies": {
"@types/graphql": "0.12.6",
"@types/lodash": "4.14.108",
"@types/node": "9.6.7",
"@types/lodash": "4.14.109",
"@types/node": "9.6.18",
"ava": "0.25.0",
"ava-ts": "0.24.4",
"ava-ts": "0.24.5",
"graphql": "0.13.2",
"nyc": "11.7.1",
"nyc": "11.8.0",
"tap-xunit": "2.3.0",
"ts-node": "6.0.1",
"tslint": "5.9.1",
"ts-node": "6.0.3",
"tslint": "5.10.0",
"tslint-config-standard": "7.0.0",
"typedoc": "0.11.1",
"typescript": "2.8.3"
168 changes: 138 additions & 30 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -183,7 +183,9 @@ test('importSchema: import all from objects', t => {
}`

const schemas = {
schemaA, schemaB, schemaC
schemaA,
schemaB,
schemaC,
}

const expectedSDL = `\
@@ -246,7 +248,7 @@ test(`importSchema: import all mix 'n match`, t => {
}`

const schemas = {
schemaB
schemaB,
}

const expectedSDL = `\
@@ -274,7 +276,6 @@ type C2 {
})

test(`importSchema: import all mix 'n match 2`, t => {

const schemaA = `
# import * from "fixtures/import-all/b.graphql"
@@ -309,23 +310,88 @@ type C2 {
t.is(importSchema(schemaA), expectedSDL)
})

test('importSchema: unions', t => {
test(`importSchema: import all - exclude Query/Mutation/Subscription type`, t => {
const schemaC = `
type C1 {
id: ID!
}
type C2 {
id: ID!
}
type C3 {
id: ID!
}
type Query {
hello: String!
}
type Mutation {
hello: String!
}
type Subscription {
hello: String!
}
`

const schemaB = `
# import * from 'schemaC'
type B {
hello: String!
c1: C1
c2: C2
}`

const schemaA = `
# import B from 'schemaB'
type Query {
greet: String!
}
type A {
# test 1
first: String
second: Float
b: B
}`

const schemas = {
schemaA,
schemaB,
schemaC,
}

const expectedSDL = `\
type Query {
greet: String!
}
type A {
first: String
second: Float
b: B
}
union B = C1 | C2
type B {
hello: String!
c1: C1
c2: C2
}
type C1 {
c1: ID
id: ID!
}
type C2 {
c2: ID
id: ID!
}
`
t.is(importSchema('fixtures/unions/a.graphql'), expectedSDL)
t.is(importSchema(schemaA, schemas), expectedSDL)
})

test('importSchema: scalar', t => {
@@ -436,7 +502,10 @@ type B2 implements B {
id: ID!
}
`
t.is(importSchema('fixtures/interfaces-implements-many/a.graphql'), expectedSDL)
t.is(
importSchema('fixtures/interfaces-implements-many/a.graphql'),
expectedSDL,
)
})

test('importSchema: input types', t => {
@@ -539,14 +608,14 @@ interface Node {

test('root field imports', t => {
const expectedSDL = `\
type Dummy {
field: String
}
type Query {
posts(filter: PostFilter): [Post]
}
type Dummy {
field: String
}
type Post {
field1: String
}
@@ -561,16 +630,16 @@ input PostFilter {

test('merged root field imports', t => {
const expectedSDL = `\
type Dummy {
field: String
}
type Query {
helloA: String
posts(filter: PostFilter): [Post]
hello: String
}
type Dummy {
field: String
}
type Post {
field1: String
}
@@ -603,36 +672,75 @@ type Shared {
})

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.`)
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.`)
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 post: Couldn't find type Post in any of the schemas.`)
const err = t.throws(
() => importSchema('fixtures/type-not-found/c.graphql'),
Error,
)
t.is(
err.message,
`Field post: 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.`)
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)
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.`)
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.`,
)
})

test('missing type on directive', t => {
const err = t.throws(() => importSchema('fixtures/type-not-found/g.graphql'), Error)
t.is(err.message, `Directive first: Couldn't find type first in any of the schemas.`)
const err = t.throws(
() => importSchema('fixtures/type-not-found/g.graphql'),
Error,
)
t.is(
err.message,
`Directive first: Couldn't find type first in any of the schemas.`,
)
})
115 changes: 79 additions & 36 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import * as fs from 'fs'
import { DefinitionNode, parse, print, TypeDefinitionNode, GraphQLObjectType, ObjectTypeDefinitionNode, DocumentNode, Kind } from 'graphql'
import { flatten, groupBy, includes } from 'lodash'
import {
DefinitionNode,
parse,
print,
TypeDefinitionNode,
GraphQLObjectType,
ObjectTypeDefinitionNode,
DocumentNode,
Kind,
} from 'graphql'
import { flatten, groupBy, includes, keyBy } from 'lodash'
import * as path from 'path'

import {
completeDefinitionPool,
ValidDefinitionNode,
} from './definition'
import { completeDefinitionPool, ValidDefinitionNode } from './definition'

/**
* Describes the information from a single import line
@@ -17,6 +23,8 @@ export interface RawModule {
from: string
}

const rootFields = ['Query', 'Mutation', 'Subscription']

const read = (schema: string, schemas?: { [key: string]: string }) => {
if (isFile(schema)) {
return fs.readFileSync(schema, { encoding: 'utf8' })
@@ -71,7 +79,10 @@ export function parseSDL(sdl: string): RawModule[] {
* @param filePath File path to the initial schema file
* @returns Single bundled schema with all imported types
*/
export function importSchema(schema: string, schemas?: { [key: string]: string }): string {
export function importSchema(
schema: string,
schemas?: { [key: string]: string },
): string {
const sdl = read(schema, schemas) || schema
const document = getDocumentFromSDL(sdl)

@@ -80,26 +91,33 @@ export function importSchema(schema: string, schemas?: { [key: string]: string }
['*'],
sdl,
schema,
schemas
schemas,
)

// 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 => includes(typesToFilter, d.name.value))
const otherFirstTypes = typeDefinitions[0].filter(d => !includes(typesToFilter, d.name.value))
const firstSet = otherFirstTypes.concat(firstTypes)
const firstTypes = flatten(typeDefinitions).filter(d =>
includes(rootFields, d.name.value),
)
const otherFirstTypes = typeDefinitions[0].filter(
d => !includes(rootFields, d.name.value),
)
const firstSet = firstTypes.concat(otherFirstTypes)
const processedTypeNames = []
const mergedFirstTypes = []
for (const type of firstSet) {
if (!includes(processedTypeNames, 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)
const existingType = mergedFirstTypes.find(
t => t.name.value === type.name.value,
)
existingType.fields = existingType.fields.concat(
(type as ObjectTypeDefinitionNode).fields,
)
}
}

@@ -138,11 +156,12 @@ function getDocumentFromSDL(sdl: string): DocumentNode {
* @returns True if SDL only contains comments and/or whitespaces
*/
function isEmptySDL(sdl: string): boolean {
return sdl
.split('\n')
.map(l => l.trim())
.filter(l => !(l.length === 0 || l.startsWith('#')))
.length === 0
return (
sdl
.split('\n')
.map(l => l.trim())
.filter(l => !(l.length === 0 || l.startsWith('#'))).length === 0
)
}

/**
@@ -165,7 +184,7 @@ function collectDefinitions(
schemas?: { [key: string]: string },
processedFiles: Set<string> = new Set(),
typeDefinitions: ValidDefinitionNode[][] = [],
allDefinitions: ValidDefinitionNode[][] = []
allDefinitions: ValidDefinitionNode[][] = [],
): {
allDefinitions: ValidDefinitionNode[][]
typeDefinitions: ValidDefinitionNode[][]
@@ -182,7 +201,8 @@ function collectDefinitions(
// Filter TypeDefinitionNodes by type and defined imports
const currentTypeDefinitions = filterImportedDefinitions(
imports,
document.definitions
document.definitions,
allDefinitions,
)

// Add typedefinitions to running total
@@ -208,9 +228,10 @@ function collectDefinitions(
// Process each file (recursively)
mergedModules.forEach(m => {
// If it was not yet processed (in case of circular dependencies)
const moduleFilePath = isFile(filePath) && isFile(m.from)
? path.resolve(path.join(dirname, m.from))
: m.from
const moduleFilePath =
isFile(filePath) && isFile(m.from)
? path.resolve(path.join(dirname, m.from))
: m.from
if (!processedFiles.has(moduleFilePath)) {
collectDefinitions(
m.imports,
@@ -219,7 +240,7 @@ function collectDefinitions(
schemas,
processedFiles,
typeDefinitions,
allDefinitions
allDefinitions,
)
}
})
@@ -238,26 +259,48 @@ function collectDefinitions(
*/
function filterImportedDefinitions(
imports: string[],
typeDefinitions: DefinitionNode[]
typeDefinitions: DefinitionNode[],
allDefinitions: ValidDefinitionNode[][] = [],
): ValidDefinitionNode[] {

// This should do something smart with fields

const filteredDefinitions = filterTypeDefinitions(typeDefinitions)

if (includes(imports, '*')) {
if (
imports.length === 1 &&
imports[0] === '*' &&
allDefinitions.length > 1
) {
const previousTypeDefinitions: { [key: string]: DefinitionNode } = keyBy(
flatten(allDefinitions.slice(0, allDefinitions.length - 1)).filter(
def => !includes(rootFields, def.name.value),
),
def => def.name.value,
)
return typeDefinitions.filter(
typeDef =>
typeDef.kind === 'ObjectTypeDefinition' &&
previousTypeDefinitions[typeDef.name.value],
) as ObjectTypeDefinitionNode[]
}
return filteredDefinitions
} else {
const result = filteredDefinitions.filter(d => includes(imports.map(i => i.split('.')[0]), d.name.value))
const fieldImports = imports
.filter(i => i.split('.').length > 1)
const result = filteredDefinitions.filter(d =>
includes(imports.map(i => i.split('.')[0]), 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 => includes(fields, f.name.value) || includes(fields, '*'))
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 => includes(fields, f.name.value) || includes(fields, '*'),
)
}

return result
@@ -271,7 +314,7 @@ function filterImportedDefinitions(
* @returns Relevant type definitions
*/
function filterTypeDefinitions(
definitions: DefinitionNode[]
definitions: DefinitionNode[],
): ValidDefinitionNode[] {
const validKinds = [
'DirectiveDefinition',
@@ -280,7 +323,7 @@ function filterTypeDefinitions(
'InterfaceTypeDefinition',
'EnumTypeDefinition',
'UnionTypeDefinition',
'InputObjectTypeDefinition'
'InputObjectTypeDefinition',
]
return definitions
.filter(d => includes(validKinds, d.kind))