Add tags, aliases, more optimized API calls, fix offset

This commit is contained in:
Brian Beck 2016-11-28 05:49:04 -08:00
parent 8270397137
commit 092c37bad7
26 changed files with 2425 additions and 638 deletions

View file

@ -91,6 +91,14 @@ The `graphbrainz` middleware function accepts the following options:
if you are running your own MusicBrainz mirror. Defaults to `http://musicbrainz.org/ws/2/`.
* **`GRAPHBRAINZ_PATH`**: The URL route at which to expose the GraphQL endpoint,
if running the standalone server. Defaults to `/`.
* **`GRAPHBRAINZ_CACHE_SIZE`**: The maximum number of REST API responses to
cache. Increasing the cache size and TTL will greatly lower query execution
time for complex queries involving frequently accessed entities. Defaults to
`8192`.
* **`GRAPHBRAINZ_CACHE_TTL`**: The maximum age of REST API responses in the
cache, in milliseconds. Responses older than this will be disposed of (and
re-requested) the next time they are accessed. Defaults to `86400000` (one
day).
* **`GRAPHBRAINZ_GRAPHIQL`**: Set this to `true` if you want to force the
[GraphiQL][] interface to be available even in production mode.
* **`PORT`**: Port number to use, if running the standalone server.

File diff suppressed because it is too large Load diff

382
schema.md
View file

@ -51,24 +51,34 @@ type Area implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# [ISO 3166 codes](https://en.wikipedia.org/wiki/ISO_3166) are
# the codes assigned by ISO to countries and subdivisions.
isoCodes: [String]
# A list of artist entities linked to this entity.
# A list of artists linked to this entity.
artists(after: String, first: Int): ArtistConnection
# A list of event entities linked to this entity.
# A list of events linked to this entity.
events(after: String, first: Int): EventConnection
# A list of label entities linked to this entity.
# A list of labels linked to this entity.
labels(after: String, first: Int): LabelConnection
# A list of place entities linked to this entity.
# A list of places linked to this entity.
places(after: String, first: Int): PlaceConnection
# A list of release entities linked to this entity.
# A list of releases linked to this entity.
releases(after: String, first: Int, type: [ReleaseGroupType], status: [ReleaseStatus]): ReleaseConnection
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -78,6 +88,10 @@ type AreaConnection {
# A list of edges.
edges: [AreaEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -112,8 +126,8 @@ type Artist implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to
# store alternate names or misspellings.
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# The country with which an artist is primarily identified. It
@ -151,20 +165,23 @@ type Artist implements Node, Entity {
# field.
typeID: MBID
# A list of recording entities linked to this entity.
# A list of recordings linked to this entity.
recordings(after: String, first: Int): RecordingConnection
# A list of release entities linked to this entity.
# A list of releases linked to this entity.
releases(after: String, first: Int, type: [ReleaseGroupType], status: [ReleaseStatus]): ReleaseConnection
# A list of release group entities linked to this entity.
# A list of release groups linked to this entity.
releaseGroups(after: String, first: Int, type: [ReleaseGroupType]): ReleaseGroupConnection
# A list of work entities linked to this entity.
# A list of works linked to this entity.
works(after: String, first: Int): WorkConnection
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -174,6 +191,10 @@ type ArtistConnection {
# A list of edges.
edges: [ArtistEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# [Artist credits](https://musicbrainz.org/doc/Artist_Credits)
@ -207,6 +228,15 @@ type ArtistEdge {
# A query for all MusicBrainz entities directly linked to another
# entity.
type BrowseQuery {
# Browse area entities linked to the given arguments.
areas(
after: String
first: Int
# The MBID of a collection in which the entity is found.
collection: MBID
): AreaConnection
# Browse artist entities linked to the given arguments.
artists(
after: String
@ -215,6 +245,9 @@ type BrowseQuery {
# The MBID of an area to which the entity is linked.
area: MBID
# The MBID of a collection in which the entity is found.
collection: MBID
# The MBID of a recording to which the entity is linked.
recording: MBID
@ -239,6 +272,9 @@ type BrowseQuery {
# The MBID of an artist to which the entity is linked.
artist: MBID
# The MBID of a collection in which the entity is found.
collection: MBID
# The MBID of a place to which the event is linked.
place: MBID
): EventConnection
@ -251,6 +287,9 @@ type BrowseQuery {
# The MBID of an area to which the entity is linked.
area: MBID
# The MBID of a collection in which the entity is found.
collection: MBID
# The MBID of a release to which the entity is linked.
release: MBID
): LabelConnection
@ -262,6 +301,9 @@ type BrowseQuery {
# The MBID of an area to which the entity is linked.
area: MBID
# The MBID of a collection in which the entity is found.
collection: MBID
): PlaceConnection
# Browse recording entities linked to the given arguments.
@ -272,6 +314,9 @@ type BrowseQuery {
# The MBID of an artist to which the entity is linked.
artist: MBID
# The MBID of a collection in which the entity is found.
collection: MBID
# The MBID of a release to which the entity is linked.
release: MBID
): RecordingConnection
@ -287,6 +332,9 @@ type BrowseQuery {
# The MBID of an artist to which the entity is linked.
artist: MBID
# The MBID of a collection in which the entity is found.
collection: MBID
# The MBID of a label to which the release is linked.
label: MBID
@ -312,6 +360,9 @@ type BrowseQuery {
# The MBID of an artist to which the entity is linked.
artist: MBID
# The MBID of a collection in which the entity is found.
collection: MBID
# The MBID of a release to which the entity is linked.
release: MBID
): ReleaseGroupConnection
@ -323,6 +374,9 @@ type BrowseQuery {
# The MBID of an artist to which the entity is linked.
artist: MBID
# The MBID of a collection in which the entity is found.
collection: MBID
): WorkConnection
# Browse URL entities linked to the given arguments.
@ -372,6 +426,10 @@ type Event implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# The begin and end dates of the entitys existence. Its exact
# meaning depends on the type of entity.
lifeSpan: LifeSpan
@ -393,6 +451,12 @@ type Event implements Node, Entity {
# The MBID associated with the value of the `type`
# field.
typeID: MBID
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -402,6 +466,10 @@ type EventConnection {
# A list of edges.
edges: [EventEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -429,6 +497,10 @@ type Instrument implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# A brief description of the main characteristics of the
# instrument.
description: String
@ -441,6 +513,34 @@ type Instrument implements Node, Entity {
# The MBID associated with the value of the `type`
# field.
typeID: MBID
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
type InstrumentConnection {
# Information to aid in pagination.
pageInfo: PageInfo!
# A list of edges.
edges: [InstrumentEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
type InstrumentEdge {
# The item at the end of the edge
node: Instrument
# A cursor for use in pagination
cursor: String!
}
# An [IPI](https://musicbrainz.org/doc/IPI) (interested party
@ -469,6 +569,10 @@ type Label implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# The country of origin for the label.
country: String
@ -495,8 +599,14 @@ type Label implements Node, Entity {
# field.
typeID: MBID
# A list of release entities linked to this entity.
# A list of releases linked to this entity.
releases(after: String, first: Int, type: [ReleaseGroupType], status: [ReleaseStatus]): ReleaseConnection
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -506,6 +616,10 @@ type LabelConnection {
# A list of edges.
edges: [LabelEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -640,6 +754,10 @@ type Place implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# The address describes the location of the place using the
# standard addressing format for the country it is located in.
address: String
@ -663,8 +781,14 @@ type Place implements Node, Entity {
# field.
typeID: MBID
# A list of event entities linked to this entity.
# A list of events linked to this entity.
events(after: String, first: Int): EventConnection
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -674,6 +798,10 @@ type PlaceConnection {
# A list of edges.
edges: [PlaceEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -697,12 +825,10 @@ type Query {
# Perform a lookup of a MusicBrainz entity by its MBID.
lookup: LookupQuery
# Browse all MusicBrainz entities directly linked to another
# entity.
# Browse all MusicBrainz entities directly linked to another entity.
browse: BrowseQuery
# Search for MusicBrainz entities using Lucene query
# syntax.
# Search for MusicBrainz entities using Lucene query syntax.
search: SearchQuery
}
@ -731,6 +857,10 @@ type Recording implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# The main credited artist(s).
artistCredit: [ArtistCredit]
@ -741,14 +871,17 @@ type Recording implements Node, Entity {
# Whether this is a video recording.
video: Boolean
# A list of artist entities linked to this entity.
# A list of artists linked to this entity.
artists(after: String, first: Int): ArtistConnection
# A list of release entities linked to this entity.
# A list of releases linked to this entity.
releases(after: String, first: Int, type: [ReleaseGroupType], status: [ReleaseStatus]): ReleaseConnection
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -758,6 +891,10 @@ type RecordingConnection {
# A list of edges.
edges: [RecordingEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -820,6 +957,10 @@ type RelationshipConnection {
# A list of edges.
edges: [RelationshipEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -1068,6 +1209,10 @@ type Release implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# The main credited artist(s).
artistCredit: [ArtistCredit]
@ -1109,20 +1254,23 @@ type Release implements Node, Entity {
# [ratings](https://musicbrainz.org/doc/Rating_System).
quality: String
# A list of artist entities linked to this entity.
# A list of artists linked to this entity.
artists(after: String, first: Int): ArtistConnection
# A list of label entities linked to this entity.
# A list of labels linked to this entity.
labels(after: String, first: Int): LabelConnection
# A list of recording entities linked to this entity.
# A list of recordings linked to this entity.
recordings(after: String, first: Int): RecordingConnection
# A list of release group entities linked to this entity.
# A list of release groups linked to this entity.
releaseGroups(after: String, first: Int, type: [ReleaseGroupType]): ReleaseGroupConnection
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -1132,6 +1280,10 @@ type ReleaseConnection {
# A list of edges.
edges: [ReleaseEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -1171,6 +1323,10 @@ type ReleaseGroup implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# The main credited artist(s).
artistCredit: [ArtistCredit]
@ -1195,14 +1351,17 @@ type ReleaseGroup implements Node, Entity {
# field.
secondaryTypeIDs: [MBID]
# A list of artist entities linked to this entity.
# A list of artists linked to this entity.
artists(after: String, first: Int): ArtistConnection
# A list of release entities linked to this entity.
# A list of releases linked to this entity.
releases(after: String, first: Int, type: [ReleaseGroupType], status: [ReleaseStatus]): ReleaseConnection
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -1212,6 +1371,10 @@ type ReleaseGroupConnection {
# A list of edges.
edges: [ReleaseGroupEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -1326,28 +1489,103 @@ enum ReleaseStatus {
# A search for MusicBrainz entities using Lucene query syntax.
type SearchQuery {
# Search for area entities matching the given query.
areas(query: String!, after: String, first: Int): AreaConnection
areas(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): AreaConnection
# Search for artist entities matching the given query.
artists(query: String!, after: String, first: Int): ArtistConnection
artists(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): ArtistConnection
# Search for event entities matching the given query.
events(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): EventConnection
# Search for instrument entities matching the given query.
instruments(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): InstrumentConnection
# Search for label entities matching the given query.
labels(query: String!, after: String, first: Int): LabelConnection
labels(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): LabelConnection
# Search for place entities matching the given query.
places(query: String!, after: String, first: Int): PlaceConnection
places(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): PlaceConnection
# Search for recording entities matching the given query.
recordings(query: String!, after: String, first: Int): RecordingConnection
recordings(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): RecordingConnection
# Search for release entities matching the given query.
releases(query: String!, after: String, first: Int): ReleaseConnection
releases(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): ReleaseConnection
# Search for release group entities matching the given query.
releaseGroups(query: String!, after: String, first: Int): ReleaseGroupConnection
releaseGroups(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): ReleaseGroupConnection
# Search for series entities matching the given query.
series(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): SeriesConnection
# Search for work entities matching the given query.
works(query: String!, after: String, first: Int): WorkConnection
works(
# The query terms, in Lucene search syntax. See [examples
# and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).
query: String!
after: String
first: Int
): WorkConnection
}
# A [series](https://musicbrainz.org/doc/Series) is a sequence of
@ -1373,6 +1611,67 @@ type Series implements Node, Entity {
# The MBID associated with the value of the `type`
# field.
typeID: MBID
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
type SeriesConnection {
# Information to aid in pagination.
pageInfo: PageInfo!
# A list of edges.
edges: [SeriesEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
type SeriesEdge {
# The item at the end of the edge
node: Series
# A cursor for use in pagination
cursor: String!
}
# [Tags](https://musicbrainz.org/tags) are a way mark entities
# with extra information for example, the genres that apply to an artist,
# release, or recording.
type Tag {
# The tag label.
name: String!
# How many times this tag has been applied to the entity.
count: Int
}
# A connection to a list of items.
type TagConnection {
# Information to aid in pagination.
pageInfo: PageInfo!
# A list of edges.
edges: [TagEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
type TagEdge {
# The item at the end of the edge
node: Tag
# A cursor for use in pagination
cursor: String!
}
# A time of day, in 24-hour hh:mm notation.
@ -1402,6 +1701,10 @@ type URLConnection {
# A list of edges.
edges: [URLEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.
@ -1432,6 +1735,10 @@ type Work implements Node, Entity {
# A comment used to help distinguish identically named entitites.
disambiguation: String
# [Aliases](https://musicbrainz.org/doc/Aliases) are used to store
# alternate names or misspellings.
aliases: [Alias]
# A list of [ISWCs](https://musicbrainz.org/doc/ISWC) assigned
# to the work by copyright collecting agencies.
iswcs: [String]
@ -1446,11 +1753,14 @@ type Work implements Node, Entity {
# field.
typeID: MBID
# A list of artist entities linked to this entity.
# A list of artists linked to this entity.
artists(after: String, first: Int): ArtistConnection
# Relationships between this entity and other entitites.
relationships: Relationships
# A list of tags linked to this entity.
tags(after: String, first: Int): TagConnection
}
# A connection to a list of items.
@ -1460,6 +1770,10 @@ type WorkConnection {
# A list of edges.
edges: [WorkEdge]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
}
# An edge in a connection.

View file

@ -3,13 +3,14 @@ import LRUCache from 'lru-cache'
import { toPlural } from './types/helpers'
const debug = require('debug')('graphbrainz:loaders')
const ONE_DAY = 24 * 60 * 60 * 1000
export default function createLoaders (client) {
// All loaders share a single LRU cache that will remember 8192 responses,
// each cached for 1 day.
const cache = LRUCache({
max: 8192,
maxAge: 24 * 60 * 60 * 1000,
max: parseInt(process.env.GRAPHBRAINZ_CACHE_SIZE || 8192, 10),
maxAge: parseInt(process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY, 10),
dispose (key) {
debug(`Removed '${key}' from cache.`)
}
@ -25,7 +26,8 @@ export default function createLoaders (client) {
if (entity) {
// Store the entity type so we can determine what type of object this
// is elsewhere in the code.
entity.entityType = entityType
entity._type = entityType
entity._inc = params.inc
}
return entity
})
@ -42,7 +44,8 @@ export default function createLoaders (client) {
list[toPlural(entityType)].forEach(entity => {
// Store the entity type so we can determine what type of object this
// is elsewhere in the code.
entity.entityType = entityType
entity._type = entityType
entity._inc = params.inc
})
return list
})
@ -59,7 +62,8 @@ export default function createLoaders (client) {
list[toPlural(entityType)].forEach(entity => {
// Store the entity type so we can determine what type of object this
// is elsewhere in the code.
entity.entityType = entityType
entity._type = entityType
entity._inc = params.inc
})
return list
})

View file

@ -4,6 +4,7 @@ import { browseResolver } from '../resolvers'
import {
MBID,
URLString,
AreaConnection,
ArtistConnection,
EventConnection,
LabelConnection,
@ -24,6 +25,10 @@ const artist = {
type: MBID,
description: 'The MBID of an artist to which the entity is linked.'
}
const collection = {
type: MBID,
description: 'The MBID of a collection in which the entity is found.'
}
const recording = {
type: MBID,
description: 'The MBID of a recording to which the entity is linked.'
@ -55,8 +60,12 @@ export const BrowseQuery = new GraphQLObjectType({
description: `A query for all MusicBrainz entities directly linked to another
entity.`,
fields: {
areas: browseQuery(AreaConnection, {
collection
}),
artists: browseQuery(ArtistConnection, {
area,
collection,
recording,
release,
releaseGroup,
@ -68,6 +77,7 @@ entity.`,
events: browseQuery(EventConnection, {
area,
artist,
collection,
place: {
type: MBID,
description: 'The MBID of a place to which the event is linked.'
@ -75,18 +85,22 @@ entity.`,
}),
labels: browseQuery(LabelConnection, {
area,
collection,
release
}),
places: browseQuery(PlaceConnection, {
area
area,
collection
}),
recordings: browseQuery(RecordingConnection, {
artist,
collection,
release
}),
releases: browseQuery(ReleaseConnection, {
area,
artist,
collection,
label: {
type: MBID,
description: 'The MBID of a label to which the release is linked.'
@ -105,10 +119,12 @@ release, but is not included in the credits for the release itself.`
}),
releaseGroups: browseQuery(ReleaseGroupConnection, {
artist,
collection,
release
}),
works: browseQuery(WorkConnection, {
artist
artist,
collection
}),
urls: browseQuery(URLConnection, {
resource: {

View file

@ -4,11 +4,14 @@ import { searchResolver } from '../resolvers'
import {
AreaConnection,
ArtistConnection,
EventConnection,
InstrumentConnection,
LabelConnection,
PlaceConnection,
RecordingConnection,
ReleaseConnection,
ReleaseGroupConnection,
SeriesConnection,
WorkConnection
} from '../types'
import { toWords } from '../types/helpers'
@ -19,7 +22,11 @@ function searchQuery (connectionType) {
type: connectionType,
description: `Search for ${typeName} entities matching the given query.`,
args: {
query: { type: new GraphQLNonNull(GraphQLString) },
query: {
type: new GraphQLNonNull(GraphQLString),
description: `The query terms, in Lucene search syntax. See [examples
and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search).`
},
...forwardConnectionArgs
},
resolve: searchResolver()
@ -32,11 +39,14 @@ export const SearchQuery = new GraphQLObjectType({
fields: {
areas: searchQuery(AreaConnection),
artists: searchQuery(ArtistConnection),
events: searchQuery(EventConnection),
instruments: searchQuery(InstrumentConnection),
labels: searchQuery(LabelConnection),
places: searchQuery(PlaceConnection),
recordings: searchQuery(RecordingConnection),
releases: searchQuery(ReleaseConnection),
releaseGroups: searchQuery(ReleaseGroupConnection),
series: searchQuery(SeriesConnection),
works: searchQuery(WorkConnection)
}
})

View file

@ -12,6 +12,12 @@ export function includeRelationships (params, info) {
if (fields.relationships) {
fields = getFields(fields.relationships)
} else {
if (fields.edges) {
fields = getFields(fields.edges)
if (fields.node) {
return includeRelationships(params, fields.node)
}
}
return params
}
}
@ -31,11 +37,27 @@ export function includeRelationships (params, info) {
}
export function includeSubqueries (params, info) {
const fields = getFields(info)
if (fields.artistCredit) {
params = {
...params,
inc: extendIncludes(params.inc, ['artist-credits'])
const subqueryIncludes = {
aliases: 'aliases',
artistCredit: 'artist-credits',
tags: 'tags'
}
let fields = getFields(info)
const include = []
for (const key in subqueryIncludes) {
if (fields[key]) {
const value = subqueryIncludes[key]
include.push(value)
}
}
params = {
...params,
inc: extendIncludes(params.inc, include)
}
if (fields.edges) {
fields = getFields(fields.edges)
if (fields.node) {
params = includeSubqueries(params, fields.node)
}
}
return params
@ -44,7 +66,8 @@ export function includeSubqueries (params, info) {
export function lookupResolver () {
return (root, { mbid }, { loaders }, info) => {
const entityType = toDashed(info.fieldName)
const params = includeRelationships({}, info)
let params = includeSubqueries({}, info)
params = includeRelationships(params, info)
return loaders.lookup.load([entityType, mbid, params])
}
}
@ -58,7 +81,7 @@ export function browseResolver () {
type,
status,
limit: first,
offset: getOffsetWithDefault(after, 0)
offset: getOffsetWithDefault(after, -1) + 1
}
params = includeSubqueries(params, info)
params = includeRelationships(params, info)
@ -74,18 +97,24 @@ export function browseResolver () {
[`${singularName}-count`]: arrayLength
} = list
const meta = { sliceStart, arrayLength }
return connectionFromArraySlice(arraySlice, { first, after }, meta)
return {
totalCount: arrayLength,
...connectionFromArraySlice(arraySlice, { first, after }, meta)
}
})
}
}
export function searchResolver () {
return (source, { first = 25, after, ...args }, { loaders }, info) => {
return (source, { first = 25, after, query, ...args }, { loaders }, info) => {
const pluralName = toDashed(info.fieldName)
const singularName = toSingular(pluralName)
const { query, ...params } = args
params.limit = first
params.offset = getOffsetWithDefault(after, 0)
let params = {
...args,
limit: first,
offset: getOffsetWithDefault(after, -1) + 1
}
params = includeSubqueries(params, info)
return loaders.search.load([singularName, query, params]).then(list => {
const {
[pluralName]: arraySlice,
@ -93,7 +122,10 @@ export function searchResolver () {
count: arrayLength
} = list
const meta = { sliceStart, arrayLength }
return connectionFromArraySlice(arraySlice, { first, after }, meta)
return {
totalCount: arrayLength,
...connectionFromArraySlice(arraySlice, { first, after }, meta)
}
})
}
}
@ -117,7 +149,10 @@ export function relationshipResolver () {
}
return true
})
return connectionFromArray(relationships, args)
return {
totalCount: relationships.length,
...connectionFromArray(relationships, args)
}
}
}
@ -128,3 +163,25 @@ export function linkedResolver () {
return browseResolver()(source, args, context, info)
}
}
const noop = value => value
/**
* If we weren't smart enough or weren't able to include the `inc` parameter
* for a particular field that's being requested, make another request to grab
* it (after making sure it isn't already available).
*/
export function subqueryResolver (includeValue, handler = noop) {
return (source, args, { loaders }, info) => {
const key = toDashed(info.fieldName)
if (key in source || (source._inc && source._inc.indexOf(key) !== -1)) {
return handler(source[key], args)
} else {
const { _type: entityType, id } = source
const params = { inc: [includeValue || key] }
return loaders.lookup.load([entityType, id, params]).then(entity => {
return handler(entity[key], args)
})
}
}
}

View file

@ -1,5 +1,4 @@
import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import {
@ -8,11 +7,15 @@ import {
name,
sortName,
disambiguation,
aliases,
artists,
events,
labels,
places,
releases
releases,
relationships,
tags,
connectionWithCount
} from './helpers'
const Area = new GraphQLObjectType({
@ -26,6 +29,7 @@ or settlements (countries, cities, or the like).`,
name,
sortName,
disambiguation,
aliases,
isoCodes: {
type: new GraphQLList(GraphQLString),
description: `[ISO 3166 codes](https://en.wikipedia.org/wiki/ISO_3166) are
@ -36,10 +40,11 @@ the codes assigned by ISO to countries and subdivisions.`,
events,
labels,
places,
releases
releases,
relationships,
tags
})
})
const { connectionType: AreaConnection } = connectionDefinitions({ nodeType: Area })
export { AreaConnection }
export const AreaConnection = connectionWithCount(Area)
export default Area

View file

@ -16,7 +16,7 @@ credits.`,
resolve: (source) => {
const { artist } = source
if (artist) {
artist.entityType = 'artist'
artist._type = 'artist'
}
return artist
}

View file

@ -1,8 +1,6 @@
import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import { GraphQLObjectType, GraphQLString } from 'graphql/type'
import Node from './node'
import Entity from './entity'
import Alias from './alias'
import Area from './area'
import {
getFallback,
@ -12,12 +10,15 @@ import {
name,
sortName,
disambiguation,
aliases,
lifeSpan,
recordings,
releases,
releaseGroups,
works,
relationships
relationships,
tags,
connectionWithCount
} from './helpers'
const Artist = new GraphQLObjectType({
@ -34,23 +35,7 @@ even a fictional character.`,
name,
sortName,
disambiguation,
aliases: {
type: new GraphQLList(Alias),
description: `[Aliases](https://musicbrainz.org/doc/Aliases) are used to
store alternate names or misspellings.`,
resolve: (source, args, { loaders }, info) => {
const key = 'aliases'
if (key in source) {
return source[key]
} else {
const { entityType, id } = source
const params = { inc: ['aliases'] }
return loaders.lookup.load([entityType, id, params]).then(entity => {
return entity[key]
})
}
}
},
aliases,
country: {
type: GraphQLString,
description: `The country with which an artist is primarily identified. It
@ -85,10 +70,10 @@ neither. Groups do not have genders.`
releases,
releaseGroups,
works,
relationships
relationships,
tags
})
})
const { connectionType: ArtistConnection } = connectionDefinitions({ nodeType: Artist })
export { ArtistConnection }
export const ArtistConnection = connectionWithCount(Artist)
export default Artist

View file

@ -5,11 +5,9 @@ export default new GraphQLInterfaceType({
name: 'Entity',
description: 'An entity in the MusicBrainz schema.',
resolveType (value) {
if (value.entityType && require.resolve(`./${value.entityType}`)) {
return require(`./${value.entityType}`).default
if (value._type && require.resolve(`./${value._type}`)) {
return require(`./${value._type}`).default
}
},
fields: () => ({
mbid
})
fields: () => ({ mbid })
})

View file

@ -1,5 +1,4 @@
import { GraphQLObjectType, GraphQLString, GraphQLBoolean } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import { Time } from './scalars'
@ -9,7 +8,11 @@ import {
mbid,
name,
disambiguation,
lifeSpan
aliases,
lifeSpan,
relationships,
tags,
connectionWithCount
} from './helpers'
const Event = new GraphQLObjectType({
@ -23,6 +26,7 @@ Generally this means live performances, like concerts and festivals.`,
mbid,
name,
disambiguation,
aliases,
lifeSpan,
time: {
type: Time,
@ -40,10 +44,11 @@ for syntax and examples.`
},
...fieldWithID('type', {
description: 'What kind of event the event is, e.g. concert, festival, etc.'
})
}),
relationships,
tags
})
})
const { connectionType: EventConnection } = connectionDefinitions({ nodeType: Event })
export { EventConnection }
export const EventConnection = connectionWithCount(Event)
export default Event

View file

@ -3,16 +3,20 @@ import pascalCase from 'pascalcase'
import {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLList,
GraphQLNonNull
} from 'graphql'
import {
globalIdField,
connectionArgs,
connectionDefinitions,
connectionFromArray,
forwardConnectionArgs
} from 'graphql-relay'
import { MBID } from './scalars'
import { ReleaseGroupType, ReleaseStatus } from './enums'
import Alias from './alias'
import ArtistCredit from './artist-credit'
import { ArtistConnection } from './artist'
import { EventConnection } from './event'
@ -23,10 +27,12 @@ import { RecordingConnection } from './recording'
import { RelationshipConnection } from './relationship'
import { ReleaseConnection } from './release'
import { ReleaseGroupConnection } from './release-group'
import { TagConnection } from './tag'
import { WorkConnection } from './work'
import {
linkedResolver,
relationshipResolver,
subqueryResolver,
includeRelationships
} from '../resolvers'
@ -119,16 +125,17 @@ meaning depends on the type of entity.`,
resolve: getHyphenated
}
function linkedQuery (connectionType, args) {
const typeName = toWords(connectionType.name.slice(0, -10))
function linkedQuery (connectionType, { args, ...config } = {}) {
const typeName = toPlural(toWords(connectionType.name.slice(0, -10)))
return {
type: connectionType,
description: `A list of ${typeName} entities linked to this entity.`,
description: `A list of ${typeName} linked to this entity.`,
args: {
...forwardConnectionArgs,
...args
},
resolve: linkedResolver()
resolve: linkedResolver(),
...config
}
}
@ -181,21 +188,17 @@ export const relationships = {
}
}
export const aliases = {
type: new GraphQLList(Alias),
description: `[Aliases](https://musicbrainz.org/doc/Aliases) are used to store
alternate names or misspellings.`,
resolve: subqueryResolver()
}
export const artistCredit = {
type: new GraphQLList(ArtistCredit),
description: 'The main credited artist(s).',
resolve: (source, args, { loaders }, info) => {
const key = 'artist-credit'
if (key in source) {
return source[key]
} else {
const { entityType, id } = source
const params = { inc: ['artists'] }
return loaders.lookup.load([entityType, id, params]).then(entity => {
return entity[key]
})
}
}
resolve: subqueryResolver()
}
export const artists = linkedQuery(ArtistConnection)
@ -204,10 +207,33 @@ export const labels = linkedQuery(LabelConnection)
export const places = linkedQuery(PlaceConnection)
export const recordings = linkedQuery(RecordingConnection)
export const releases = linkedQuery(ReleaseConnection, {
type: { type: new GraphQLList(ReleaseGroupType) },
status: { type: new GraphQLList(ReleaseStatus) }
args: {
type: { type: new GraphQLList(ReleaseGroupType) },
status: { type: new GraphQLList(ReleaseStatus) }
}
})
export const releaseGroups = linkedQuery(ReleaseGroupConnection, {
type: { type: new GraphQLList(ReleaseGroupType) }
args: {
type: { type: new GraphQLList(ReleaseGroupType) }
}
})
export const tags = linkedQuery(TagConnection, {
resolve: subqueryResolver('tags', (value = [], args) => ({
totalCount: value.length,
...connectionFromArray(value, args)
}))
})
export const works = linkedQuery(WorkConnection)
export const totalCount = {
type: GraphQLInt,
description: `A count of the total number of items in this connection,
ignoring pagination.`
}
export function connectionWithCount (nodeType) {
return connectionDefinitions({
nodeType,
connectionFields: () => ({ totalCount })
}).connectionType
}

View file

@ -5,12 +5,13 @@ export { default as Entity } from './entity'
export { default as Area, AreaConnection } from './area'
export { default as Artist, ArtistConnection } from './artist'
export { default as Event, EventConnection } from './event'
export { default as Instrument } from './instrument'
export { default as Instrument, InstrumentConnection } from './instrument'
export { default as Label, LabelConnection } from './label'
export { default as Place, PlaceConnection } from './place'
export { default as Recording, RecordingConnection } from './recording'
export { default as Release, ReleaseConnection } from './release'
export { default as ReleaseGroup, ReleaseGroupConnection } from './release-group'
export { default as Series, SeriesConnection } from './series'
export { default as Tag, TagConnection } from './tag'
export { default as URL, URLConnection } from './url'
export { default as Work, WorkConnection } from './work'

View file

@ -6,7 +6,11 @@ import {
id,
mbid,
name,
disambiguation
disambiguation,
aliases,
relationships,
tags,
connectionWithCount
} from './helpers'
const Instrument = new GraphQLObjectType({
@ -20,6 +24,7 @@ used in relationships between two other entities.`,
mbid,
name,
disambiguation,
aliases,
description: {
type: GraphQLString,
description: `A brief description of the main characteristics of the
@ -29,8 +34,11 @@ instrument.`
description: `The type categorises the instrument by the way the sound is
created, similar to the [Hornbostel-Sachs](https://en.wikipedia.org/wiki/Hornbostel%E2%80%93Sachs)
classification.`
})
}),
relationships,
tags
})
})
export const InstrumentConnection = connectionWithCount(Instrument)
export default Instrument

View file

@ -4,7 +4,6 @@ import {
GraphQLString,
GraphQLInt
} from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import { IPI } from './scalars'
@ -15,9 +14,13 @@ import {
name,
sortName,
disambiguation,
aliases,
lifeSpan,
releases,
fieldWithID
relationships,
tags,
fieldWithID,
connectionWithCount
} from './helpers'
const Label = new GraphQLObjectType({
@ -32,6 +35,7 @@ represent a record company.`,
name,
sortName,
disambiguation,
aliases,
country: {
type: GraphQLString,
description: 'The country of origin for the label.'
@ -55,10 +59,11 @@ label.`
description: `A type describing the main activity of the label, e.g.
imprint, production, distributor, rights society, etc.`
}),
releases
releases,
relationships,
tags
})
})
const { connectionType: LabelConnection } = connectionDefinitions({ nodeType: Label })
export { LabelConnection }
export const LabelConnection = connectionWithCount(Label)
export default Label

View file

@ -8,9 +8,8 @@ const { nodeInterface, nodeField } = nodeDefinitions(
return loaders.lookup.load([entityType, id])
},
(obj) => {
console.log(obj.entityType)
try {
return require(`./${obj.entityType}`).default
return require(`./${obj._type}`).default
} catch (err) {
console.error(err)
return null

View file

@ -1,5 +1,4 @@
import { GraphQLObjectType, GraphQLString } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import { Degrees } from './scalars'
@ -9,9 +8,13 @@ import {
mbid,
name,
disambiguation,
aliases,
lifeSpan,
events,
fieldWithID
fieldWithID,
relationships,
tags,
connectionWithCount
} from './helpers'
export const Coordinates = new GraphQLObjectType({
@ -39,6 +42,7 @@ or other place where music is performed, recorded, engineered, etc.`,
mbid,
name,
disambiguation,
aliases,
address: {
type: GraphQLString,
description: `The address describes the location of the place using the
@ -58,10 +62,11 @@ which the place is located.`
description: `The type categorises the place based on its primary
function.`
}),
events
events,
relationships,
tags
})
})
const { connectionType: PlaceConnection } = connectionDefinitions({ nodeType: Place })
export { PlaceConnection }
export const PlaceConnection = connectionWithCount(Place)
export default Place

View file

@ -1,5 +1,4 @@
import { GraphQLObjectType, GraphQLInt, GraphQLBoolean } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import {
@ -7,10 +6,13 @@ import {
mbid,
title,
disambiguation,
aliases,
artistCredit,
artists,
releases,
relationships
relationships,
tags,
connectionWithCount
} from './helpers'
const Recording = new GraphQLObjectType({
@ -33,6 +35,7 @@ or mixing.`,
mbid,
title,
disambiguation,
aliases,
artistCredit,
length: {
type: GraphQLInt,
@ -45,10 +48,10 @@ from the lengths of the tracks using it.`
},
artists,
releases,
relationships
relationships,
tags
})
})
const { connectionType: RecordingConnection } = connectionDefinitions({ nodeType: Recording })
export { RecordingConnection }
export const RecordingConnection = connectionWithCount(Recording)
export default Recording

View file

@ -5,12 +5,12 @@ import {
GraphQLList,
GraphQLBoolean
} from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import { DateType } from './scalars'
import Entity from './entity'
import {
getHyphenated,
fieldWithID
fieldWithID,
connectionWithCount
} from './helpers'
const Relationship = new GraphQLObjectType({
@ -25,7 +25,7 @@ other and to URLs outside MusicBrainz.`,
resolve: source => {
const targetType = source['target-type']
const target = source[targetType]
target.entityType = targetType.replace('_', '-')
target._type = targetType.replace('_', '-')
return target
}
},
@ -78,6 +78,5 @@ relationship type.`
})
})
const { connectionType: RelationshipConnection } = connectionDefinitions({ nodeType: Relationship })
export { RelationshipConnection }
export const RelationshipConnection = connectionWithCount(Relationship)
export default Relationship

View file

@ -1,5 +1,4 @@
import { GraphQLObjectType, GraphQLList } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import { DateType } from './scalars'
@ -9,12 +8,15 @@ import {
mbid,
title,
disambiguation,
aliases,
artistCredit,
artists,
releases,
relationships,
tags,
fieldWithID,
getHyphenated,
fieldWithID
connectionWithCount
} from './helpers'
const ReleaseGroup = new GraphQLObjectType({
@ -33,6 +35,7 @@ album it doesnt matter how many CDs or editions/versions it had.`,
mbid,
title,
disambiguation,
aliases,
artistCredit,
firstReleaseDate: {
type: DateType,
@ -53,10 +56,10 @@ that apply to this release group.`
}),
artists,
releases,
relationships
relationships,
tags
})
})
const { connectionType: ReleaseGroupConnection } = connectionDefinitions({ nodeType: ReleaseGroup })
export { ReleaseGroupConnection }
export const ReleaseGroupConnection = connectionWithCount(ReleaseGroup)
export default ReleaseGroup

View file

@ -1,5 +1,4 @@
import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import { DateType } from './scalars'
@ -10,14 +9,17 @@ import {
mbid,
title,
disambiguation,
aliases,
artistCredit,
artists,
labels,
recordings,
releaseGroups,
relationships,
tags,
fieldWithID,
getHyphenated,
fieldWithID
connectionWithCount
} from './helpers'
const Release = new GraphQLObjectType({
@ -33,6 +35,7 @@ MusicBrainz as one release.`,
mbid,
title,
disambiguation,
aliases,
artistCredit,
releaseEvents: {
type: new GraphQLList(ReleaseEvent),
@ -75,10 +78,10 @@ It is not a mark of how good or bad the music itself is for that, use
labels,
recordings,
releaseGroups,
relationships
relationships,
tags
})
})
const { connectionType: ReleaseConnection } = connectionDefinitions({ nodeType: Release })
export { ReleaseConnection }
export const ReleaseConnection = connectionWithCount(Release)
export default Release

View file

@ -1,8 +1,16 @@
import { GraphQLObjectType } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import { id, mbid, name, disambiguation, fieldWithID } from './helpers'
import {
id,
mbid,
name,
disambiguation,
relationships,
tags,
fieldWithID,
connectionWithCount
} from './helpers'
const Series = new GraphQLObjectType({
name: 'Series',
@ -18,10 +26,11 @@ theme.`,
...fieldWithID('type', {
description: `The type primarily describes what type of entity the series
contains.`
})
}),
relationships,
tags
})
})
const { connectionType: SeriesConnection } = connectionDefinitions({ nodeType: Series })
export { SeriesConnection }
export const SeriesConnection = connectionWithCount(Series)
export default Series

27
src/types/tag.js Normal file
View file

@ -0,0 +1,27 @@
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLInt
} from 'graphql/type'
import { connectionWithCount } from './helpers'
const Tag = new GraphQLObjectType({
name: 'Tag',
description: `[Tags](https://musicbrainz.org/tags) are a way mark entities
with extra information for example, the genres that apply to an artist,
release, or recording.`,
fields: () => ({
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'The tag label.'
},
count: {
type: GraphQLInt,
description: 'How many times this tag has been applied to the entity.'
}
})
})
export const TagConnection = connectionWithCount(Tag)
export default Tag

View file

@ -1,9 +1,8 @@
import { GraphQLObjectType, GraphQLNonNull } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import { URLString } from './scalars'
import { id, mbid, relationships } from './helpers'
import { id, mbid, relationships, connectionWithCount } from './helpers'
const URL = new GraphQLObjectType({
name: 'URL',
@ -22,6 +21,5 @@ acquired, an entry in another database, etc.`,
})
})
const { connectionType: URLConnection } = connectionDefinitions({ nodeType: URL })
export { URLConnection }
export const URLConnection = connectionWithCount(URL)
export default URL

View file

@ -1,5 +1,4 @@
import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql/type'
import { connectionDefinitions } from 'graphql-relay'
import Node from './node'
import Entity from './entity'
import {
@ -7,9 +6,12 @@ import {
mbid,
title,
disambiguation,
aliases,
artists,
relationships,
fieldWithID
tags,
fieldWithID,
connectionWithCount
} from './helpers'
const Work = new GraphQLObjectType({
@ -23,6 +25,7 @@ more audio recordings.`,
mbid,
title,
disambiguation,
aliases,
iswcs: {
type: new GraphQLList(GraphQLString),
description: `A list of [ISWCs](https://musicbrainz.org/doc/ISWC) assigned
@ -36,10 +39,10 @@ to the work by copyright collecting agencies.`
description: 'The type of work.'
}),
artists,
relationships
relationships,
tags
})
})
const { connectionType: WorkConnection } = connectionDefinitions({ nodeType: Work })
export { WorkConnection }
export const WorkConnection = connectionWithCount(Work)
export default Work