mirror of
https://github.com/BradNut/graphbrainz
synced 2025-09-08 17:40:32 +00:00
More resolveType fixes, this time for Relay's nodeDefinitions (#51)
* More resolveType fixes, this time for Relay's nodeDefinitions * Add a loadExtension helper * Use createContext in test helpers
This commit is contained in:
parent
2de2e60079
commit
c3be2a2e98
18 changed files with 236 additions and 82 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import createLoaders from './loaders'
|
import createLoaders from './loaders'
|
||||||
|
import { loadExtension } from './extensions'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:context')
|
const debug = require('debug')('graphbrainz:context')
|
||||||
|
|
||||||
|
|
@ -27,6 +28,6 @@ export function createContext(options = {}) {
|
||||||
const context = { client, loaders }
|
const context = { client, loaders }
|
||||||
const { extensions = [] } = options
|
const { extensions = [] } = options
|
||||||
return extensions.reduce((context, extension) => {
|
return extensions.reduce((context, extension) => {
|
||||||
return extendContext(extension, context, options)
|
return extendContext(loadExtension(extension), context, options)
|
||||||
}, context)
|
}, context)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/extensions/index.js
Normal file
18
src/extensions/index.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
export function loadExtension(extensionModule) {
|
||||||
|
let extension
|
||||||
|
if (typeof extensionModule === 'string') {
|
||||||
|
extension = require(extensionModule)
|
||||||
|
} else {
|
||||||
|
extension = extensionModule
|
||||||
|
}
|
||||||
|
if (extension == null || typeof extension !== 'object') {
|
||||||
|
throw new Error(
|
||||||
|
`Expected ${extensionModule} to export an extension but instead ` +
|
||||||
|
`got: ${extension}`
|
||||||
|
)
|
||||||
|
} else if (extension.default) {
|
||||||
|
// ECMAScript module interop.
|
||||||
|
extension = extension.default
|
||||||
|
}
|
||||||
|
return extension
|
||||||
|
}
|
||||||
29
src/index.js
29
src/index.js
|
|
@ -6,40 +6,29 @@ import MusicBrainz from './api'
|
||||||
import schema, { createSchema } from './schema'
|
import schema, { createSchema } from './schema'
|
||||||
import { createContext } from './context'
|
import { createContext } from './context'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz')
|
|
||||||
|
|
||||||
const formatError = err => ({
|
const formatError = err => ({
|
||||||
message: err.message,
|
message: err.message,
|
||||||
locations: err.locations,
|
locations: err.locations,
|
||||||
stack: err.stack
|
stack: err.stack
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const defaultExtensions = [
|
||||||
|
require.resolve('./extensions/cover-art-archive'),
|
||||||
|
require.resolve('./extensions/fanart-tv'),
|
||||||
|
require.resolve('./extensions/mediawiki'),
|
||||||
|
require.resolve('./extensions/the-audio-db')
|
||||||
|
]
|
||||||
|
|
||||||
const middleware = (
|
const middleware = (
|
||||||
{
|
{
|
||||||
client = new MusicBrainz(),
|
client = new MusicBrainz(),
|
||||||
extensions = process.env.GRAPHBRAINZ_EXTENSIONS
|
extensions = process.env.GRAPHBRAINZ_EXTENSIONS
|
||||||
? JSON.parse(process.env.GRAPHBRAINZ_EXTENSIONS)
|
? JSON.parse(process.env.GRAPHBRAINZ_EXTENSIONS)
|
||||||
: [
|
: defaultExtensions,
|
||||||
'./extensions/cover-art-archive',
|
|
||||||
'./extensions/fanart-tv',
|
|
||||||
'./extensions/mediawiki',
|
|
||||||
'./extensions/the-audio-db'
|
|
||||||
],
|
|
||||||
...middlewareOptions
|
...middlewareOptions
|
||||||
} = {}
|
} = {}
|
||||||
) => {
|
) => {
|
||||||
debug(`Loading ${extensions.length} extension(s).`)
|
const options = { client, extensions, ...middlewareOptions }
|
||||||
const options = {
|
|
||||||
client,
|
|
||||||
extensions: extensions.map(extensionModule => {
|
|
||||||
if (typeof extensionModule === 'object') {
|
|
||||||
return extensionModule
|
|
||||||
}
|
|
||||||
const extension = require(extensionModule)
|
|
||||||
return extension.default ? extension.default : extension
|
|
||||||
}),
|
|
||||||
...middlewareOptions
|
|
||||||
}
|
|
||||||
const DEV = process.env.NODE_ENV !== 'production'
|
const DEV = process.env.NODE_ENV !== 'production'
|
||||||
const graphiql = DEV || process.env.GRAPHBRAINZ_GRAPHIQL === 'true'
|
const graphiql = DEV || process.env.GRAPHBRAINZ_GRAPHIQL === 'true'
|
||||||
return graphqlHTTP({
|
return graphqlHTTP({
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { GraphQLSchema, GraphQLObjectType, extendSchema, parse } from 'graphql'
|
||||||
import { addResolveFunctionsToSchema } from 'graphql-tools'
|
import { addResolveFunctionsToSchema } from 'graphql-tools'
|
||||||
import { lookup, browse, search } from './queries'
|
import { lookup, browse, search } from './queries'
|
||||||
import { nodeField } from './types/node'
|
import { nodeField } from './types/node'
|
||||||
|
import { loadExtension } from './extensions'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:schema')
|
const debug = require('debug')('graphbrainz:schema')
|
||||||
|
|
||||||
|
|
@ -45,9 +46,9 @@ export function applyExtension(extension, schema, options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSchema(schema, options = {}) {
|
export function createSchema(schema, options = {}) {
|
||||||
const extensions = options.extensions || []
|
const { extensions = [] } = options
|
||||||
return extensions.reduce((updatedSchema, extension) => {
|
return extensions.reduce((updatedSchema, extension) => {
|
||||||
return applyExtension(extension, updatedSchema, options)
|
return applyExtension(loadExtension(extension), updatedSchema, options)
|
||||||
}, schema)
|
}, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
import { GraphQLInterfaceType } from 'graphql'
|
import { GraphQLInterfaceType } from 'graphql'
|
||||||
import { mbid, connectionWithExtras } from './helpers'
|
import { mbid, connectionWithExtras } from './helpers'
|
||||||
|
|
||||||
|
const debug = require('debug')('graphbrainz:types/entity')
|
||||||
|
|
||||||
const Entity = new GraphQLInterfaceType({
|
const Entity = new GraphQLInterfaceType({
|
||||||
name: 'Entity',
|
name: 'Entity',
|
||||||
description: 'An entity in the MusicBrainz schema.',
|
description: 'An entity in the MusicBrainz schema.',
|
||||||
resolveType(value, context, info) {
|
resolveType(value, context, info) {
|
||||||
if (value._type) {
|
if (value._type) {
|
||||||
const typeMap = info.schema.getTypeMap()
|
|
||||||
let originalType
|
let originalType
|
||||||
try {
|
try {
|
||||||
originalType = require(`./${value._type}`).default
|
originalType = require(`./${value._type}`).default
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
debug(`Failed to load type: ${value._type}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Don't use `originalType`! The schema may have been extended in which
|
// Don't use `originalType`! The schema may have been extended in which
|
||||||
// case the types have all been replaced. Instead, find the current type
|
// case the types have all been replaced. Instead, find the current type
|
||||||
// of the same name.
|
// of the same name.
|
||||||
|
const typeMap = info.schema.getTypeMap()
|
||||||
return typeMap[originalType.name]
|
return typeMap[originalType.name]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,21 @@ const { nodeInterface, nodeField } = nodeDefinitions(
|
||||||
const entityType = toDashed(type)
|
const entityType = toDashed(type)
|
||||||
return loaders.lookup.load([entityType, id])
|
return loaders.lookup.load([entityType, id])
|
||||||
},
|
},
|
||||||
obj => {
|
(obj, context, info) => {
|
||||||
const type = TYPE_MODULES[obj._type] || obj._type
|
const type = TYPE_MODULES[obj._type] || obj._type
|
||||||
try {
|
if (type) {
|
||||||
return require(`./${type}`).default
|
let originalType
|
||||||
} catch (err) {
|
try {
|
||||||
debug(`Failed to load type: ${type}`)
|
originalType = require(`./${type}`).default
|
||||||
return null
|
} catch (err) {
|
||||||
|
debug(`Failed to load type: ${type}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Don't use `originalType`! The schema may have been extended in which
|
||||||
|
// case the types have all been replaced. Instead, find the current type
|
||||||
|
// of the same name.
|
||||||
|
const typeMap = info.schema.getTypeMap()
|
||||||
|
return typeMap[originalType.name]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import test from 'ava'
|
import test from 'ava'
|
||||||
import { graphql } from 'graphql'
|
import { graphql } from 'graphql'
|
||||||
import schema from '../src/schema'
|
import schemas from './helpers/schema'
|
||||||
import context from './helpers/context'
|
import context from './helpers/context'
|
||||||
|
|
||||||
|
const TEST_SCHEMA = process.env.TEST_SCHEMA || 'baseSchema'
|
||||||
|
const schema = schemas[TEST_SCHEMA]
|
||||||
|
|
||||||
function testData(t, query, handler) {
|
function testData(t, query, handler) {
|
||||||
return graphql(schema, query, null, context).then(result => {
|
return graphql(schema, query, null, context).then(result => {
|
||||||
if (result.errors !== undefined) {
|
if (result.errors !== undefined) {
|
||||||
2
test/base-schema.js
Normal file
2
test/base-schema.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
process.env.TEST_SCHEMA = 'baseSchema'
|
||||||
|
require('./_schema')
|
||||||
2
test/extended-schema.js
Normal file
2
test/extended-schema.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
process.env.TEST_SCHEMA = 'extendedSchema'
|
||||||
|
require('./_schema')
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import createLoaders from '../../src/loaders'
|
import { createContext } from '../../src/context'
|
||||||
import client from './client/musicbrainz'
|
import client from './client/musicbrainz'
|
||||||
|
|
||||||
export default {
|
export default createContext({ client })
|
||||||
client,
|
|
||||||
loaders: createLoaders(client)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
7
test/helpers/schema.js
Normal file
7
test/helpers/schema.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import schema, { createSchema } from '../../src/schema'
|
||||||
|
import { defaultExtensions } from '../../src'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
baseSchema: schema,
|
||||||
|
extendedSchema: createSchema(schema, { extensions: defaultExtensions })
|
||||||
|
}
|
||||||
81
test/snapshots/base-schema.js.md
Normal file
81
test/snapshots/base-schema.js.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Snapshot report for `test/base-schema.js`
|
||||||
|
|
||||||
|
The actual snapshot is saved in `base-schema.js.snap`.
|
||||||
|
|
||||||
|
Generated by [AVA](https://ava.li).
|
||||||
|
|
||||||
|
## areas have a type and typeID
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
search: {
|
||||||
|
areas: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: 'Germany',
|
||||||
|
type: 'Country',
|
||||||
|
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'East Germany',
|
||||||
|
type: 'Country',
|
||||||
|
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Germany',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Germany',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Brakel',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
## baseSchema: areas have a type and typeID
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
search: {
|
||||||
|
areas: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: 'Germany',
|
||||||
|
type: 'Country',
|
||||||
|
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'East Germany',
|
||||||
|
type: 'Country',
|
||||||
|
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Germany',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Germany',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Brakel',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
BIN
test/snapshots/base-schema.js.snap
Normal file
BIN
test/snapshots/base-schema.js.snap
Normal file
Binary file not shown.
81
test/snapshots/extended-schema.js.md
Normal file
81
test/snapshots/extended-schema.js.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Snapshot report for `test/extended-schema.js`
|
||||||
|
|
||||||
|
The actual snapshot is saved in `extended-schema.js.snap`.
|
||||||
|
|
||||||
|
Generated by [AVA](https://ava.li).
|
||||||
|
|
||||||
|
## areas have a type and typeID
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
search: {
|
||||||
|
areas: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: 'Germany',
|
||||||
|
type: 'Country',
|
||||||
|
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'East Germany',
|
||||||
|
type: 'Country',
|
||||||
|
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Germany',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Germany',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Brakel',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
## extendedSchema: areas have a type and typeID
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
search: {
|
||||||
|
areas: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: 'Germany',
|
||||||
|
type: 'Country',
|
||||||
|
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'East Germany',
|
||||||
|
type: 'Country',
|
||||||
|
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Germany',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Germany',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Brakel',
|
||||||
|
type: 'City',
|
||||||
|
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
BIN
test/snapshots/extended-schema.js.snap
Normal file
BIN
test/snapshots/extended-schema.js.snap
Normal file
Binary file not shown.
|
|
@ -1,43 +0,0 @@
|
||||||
# Snapshot report for `test/schema.js`
|
|
||||||
|
|
||||||
The actual snapshot is saved in `schema.js.snap`.
|
|
||||||
|
|
||||||
Generated by [AVA](https://ava.li).
|
|
||||||
|
|
||||||
## areas have a type and typeID
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
{
|
|
||||||
search: {
|
|
||||||
areas: {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
name: 'Germany',
|
|
||||||
type: 'Country',
|
|
||||||
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'East Germany',
|
|
||||||
type: 'Country',
|
|
||||||
typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'New Germany',
|
|
||||||
type: 'City',
|
|
||||||
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'New Germany',
|
|
||||||
type: 'City',
|
|
||||||
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Brakel',
|
|
||||||
type: 'City',
|
|
||||||
typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
|
@ -1,11 +1,15 @@
|
||||||
import test from 'ava'
|
import test from 'ava'
|
||||||
import Node from '../../src/types/node'
|
import Node from '../../src/types/node'
|
||||||
import ReleaseGroup from '../../src/types/release-group'
|
import ReleaseGroup from '../../src/types/release-group'
|
||||||
|
import schema from '../../src/schema'
|
||||||
|
|
||||||
test('loads types from their module', t => {
|
test('loads types from their module', t => {
|
||||||
t.is(Node.resolveType({ _type: 'release-group' }), ReleaseGroup)
|
t.is(
|
||||||
|
Node.resolveType({ _type: 'release-group' }, {}, { schema }),
|
||||||
|
ReleaseGroup
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('returns null for unknown types', t => {
|
test('returns undefined for unknown types', t => {
|
||||||
t.is(Node.resolveType({ _type: 'foo' }), null)
|
t.is(Node.resolveType({ _type: 'foo' }, {}, { schema }), undefined)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue