diff --git a/src/context.js b/src/context.js index 506fda6..4464e1a 100644 --- a/src/context.js +++ b/src/context.js @@ -1,4 +1,5 @@ import createLoaders from './loaders' +import { loadExtension } from './extensions' const debug = require('debug')('graphbrainz:context') @@ -27,6 +28,6 @@ export function createContext(options = {}) { const context = { client, loaders } const { extensions = [] } = options return extensions.reduce((context, extension) => { - return extendContext(extension, context, options) + return extendContext(loadExtension(extension), context, options) }, context) } diff --git a/src/extensions/index.js b/src/extensions/index.js new file mode 100644 index 0000000..a2b3189 --- /dev/null +++ b/src/extensions/index.js @@ -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 +} diff --git a/src/index.js b/src/index.js index f879176..28f292d 100644 --- a/src/index.js +++ b/src/index.js @@ -6,40 +6,29 @@ import MusicBrainz from './api' import schema, { createSchema } from './schema' import { createContext } from './context' -const debug = require('debug')('graphbrainz') - const formatError = err => ({ message: err.message, locations: err.locations, 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 = ( { client = new MusicBrainz(), extensions = process.env.GRAPHBRAINZ_EXTENSIONS ? JSON.parse(process.env.GRAPHBRAINZ_EXTENSIONS) - : [ - './extensions/cover-art-archive', - './extensions/fanart-tv', - './extensions/mediawiki', - './extensions/the-audio-db' - ], + : defaultExtensions, ...middlewareOptions } = {} ) => { - debug(`Loading ${extensions.length} extension(s).`) - 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 options = { client, extensions, ...middlewareOptions } const DEV = process.env.NODE_ENV !== 'production' const graphiql = DEV || process.env.GRAPHBRAINZ_GRAPHIQL === 'true' return graphqlHTTP({ diff --git a/src/schema.js b/src/schema.js index 5925bc2..d5da489 100644 --- a/src/schema.js +++ b/src/schema.js @@ -2,6 +2,7 @@ import { GraphQLSchema, GraphQLObjectType, extendSchema, parse } from 'graphql' import { addResolveFunctionsToSchema } from 'graphql-tools' import { lookup, browse, search } from './queries' import { nodeField } from './types/node' +import { loadExtension } from './extensions' const debug = require('debug')('graphbrainz:schema') @@ -45,9 +46,9 @@ export function applyExtension(extension, schema, options = {}) { } export function createSchema(schema, options = {}) { - const extensions = options.extensions || [] + const { extensions = [] } = options return extensions.reduce((updatedSchema, extension) => { - return applyExtension(extension, updatedSchema, options) + return applyExtension(loadExtension(extension), updatedSchema, options) }, schema) } diff --git a/src/types/entity.js b/src/types/entity.js index 7e1dabd..81ca130 100644 --- a/src/types/entity.js +++ b/src/types/entity.js @@ -1,21 +1,24 @@ import { GraphQLInterfaceType } from 'graphql' import { mbid, connectionWithExtras } from './helpers' +const debug = require('debug')('graphbrainz:types/entity') + const Entity = new GraphQLInterfaceType({ name: 'Entity', description: 'An entity in the MusicBrainz schema.', resolveType(value, context, info) { if (value._type) { - const typeMap = info.schema.getTypeMap() let originalType try { originalType = require(`./${value._type}`).default } catch (err) { + debug(`Failed to load type: ${value._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] } }, diff --git a/src/types/node.js b/src/types/node.js index 8adab4a..210acb1 100644 --- a/src/types/node.js +++ b/src/types/node.js @@ -13,13 +13,21 @@ const { nodeInterface, nodeField } = nodeDefinitions( const entityType = toDashed(type) return loaders.lookup.load([entityType, id]) }, - obj => { + (obj, context, info) => { const type = TYPE_MODULES[obj._type] || obj._type - try { - return require(`./${type}`).default - } catch (err) { - debug(`Failed to load type: ${type}`) - return null + if (type) { + let originalType + try { + originalType = require(`./${type}`).default + } 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] } } ) diff --git a/test/schema.js b/test/_schema.js similarity index 99% rename from test/schema.js rename to test/_schema.js index 5ae0d43..4defba0 100644 --- a/test/schema.js +++ b/test/_schema.js @@ -1,8 +1,11 @@ import test from 'ava' import { graphql } from 'graphql' -import schema from '../src/schema' +import schemas from './helpers/schema' import context from './helpers/context' +const TEST_SCHEMA = process.env.TEST_SCHEMA || 'baseSchema' +const schema = schemas[TEST_SCHEMA] + function testData(t, query, handler) { return graphql(schema, query, null, context).then(result => { if (result.errors !== undefined) { diff --git a/test/base-schema.js b/test/base-schema.js new file mode 100644 index 0000000..21a1322 --- /dev/null +++ b/test/base-schema.js @@ -0,0 +1,2 @@ +process.env.TEST_SCHEMA = 'baseSchema' +require('./_schema') diff --git a/test/extended-schema.js b/test/extended-schema.js new file mode 100644 index 0000000..5092790 --- /dev/null +++ b/test/extended-schema.js @@ -0,0 +1,2 @@ +process.env.TEST_SCHEMA = 'extendedSchema' +require('./_schema') diff --git a/test/helpers/context.js b/test/helpers/context.js index 0dd5688..6e32613 100644 --- a/test/helpers/context.js +++ b/test/helpers/context.js @@ -1,7 +1,4 @@ -import createLoaders from '../../src/loaders' +import { createContext } from '../../src/context' import client from './client/musicbrainz' -export default { - client, - loaders: createLoaders(client) -} +export default createContext({ client }) diff --git a/test/helpers/schema.js b/test/helpers/schema.js new file mode 100644 index 0000000..a632ef1 --- /dev/null +++ b/test/helpers/schema.js @@ -0,0 +1,7 @@ +import schema, { createSchema } from '../../src/schema' +import { defaultExtensions } from '../../src' + +export default { + baseSchema: schema, + extendedSchema: createSchema(schema, { extensions: defaultExtensions }) +} diff --git a/test/snapshots/base-schema.js.md b/test/snapshots/base-schema.js.md new file mode 100644 index 0000000..40e668c --- /dev/null +++ b/test/snapshots/base-schema.js.md @@ -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', + }, + ], + }, + }, + } \ No newline at end of file diff --git a/test/snapshots/base-schema.js.snap b/test/snapshots/base-schema.js.snap new file mode 100644 index 0000000..ae26ae6 Binary files /dev/null and b/test/snapshots/base-schema.js.snap differ diff --git a/test/snapshots/extended-schema.js.md b/test/snapshots/extended-schema.js.md new file mode 100644 index 0000000..3feaf6c --- /dev/null +++ b/test/snapshots/extended-schema.js.md @@ -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', + }, + ], + }, + }, + } \ No newline at end of file diff --git a/test/snapshots/extended-schema.js.snap b/test/snapshots/extended-schema.js.snap new file mode 100644 index 0000000..6b18469 Binary files /dev/null and b/test/snapshots/extended-schema.js.snap differ diff --git a/test/snapshots/schema.js.md b/test/snapshots/schema.js.md deleted file mode 100644 index 076020f..0000000 --- a/test/snapshots/schema.js.md +++ /dev/null @@ -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', - }, - ], - }, - }, - } diff --git a/test/snapshots/schema.js.snap b/test/snapshots/schema.js.snap deleted file mode 100644 index 3d34741..0000000 Binary files a/test/snapshots/schema.js.snap and /dev/null differ diff --git a/test/types/node.js b/test/types/node.js index d6a8436..d3f3ba3 100644 --- a/test/types/node.js +++ b/test/types/node.js @@ -1,11 +1,15 @@ import test from 'ava' import Node from '../../src/types/node' import ReleaseGroup from '../../src/types/release-group' +import schema from '../../src/schema' 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 => { - t.is(Node.resolveType({ _type: 'foo' }), null) +test('returns undefined for unknown types', t => { + t.is(Node.resolveType({ _type: 'foo' }, {}, { schema }), undefined) })