mirror of
https://github.com/BradNut/graphbrainz
synced 2025-09-08 17:40:32 +00:00
Adopt Prettier (but keep Standard) via ESLint (#48)
This commit is contained in:
parent
50888c9fb9
commit
8c0a9f44ef
61 changed files with 1830 additions and 1487 deletions
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/coverage
|
||||||
|
/lib
|
||||||
18
.eslintrc.js
Normal file
18
.eslintrc.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'standard',
|
||||||
|
'prettier',
|
||||||
|
'prettier/standard'
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
es6: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
plugins: ['prettier'],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': ['error', {
|
||||||
|
singleQuote: true,
|
||||||
|
semi: false
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
18
package.json
18
package.json
|
|
@ -30,8 +30,9 @@
|
||||||
"clean": "npm run clean:lib",
|
"clean": "npm run clean:lib",
|
||||||
"clean:lib": "rm -rf lib",
|
"clean:lib": "rm -rf lib",
|
||||||
"deploy": "./scripts/deploy.sh",
|
"deploy": "./scripts/deploy.sh",
|
||||||
"lint": "standard --verbose | snazzy",
|
"format": "npm run lint:fix",
|
||||||
"lint:fix": "standard --verbose --fix",
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint --fix .",
|
||||||
"postinstall": "postinstall-build lib --script build:lib",
|
"postinstall": "postinstall-build lib --script build:lib",
|
||||||
"prepublish": "npm run clean:lib && npm run build:lib",
|
"prepublish": "npm run clean:lib && npm run build:lib",
|
||||||
"preversion": "npm run update-schema && npm run build:docs && git add schema.json docs",
|
"preversion": "npm run update-schema && npm run build:docs && git add schema.json docs",
|
||||||
|
|
@ -100,15 +101,22 @@
|
||||||
"coveralls": "^3.0.0",
|
"coveralls": "^3.0.0",
|
||||||
"cross-env": "^5.1.1",
|
"cross-env": "^5.1.1",
|
||||||
"doctoc": "^1.3.0",
|
"doctoc": "^1.3.0",
|
||||||
|
"eslint": "^4.10.0",
|
||||||
|
"eslint-config-prettier": "^2.7.0",
|
||||||
|
"eslint-config-standard": "^10.2.1",
|
||||||
|
"eslint-plugin-import": "^2.8.0",
|
||||||
"eslint-plugin-markdown": "^1.0.0-beta.6",
|
"eslint-plugin-markdown": "^1.0.0-beta.6",
|
||||||
|
"eslint-plugin-node": "^5.2.1",
|
||||||
|
"eslint-plugin-prettier": "^2.3.1",
|
||||||
|
"eslint-plugin-promise": "^3.6.0",
|
||||||
|
"eslint-plugin-standard": "^3.0.1",
|
||||||
"graphql-markdown": "^3.2.0",
|
"graphql-markdown": "^3.2.0",
|
||||||
"nodemon": "^1.11.0",
|
"nodemon": "^1.11.0",
|
||||||
"nyc": "^11.1.0",
|
"nyc": "^11.1.0",
|
||||||
|
"prettier": "^1.8.0",
|
||||||
"rimraf": "^2.6.1",
|
"rimraf": "^2.6.1",
|
||||||
"sepia": "^2.0.2",
|
"sepia": "^2.0.2",
|
||||||
"sinon": "^4.0.2",
|
"sinon": "^4.0.2"
|
||||||
"snazzy": "^7.0.0",
|
|
||||||
"standard": "^10.0.3"
|
|
||||||
},
|
},
|
||||||
"standard": {
|
"standard": {
|
||||||
"parser": "babel-eslint"
|
"parser": "babel-eslint"
|
||||||
|
|
|
||||||
|
|
@ -10,40 +10,43 @@ const extensionModules = [
|
||||||
'the-audio-db'
|
'the-audio-db'
|
||||||
]
|
]
|
||||||
|
|
||||||
function getSchemaJSON (schema) {
|
function getSchemaJSON(schema) {
|
||||||
return graphql(schema, introspectionQuery).then(result => result.data)
|
return graphql(schema, introspectionQuery).then(result => result.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(extensionModules.map(extensionModule => {
|
Promise.all(
|
||||||
const extension = require(`../src/extensions/${extensionModule}`).default
|
extensionModules.map(extensionModule => {
|
||||||
console.log(`Generating docs for “${extension.name}” extension...`)
|
const extension = require(`../src/extensions/${extensionModule}`).default
|
||||||
const schema = createSchema(baseSchema, { extensions: [extension] })
|
console.log(`Generating docs for “${extension.name}” extension...`)
|
||||||
return Promise.all([
|
const schema = createSchema(baseSchema, { extensions: [extension] })
|
||||||
getSchemaJSON(baseSchema),
|
return Promise.all([getSchemaJSON(baseSchema), getSchemaJSON(schema)]).then(
|
||||||
getSchemaJSON(schema)
|
([baseSchemaJSON, schemaJSON]) => {
|
||||||
]).then(([baseSchemaJSON, schemaJSON]) => {
|
const outputSchema = diffSchema(baseSchemaJSON, schemaJSON, {
|
||||||
const outputSchema = diffSchema(baseSchemaJSON, schemaJSON, {
|
processTypeDiff(type) {
|
||||||
processTypeDiff (type) {
|
if (type.description === undefined) {
|
||||||
if (type.description === undefined) {
|
type.description =
|
||||||
type.description =
|
':small_blue_diamond: *This type has been extended. See the ' +
|
||||||
':small_blue_diamond: *This type has been extended. See the ' +
|
'[base schema](../types.md)\nfor a description and additional ' +
|
||||||
'[base schema](../types.md)\nfor a description and additional ' +
|
'fields.*'
|
||||||
'fields.*'
|
}
|
||||||
}
|
return type
|
||||||
return type
|
}
|
||||||
|
})
|
||||||
|
const outputPath = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
`../docs/extensions/${extensionModule}.md`
|
||||||
|
)
|
||||||
|
return updateSchema(outputPath, outputSchema, {
|
||||||
|
unknownTypeURL: '../types.md',
|
||||||
|
headingLevel: 2
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
const outputPath = path.resolve(
|
|
||||||
__dirname,
|
|
||||||
`../docs/extensions/${extensionModule}.md`
|
|
||||||
)
|
)
|
||||||
return updateSchema(outputPath, outputSchema, {
|
|
||||||
unknownTypeURL: '../types.md',
|
|
||||||
headingLevel: 2
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})).then((extensions) => {
|
)
|
||||||
console.log(`Built docs for ${extensions.length} extension(s).`)
|
.then(extensions => {
|
||||||
}).catch(err => {
|
console.log(`Built docs for ${extensions.length} extension(s).`)
|
||||||
console.log('Error:', err)
|
})
|
||||||
})
|
.catch(err => {
|
||||||
|
console.log('Error:', err)
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ import { graphql, introspectionQuery, printSchema } from 'graphql'
|
||||||
import schema from '../src/schema'
|
import schema from '../src/schema'
|
||||||
|
|
||||||
if (process.argv[2] === '--json') {
|
if (process.argv[2] === '--json') {
|
||||||
graphql(schema, introspectionQuery).then(result => {
|
graphql(schema, introspectionQuery)
|
||||||
console.log(JSON.stringify(result, null, 2))
|
.then(result => {
|
||||||
}).catch(err => {
|
console.log(JSON.stringify(result, null, 2))
|
||||||
console.error(err)
|
})
|
||||||
})
|
.catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log(printSchema(schema))
|
console.log(printSchema(schema))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,33 +21,35 @@ const RETRY_CODES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClientError extends ExtendableError {
|
export class ClientError extends ExtendableError {
|
||||||
constructor (message, statusCode) {
|
constructor(message, statusCode) {
|
||||||
super(message)
|
super(message)
|
||||||
this.statusCode = statusCode
|
this.statusCode = statusCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Client {
|
export default class Client {
|
||||||
constructor ({
|
constructor(
|
||||||
baseURL,
|
{
|
||||||
userAgent = `${pkg.name}/${pkg.version} ` +
|
baseURL,
|
||||||
`( ${pkg.homepage || pkg.author.url || pkg.author.email} )`,
|
userAgent = `${pkg.name}/${pkg.version} ` +
|
||||||
extraHeaders = {},
|
`( ${pkg.homepage || pkg.author.url || pkg.author.email} )`,
|
||||||
errorClass = ClientError,
|
extraHeaders = {},
|
||||||
timeout = 60000,
|
errorClass = ClientError,
|
||||||
limit = 1,
|
timeout = 60000,
|
||||||
period = 1000,
|
limit = 1,
|
||||||
concurrency = 10,
|
period = 1000,
|
||||||
retries = 10,
|
concurrency = 10,
|
||||||
// It's OK for `retryDelayMin` to be less than one second, even 0, because
|
retries = 10,
|
||||||
// `RateLimit` will already make sure we don't exceed the API rate limit.
|
// It's OK for `retryDelayMin` to be less than one second, even 0, because
|
||||||
// We're not doing exponential backoff because it will help with being
|
// `RateLimit` will already make sure we don't exceed the API rate limit.
|
||||||
// rate limited, but rather to be chill in case MusicBrainz is returning
|
// We're not doing exponential backoff because it will help with being
|
||||||
// some other error or our network is failing.
|
// rate limited, but rather to be chill in case MusicBrainz is returning
|
||||||
retryDelayMin = 100,
|
// some other error or our network is failing.
|
||||||
retryDelayMax = 60000,
|
retryDelayMin = 100,
|
||||||
randomizeRetry = true
|
retryDelayMax = 60000,
|
||||||
} = {}) {
|
randomizeRetry = true
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
this.baseURL = baseURL
|
this.baseURL = baseURL
|
||||||
this.userAgent = userAgent
|
this.userAgent = userAgent
|
||||||
this.extraHeaders = extraHeaders
|
this.extraHeaders = extraHeaders
|
||||||
|
|
@ -67,14 +69,14 @@ export default class Client {
|
||||||
* Retry any 5XX response from MusicBrainz, as well as any error in
|
* Retry any 5XX response from MusicBrainz, as well as any error in
|
||||||
* `RETRY_CODES`.
|
* `RETRY_CODES`.
|
||||||
*/
|
*/
|
||||||
shouldRetry (err) {
|
shouldRetry(err) {
|
||||||
if (err instanceof this.errorClass) {
|
if (err instanceof this.errorClass) {
|
||||||
return err.statusCode >= 500 && err.statusCode < 600
|
return err.statusCode >= 500 && err.statusCode < 600
|
||||||
}
|
}
|
||||||
return RETRY_CODES[err.code] || false
|
return RETRY_CODES[err.code] || false
|
||||||
}
|
}
|
||||||
|
|
||||||
parseErrorMessage (response, body) {
|
parseErrorMessage(response, body) {
|
||||||
return typeof body === 'string' && body ? body : `${response.statusCode}`
|
return typeof body === 'string' && body ? body : `${response.statusCode}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +84,7 @@ export default class Client {
|
||||||
* Send a request without any retrying or rate limiting.
|
* Send a request without any retrying or rate limiting.
|
||||||
* Use `get` instead.
|
* Use `get` instead.
|
||||||
*/
|
*/
|
||||||
_get (path, options = {}, info = {}) {
|
_get(path, options = {}, info = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
options = {
|
options = {
|
||||||
baseUrl: this.baseURL,
|
baseUrl: this.baseURL,
|
||||||
|
|
@ -122,7 +124,7 @@ export default class Client {
|
||||||
/**
|
/**
|
||||||
* Send a request with retrying and rate limiting.
|
* Send a request with retrying and rate limiting.
|
||||||
*/
|
*/
|
||||||
get (path, options = {}) {
|
get(path, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const fn = this._get.bind(this)
|
const fn = this._get.bind(this)
|
||||||
const operation = retry.operation(this.retryOptions)
|
const operation = retry.operation(this.retryOptions)
|
||||||
|
|
@ -130,7 +132,8 @@ export default class Client {
|
||||||
// This will increase the priority in our `RateLimit` queue for each
|
// This will increase the priority in our `RateLimit` queue for each
|
||||||
// retry, so that newer requests don't delay this one further.
|
// retry, so that newer requests don't delay this one further.
|
||||||
const priority = currentAttempt
|
const priority = currentAttempt
|
||||||
this.limiter.enqueue(fn, [path, options, { currentAttempt }], priority)
|
this.limiter
|
||||||
|
.enqueue(fn, [path, options, { currentAttempt }], priority)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (!this.shouldRetry(err) || !operation.retry(err)) {
|
if (!this.shouldRetry(err) || !operation.retry(err)) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
import MusicBrainz, { MusicBrainzError } from './musicbrainz'
|
import MusicBrainz, { MusicBrainzError } from './musicbrainz'
|
||||||
|
|
||||||
export {
|
export { MusicBrainz as default, MusicBrainz, MusicBrainzError }
|
||||||
MusicBrainz as default,
|
|
||||||
MusicBrainz,
|
|
||||||
MusicBrainzError
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,30 +4,33 @@ import Client, { ClientError } from './client'
|
||||||
export class MusicBrainzError extends ClientError {}
|
export class MusicBrainzError extends ClientError {}
|
||||||
|
|
||||||
export default class MusicBrainz extends Client {
|
export default class MusicBrainz extends Client {
|
||||||
constructor ({
|
constructor(
|
||||||
baseURL = process.env.MUSICBRAINZ_BASE_URL || 'http://musicbrainz.org/ws/2/',
|
{
|
||||||
errorClass = MusicBrainzError,
|
baseURL = process.env.MUSICBRAINZ_BASE_URL ||
|
||||||
// MusicBrainz API requests are limited to an *average* of 1 req/sec.
|
'http://musicbrainz.org/ws/2/',
|
||||||
// That means if, for example, we only need to make a few API requests to
|
errorClass = MusicBrainzError,
|
||||||
// fulfill a query, we might as well make them all at once - as long as
|
// MusicBrainz API requests are limited to an *average* of 1 req/sec.
|
||||||
// we then wait a few seconds before making more. In practice this can
|
// That means if, for example, we only need to make a few API requests to
|
||||||
// seemingly be set to about 5 requests every 5 seconds before we're
|
// fulfill a query, we might as well make them all at once - as long as
|
||||||
// considered to exceed the rate limit.
|
// we then wait a few seconds before making more. In practice this can
|
||||||
limit = 5,
|
// seemingly be set to about 5 requests every 5 seconds before we're
|
||||||
period = 5500,
|
// considered to exceed the rate limit.
|
||||||
...options
|
limit = 5,
|
||||||
} = {}) {
|
period = 5500,
|
||||||
|
...options
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
super({ baseURL, errorClass, limit, period, ...options })
|
super({ baseURL, errorClass, limit, period, ...options })
|
||||||
}
|
}
|
||||||
|
|
||||||
parseErrorMessage (response, body) {
|
parseErrorMessage(response, body) {
|
||||||
if (body && body.error) {
|
if (body && body.error) {
|
||||||
return body.error
|
return body.error
|
||||||
}
|
}
|
||||||
return super.parseErrorMessage(response, body)
|
return super.parseErrorMessage(response, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
stringifyParams (params) {
|
stringifyParams(params) {
|
||||||
if (Array.isArray(params.inc)) {
|
if (Array.isArray(params.inc)) {
|
||||||
params = {
|
params = {
|
||||||
...params,
|
...params,
|
||||||
|
|
@ -48,41 +51,41 @@ export default class MusicBrainz extends Client {
|
||||||
}
|
}
|
||||||
return qs.stringify(params, {
|
return qs.stringify(params, {
|
||||||
skipNulls: true,
|
skipNulls: true,
|
||||||
filter: (key, value) => value === '' ? undefined : value
|
filter: (key, value) => (value === '' ? undefined : value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getURL (path, params) {
|
getURL(path, params) {
|
||||||
const query = params ? this.stringifyParams(params) : ''
|
const query = params ? this.stringifyParams(params) : ''
|
||||||
return query ? `${path}?${query}` : path
|
return query ? `${path}?${query}` : path
|
||||||
}
|
}
|
||||||
|
|
||||||
getLookupURL (entity, id, params) {
|
getLookupURL(entity, id, params) {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
return this.getBrowseURL(entity, params)
|
return this.getBrowseURL(entity, params)
|
||||||
}
|
}
|
||||||
return this.getURL(`${entity}/${id}`, params)
|
return this.getURL(`${entity}/${id}`, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup (entity, id, params = {}) {
|
lookup(entity, id, params = {}) {
|
||||||
const url = this.getLookupURL(entity, id, params)
|
const url = this.getLookupURL(entity, id, params)
|
||||||
return this.get(url, { json: true, qs: { fmt: 'json' } })
|
return this.get(url, { json: true, qs: { fmt: 'json' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseURL (entity, params) {
|
getBrowseURL(entity, params) {
|
||||||
return this.getURL(entity, params)
|
return this.getURL(entity, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
browse (entity, params = {}) {
|
browse(entity, params = {}) {
|
||||||
const url = this.getBrowseURL(entity, params)
|
const url = this.getBrowseURL(entity, params)
|
||||||
return this.get(url, { json: true, qs: { fmt: 'json' } })
|
return this.get(url, { json: true, qs: { fmt: 'json' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
getSearchURL (entity, query, params) {
|
getSearchURL(entity, query, params) {
|
||||||
return this.getURL(entity, { ...params, query })
|
return this.getURL(entity, { ...params, query })
|
||||||
}
|
}
|
||||||
|
|
||||||
search (entity, query, params = {}) {
|
search(entity, query, params = {}) {
|
||||||
const url = this.getSearchURL(entity, query, params)
|
const url = this.getSearchURL(entity, query, params)
|
||||||
return this.get(url, { json: true, qs: { fmt: 'json' } })
|
return this.get(url, { json: true, qs: { fmt: 'json' } })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,26 @@ import createLoaders from './loaders'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:context')
|
const debug = require('debug')('graphbrainz:context')
|
||||||
|
|
||||||
export function extendContext (extension, context, options) {
|
export function extendContext(extension, context, options) {
|
||||||
if (extension.extendContext) {
|
if (extension.extendContext) {
|
||||||
if (typeof extension.extendContext === 'function') {
|
if (typeof extension.extendContext === 'function') {
|
||||||
debug(`Extending context via a function from the “${extension.name}” extension.`)
|
debug(
|
||||||
|
`Extending context via a function from the “${
|
||||||
|
extension.name
|
||||||
|
}” extension.`
|
||||||
|
)
|
||||||
context = extension.extendContext(context, options)
|
context = extension.extendContext(context, options)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Extension “${extension.name}” contains an invalid \`extendContext\` ` +
|
`Extension “${extension.name}” contains an invalid \`extendContext\` ` +
|
||||||
`value: ${extension.extendContext}`
|
`value: ${extension.extendContext}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createContext (options = {}) {
|
export function createContext(options = {}) {
|
||||||
const { client } = options
|
const { client } = options
|
||||||
const loaders = createLoaders(client)
|
const loaders = createLoaders(client)
|
||||||
const context = { client, loaders }
|
const context = { client, loaders }
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
import Client from '../../api/client'
|
import Client from '../../api/client'
|
||||||
|
|
||||||
export default class CoverArtArchiveClient extends Client {
|
export default class CoverArtArchiveClient extends Client {
|
||||||
constructor ({
|
constructor(
|
||||||
baseURL = process.env.COVER_ART_ARCHIVE_BASE_URL || 'http://coverartarchive.org/',
|
{
|
||||||
limit = 10,
|
baseURL = process.env.COVER_ART_ARCHIVE_BASE_URL ||
|
||||||
period = 1000,
|
'http://coverartarchive.org/',
|
||||||
...options
|
limit = 10,
|
||||||
} = {}) {
|
period = 1000,
|
||||||
|
...options
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
super({ baseURL, limit, period, ...options })
|
super({ baseURL, limit, period, ...options })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sinfully attempt to parse HTML responses for the error message.
|
* Sinfully attempt to parse HTML responses for the error message.
|
||||||
*/
|
*/
|
||||||
parseErrorMessage (response, body) {
|
parseErrorMessage(response, body) {
|
||||||
if (typeof body === 'string' && body.startsWith('<!')) {
|
if (typeof body === 'string' && body.startsWith('<!')) {
|
||||||
const heading = /<h1>([^<]+)<\/h1>/i.exec(body)
|
const heading = /<h1>([^<]+)<\/h1>/i.exec(body)
|
||||||
const message = /<p>([^<]+)<\/p>/i.exec(body)
|
const message = /<p>([^<]+)<\/p>/i.exec(body)
|
||||||
|
|
@ -22,16 +25,17 @@ export default class CoverArtArchiveClient extends Client {
|
||||||
return super.parseErrorMessage(response, body)
|
return super.parseErrorMessage(response, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
images (entityType, mbid) {
|
images(entityType, mbid) {
|
||||||
return this.get(`${entityType}/${mbid}`, { json: true })
|
return this.get(`${entityType}/${mbid}`, { json: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
imageURL (entityType, mbid, typeOrID = 'front', size) {
|
imageURL(entityType, mbid, typeOrID = 'front', size) {
|
||||||
let url = `${entityType}/${mbid}/${typeOrID}`
|
let url = `${entityType}/${mbid}/${typeOrID}`
|
||||||
if (size != null) {
|
if (size != null) {
|
||||||
url += `-${size}`
|
url += `-${size}`
|
||||||
}
|
}
|
||||||
return this.get(url, { method: 'HEAD', followRedirect: false })
|
return this.get(url, { method: 'HEAD', followRedirect: false }).then(
|
||||||
.then(headers => headers.location)
|
headers => headers.location
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,18 @@ export default {
|
||||||
name: 'Cover Art Archive',
|
name: 'Cover Art Archive',
|
||||||
description: `Retrieve cover art images for releases from the [Cover Art
|
description: `Retrieve cover art images for releases from the [Cover Art
|
||||||
Archive](https://coverartarchive.org/).`,
|
Archive](https://coverartarchive.org/).`,
|
||||||
extendContext (context, { coverArtClient, coverArtArchive = {} } = {}) {
|
extendContext(context, { coverArtClient, coverArtArchive = {} } = {}) {
|
||||||
const client = coverArtClient || new CoverArtArchiveClient(coverArtArchive)
|
const client = coverArtClient || new CoverArtArchiveClient(coverArtArchive)
|
||||||
const cacheSize = parseInt(
|
const cacheSize = parseInt(
|
||||||
process.env.COVER_ART_ARCHIVE_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
|
process.env.COVER_ART_ARCHIVE_CACHE_SIZE ||
|
||||||
|
process.env.GRAPHBRAINZ_CACHE_SIZE ||
|
||||||
|
8192,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
const cacheTTL = parseInt(
|
const cacheTTL = parseInt(
|
||||||
process.env.COVER_ART_ARCHIVE_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
|
process.env.COVER_ART_ARCHIVE_CACHE_TTL ||
|
||||||
|
process.env.GRAPHBRAINZ_CACHE_TTL ||
|
||||||
|
ONE_DAY,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ import LRUCache from 'lru-cache'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:extensions/cover-art-archive')
|
const debug = require('debug')('graphbrainz:extensions/cover-art-archive')
|
||||||
|
|
||||||
export default function createLoaders (options) {
|
export default function createLoaders(options) {
|
||||||
const { client } = options
|
const { client } = options
|
||||||
const cache = LRUCache({
|
const cache = LRUCache({
|
||||||
max: options.cacheSize,
|
max: options.cacheSize,
|
||||||
maxAge: options.cacheTTL,
|
maxAge: options.cacheTTL,
|
||||||
dispose (key) {
|
dispose(key) {
|
||||||
debug(`Removed from cache. key=${key}`)
|
debug(`Removed from cache. key=${key}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -17,37 +17,50 @@ export default function createLoaders (options) {
|
||||||
cache.clear = cache.reset
|
cache.clear = cache.reset
|
||||||
|
|
||||||
return {
|
return {
|
||||||
coverArtArchive: new DataLoader(keys => {
|
coverArtArchive: new DataLoader(
|
||||||
return Promise.all(keys.map(key => {
|
keys => {
|
||||||
const [ entityType, id ] = key
|
return Promise.all(
|
||||||
return client.images(entityType, id)
|
keys.map(key => {
|
||||||
.catch(err => {
|
const [entityType, id] = key
|
||||||
if (err.statusCode === 404) {
|
return client
|
||||||
return { images: [] }
|
.images(entityType, id)
|
||||||
}
|
.catch(err => {
|
||||||
throw err
|
if (err.statusCode === 404) {
|
||||||
}).then(coverArt => ({
|
return { images: [] }
|
||||||
...coverArt,
|
}
|
||||||
_entityType: entityType,
|
throw err
|
||||||
_id: id,
|
})
|
||||||
_releaseID: coverArt.release && coverArt.release.split('/').pop()
|
.then(coverArt => ({
|
||||||
}))
|
...coverArt,
|
||||||
}))
|
_entityType: entityType,
|
||||||
}, {
|
_id: id,
|
||||||
cacheKeyFn: ([ entityType, id ]) => `${entityType}/${id}`,
|
_releaseID:
|
||||||
cacheMap: cache
|
coverArt.release && coverArt.release.split('/').pop()
|
||||||
}),
|
}))
|
||||||
coverArtArchiveURL: new DataLoader(keys => {
|
})
|
||||||
return Promise.all(keys.map(key => {
|
)
|
||||||
const [ entityType, id, type, size ] = key
|
|
||||||
return client.imageURL(entityType, id, type, size)
|
|
||||||
}))
|
|
||||||
}, {
|
|
||||||
cacheKeyFn: ([ entityType, id, type, size ]) => {
|
|
||||||
const key = `${entityType}/${id}/${type}`
|
|
||||||
return size ? `${key}-${size}` : key
|
|
||||||
},
|
},
|
||||||
cacheMap: cache
|
{
|
||||||
})
|
cacheKeyFn: ([entityType, id]) => `${entityType}/${id}`,
|
||||||
|
cacheMap: cache
|
||||||
|
}
|
||||||
|
),
|
||||||
|
coverArtArchiveURL: new DataLoader(
|
||||||
|
keys => {
|
||||||
|
return Promise.all(
|
||||||
|
keys.map(key => {
|
||||||
|
const [entityType, id, type, size] = key
|
||||||
|
return client.imageURL(entityType, id, type, size)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cacheKeyFn: ([entityType, id, type, size]) => {
|
||||||
|
const key = `${entityType}/${id}/${type}`
|
||||||
|
return size ? `${key}-${size}` : key
|
||||||
|
},
|
||||||
|
cacheMap: cache
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const SIZES = new Map([
|
||||||
['LARGE', 500]
|
['LARGE', 500]
|
||||||
])
|
])
|
||||||
|
|
||||||
function resolveImage (coverArt, args, { loaders }, info) {
|
function resolveImage(coverArt, args, { loaders }, info) {
|
||||||
// Since migrating the schema to an extension, we lost custom enum values
|
// Since migrating the schema to an extension, we lost custom enum values
|
||||||
// for the time being. Translate any incoming `size` arg to the old enum
|
// for the time being. Translate any incoming `size` arg to the old enum
|
||||||
// values.
|
// values.
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,26 @@
|
||||||
import Client from '../../api/client'
|
import Client from '../../api/client'
|
||||||
|
|
||||||
export default class FanArtClient extends Client {
|
export default class FanArtClient extends Client {
|
||||||
constructor ({
|
constructor(
|
||||||
apiKey = process.env.FANART_API_KEY,
|
{
|
||||||
baseURL = process.env.FANART_BASE_URL || 'http://webservice.fanart.tv/v3/',
|
apiKey = process.env.FANART_API_KEY,
|
||||||
limit = 10,
|
baseURL = process.env.FANART_BASE_URL ||
|
||||||
period = 1000,
|
'http://webservice.fanart.tv/v3/',
|
||||||
...options
|
limit = 10,
|
||||||
} = {}) {
|
period = 1000,
|
||||||
|
...options
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
super({ baseURL, limit, period, ...options })
|
super({ baseURL, limit, period, ...options })
|
||||||
this.apiKey = apiKey
|
this.apiKey = apiKey
|
||||||
}
|
}
|
||||||
|
|
||||||
get (path, options = {}) {
|
get(path, options = {}) {
|
||||||
const ClientError = this.errorClass
|
const ClientError = this.errorClass
|
||||||
if (!this.apiKey) {
|
if (!this.apiKey) {
|
||||||
return Promise.reject(new ClientError(
|
return Promise.reject(
|
||||||
'No API key was configured for the fanart.tv client.'
|
new ClientError('No API key was configured for the fanart.tv client.')
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
options = {
|
options = {
|
||||||
json: true,
|
json: true,
|
||||||
|
|
@ -30,7 +33,7 @@ export default class FanArtClient extends Client {
|
||||||
return super.get(path, options)
|
return super.get(path, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
musicEntity (entityType, mbid) {
|
musicEntity(entityType, mbid) {
|
||||||
const ClientError = this.errorClass
|
const ClientError = this.errorClass
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
case 'artist':
|
case 'artist':
|
||||||
|
|
@ -40,21 +43,21 @@ export default class FanArtClient extends Client {
|
||||||
case 'release-group':
|
case 'release-group':
|
||||||
return this.musicAlbum(mbid)
|
return this.musicAlbum(mbid)
|
||||||
default:
|
default:
|
||||||
return Promise.reject(new ClientError(
|
return Promise.reject(
|
||||||
`Entity type unsupported: ${entityType}`
|
new ClientError(`Entity type unsupported: ${entityType}`)
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
musicArtist (mbid) {
|
musicArtist(mbid) {
|
||||||
return this.get(`music/${mbid}`)
|
return this.get(`music/${mbid}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
musicAlbum (mbid) {
|
musicAlbum(mbid) {
|
||||||
return this.get(`music/albums/${mbid}`)
|
return this.get(`music/albums/${mbid}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
musicLabel (mbid) {
|
musicLabel(mbid) {
|
||||||
return this.get(`music/${mbid}`)
|
return this.get(`music/${mbid}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,18 @@ export default {
|
||||||
name: 'fanart.tv',
|
name: 'fanart.tv',
|
||||||
description: `Retrieve high quality artwork for artists, releases, and labels
|
description: `Retrieve high quality artwork for artists, releases, and labels
|
||||||
from [fanart.tv](https://fanart.tv/).`,
|
from [fanart.tv](https://fanart.tv/).`,
|
||||||
extendContext (context, { fanArt = {} } = {}) {
|
extendContext(context, { fanArt = {} } = {}) {
|
||||||
const client = new FanArtClient(fanArt)
|
const client = new FanArtClient(fanArt)
|
||||||
const cacheSize = parseInt(
|
const cacheSize = parseInt(
|
||||||
process.env.FANART_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
|
process.env.FANART_CACHE_SIZE ||
|
||||||
|
process.env.GRAPHBRAINZ_CACHE_SIZE ||
|
||||||
|
8192,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
const cacheTTL = parseInt(
|
const cacheTTL = parseInt(
|
||||||
process.env.FANART_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
|
process.env.FANART_CACHE_TTL ||
|
||||||
|
process.env.GRAPHBRAINZ_CACHE_TTL ||
|
||||||
|
ONE_DAY,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ import LRUCache from 'lru-cache'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:extensions/fanart-tv')
|
const debug = require('debug')('graphbrainz:extensions/fanart-tv')
|
||||||
|
|
||||||
export default function createLoader (options) {
|
export default function createLoader(options) {
|
||||||
const { client } = options
|
const { client } = options
|
||||||
const cache = LRUCache({
|
const cache = LRUCache({
|
||||||
max: options.cacheSize,
|
max: options.cacheSize,
|
||||||
maxAge: options.cacheTTL,
|
maxAge: options.cacheTTL,
|
||||||
dispose (key) {
|
dispose(key) {
|
||||||
debug(`Removed from cache. key=${key}`)
|
debug(`Removed from cache. key=${key}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -16,37 +16,48 @@ export default function createLoader (options) {
|
||||||
cache.delete = cache.del
|
cache.delete = cache.del
|
||||||
cache.clear = cache.reset
|
cache.clear = cache.reset
|
||||||
|
|
||||||
const loader = new DataLoader(keys => {
|
const loader = new DataLoader(
|
||||||
return Promise.all(keys.map(key => {
|
keys => {
|
||||||
const [ entityType, id ] = key
|
return Promise.all(
|
||||||
return client.musicEntity(entityType, id)
|
keys.map(key => {
|
||||||
.catch(err => {
|
const [entityType, id] = key
|
||||||
if (err.statusCode === 404) {
|
return client
|
||||||
// 404s are OK, just return empty data.
|
.musicEntity(entityType, id)
|
||||||
return {
|
.catch(err => {
|
||||||
artistbackground: [],
|
if (err.statusCode === 404) {
|
||||||
artistthumb: [],
|
// 404s are OK, just return empty data.
|
||||||
musiclogo: [],
|
return {
|
||||||
hdmusiclogo: [],
|
artistbackground: [],
|
||||||
musicbanner: [],
|
artistthumb: [],
|
||||||
musiclabel: [],
|
musiclogo: [],
|
||||||
albums: {}
|
hdmusiclogo: [],
|
||||||
}
|
musicbanner: [],
|
||||||
}
|
musiclabel: [],
|
||||||
throw err
|
albums: {}
|
||||||
}).then(body => {
|
}
|
||||||
if (entityType === 'artist') {
|
}
|
||||||
const releaseGroupIDs = Object.keys(body.albums)
|
throw err
|
||||||
debug(`Priming album cache with ${releaseGroupIDs.length} album(s).`)
|
})
|
||||||
releaseGroupIDs.forEach(key => loader.prime(['release-group', key], body))
|
.then(body => {
|
||||||
}
|
if (entityType === 'artist') {
|
||||||
return body
|
const releaseGroupIDs = Object.keys(body.albums)
|
||||||
|
debug(
|
||||||
|
`Priming album cache with ${releaseGroupIDs.length} album(s).`
|
||||||
|
)
|
||||||
|
releaseGroupIDs.forEach(key =>
|
||||||
|
loader.prime(['release-group', key], body)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}))
|
)
|
||||||
}, {
|
},
|
||||||
cacheKeyFn: ([ entityType, id ]) => `${entityType}/${id}`,
|
{
|
||||||
cacheMap: cache
|
cacheKeyFn: ([entityType, id]) => `${entityType}/${id}`,
|
||||||
})
|
cacheMap: cache
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return loader
|
return loader
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,8 @@ export default {
|
||||||
},
|
},
|
||||||
ReleaseGroup: {
|
ReleaseGroup: {
|
||||||
fanArt: (releaseGroup, args, context) => {
|
fanArt: (releaseGroup, args, context) => {
|
||||||
return context.loaders.fanArt.load(['release-group', releaseGroup.id])
|
return context.loaders.fanArt
|
||||||
|
.load(['release-group', releaseGroup.id])
|
||||||
.then(artist => artist.albums[releaseGroup.id])
|
.then(artist => artist.albums[releaseGroup.id])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,20 @@ import URL from 'url'
|
||||||
import Client from '../../api/client'
|
import Client from '../../api/client'
|
||||||
|
|
||||||
export default class MediaWikiClient extends Client {
|
export default class MediaWikiClient extends Client {
|
||||||
constructor ({
|
constructor({ limit = 10, period = 1000, ...options } = {}) {
|
||||||
limit = 10,
|
|
||||||
period = 1000,
|
|
||||||
...options
|
|
||||||
} = {}) {
|
|
||||||
super({ limit, period, ...options })
|
super({ limit, period, ...options })
|
||||||
}
|
}
|
||||||
|
|
||||||
imageInfo (page) {
|
imageInfo(page) {
|
||||||
const pageURL = URL.parse(page, true)
|
const pageURL = URL.parse(page, true)
|
||||||
const ClientError = this.errorClass
|
const ClientError = this.errorClass
|
||||||
|
|
||||||
if (!pageURL.pathname.startsWith('/wiki/')) {
|
if (!pageURL.pathname.startsWith('/wiki/')) {
|
||||||
return Promise.reject(new ClientError(
|
return Promise.reject(
|
||||||
`MediaWiki page URL does not have the expected /wiki/ prefix: ${page}`
|
new ClientError(
|
||||||
))
|
`MediaWiki page URL does not have the expected /wiki/ prefix: ${page}`
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiURL = URL.format({
|
const apiURL = URL.format({
|
||||||
|
|
@ -34,21 +32,20 @@ export default class MediaWikiClient extends Client {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.get(apiURL, { json: true })
|
return this.get(apiURL, { json: true }).then(body => {
|
||||||
.then(body => {
|
const pageIDs = Object.keys(body.query.pages)
|
||||||
const pageIDs = Object.keys(body.query.pages)
|
if (pageIDs.length !== 1) {
|
||||||
if (pageIDs.length !== 1) {
|
throw new ClientError(
|
||||||
throw new ClientError(
|
`Query returned multiple pages: [${pageIDs.join(', ')}]`
|
||||||
`Query returned multiple pages: [${pageIDs.join(', ')}]`
|
)
|
||||||
)
|
}
|
||||||
}
|
const imageInfo = body.query.pages[pageIDs[0]].imageinfo
|
||||||
const imageInfo = body.query.pages[pageIDs[0]].imageinfo
|
if (imageInfo.length !== 1) {
|
||||||
if (imageInfo.length !== 1) {
|
throw new ClientError(
|
||||||
throw new ClientError(
|
`Query returned info for ${imageInfo.length} images, expected 1.`
|
||||||
`Query returned info for ${imageInfo.length} images, expected 1.`
|
)
|
||||||
)
|
}
|
||||||
}
|
return imageInfo[0]
|
||||||
return imageInfo[0]
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,18 @@ export default {
|
||||||
name: 'MediaWiki',
|
name: 'MediaWiki',
|
||||||
description: `Retrieve information from MediaWiki image pages, like the actual
|
description: `Retrieve information from MediaWiki image pages, like the actual
|
||||||
image file URL and EXIF metadata.`,
|
image file URL and EXIF metadata.`,
|
||||||
extendContext (context, { mediaWiki = {} } = {}) {
|
extendContext(context, { mediaWiki = {} } = {}) {
|
||||||
const client = new MediaWikiClient(mediaWiki)
|
const client = new MediaWikiClient(mediaWiki)
|
||||||
const cacheSize = parseInt(
|
const cacheSize = parseInt(
|
||||||
process.env.MEDIAWIKI_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
|
process.env.MEDIAWIKI_CACHE_SIZE ||
|
||||||
|
process.env.GRAPHBRAINZ_CACHE_SIZE ||
|
||||||
|
8192,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
const cacheTTL = parseInt(
|
const cacheTTL = parseInt(
|
||||||
process.env.MEDIAWIKI_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
|
process.env.MEDIAWIKI_CACHE_TTL ||
|
||||||
|
process.env.GRAPHBRAINZ_CACHE_TTL ||
|
||||||
|
ONE_DAY,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ import LRUCache from 'lru-cache'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:extensions/mediawiki')
|
const debug = require('debug')('graphbrainz:extensions/mediawiki')
|
||||||
|
|
||||||
export default function createLoader (options) {
|
export default function createLoader(options) {
|
||||||
const { client } = options
|
const { client } = options
|
||||||
const cache = LRUCache({
|
const cache = LRUCache({
|
||||||
max: options.cacheSize,
|
max: options.cacheSize,
|
||||||
maxAge: options.cacheTTL,
|
maxAge: options.cacheTTL,
|
||||||
dispose (key) {
|
dispose(key) {
|
||||||
debug(`Removed from cache. key=${key}`)
|
debug(`Removed from cache. key=${key}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -16,7 +16,10 @@ export default function createLoader (options) {
|
||||||
cache.delete = cache.del
|
cache.delete = cache.del
|
||||||
cache.clear = cache.reset
|
cache.clear = cache.reset
|
||||||
|
|
||||||
return new DataLoader(keys => {
|
return new DataLoader(
|
||||||
return Promise.all(keys.map(key => client.imageInfo(key)))
|
keys => {
|
||||||
}, { cacheMap: cache })
|
return Promise.all(keys.map(key => client.imageInfo(key)))
|
||||||
|
},
|
||||||
|
{ cacheMap: cache }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
import URL from 'url'
|
import URL from 'url'
|
||||||
|
|
||||||
function resolveMediaWikiImages (source, args, { loaders }) {
|
function resolveMediaWikiImages(source, args, { loaders }) {
|
||||||
const isURL = (relation) => relation['target-type'] === 'url'
|
const isURL = relation => relation['target-type'] === 'url'
|
||||||
let rels = source.relations ? source.relations.filter(isURL) : []
|
let rels = source.relations ? source.relations.filter(isURL) : []
|
||||||
if (!rels.length) {
|
if (!rels.length) {
|
||||||
rels = loaders.lookup.load([source._type, source.id, { inc: 'url-rels' }])
|
rels = loaders.lookup
|
||||||
|
.load([source._type, source.id, { inc: 'url-rels' }])
|
||||||
.then(source => source.relations.filter(isURL))
|
.then(source => source.relations.filter(isURL))
|
||||||
}
|
}
|
||||||
return Promise.resolve(rels).then(rels => {
|
return Promise.resolve(rels).then(rels => {
|
||||||
const pages = rels.filter(rel => {
|
const pages = rels
|
||||||
if (rel.type === args.type) {
|
.filter(rel => {
|
||||||
const url = URL.parse(rel.url.resource)
|
if (rel.type === args.type) {
|
||||||
if (url.pathname.match(/^\/wiki\/(File|Image):/)) {
|
const url = URL.parse(rel.url.resource)
|
||||||
return true
|
if (url.pathname.match(/^\/wiki\/(File|Image):/)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return false
|
||||||
return false
|
})
|
||||||
}).map(rel => rel.url.resource)
|
.map(rel => rel.url.resource)
|
||||||
return loaders.mediaWiki.loadMany(pages)
|
return loaders.mediaWiki.loadMany(pages)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -57,13 +60,14 @@ export default {
|
||||||
const data = imageInfo.extmetadata.LicenseUrl
|
const data = imageInfo.extmetadata.LicenseUrl
|
||||||
return data ? data.value : null
|
return data ? data.value : null
|
||||||
},
|
},
|
||||||
metadata: imageInfo => Object.keys(imageInfo.extmetadata).map(key => {
|
metadata: imageInfo =>
|
||||||
const data = imageInfo.extmetadata[key]
|
Object.keys(imageInfo.extmetadata).map(key => {
|
||||||
return { ...data, name: key }
|
const data = imageInfo.extmetadata[key]
|
||||||
})
|
return { ...data, name: key }
|
||||||
|
})
|
||||||
},
|
},
|
||||||
MediaWikiImageMetadata: {
|
MediaWikiImageMetadata: {
|
||||||
value: obj => obj.value == null ? obj.value : `${obj.value}`
|
value: obj => (obj.value == null ? obj.value : `${obj.value}`)
|
||||||
},
|
},
|
||||||
Artist: {
|
Artist: {
|
||||||
mediaWikiImages: resolveMediaWikiImages
|
mediaWikiImages: resolveMediaWikiImages
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,31 @@
|
||||||
import Client from '../../api/client'
|
import Client from '../../api/client'
|
||||||
|
|
||||||
export default class TheAudioDBClient extends Client {
|
export default class TheAudioDBClient extends Client {
|
||||||
constructor ({
|
constructor(
|
||||||
apiKey = process.env.THEAUDIODB_API_KEY,
|
{
|
||||||
baseURL = process.env.THEAUDIODB_BASE_URL || 'http://www.theaudiodb.com/api/v1/json/',
|
apiKey = process.env.THEAUDIODB_API_KEY,
|
||||||
limit = 10,
|
baseURL = process.env.THEAUDIODB_BASE_URL ||
|
||||||
period = 1000,
|
'http://www.theaudiodb.com/api/v1/json/',
|
||||||
...options
|
limit = 10,
|
||||||
} = {}) {
|
period = 1000,
|
||||||
|
...options
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
super({ baseURL, limit, period, ...options })
|
super({ baseURL, limit, period, ...options })
|
||||||
this.apiKey = apiKey
|
this.apiKey = apiKey
|
||||||
}
|
}
|
||||||
|
|
||||||
get (path, options = {}) {
|
get(path, options = {}) {
|
||||||
const ClientError = this.errorClass
|
const ClientError = this.errorClass
|
||||||
if (!this.apiKey) {
|
if (!this.apiKey) {
|
||||||
return Promise.reject(new ClientError(
|
return Promise.reject(
|
||||||
'No API key was configured for TheAudioDB client.'
|
new ClientError('No API key was configured for TheAudioDB client.')
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
return super.get(`${this.apiKey}/${path}`, { json: true, ...options })
|
return super.get(`${this.apiKey}/${path}`, { json: true, ...options })
|
||||||
}
|
}
|
||||||
|
|
||||||
entity (entityType, mbid) {
|
entity(entityType, mbid) {
|
||||||
const ClientError = this.errorClass
|
const ClientError = this.errorClass
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
case 'artist':
|
case 'artist':
|
||||||
|
|
@ -32,39 +35,36 @@ export default class TheAudioDBClient extends Client {
|
||||||
case 'recording':
|
case 'recording':
|
||||||
return this.track(mbid)
|
return this.track(mbid)
|
||||||
default:
|
default:
|
||||||
return Promise.reject(new ClientError(
|
return Promise.reject(
|
||||||
`Entity type unsupported: ${entityType}`
|
new ClientError(`Entity type unsupported: ${entityType}`)
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
artist (mbid) {
|
artist(mbid) {
|
||||||
return this.get('artist-mb.php', { qs: { i: mbid } })
|
return this.get('artist-mb.php', { qs: { i: mbid } }).then(body => {
|
||||||
.then(body => {
|
if (body.artists && body.artists.length === 1) {
|
||||||
if (body.artists && body.artists.length === 1) {
|
return body.artists[0]
|
||||||
return body.artists[0]
|
}
|
||||||
}
|
return null
|
||||||
return null
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
album (mbid) {
|
album(mbid) {
|
||||||
return this.get('album-mb.php', { qs: { i: mbid } })
|
return this.get('album-mb.php', { qs: { i: mbid } }).then(body => {
|
||||||
.then(body => {
|
if (body.album && body.album.length === 1) {
|
||||||
if (body.album && body.album.length === 1) {
|
return body.album[0]
|
||||||
return body.album[0]
|
}
|
||||||
}
|
return null
|
||||||
return null
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
track (mbid) {
|
track(mbid) {
|
||||||
return this.get('track-mb.php', { qs: { i: mbid } })
|
return this.get('track-mb.php', { qs: { i: mbid } }).then(body => {
|
||||||
.then(body => {
|
if (body.track && body.track.length === 1) {
|
||||||
if (body.track && body.track.length === 1) {
|
return body.track[0]
|
||||||
return body.track[0]
|
}
|
||||||
}
|
return null
|
||||||
return null
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,18 @@ export default {
|
||||||
name: 'TheAudioDB',
|
name: 'TheAudioDB',
|
||||||
description: `Retrieve images and information about artists, releases, and
|
description: `Retrieve images and information about artists, releases, and
|
||||||
recordings from [TheAudioDB.com](http://www.theaudiodb.com/).`,
|
recordings from [TheAudioDB.com](http://www.theaudiodb.com/).`,
|
||||||
extendContext (context, { theAudioDB = {} } = {}) {
|
extendContext(context, { theAudioDB = {} } = {}) {
|
||||||
const client = new TheAudioDBClient(theAudioDB)
|
const client = new TheAudioDBClient(theAudioDB)
|
||||||
const cacheSize = parseInt(
|
const cacheSize = parseInt(
|
||||||
process.env.THEAUDIODB_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
|
process.env.THEAUDIODB_CACHE_SIZE ||
|
||||||
|
process.env.GRAPHBRAINZ_CACHE_SIZE ||
|
||||||
|
8192,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
const cacheTTL = parseInt(
|
const cacheTTL = parseInt(
|
||||||
process.env.THEAUDIODB_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
|
process.env.THEAUDIODB_CACHE_TTL ||
|
||||||
|
process.env.GRAPHBRAINZ_CACHE_TTL ||
|
||||||
|
ONE_DAY,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ import LRUCache from 'lru-cache'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:extensions/the-audio-db')
|
const debug = require('debug')('graphbrainz:extensions/the-audio-db')
|
||||||
|
|
||||||
export default function createLoader (options) {
|
export default function createLoader(options) {
|
||||||
const { client } = options
|
const { client } = options
|
||||||
const cache = LRUCache({
|
const cache = LRUCache({
|
||||||
max: options.cacheSize,
|
max: options.cacheSize,
|
||||||
maxAge: options.cacheTTL,
|
maxAge: options.cacheTTL,
|
||||||
dispose (key) {
|
dispose(key) {
|
||||||
debug(`Removed from cache. key=${key}`)
|
debug(`Removed from cache. key=${key}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -16,13 +16,18 @@ export default function createLoader (options) {
|
||||||
cache.delete = cache.del
|
cache.delete = cache.del
|
||||||
cache.clear = cache.reset
|
cache.clear = cache.reset
|
||||||
|
|
||||||
return new DataLoader(keys => {
|
return new DataLoader(
|
||||||
return Promise.all(keys.map(key => {
|
keys => {
|
||||||
const [ entityType, id ] = key
|
return Promise.all(
|
||||||
return client.entity(entityType, id)
|
keys.map(key => {
|
||||||
}))
|
const [entityType, id] = key
|
||||||
}, {
|
return client.entity(entityType, id)
|
||||||
cacheKeyFn: ([ entityType, id ]) => `${entityType}/${id}`,
|
})
|
||||||
cacheMap: cache
|
)
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
cacheKeyFn: ([entityType, id]) => `${entityType}/${id}`,
|
||||||
|
cacheMap: cache
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
function handleImageSize (resolver) {
|
function handleImageSize(resolver) {
|
||||||
return (source, args, context, info) => {
|
return (source, args, context, info) => {
|
||||||
const getURL = (url) => args.size === 'PREVIEW' ? `${url}/preview` : url
|
const getURL = url => (args.size === 'PREVIEW' ? `${url}/preview` : url)
|
||||||
const url = resolver(source, args, context, info)
|
const url = resolver(source, args, context, info)
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return null
|
return null
|
||||||
|
|
|
||||||
30
src/index.js
30
src/index.js
|
|
@ -8,24 +8,26 @@ import { createContext } from './context'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz')
|
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
|
||||||
})
|
})
|
||||||
|
|
||||||
const middleware = ({
|
const middleware = (
|
||||||
client = new MusicBrainz(),
|
{
|
||||||
extensions = process.env.GRAPHBRAINZ_EXTENSIONS
|
client = new MusicBrainz(),
|
||||||
? JSON.parse(process.env.GRAPHBRAINZ_EXTENSIONS)
|
extensions = process.env.GRAPHBRAINZ_EXTENSIONS
|
||||||
: [
|
? JSON.parse(process.env.GRAPHBRAINZ_EXTENSIONS)
|
||||||
'./extensions/cover-art-archive',
|
: [
|
||||||
'./extensions/fanart-tv',
|
'./extensions/cover-art-archive',
|
||||||
'./extensions/mediawiki',
|
'./extensions/fanart-tv',
|
||||||
'./extensions/the-audio-db'
|
'./extensions/mediawiki',
|
||||||
],
|
'./extensions/the-audio-db'
|
||||||
...middlewareOptions
|
],
|
||||||
} = {}) => {
|
...middlewareOptions
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
debug(`Loading ${extensions.length} extension(s).`)
|
debug(`Loading ${extensions.length} extension(s).`)
|
||||||
const options = {
|
const options = {
|
||||||
client,
|
client,
|
||||||
|
|
@ -52,7 +54,7 @@ const middleware = ({
|
||||||
|
|
||||||
export default middleware
|
export default middleware
|
||||||
|
|
||||||
export function start (options) {
|
export function start(options) {
|
||||||
require('dotenv').config({ silent: true })
|
require('dotenv').config({ silent: true })
|
||||||
const app = express()
|
const app = express()
|
||||||
const port = process.env.PORT || 3000
|
const port = process.env.PORT || 3000
|
||||||
|
|
|
||||||
113
src/loaders.js
113
src/loaders.js
|
|
@ -5,13 +5,13 @@ import { ONE_DAY } from './util'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:loaders')
|
const debug = require('debug')('graphbrainz:loaders')
|
||||||
|
|
||||||
export default function createLoaders (client) {
|
export default function createLoaders(client) {
|
||||||
// All loaders share a single LRU cache that will remember 8192 responses,
|
// All loaders share a single LRU cache that will remember 8192 responses,
|
||||||
// each cached for 1 day.
|
// each cached for 1 day.
|
||||||
const cache = LRUCache({
|
const cache = LRUCache({
|
||||||
max: parseInt(process.env.GRAPHBRAINZ_CACHE_SIZE || 8192, 10),
|
max: parseInt(process.env.GRAPHBRAINZ_CACHE_SIZE || 8192, 10),
|
||||||
maxAge: parseInt(process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY, 10),
|
maxAge: parseInt(process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY, 10),
|
||||||
dispose (key) {
|
dispose(key) {
|
||||||
debug(`Removed from cache. key=${key}`)
|
debug(`Removed from cache. key=${key}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -19,56 +19,71 @@ export default function createLoaders (client) {
|
||||||
cache.delete = cache.del
|
cache.delete = cache.del
|
||||||
cache.clear = cache.reset
|
cache.clear = cache.reset
|
||||||
|
|
||||||
const lookup = new DataLoader(keys => {
|
const lookup = new DataLoader(
|
||||||
return Promise.all(keys.map(key => {
|
keys => {
|
||||||
const [ entityType, id, params = {} ] = key
|
return Promise.all(
|
||||||
return client.lookup(entityType, id, params).then(entity => {
|
keys.map(key => {
|
||||||
if (entity) {
|
const [entityType, id, params = {}] = key
|
||||||
// Store the entity type so we can determine what type of object this
|
return client.lookup(entityType, id, params).then(entity => {
|
||||||
// is elsewhere in the code.
|
if (entity) {
|
||||||
entity._type = entityType
|
// Store the entity type so we can determine what type of object this
|
||||||
}
|
// is elsewhere in the code.
|
||||||
return entity
|
entity._type = entityType
|
||||||
})
|
}
|
||||||
}))
|
return entity
|
||||||
}, {
|
})
|
||||||
cacheKeyFn: (key) => client.getLookupURL(...key),
|
|
||||||
cacheMap: cache
|
|
||||||
})
|
|
||||||
|
|
||||||
const browse = new DataLoader(keys => {
|
|
||||||
return Promise.all(keys.map(key => {
|
|
||||||
const [ entityType, params = {} ] = key
|
|
||||||
return client.browse(entityType, params).then(list => {
|
|
||||||
list[toPlural(entityType)].forEach(entity => {
|
|
||||||
// Store the entity type so we can determine what type of object this
|
|
||||||
// is elsewhere in the code.
|
|
||||||
entity._type = entityType
|
|
||||||
})
|
})
|
||||||
return list
|
)
|
||||||
})
|
},
|
||||||
}))
|
{
|
||||||
}, {
|
cacheKeyFn: key => client.getLookupURL(...key),
|
||||||
cacheKeyFn: (key) => client.getBrowseURL(...key),
|
cacheMap: cache
|
||||||
cacheMap: cache
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
const search = new DataLoader(keys => {
|
const browse = new DataLoader(
|
||||||
return Promise.all(keys.map(key => {
|
keys => {
|
||||||
const [ entityType, query, params = {} ] = key
|
return Promise.all(
|
||||||
return client.search(entityType, query, params).then(list => {
|
keys.map(key => {
|
||||||
list[toPlural(entityType)].forEach(entity => {
|
const [entityType, params = {}] = key
|
||||||
// Store the entity type so we can determine what type of object this
|
return client.browse(entityType, params).then(list => {
|
||||||
// is elsewhere in the code.
|
list[toPlural(entityType)].forEach(entity => {
|
||||||
entity._type = entityType
|
// Store the entity type so we can determine what type of object this
|
||||||
|
// is elsewhere in the code.
|
||||||
|
entity._type = entityType
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
})
|
||||||
})
|
})
|
||||||
return list
|
)
|
||||||
})
|
},
|
||||||
}))
|
{
|
||||||
}, {
|
cacheKeyFn: key => client.getBrowseURL(...key),
|
||||||
cacheKeyFn: key => client.getSearchURL(...key),
|
cacheMap: cache
|
||||||
cacheMap: cache
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
|
const search = new DataLoader(
|
||||||
|
keys => {
|
||||||
|
return Promise.all(
|
||||||
|
keys.map(key => {
|
||||||
|
const [entityType, query, params = {}] = key
|
||||||
|
return client.search(entityType, query, params).then(list => {
|
||||||
|
list[toPlural(entityType)].forEach(entity => {
|
||||||
|
// Store the entity type so we can determine what type of object this
|
||||||
|
// is elsewhere in the code.
|
||||||
|
entity._type = entityType
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cacheKeyFn: key => client.getSearchURL(...key),
|
||||||
|
cacheMap: cache
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return { lookup, browse, search }
|
return { lookup, browse, search }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ const work = {
|
||||||
description: 'The MBID of a work to which the entity is linked.'
|
description: 'The MBID of a work to which the entity is linked.'
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBrowseField (connectionType, args) {
|
function createBrowseField(connectionType, args) {
|
||||||
const typeName = toWords(connectionType.name.slice(0, -10))
|
const typeName = toWords(connectionType.name.slice(0, -10))
|
||||||
return {
|
return {
|
||||||
type: connectionType,
|
type: connectionType,
|
||||||
|
|
@ -173,7 +173,8 @@ release, but is not included in the credits for the release itself.`
|
||||||
|
|
||||||
export const browse = {
|
export const browse = {
|
||||||
type: BrowseQuery,
|
type: BrowseQuery,
|
||||||
description: 'Browse all MusicBrainz entities directly linked to another entity.',
|
description:
|
||||||
|
'Browse all MusicBrainz entities directly linked to another entity.',
|
||||||
// We only have work to do once we know what entity types are being requested,
|
// We only have work to do once we know what entity types are being requested,
|
||||||
// so this can just resolve to an empty object.
|
// so this can just resolve to an empty object.
|
||||||
resolve: () => ({})
|
resolve: () => ({})
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import {
|
||||||
Work
|
Work
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
function createLookupField (entity, args) {
|
function createLookupField(entity, args) {
|
||||||
const typeName = toWords(entity.name)
|
const typeName = toWords(entity.name)
|
||||||
return {
|
return {
|
||||||
type: entity,
|
type: entity,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import { toWords } from '../types/helpers'
|
import { toWords } from '../types/helpers'
|
||||||
|
|
||||||
function createSearchField (connectionType) {
|
function createSearchField(connectionType) {
|
||||||
const typeName = toWords(connectionType.name.slice(0, -10))
|
const typeName = toWords(connectionType.name.slice(0, -10))
|
||||||
return {
|
return {
|
||||||
type: connectionType,
|
type: connectionType,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
const debug = require('debug')('graphbrainz:rate-limit')
|
const debug = require('debug')('graphbrainz:rate-limit')
|
||||||
|
|
||||||
export default class RateLimit {
|
export default class RateLimit {
|
||||||
constructor ({
|
constructor(
|
||||||
limit = 1,
|
{
|
||||||
period = 1000,
|
limit = 1,
|
||||||
concurrency = limit || 1,
|
period = 1000,
|
||||||
defaultPriority = 1
|
concurrency = limit || 1,
|
||||||
} = {}) {
|
defaultPriority = 1
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
this.limit = limit
|
this.limit = limit
|
||||||
this.period = period
|
this.period = period
|
||||||
this.defaultPriority = defaultPriority
|
this.defaultPriority = defaultPriority
|
||||||
|
|
@ -20,16 +22,16 @@ export default class RateLimit {
|
||||||
this.prevTaskID = null
|
this.prevTaskID = null
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTaskID (prevTaskID = this.prevTaskID) {
|
nextTaskID(prevTaskID = this.prevTaskID) {
|
||||||
const id = (prevTaskID || 0) + 1
|
const id = (prevTaskID || 0) + 1
|
||||||
this.prevTaskID = id
|
this.prevTaskID = id
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueue (fn, args, priority = this.defaultPriority) {
|
enqueue(fn, args, priority = this.defaultPriority) {
|
||||||
priority = Math.max(0, priority)
|
priority = Math.max(0, priority)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const queue = this.queues[priority] = this.queues[priority] || []
|
const queue = (this.queues[priority] = this.queues[priority] || [])
|
||||||
const id = this.nextTaskID()
|
const id = this.nextTaskID()
|
||||||
debug(`Enqueuing task. id=${id} priority=${priority}`)
|
debug(`Enqueuing task. id=${id} priority=${priority}`)
|
||||||
queue.push({ fn, args, resolve, reject, id })
|
queue.push({ fn, args, resolve, reject, id })
|
||||||
|
|
@ -43,7 +45,7 @@ export default class RateLimit {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
dequeue () {
|
dequeue() {
|
||||||
let task
|
let task
|
||||||
for (let i = this.queues.length - 1; i >= 0; i--) {
|
for (let i = this.queues.length - 1; i >= 0; i--) {
|
||||||
const queue = this.queues[i]
|
const queue = this.queues[i]
|
||||||
|
|
@ -60,7 +62,7 @@ export default class RateLimit {
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
flush () {
|
flush() {
|
||||||
if (this.numPending < this.concurrency && this.periodCapacity > 0) {
|
if (this.numPending < this.concurrency && this.periodCapacity > 0) {
|
||||||
const task = this.dequeue()
|
const task = this.dequeue()
|
||||||
if (task) {
|
if (task) {
|
||||||
|
|
@ -83,12 +85,12 @@ export default class RateLimit {
|
||||||
}
|
}
|
||||||
this.numPending += 1
|
this.numPending += 1
|
||||||
this.periodCapacity -= 1
|
this.periodCapacity -= 1
|
||||||
const onResolve = (value) => {
|
const onResolve = value => {
|
||||||
this.numPending -= 1
|
this.numPending -= 1
|
||||||
resolve(value)
|
resolve(value)
|
||||||
this.flush()
|
this.flush()
|
||||||
}
|
}
|
||||||
const onReject = (err) => {
|
const onReject = err => {
|
||||||
this.numPending -= 1
|
this.numPending -= 1
|
||||||
reject(err)
|
reject(err)
|
||||||
this.flush()
|
this.flush()
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
} from 'graphql-relay'
|
} from 'graphql-relay'
|
||||||
import { getFields, extendIncludes } from './util'
|
import { getFields, extendIncludes } from './util'
|
||||||
|
|
||||||
export function includeRelationships (params, info, fragments = info.fragments) {
|
export function includeRelationships(params, info, fragments = info.fragments) {
|
||||||
let fields = getFields(info, fragments)
|
let fields = getFields(info, fragments)
|
||||||
if (info.fieldName !== 'relationships') {
|
if (info.fieldName !== 'relationships') {
|
||||||
if (fields.relationships) {
|
if (fields.relationships) {
|
||||||
|
|
@ -36,7 +36,7 @@ export function includeRelationships (params, info, fragments = info.fragments)
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
export function includeSubqueries (params, info, fragments = info.fragments) {
|
export function includeSubqueries(params, info, fragments = info.fragments) {
|
||||||
const subqueryIncludes = {
|
const subqueryIncludes = {
|
||||||
aliases: ['aliases'],
|
aliases: ['aliases'],
|
||||||
artistCredit: ['artist-credits'],
|
artistCredit: ['artist-credits'],
|
||||||
|
|
@ -67,7 +67,7 @@ export function includeSubqueries (params, info, fragments = info.fragments) {
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveLookup (root, { mbid, ...params }, { loaders }, info) {
|
export function resolveLookup(root, { mbid, ...params }, { loaders }, info) {
|
||||||
if (!mbid && !params.resource) {
|
if (!mbid && !params.resource) {
|
||||||
throw new Error('Lookups by a field other than MBID must provide: resource')
|
throw new Error('Lookups by a field other than MBID must provide: resource')
|
||||||
}
|
}
|
||||||
|
|
@ -77,16 +77,12 @@ export function resolveLookup (root, { mbid, ...params }, { loaders }, info) {
|
||||||
return loaders.lookup.load([entityType, mbid, params])
|
return loaders.lookup.load([entityType, mbid, params])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveBrowse (root, {
|
export function resolveBrowse(
|
||||||
first,
|
root,
|
||||||
after,
|
{ first, after, type = [], status = [], discID, isrc, iswc, ...args },
|
||||||
type = [],
|
{ loaders },
|
||||||
status = [],
|
info
|
||||||
discID,
|
) {
|
||||||
isrc,
|
|
||||||
iswc,
|
|
||||||
...args
|
|
||||||
}, { loaders }, info) {
|
|
||||||
const pluralName = toDashed(info.fieldName)
|
const pluralName = toDashed(info.fieldName)
|
||||||
const singularName = toSingular(pluralName)
|
const singularName = toSingular(pluralName)
|
||||||
let params = {
|
let params = {
|
||||||
|
|
@ -127,7 +123,11 @@ export function resolveBrowse (root, {
|
||||||
[`${singularName}-count`]: arrayLength = arraySlice.length
|
[`${singularName}-count`]: arrayLength = arraySlice.length
|
||||||
} = list
|
} = list
|
||||||
const meta = { sliceStart, arrayLength }
|
const meta = { sliceStart, arrayLength }
|
||||||
const connection = connectionFromArraySlice(arraySlice, { first, after }, meta)
|
const connection = connectionFromArraySlice(
|
||||||
|
arraySlice,
|
||||||
|
{ first, after },
|
||||||
|
meta
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
nodes: connection.edges.map(edge => edge.node),
|
nodes: connection.edges.map(edge => edge.node),
|
||||||
totalCount: arrayLength,
|
totalCount: arrayLength,
|
||||||
|
|
@ -136,12 +136,12 @@ export function resolveBrowse (root, {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveSearch (root, {
|
export function resolveSearch(
|
||||||
after,
|
root,
|
||||||
first,
|
{ after, first, query, ...args },
|
||||||
query,
|
{ loaders },
|
||||||
...args
|
info
|
||||||
}, { loaders }, info) {
|
) {
|
||||||
const pluralName = toDashed(info.fieldName)
|
const pluralName = toDashed(info.fieldName)
|
||||||
const singularName = toSingular(pluralName)
|
const singularName = toSingular(pluralName)
|
||||||
let params = {
|
let params = {
|
||||||
|
|
@ -157,10 +157,17 @@ export function resolveSearch (root, {
|
||||||
count: arrayLength
|
count: arrayLength
|
||||||
} = list
|
} = list
|
||||||
const meta = { sliceStart, arrayLength }
|
const meta = { sliceStart, arrayLength }
|
||||||
const connection = connectionFromArraySlice(arraySlice, { first, after }, meta)
|
const connection = connectionFromArraySlice(
|
||||||
|
arraySlice,
|
||||||
|
{ first, after },
|
||||||
|
meta
|
||||||
|
)
|
||||||
// Move the `score` field up to the edge object and make sure it's a
|
// Move the `score` field up to the edge object and make sure it's a
|
||||||
// number (MusicBrainz returns a string).
|
// number (MusicBrainz returns a string).
|
||||||
const edges = connection.edges.map(edge => ({ ...edge, score: +edge.node.score }))
|
const edges = connection.edges.map(edge => ({
|
||||||
|
...edge,
|
||||||
|
score: +edge.node.score
|
||||||
|
}))
|
||||||
const connectionWithExtras = {
|
const connectionWithExtras = {
|
||||||
nodes: edges.map(edge => edge.node),
|
nodes: edges.map(edge => edge.node),
|
||||||
totalCount: arrayLength,
|
totalCount: arrayLength,
|
||||||
|
|
@ -171,7 +178,7 @@ export function resolveSearch (root, {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveRelationship (rels, args, context, info) {
|
export function resolveRelationship(rels, args, context, info) {
|
||||||
const targetType = toDashed(toSingular(info.fieldName)).replace('-', '_')
|
const targetType = toDashed(toSingular(info.fieldName)).replace('-', '_')
|
||||||
let matches = rels.filter(rel => rel['target-type'] === targetType)
|
let matches = rels.filter(rel => rel['target-type'] === targetType)
|
||||||
// There's no way to filter these at the API level, so do it here.
|
// There's no way to filter these at the API level, so do it here.
|
||||||
|
|
@ -192,7 +199,7 @@ export function resolveRelationship (rels, args, context, info) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveLinked (entity, args, context, info) {
|
export function resolveLinked(entity, args, context, info) {
|
||||||
const parentEntity = toDashed(info.parentType.name)
|
const parentEntity = toDashed(info.parentType.name)
|
||||||
args = { ...args, [parentEntity]: entity.id }
|
args = { ...args, [parentEntity]: entity.id }
|
||||||
return resolveBrowse(entity, args, context, info)
|
return resolveBrowse(entity, args, context, info)
|
||||||
|
|
@ -203,7 +210,10 @@ export function resolveLinked (entity, args, context, info) {
|
||||||
* for a particular field that's being requested, make another request to grab
|
* for a particular field that's being requested, make another request to grab
|
||||||
* it (after making sure it isn't already available).
|
* it (after making sure it isn't already available).
|
||||||
*/
|
*/
|
||||||
export function createSubqueryResolver ({ inc, key } = {}, handler = value => value) {
|
export function createSubqueryResolver(
|
||||||
|
{ inc, key } = {},
|
||||||
|
handler = value => value
|
||||||
|
) {
|
||||||
return (entity, args, { loaders }, info) => {
|
return (entity, args, { loaders }, info) => {
|
||||||
key = key || toDashed(info.fieldName)
|
key = key || toDashed(info.fieldName)
|
||||||
let promise
|
let promise
|
||||||
|
|
@ -218,7 +228,7 @@ export function createSubqueryResolver ({ inc, key } = {}, handler = value => va
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveDiscReleases (disc, args, context, info) {
|
export function resolveDiscReleases(disc, args, context, info) {
|
||||||
const { releases } = disc
|
const { releases } = disc
|
||||||
if (releases != null) {
|
if (releases != null) {
|
||||||
const connection = connectionFromArray(releases, args)
|
const connection = connectionFromArray(releases, args)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ import { nodeField } from './types/node'
|
||||||
|
|
||||||
const debug = require('debug')('graphbrainz:schema')
|
const debug = require('debug')('graphbrainz:schema')
|
||||||
|
|
||||||
export function applyExtension (extension, schema, options = {}) {
|
export function applyExtension(extension, schema, options = {}) {
|
||||||
let outputSchema = schema
|
let outputSchema = schema
|
||||||
if (extension.extendSchema) {
|
if (extension.extendSchema) {
|
||||||
if (typeof extension.extendSchema === 'object') {
|
if (typeof extension.extendSchema === 'object') {
|
||||||
debug(`Extending schema via an object from the “${extension.name}” extension.`)
|
debug(
|
||||||
|
`Extending schema via an object from the “${extension.name}” extension.`
|
||||||
|
)
|
||||||
const { schemas = [], resolvers } = extension.extendSchema
|
const { schemas = [], resolvers } = extension.extendSchema
|
||||||
outputSchema = schemas.reduce((updatedSchema, extensionSchema) => {
|
outputSchema = schemas.reduce((updatedSchema, extensionSchema) => {
|
||||||
if (typeof extensionSchema === 'string') {
|
if (typeof extensionSchema === 'string') {
|
||||||
|
|
@ -21,12 +23,16 @@ export function applyExtension (extension, schema, options = {}) {
|
||||||
addResolveFunctionsToSchema(outputSchema, resolvers)
|
addResolveFunctionsToSchema(outputSchema, resolvers)
|
||||||
}
|
}
|
||||||
} else if (typeof extension.extendSchema === 'function') {
|
} else if (typeof extension.extendSchema === 'function') {
|
||||||
debug(`Extending schema via a function from the “${extension.name}” extension.`)
|
debug(
|
||||||
|
`Extending schema via a function from the “${
|
||||||
|
extension.name
|
||||||
|
}” extension.`
|
||||||
|
)
|
||||||
outputSchema = extension.extendSchema(schema, options)
|
outputSchema = extension.extendSchema(schema, options)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The “${extension.name}” extension contains an invalid ` +
|
`The “${extension.name}” extension contains an invalid ` +
|
||||||
`\`extendSchema\` value: ${extension.extendSchema}`
|
`\`extendSchema\` value: ${extension.extendSchema}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +44,7 @@ export function applyExtension (extension, schema, options = {}) {
|
||||||
return outputSchema
|
return outputSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSchema (schema, options = {}) {
|
export function createSchema(schema, options = {}) {
|
||||||
const extensions = options.extensions || []
|
const extensions = options.extensions || []
|
||||||
return extensions.reduce((updatedSchema, extension) => {
|
return extensions.reduce((updatedSchema, extension) => {
|
||||||
return applyExtension(extension, updatedSchema, options)
|
return applyExtension(extension, updatedSchema, options)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { GraphQLObjectType, GraphQLBoolean } from 'graphql/type'
|
||||||
GraphQLObjectType,
|
|
||||||
GraphQLBoolean
|
|
||||||
} from 'graphql/type'
|
|
||||||
import { Locale } from './scalars'
|
import { Locale } from './scalars'
|
||||||
import { name, sortName, fieldWithID } from './helpers'
|
import { name, sortName, fieldWithID } from './helpers'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ track, etc., and join phrases between them.`,
|
||||||
type: Artist,
|
type: Artist,
|
||||||
description: `The entity representing the artist referenced in the
|
description: `The entity representing the artist referenced in the
|
||||||
credits.`,
|
credits.`,
|
||||||
resolve: (source) => {
|
resolve: source => {
|
||||||
const { artist } = source
|
const { artist } = source
|
||||||
if (artist) {
|
if (artist) {
|
||||||
artist._type = 'artist'
|
artist._type = 'artist'
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { GraphQLObjectType, GraphQLNonNull, GraphQLString } from 'graphql/type'
|
||||||
GraphQLObjectType,
|
|
||||||
GraphQLNonNull,
|
|
||||||
GraphQLString
|
|
||||||
} from 'graphql/type'
|
|
||||||
import Node from './node'
|
import Node from './node'
|
||||||
import Entity from './entity'
|
import Entity from './entity'
|
||||||
import {
|
import {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { mbid, connectionWithExtras } from './helpers'
|
||||||
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) {
|
resolveType(value) {
|
||||||
if (value._type && require.resolve(`./${value._type}`)) {
|
if (value._type && require.resolve(`./${value._type}`)) {
|
||||||
return require(`./${value._type}`).default
|
return require(`./${value._type}`).default
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ distinctive name.`,
|
||||||
},
|
},
|
||||||
ORCHESTRA: {
|
ORCHESTRA: {
|
||||||
name: 'Orchestra',
|
name: 'Orchestra',
|
||||||
description: 'This indicates an orchestra (a large instrumental ensemble).',
|
description:
|
||||||
|
'This indicates an orchestra (a large instrumental ensemble).',
|
||||||
value: 'Orchestra'
|
value: 'Orchestra'
|
||||||
},
|
},
|
||||||
CHOIR: {
|
CHOIR: {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ artists and works. See the [setlist documentation](https://musicbrainz.org/doc/E
|
||||||
for syntax and examples.`
|
for syntax and examples.`
|
||||||
},
|
},
|
||||||
...fieldWithID('type', {
|
...fieldWithID('type', {
|
||||||
description: 'What kind of event the event is, e.g. concert, festival, etc.'
|
description:
|
||||||
|
'What kind of event the event is, e.g. concert, festival, etc.'
|
||||||
}),
|
}),
|
||||||
relationships,
|
relationships,
|
||||||
collections,
|
collections,
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,15 @@ import {
|
||||||
export const toPascal = pascalCase
|
export const toPascal = pascalCase
|
||||||
export const toDashed = dashify
|
export const toDashed = dashify
|
||||||
|
|
||||||
export function toPlural (name) {
|
export function toPlural(name) {
|
||||||
return name.endsWith('s') ? name : name + 's'
|
return name.endsWith('s') ? name : name + 's'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toSingular (name) {
|
export function toSingular(name) {
|
||||||
return name.endsWith('s') && !/series/i.test(name) ? name.slice(0, -1) : name
|
return name.endsWith('s') && !/series/i.test(name) ? name.slice(0, -1) : name
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toWords (name) {
|
export function toWords(name) {
|
||||||
return toPascal(name).replace(/([^A-Z])?([A-Z]+)/g, (match, tail, head) => {
|
return toPascal(name).replace(/([^A-Z])?([A-Z]+)/g, (match, tail, head) => {
|
||||||
tail = tail ? tail + ' ' : ''
|
tail = tail ? tail + ' ' : ''
|
||||||
head = head.length > 1 ? head : head.toLowerCase()
|
head = head.length > 1 ? head : head.toLowerCase()
|
||||||
|
|
@ -60,13 +60,13 @@ export function toWords (name) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveHyphenated (obj, args, context, info) {
|
export function resolveHyphenated(obj, args, context, info) {
|
||||||
const name = toDashed(info.fieldName)
|
const name = toDashed(info.fieldName)
|
||||||
return obj[name]
|
return obj[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveWithFallback (keys) {
|
export function resolveWithFallback(keys) {
|
||||||
return (obj) => {
|
return obj => {
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
const key = keys[i]
|
const key = keys[i]
|
||||||
if (key in obj) {
|
if (key in obj) {
|
||||||
|
|
@ -76,7 +76,7 @@ export function resolveWithFallback (keys) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fieldWithID (name, config = {}) {
|
export function fieldWithID(name, config = {}) {
|
||||||
config = {
|
config = {
|
||||||
type: GraphQLString,
|
type: GraphQLString,
|
||||||
resolve: resolveHyphenated,
|
resolve: resolveHyphenated,
|
||||||
|
|
@ -95,7 +95,8 @@ field.`,
|
||||||
if (fieldName in entity) {
|
if (fieldName in entity) {
|
||||||
return entity[fieldName]
|
return entity[fieldName]
|
||||||
}
|
}
|
||||||
return loaders.lookup.load([entity._type, entity.id])
|
return loaders.lookup
|
||||||
|
.load([entity._type, entity.id])
|
||||||
.then(data => data[fieldName])
|
.then(data => data[fieldName])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +106,7 @@ field.`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCollectionField (config) {
|
export function createCollectionField(config) {
|
||||||
const typeName = toPlural(toWords(config.type.name.slice(0, -10)))
|
const typeName = toPlural(toWords(config.type.name.slice(0, -10)))
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
|
|
@ -145,7 +146,7 @@ meaning depends on the type of entity.`,
|
||||||
resolve: resolveHyphenated
|
resolve: resolveHyphenated
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkedQuery (connectionType, { args, ...config } = {}) {
|
function linkedQuery(connectionType, { args, ...config } = {}) {
|
||||||
const typeName = toPlural(toWords(connectionType.name.slice(0, -10)))
|
const typeName = toPlural(toWords(connectionType.name.slice(0, -10)))
|
||||||
return {
|
return {
|
||||||
type: connectionType,
|
type: connectionType,
|
||||||
|
|
@ -293,7 +294,7 @@ export const score = {
|
||||||
these results were found through a search.`
|
these results were found through a search.`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function connectionWithExtras (nodeType) {
|
export function connectionWithExtras(nodeType) {
|
||||||
return connectionDefinitions({
|
return connectionDefinitions({
|
||||||
nodeType,
|
nodeType,
|
||||||
connectionFields: () => ({
|
connectionFields: () => ({
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ export { default as Label, LabelConnection } from './label'
|
||||||
export { default as Place, PlaceConnection } from './place'
|
export { default as Place, PlaceConnection } from './place'
|
||||||
export { default as Recording, RecordingConnection } from './recording'
|
export { default as Recording, RecordingConnection } from './recording'
|
||||||
export { default as Release, ReleaseConnection } from './release'
|
export { default as Release, ReleaseConnection } from './release'
|
||||||
export { default as ReleaseGroup, ReleaseGroupConnection } from './release-group'
|
export {
|
||||||
|
default as ReleaseGroup,
|
||||||
|
ReleaseGroupConnection
|
||||||
|
} from './release-group'
|
||||||
export { default as Series, SeriesConnection } from './series'
|
export { default as Series, SeriesConnection } from './series'
|
||||||
export { default as Tag, TagConnection } from './tag'
|
export { default as Tag, TagConnection } from './tag'
|
||||||
export { default as URL, URLConnection } from './url'
|
export { default as URL, URLConnection } from './url'
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ multi-disc release).`
|
||||||
},
|
},
|
||||||
discs: {
|
discs: {
|
||||||
type: new GraphQLList(Disc),
|
type: new GraphQLList(Disc),
|
||||||
description: 'A list of physical discs and their disc IDs for this medium.'
|
description:
|
||||||
|
'A list of physical discs and their disc IDs for this medium.'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ 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 => {
|
||||||
const type = TYPE_MODULES[obj._type] || obj._type
|
const type = TYPE_MODULES[obj._type] || obj._type
|
||||||
try {
|
try {
|
||||||
return require(`./${type}`).default
|
return require(`./${type}`).default
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { GraphQLObjectType, GraphQLList, GraphQLBoolean } from 'graphql/type'
|
||||||
GraphQLObjectType,
|
|
||||||
GraphQLList,
|
|
||||||
GraphQLBoolean
|
|
||||||
} from 'graphql/type'
|
|
||||||
import Node from './node'
|
import Node from './node'
|
||||||
import Entity from './entity'
|
import Entity from './entity'
|
||||||
import { Duration, ISRC } from './scalars'
|
import { Duration, ISRC } from './scalars'
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,7 @@ import {
|
||||||
} from 'graphql/type'
|
} from 'graphql/type'
|
||||||
import { DateType } from './scalars'
|
import { DateType } from './scalars'
|
||||||
import Entity from './entity'
|
import Entity from './entity'
|
||||||
import {
|
import { resolveHyphenated, fieldWithID, connectionWithExtras } from './helpers'
|
||||||
resolveHyphenated,
|
|
||||||
fieldWithID,
|
|
||||||
connectionWithExtras
|
|
||||||
} from './helpers'
|
|
||||||
|
|
||||||
const Relationship = new GraphQLObjectType({
|
const Relationship = new GraphQLObjectType({
|
||||||
name: 'Relationship',
|
name: 'Relationship',
|
||||||
|
|
@ -35,7 +31,8 @@ other and to URLs outside MusicBrainz.`,
|
||||||
},
|
},
|
||||||
targetType: {
|
targetType: {
|
||||||
type: new GraphQLNonNull(GraphQLString),
|
type: new GraphQLNonNull(GraphQLString),
|
||||||
description: 'The type of entity on the receiving end of the relationship.',
|
description:
|
||||||
|
'The type of entity on the receiving end of the relationship.',
|
||||||
resolve: resolveHyphenated
|
resolve: resolveHyphenated
|
||||||
},
|
},
|
||||||
sourceCredit: {
|
sourceCredit: {
|
||||||
|
|
@ -56,7 +53,8 @@ from its main (performance) name.`,
|
||||||
},
|
},
|
||||||
end: {
|
end: {
|
||||||
type: DateType,
|
type: DateType,
|
||||||
description: 'The date on which the relationship became no longer applicable.'
|
description:
|
||||||
|
'The date on which the relationship became no longer applicable.'
|
||||||
},
|
},
|
||||||
ended: {
|
ended: {
|
||||||
type: GraphQLBoolean,
|
type: GraphQLBoolean,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql/type'
|
||||||
GraphQLObjectType,
|
|
||||||
GraphQLString,
|
|
||||||
GraphQLList
|
|
||||||
} from 'graphql/type'
|
|
||||||
import Node from './node'
|
import Node from './node'
|
||||||
import Entity from './entity'
|
import Entity from './entity'
|
||||||
import { ASIN, DateType } from './scalars'
|
import { ASIN, DateType } from './scalars'
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Kind } from 'graphql/language'
|
import { Kind } from 'graphql/language'
|
||||||
import { GraphQLScalarType } from 'graphql/type'
|
import { GraphQLScalarType } from 'graphql/type'
|
||||||
|
|
||||||
function createScalar (config) {
|
function createScalar(config) {
|
||||||
return new GraphQLScalarType({
|
return new GraphQLScalarType({
|
||||||
serialize: value => value,
|
serialize: value => value,
|
||||||
parseValue: value => value,
|
parseValue: value => value,
|
||||||
parseLiteral (ast) {
|
parseLiteral(ast) {
|
||||||
if (ast.kind === Kind.STRING) {
|
if (ast.kind === Kind.STRING) {
|
||||||
return ast.value
|
return ast.value
|
||||||
}
|
}
|
||||||
|
|
@ -20,28 +20,28 @@ const locale = /^([a-z]{2})(_[A-Z]{2})?(\.[a-zA-Z0-9-]+)?$/
|
||||||
// Be extremely lenient; just prevent major input errors.
|
// Be extremely lenient; just prevent major input errors.
|
||||||
const url = /^\w+:\/\/[\w-]+\.\w+/
|
const url = /^\w+:\/\/[\w-]+\.\w+/
|
||||||
|
|
||||||
function validateMBID (value) {
|
function validateMBID(value) {
|
||||||
if (typeof value === 'string' && uuid.test(value)) {
|
if (typeof value === 'string' && uuid.test(value)) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
throw new TypeError(`Malformed MBID: ${value}`)
|
throw new TypeError(`Malformed MBID: ${value}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePositive (value) {
|
function validatePositive(value) {
|
||||||
if (value >= 0) {
|
if (value >= 0) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
throw new TypeError(`Expected positive value: ${value}`)
|
throw new TypeError(`Expected positive value: ${value}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateLocale (value) {
|
function validateLocale(value) {
|
||||||
if (typeof value === 'string' && locale.test(value)) {
|
if (typeof value === 'string' && locale.test(value)) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
throw new TypeError(`Malformed locale: ${value}`)
|
throw new TypeError(`Malformed locale: ${value}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateURL (value) {
|
function validateURL(value) {
|
||||||
if (typeof value === 'string' && url.test(value)) {
|
if (typeof value === 'string' && url.test(value)) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +57,8 @@ and its partners for product identification within the Amazon organization.`
|
||||||
|
|
||||||
export const DateType = createScalar({
|
export const DateType = createScalar({
|
||||||
name: 'Date',
|
name: 'Date',
|
||||||
description: 'Year, month (optional), and day (optional) in YYYY-MM-DD format.'
|
description:
|
||||||
|
'Year, month (optional), and day (optional) in YYYY-MM-DD format.'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Degrees = createScalar({
|
export const Degrees = createScalar({
|
||||||
|
|
@ -87,7 +88,7 @@ export const Duration = createScalar({
|
||||||
description: 'A length of time, in milliseconds.',
|
description: 'A length of time, in milliseconds.',
|
||||||
serialize: validatePositive,
|
serialize: validatePositive,
|
||||||
parseValue: validatePositive,
|
parseValue: validatePositive,
|
||||||
parseLiteral (ast) {
|
parseLiteral(ast) {
|
||||||
if (ast.kind === Kind.INT) {
|
if (ast.kind === Kind.INT) {
|
||||||
return validatePositive(parseInt(ast.value, 10))
|
return validatePositive(parseInt(ast.value, 10))
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +136,7 @@ export const Locale = createScalar({
|
||||||
description: 'Language code, optionally with country and encoding.',
|
description: 'Language code, optionally with country and encoding.',
|
||||||
serialize: validateLocale,
|
serialize: validateLocale,
|
||||||
parseValue: validateLocale,
|
parseValue: validateLocale,
|
||||||
parseLiteral (ast) {
|
parseLiteral(ast) {
|
||||||
if (ast.kind === Kind.STRING) {
|
if (ast.kind === Kind.STRING) {
|
||||||
return validateLocale(ast.value)
|
return validateLocale(ast.value)
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +150,7 @@ export const MBID = createScalar({
|
||||||
36-character UUIDs.`,
|
36-character UUIDs.`,
|
||||||
serialize: validateMBID,
|
serialize: validateMBID,
|
||||||
parseValue: validateMBID,
|
parseValue: validateMBID,
|
||||||
parseLiteral (ast) {
|
parseLiteral(ast) {
|
||||||
if (ast.kind === Kind.STRING) {
|
if (ast.kind === Kind.STRING) {
|
||||||
return validateMBID(ast.value)
|
return validateMBID(ast.value)
|
||||||
}
|
}
|
||||||
|
|
@ -167,7 +168,7 @@ export const URLString = createScalar({
|
||||||
description: 'A web address.',
|
description: 'A web address.',
|
||||||
serialize: validateURL,
|
serialize: validateURL,
|
||||||
parseValue: validateURL,
|
parseValue: validateURL,
|
||||||
parseLiteral (ast) {
|
parseLiteral(ast) {
|
||||||
if (ast.kind === Kind.STRING) {
|
if (ast.kind === Kind.STRING) {
|
||||||
return validateURL(ast.value)
|
return validateURL(ast.value)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
src/util.js
13
src/util.js
|
|
@ -2,7 +2,7 @@ import util from 'util'
|
||||||
|
|
||||||
export const ONE_DAY = 24 * 60 * 60 * 1000
|
export const ONE_DAY = 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
export function getFields (info, fragments = info.fragments) {
|
export function getFields(info, fragments = info.fragments) {
|
||||||
if (info.kind !== 'Field') {
|
if (info.kind !== 'Field') {
|
||||||
info = info.fieldNodes[0]
|
info = info.fieldNodes[0]
|
||||||
}
|
}
|
||||||
|
|
@ -25,17 +25,18 @@ export function getFields (info, fragments = info.fragments) {
|
||||||
return selections.reduce(reducer, {})
|
return selections.reduce(reducer, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prettyPrint (obj, { depth = 5,
|
export function prettyPrint(
|
||||||
colors = true,
|
obj,
|
||||||
breakLength = 120 } = {}) {
|
{ depth = 5, colors = true, breakLength = 120 } = {}
|
||||||
|
) {
|
||||||
console.log(util.inspect(obj, { depth, colors, breakLength }))
|
console.log(util.inspect(obj, { depth, colors, breakLength }))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toFilteredArray (obj) {
|
export function toFilteredArray(obj) {
|
||||||
return (Array.isArray(obj) ? obj : [obj]).filter(x => x)
|
return (Array.isArray(obj) ? obj : [obj]).filter(x => x)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extendIncludes (includes, moreIncludes) {
|
export function extendIncludes(includes, moreIncludes) {
|
||||||
includes = toFilteredArray(includes)
|
includes = toFilteredArray(includes)
|
||||||
moreIncludes = toFilteredArray(moreIncludes)
|
moreIncludes = toFilteredArray(moreIncludes)
|
||||||
const seen = {}
|
const seen = {}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ import Client from '../../src/api/client'
|
||||||
|
|
||||||
test('parseErrorMessage() returns the body or status code', t => {
|
test('parseErrorMessage() returns the body or status code', t => {
|
||||||
const client = new Client()
|
const client = new Client()
|
||||||
t.is(client.parseErrorMessage({ statusCode: 500 }, 'something went wrong'), 'something went wrong')
|
t.is(
|
||||||
|
client.parseErrorMessage({ statusCode: 500 }, 'something went wrong'),
|
||||||
|
'something went wrong'
|
||||||
|
)
|
||||||
t.is(client.parseErrorMessage({ statusCode: 500 }, ''), '500')
|
t.is(client.parseErrorMessage({ statusCode: 500 }, ''), '500')
|
||||||
t.is(client.parseErrorMessage({ statusCode: 404 }, {}), '404')
|
t.is(client.parseErrorMessage({ statusCode: 404 }, {}), '404')
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,28 +3,39 @@ import MusicBrainz, { MusicBrainzError } from '../../src/api'
|
||||||
import client from '../helpers/client/musicbrainz'
|
import client from '../helpers/client/musicbrainz'
|
||||||
|
|
||||||
test('getLookupURL() generates a lookup URL', t => {
|
test('getLookupURL() generates a lookup URL', t => {
|
||||||
t.is(client.getLookupURL('artist', 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8', {
|
t.is(
|
||||||
inc: ['recordings', 'release-groups']
|
client.getLookupURL('artist', 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8', {
|
||||||
}), 'artist/c8da2e40-bd28-4d4e-813a-bd2f51958ba8?inc=recordings%2Brelease-groups')
|
inc: ['recordings', 'release-groups']
|
||||||
|
}),
|
||||||
|
'artist/c8da2e40-bd28-4d4e-813a-bd2f51958ba8?inc=recordings%2Brelease-groups'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('getBrowseURL() generates a browse URL', t => {
|
test('getBrowseURL() generates a browse URL', t => {
|
||||||
t.is(client.getBrowseURL('recording', {
|
t.is(
|
||||||
artist: 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8',
|
client.getBrowseURL('recording', {
|
||||||
limit: null,
|
artist: 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8',
|
||||||
offset: 0
|
limit: null,
|
||||||
}), 'recording?artist=c8da2e40-bd28-4d4e-813a-bd2f51958ba8&offset=0')
|
offset: 0
|
||||||
|
}),
|
||||||
|
'recording?artist=c8da2e40-bd28-4d4e-813a-bd2f51958ba8&offset=0'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('getSearchURL() generates a search URL', t => {
|
test('getSearchURL() generates a search URL', t => {
|
||||||
t.is(client.getSearchURL('artist', 'Lures', { inc: null }), 'artist?query=Lures')
|
t.is(
|
||||||
|
client.getSearchURL('artist', 'Lures', { inc: null }),
|
||||||
|
'artist?query=Lures'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('lookup() sends a lookup query', t => {
|
test('lookup() sends a lookup query', t => {
|
||||||
return client.lookup('artist', 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8').then(response => {
|
return client
|
||||||
t.is(response.id, 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8')
|
.lookup('artist', 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8')
|
||||||
t.is(response.type, 'Group')
|
.then(response => {
|
||||||
})
|
t.is(response.id, 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8')
|
||||||
|
t.is(response.type, 'Group')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('rejects the promise when the API returns an error', t => {
|
test('rejects the promise when the API returns an error', t => {
|
||||||
|
|
@ -60,7 +71,10 @@ test('shouldRetry() retries only transient local connection issues', t => {
|
||||||
|
|
||||||
test('rejects non-MusicBrainz errors', t => {
|
test('rejects non-MusicBrainz errors', t => {
|
||||||
const client = new MusicBrainz({ baseURL: '$!@#$' })
|
const client = new MusicBrainz({ baseURL: '$!@#$' })
|
||||||
return t.throws(client.get('artist/5b11f4ce-a62d-471e-81fc-a69a8278c7da'), Error)
|
return t.throws(
|
||||||
|
client.get('artist/5b11f4ce-a62d-471e-81fc-a69a8278c7da'),
|
||||||
|
Error
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('uses the default error impementation if there is no JSON error', t => {
|
test('uses the default error impementation if there is no JSON error', t => {
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,35 @@ import test from 'ava'
|
||||||
import client from '../../helpers/client/cover-art-archive'
|
import client from '../../helpers/client/cover-art-archive'
|
||||||
|
|
||||||
test('can retrieve a front image URL', t => {
|
test('can retrieve a front image URL', t => {
|
||||||
return client.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'front')
|
return client
|
||||||
|
.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'front')
|
||||||
.then(url => {
|
.then(url => {
|
||||||
t.is(url, 'http://archive.org/download/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd-829521842.jpg')
|
t.is(
|
||||||
|
url,
|
||||||
|
'http://archive.org/download/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd-829521842.jpg'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('can retrieve a back image URL', t => {
|
test('can retrieve a back image URL', t => {
|
||||||
return client.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'back')
|
return client
|
||||||
|
.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'back')
|
||||||
.then(url => {
|
.then(url => {
|
||||||
t.is(url, 'http://archive.org/download/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd-5769317885.jpg')
|
t.is(
|
||||||
|
url,
|
||||||
|
'http://archive.org/download/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd-5769317885.jpg'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('can retrieve a list of release images', t => {
|
test('can retrieve a list of release images', t => {
|
||||||
return client.images('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd')
|
return client
|
||||||
|
.images('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd')
|
||||||
.then(data => {
|
.then(data => {
|
||||||
t.is(data.release, 'http://musicbrainz.org/release/76df3287-6cda-33eb-8e9a-044b5e15ffdd')
|
t.is(
|
||||||
|
data.release,
|
||||||
|
'http://musicbrainz.org/release/76df3287-6cda-33eb-8e9a-044b5e15ffdd'
|
||||||
|
)
|
||||||
t.true(data.images.length >= 3)
|
t.true(data.images.length >= 3)
|
||||||
data.images.forEach(image => {
|
data.images.forEach(image => {
|
||||||
t.true(image.approved)
|
t.true(image.approved)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import baseContext from '../../helpers/context'
|
||||||
const schema = applyExtension(extension, baseSchema)
|
const schema = applyExtension(extension, baseSchema)
|
||||||
const context = extension.extendContext(baseContext)
|
const context = extension.extendContext(baseContext)
|
||||||
|
|
||||||
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) {
|
||||||
console.log(result.errors)
|
console.log(result.errors)
|
||||||
|
|
@ -17,7 +17,10 @@ function testData (t, query, handler) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
test('releases have a cover art summary', testData, `
|
test(
|
||||||
|
'releases have a cover art summary',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
|
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
|
||||||
|
|
@ -28,13 +31,18 @@ test('releases have a cover art summary', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
const { coverArtArchive } = data.lookup.release
|
(t, data) => {
|
||||||
t.true(coverArtArchive.artwork)
|
const { coverArtArchive } = data.lookup.release
|
||||||
t.true(coverArtArchive.count >= 10)
|
t.true(coverArtArchive.artwork)
|
||||||
})
|
t.true(coverArtArchive.count >= 10)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('releases have a set of cover art images', testData, `
|
test(
|
||||||
|
'releases have a set of cover art images',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
|
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
|
||||||
|
|
@ -59,28 +67,55 @@ test('releases have a set of cover art images', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
const { coverArtArchive } = data.lookup.release
|
(t, data) => {
|
||||||
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg')
|
const { coverArtArchive } = data.lookup.release
|
||||||
t.is(coverArtArchive.back, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798.jpg')
|
t.is(
|
||||||
t.true(coverArtArchive.images.length >= 10)
|
coverArtArchive.front,
|
||||||
t.true(coverArtArchive.images.some(image => image.front === true))
|
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg'
|
||||||
t.true(coverArtArchive.images.some(image => image.back === true))
|
)
|
||||||
t.true(coverArtArchive.images.some(image => image.types.indexOf('Front') >= 0))
|
t.is(
|
||||||
t.true(coverArtArchive.images.some(image => image.types.indexOf('Back') >= 0))
|
coverArtArchive.back,
|
||||||
t.true(coverArtArchive.images.some(image => image.types.indexOf('Liner') >= 0))
|
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798.jpg'
|
||||||
t.true(coverArtArchive.images.some(image => image.types.indexOf('Poster') >= 0))
|
)
|
||||||
t.true(coverArtArchive.images.some(image => image.types.indexOf('Medium') >= 0))
|
t.true(coverArtArchive.images.length >= 10)
|
||||||
t.true(coverArtArchive.images.some(image => image.edit === 18544122))
|
t.true(coverArtArchive.images.some(image => image.front === true))
|
||||||
t.true(coverArtArchive.images.some(image => image.comment === ''))
|
t.true(coverArtArchive.images.some(image => image.back === true))
|
||||||
t.true(coverArtArchive.images.some(image => image.fileID === '1611507818'))
|
t.true(
|
||||||
t.true(coverArtArchive.images.some(image => image.image === 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536422691.jpg'))
|
coverArtArchive.images.some(image => image.types.indexOf('Front') >= 0)
|
||||||
t.true(coverArtArchive.images.every(image => image.approved === true))
|
)
|
||||||
t.true(coverArtArchive.images.every(image => image.thumbnails.small))
|
t.true(
|
||||||
t.true(coverArtArchive.images.every(image => image.thumbnails.large))
|
coverArtArchive.images.some(image => image.types.indexOf('Back') >= 0)
|
||||||
})
|
)
|
||||||
|
t.true(
|
||||||
|
coverArtArchive.images.some(image => image.types.indexOf('Liner') >= 0)
|
||||||
|
)
|
||||||
|
t.true(
|
||||||
|
coverArtArchive.images.some(image => image.types.indexOf('Poster') >= 0)
|
||||||
|
)
|
||||||
|
t.true(
|
||||||
|
coverArtArchive.images.some(image => image.types.indexOf('Medium') >= 0)
|
||||||
|
)
|
||||||
|
t.true(coverArtArchive.images.some(image => image.edit === 18544122))
|
||||||
|
t.true(coverArtArchive.images.some(image => image.comment === ''))
|
||||||
|
t.true(coverArtArchive.images.some(image => image.fileID === '1611507818'))
|
||||||
|
t.true(
|
||||||
|
coverArtArchive.images.some(
|
||||||
|
image =>
|
||||||
|
image.image ===
|
||||||
|
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536422691.jpg'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
t.true(coverArtArchive.images.every(image => image.approved === true))
|
||||||
|
t.true(coverArtArchive.images.every(image => image.thumbnails.small))
|
||||||
|
t.true(coverArtArchive.images.every(image => image.thumbnails.large))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('can request a size for front and back cover art', testData, `
|
test(
|
||||||
|
'can request a size for front and back cover art',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
|
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
|
||||||
|
|
@ -92,14 +127,28 @@ test('can request a size for front and back cover art', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
const { coverArtArchive } = data.lookup.release
|
(t, data) => {
|
||||||
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818-500.jpg')
|
const { coverArtArchive } = data.lookup.release
|
||||||
t.is(coverArtArchive.back, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798-250.jpg')
|
t.is(
|
||||||
t.is(coverArtArchive.fullFront, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg')
|
coverArtArchive.front,
|
||||||
})
|
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818-500.jpg'
|
||||||
|
)
|
||||||
|
t.is(
|
||||||
|
coverArtArchive.back,
|
||||||
|
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798-250.jpg'
|
||||||
|
)
|
||||||
|
t.is(
|
||||||
|
coverArtArchive.fullFront,
|
||||||
|
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('release groups have a front cover art image', testData, `
|
test(
|
||||||
|
'release groups have a front cover art image',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
|
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
|
||||||
|
|
@ -118,17 +167,25 @@ test('release groups have a front cover art image', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
const { coverArtArchive } = data.lookup.releaseGroup
|
(t, data) => {
|
||||||
t.true(coverArtArchive.artwork)
|
const { coverArtArchive } = data.lookup.releaseGroup
|
||||||
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275.jpg')
|
t.true(coverArtArchive.artwork)
|
||||||
t.is(coverArtArchive.release.mbid, '25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27')
|
t.is(
|
||||||
t.is(coverArtArchive.release.title, 'The Dark Side of the Moon')
|
coverArtArchive.front,
|
||||||
t.is(coverArtArchive.images.length, 1)
|
'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275.jpg'
|
||||||
t.true(coverArtArchive.images[0].front)
|
)
|
||||||
})
|
t.is(coverArtArchive.release.mbid, '25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27')
|
||||||
|
t.is(coverArtArchive.release.title, 'The Dark Side of the Moon')
|
||||||
|
t.is(coverArtArchive.images.length, 1)
|
||||||
|
t.true(coverArtArchive.images[0].front)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('release groups have different cover art sizes available', testData, `
|
test(
|
||||||
|
'release groups have different cover art sizes available',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
|
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
|
||||||
|
|
@ -139,13 +196,24 @@ test('release groups have different cover art sizes available', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
const { coverArtArchive } = data.lookup.releaseGroup
|
(t, data) => {
|
||||||
t.is(coverArtArchive.small, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-250.jpg')
|
const { coverArtArchive } = data.lookup.releaseGroup
|
||||||
t.is(coverArtArchive.large, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-500.jpg')
|
t.is(
|
||||||
})
|
coverArtArchive.small,
|
||||||
|
'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-250.jpg'
|
||||||
|
)
|
||||||
|
t.is(
|
||||||
|
coverArtArchive.large,
|
||||||
|
'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-500.jpg'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('can retrieve cover art in searches', testData, `
|
test(
|
||||||
|
'can retrieve cover art in searches',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
search {
|
search {
|
||||||
releases(query: "You Want It Darker") {
|
releases(query: "You Want It Darker") {
|
||||||
|
|
@ -164,11 +232,13 @@ test('can retrieve cover art in searches', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
const releases = data.search.releases.edges.map(edge => edge.node)
|
(t, data) => {
|
||||||
t.is(releases.length, 25)
|
const releases = data.search.releases.edges.map(edge => edge.node)
|
||||||
t.true(releases.some(release => release.coverArtArchive.artwork === true))
|
t.is(releases.length, 25)
|
||||||
t.true(releases.some(release => release.coverArtArchive.images.length > 0))
|
t.true(releases.some(release => release.coverArtArchive.artwork === true))
|
||||||
t.true(releases.some(release => release.coverArtArchive.front === null))
|
t.true(releases.some(release => release.coverArtArchive.images.length > 0))
|
||||||
t.true(releases.some(release => release.coverArtArchive.back === null))
|
t.true(releases.some(release => release.coverArtArchive.front === null))
|
||||||
})
|
t.true(releases.some(release => release.coverArtArchive.back === null))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import baseContext from '../../helpers/context'
|
||||||
const schema = applyExtension(extension, baseSchema)
|
const schema = applyExtension(extension, baseSchema)
|
||||||
const context = extension.extendContext(baseContext)
|
const context = extension.extendContext(baseContext)
|
||||||
|
|
||||||
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) {
|
||||||
console.log(result.errors)
|
console.log(result.errors)
|
||||||
|
|
@ -17,7 +17,10 @@ function testData (t, query, handler) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
test('artists have a fanArt field and preview images', testData, `
|
test(
|
||||||
|
'artists have a fanArt field and preview images',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
|
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
|
||||||
|
|
@ -56,21 +59,26 @@ test('artists have a fanArt field and preview images', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
const { fanArt } = data.lookup.artist
|
t.snapshot(data)
|
||||||
const allImages = []
|
const { fanArt } = data.lookup.artist
|
||||||
.concat(fanArt.backgrounds)
|
const allImages = []
|
||||||
.concat(fanArt.banners)
|
.concat(fanArt.backgrounds)
|
||||||
.concat(fanArt.logos)
|
.concat(fanArt.banners)
|
||||||
.concat(fanArt.logosHD)
|
.concat(fanArt.logos)
|
||||||
.concat(fanArt.thumbnails)
|
.concat(fanArt.logosHD)
|
||||||
allImages.forEach(image => {
|
.concat(fanArt.thumbnails)
|
||||||
t.not(image.url, image.fullSizeURL)
|
allImages.forEach(image => {
|
||||||
})
|
t.not(image.url, image.fullSizeURL)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('release groups have a fanArt field and preview images', testData, `
|
test(
|
||||||
|
'release groups have a fanArt field and preview images',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
|
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
|
||||||
|
|
@ -92,18 +100,21 @@ test('release groups have a fanArt field and preview images', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
const { fanArt } = data.lookup.releaseGroup
|
t.snapshot(data)
|
||||||
const allImages = []
|
const { fanArt } = data.lookup.releaseGroup
|
||||||
.concat(fanArt.albumCovers)
|
const allImages = [].concat(fanArt.albumCovers).concat(fanArt.discImages)
|
||||||
.concat(fanArt.discImages)
|
allImages.forEach(image => {
|
||||||
allImages.forEach(image => {
|
t.not(image.url, image.fullSizeURL)
|
||||||
t.not(image.url, image.fullSizeURL)
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
test('labels have a fanArt field and preview images', testData, `
|
test(
|
||||||
|
'labels have a fanArt field and preview images',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
label(mbid: "0cf56645-50ec-4411-aeb6-c9f4ce0f8edb") {
|
label(mbid: "0cf56645-50ec-4411-aeb6-c9f4ce0f8edb") {
|
||||||
|
|
@ -119,10 +130,12 @@ test('labels have a fanArt field and preview images', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
const { fanArt } = data.lookup.label
|
t.snapshot(data)
|
||||||
fanArt.logos.forEach(image => {
|
const { fanArt } = data.lookup.label
|
||||||
t.not(image.url, image.fullSizeURL)
|
fanArt.logos.forEach(image => {
|
||||||
})
|
t.not(image.url, image.fullSizeURL)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import baseContext from '../../helpers/context'
|
||||||
const schema = applyExtension(extension, baseSchema)
|
const schema = applyExtension(extension, baseSchema)
|
||||||
const context = extension.extendContext(baseContext)
|
const context = extension.extendContext(baseContext)
|
||||||
|
|
||||||
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) {
|
||||||
console.log(result.errors)
|
console.log(result.errors)
|
||||||
|
|
@ -40,7 +40,10 @@ const fragment = `
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
test('artists have a mediaWikiImages field', testData, `
|
test(
|
||||||
|
'artists have a mediaWikiImages field',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
|
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
|
||||||
|
|
@ -50,11 +53,16 @@ test('artists have a mediaWikiImages field', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
})
|
t.snapshot(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('instruments have a mediaWikiImages field', testData, `
|
test(
|
||||||
|
'instruments have a mediaWikiImages field',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
search {
|
search {
|
||||||
instruments(query: "guitar", first: 20) {
|
instruments(query: "guitar", first: 20) {
|
||||||
|
|
@ -66,11 +74,16 @@ test('instruments have a mediaWikiImages field', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
})
|
t.snapshot(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('labels have a mediaWikiImages field', testData, `
|
test(
|
||||||
|
'labels have a mediaWikiImages field',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
search {
|
search {
|
||||||
labels(query: "Sony", first: 50) {
|
labels(query: "Sony", first: 50) {
|
||||||
|
|
@ -82,11 +95,16 @@ test('labels have a mediaWikiImages field', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
})
|
t.snapshot(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('places have a mediaWikiImages field', testData, `
|
test(
|
||||||
|
'places have a mediaWikiImages field',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
place(mbid: "b5297256-8482-4cba-968a-25db61563faf") {
|
place(mbid: "b5297256-8482-4cba-968a-25db61563faf") {
|
||||||
|
|
@ -96,6 +114,8 @@ test('places have a mediaWikiImages field', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
})
|
t.snapshot(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import baseContext from '../../helpers/context'
|
||||||
const schema = applyExtension(extension, baseSchema)
|
const schema = applyExtension(extension, baseSchema)
|
||||||
const context = extension.extendContext(baseContext)
|
const context = extension.extendContext(baseContext)
|
||||||
|
|
||||||
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) {
|
||||||
console.log(result.errors)
|
console.log(result.errors)
|
||||||
|
|
@ -17,7 +17,10 @@ function testData (t, query, handler) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
test('artists have a theAudioDB field', testData, `
|
test(
|
||||||
|
'artists have a theAudioDB field',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
|
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
|
||||||
|
|
@ -41,11 +44,16 @@ test('artists have a theAudioDB field', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
})
|
t.snapshot(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('release groups have a theAudioDB field', testData, `
|
test(
|
||||||
|
'release groups have a theAudioDB field',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
releaseGroup(mbid: "aa997ea0-2936-40bd-884d-3af8a0e064dc") {
|
releaseGroup(mbid: "aa997ea0-2936-40bd-884d-3af8a0e064dc") {
|
||||||
|
|
@ -75,11 +83,16 @@ test('release groups have a theAudioDB field', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
})
|
t.snapshot(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('recordings have a theAudioDB field', testData, `
|
test(
|
||||||
|
'recordings have a theAudioDB field',
|
||||||
|
testData,
|
||||||
|
`
|
||||||
{
|
{
|
||||||
lookup {
|
lookup {
|
||||||
recording(mbid: "1109d8da-ce4a-4739-9414-242dc3e9b81c") {
|
recording(mbid: "1109d8da-ce4a-4739-9414-242dc3e9b81c") {
|
||||||
|
|
@ -113,6 +126,8 @@ test('recordings have a theAudioDB field', testData, `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, (t, data) => {
|
`,
|
||||||
t.snapshot(data)
|
(t, data) => {
|
||||||
})
|
t.snapshot(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ import CoverArtArchiveClient from '../../../src/extensions/cover-art-archive/cli
|
||||||
|
|
||||||
sepia.fixtureDir(path.join(__dirname, '..', '..', 'fixtures'))
|
sepia.fixtureDir(path.join(__dirname, '..', '..', 'fixtures'))
|
||||||
|
|
||||||
const options = process.env.VCR_MODE === 'playback'
|
const options =
|
||||||
? { limit: Infinity, period: 0 }
|
process.env.VCR_MODE === 'playback' ? { limit: Infinity, period: 0 } : {}
|
||||||
: {}
|
|
||||||
|
|
||||||
export default new CoverArtArchiveClient(options)
|
export default new CoverArtArchiveClient(options)
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ import MusicBrainz from '../../../src/api'
|
||||||
|
|
||||||
sepia.fixtureDir(path.join(__dirname, '..', '..', 'fixtures'))
|
sepia.fixtureDir(path.join(__dirname, '..', '..', 'fixtures'))
|
||||||
|
|
||||||
const options = process.env.VCR_MODE === 'playback'
|
const options =
|
||||||
? { limit: Infinity, period: 0 }
|
process.env.VCR_MODE === 'playback' ? { limit: Infinity, period: 0 } : {}
|
||||||
: {}
|
|
||||||
|
|
||||||
export default new MusicBrainz(options)
|
export default new MusicBrainz(options)
|
||||||
|
|
|
||||||
911
test/schema.js
911
test/schema.js
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,12 @@
|
||||||
import test from 'ava'
|
import test from 'ava'
|
||||||
import { Kind } from 'graphql/language'
|
import { Kind } from 'graphql/language'
|
||||||
import { Duration, Locale, MBID, ISWC, URLString } from '../../src/types/scalars'
|
import {
|
||||||
|
Duration,
|
||||||
|
Locale,
|
||||||
|
MBID,
|
||||||
|
ISWC,
|
||||||
|
URLString
|
||||||
|
} from '../../src/types/scalars'
|
||||||
|
|
||||||
test('Locale scalar allows language code', t => {
|
test('Locale scalar allows language code', t => {
|
||||||
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'en' }), 'en')
|
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'en' }), 'en')
|
||||||
|
|
@ -15,9 +21,18 @@ test('Locale scalar allows language and country code', t => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Locale scalar allows language, country, and encoding', t => {
|
test('Locale scalar allows language, country, and encoding', t => {
|
||||||
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.UTF-8' }), 'en_US.UTF-8')
|
t.is(
|
||||||
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'de_CH.utf8' }), 'de_CH.utf8')
|
Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.UTF-8' }),
|
||||||
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'zh_TW.Big5' }), 'zh_TW.Big5')
|
'en_US.UTF-8'
|
||||||
|
)
|
||||||
|
t.is(
|
||||||
|
Locale.parseLiteral({ kind: Kind.STRING, value: 'de_CH.utf8' }),
|
||||||
|
'de_CH.utf8'
|
||||||
|
)
|
||||||
|
t.is(
|
||||||
|
Locale.parseLiteral({ kind: Kind.STRING, value: 'zh_TW.Big5' }),
|
||||||
|
'zh_TW.Big5'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Locale scalar only accepts strings', t => {
|
test('Locale scalar only accepts strings', t => {
|
||||||
|
|
@ -26,16 +41,46 @@ test('Locale scalar only accepts strings', t => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Locale scalar rejects malformed locales', t => {
|
test('Locale scalar rejects malformed locales', t => {
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_' }), TypeError)
|
t.throws(
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_USA' }), TypeError)
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_' }),
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'EN' }), TypeError)
|
TypeError
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_us' }), TypeError)
|
)
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en-US' }), TypeError)
|
t.throws(
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US_foo' }), TypeError)
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_USA' }),
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US-utf8' }), TypeError)
|
TypeError
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: '12_US' }), TypeError)
|
)
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.' }), TypeError)
|
t.throws(
|
||||||
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.utf!' }), TypeError)
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'EN' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_us' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en-US' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US_foo' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US-utf8' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: '12_US' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.utf!' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Duration scalar must be a positive integer', t => {
|
test('Duration scalar must be a positive integer', t => {
|
||||||
|
|
@ -43,8 +88,14 @@ test('Duration scalar must be a positive integer', t => {
|
||||||
t.is(Duration.parseLiteral({ kind: Kind.INT, value: 1 }), 1)
|
t.is(Duration.parseLiteral({ kind: Kind.INT, value: 1 }), 1)
|
||||||
t.is(Duration.parseLiteral({ kind: Kind.INT, value: 3000 }), 3000)
|
t.is(Duration.parseLiteral({ kind: Kind.INT, value: 3000 }), 3000)
|
||||||
t.is(Duration.parseLiteral({ kind: Kind.STRING, value: '1000' }), null)
|
t.is(Duration.parseLiteral({ kind: Kind.STRING, value: '1000' }), null)
|
||||||
t.throws(() => Duration.parseLiteral({ kind: Kind.INT, value: -1 }), TypeError)
|
t.throws(
|
||||||
t.throws(() => Duration.parseLiteral({ kind: Kind.INT, value: -1000 }), TypeError)
|
() => Duration.parseLiteral({ kind: Kind.INT, value: -1 }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => Duration.parseLiteral({ kind: Kind.INT, value: -1000 }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
t.is(Duration.parseValue(0), 0)
|
t.is(Duration.parseValue(0), 0)
|
||||||
t.is(Duration.parseValue(1), 1)
|
t.is(Duration.parseValue(1), 1)
|
||||||
t.is(Duration.parseValue(3000), 3000)
|
t.is(Duration.parseValue(3000), 3000)
|
||||||
|
|
@ -54,11 +105,29 @@ test('Duration scalar must be a positive integer', t => {
|
||||||
|
|
||||||
test('URLString scalar must be a valid URL', t => {
|
test('URLString scalar must be a valid URL', t => {
|
||||||
t.is(URLString.parseLiteral({ kind: Kind.INT, value: 1000 }), null)
|
t.is(URLString.parseLiteral({ kind: Kind.INT, value: 1000 }), null)
|
||||||
t.is(URLString.parseLiteral({ kind: Kind.STRING, value: 'http://www.google.com' }), 'http://www.google.com')
|
t.is(
|
||||||
t.throws(() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo:bar' }), TypeError)
|
URLString.parseLiteral({
|
||||||
t.throws(() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo:/bar' }), TypeError)
|
kind: Kind.STRING,
|
||||||
t.throws(() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo://bar' }), TypeError)
|
value: 'http://www.google.com'
|
||||||
t.throws(() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo://bar.' }), TypeError)
|
}),
|
||||||
|
'http://www.google.com'
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo:bar' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo:/bar' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo://bar' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo://bar.' }),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ISWC scalar only accepts strings', t => {
|
test('ISWC scalar only accepts strings', t => {
|
||||||
|
|
@ -79,6 +148,19 @@ test('MBID scalar only accepts strings', t => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('MBID scalar must be a valid UUID', t => {
|
test('MBID scalar must be a valid UUID', t => {
|
||||||
t.is(MBID.parseLiteral({ kind: Kind.STRING, value: 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8' }), 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8')
|
t.is(
|
||||||
t.throws(() => MBID.parseLiteral({ kind: Kind.STRING, value: 'c8da2e40-bd28-4d4e-813a-bd2f51958bag' }), TypeError)
|
MBID.parseLiteral({
|
||||||
|
kind: Kind.STRING,
|
||||||
|
value: 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8'
|
||||||
|
}),
|
||||||
|
'c8da2e40-bd28-4d4e-813a-bd2f51958ba8'
|
||||||
|
)
|
||||||
|
t.throws(
|
||||||
|
() =>
|
||||||
|
MBID.parseLiteral({
|
||||||
|
kind: Kind.STRING,
|
||||||
|
value: 'c8da2e40-bd28-4d4e-813a-bd2f51958bag'
|
||||||
|
}),
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,12 @@ import test from 'ava'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { prettyPrint } from '../src/util'
|
import { prettyPrint } from '../src/util'
|
||||||
|
|
||||||
test.beforeEach(t => { sinon.stub(console, 'log') })
|
test.beforeEach(t => {
|
||||||
test.afterEach(t => { console.log.restore() })
|
sinon.stub(console, 'log')
|
||||||
|
})
|
||||||
|
test.afterEach(t => {
|
||||||
|
console.log.restore()
|
||||||
|
})
|
||||||
|
|
||||||
test('prettyPrint writes to stdout', t => {
|
test('prettyPrint writes to stdout', t => {
|
||||||
prettyPrint('foo')
|
prettyPrint('foo')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue