UNPKG

7.19 kBJavaScriptView Raw
1import { toDashed, toSingular } from './types/helpers'
2import {
3 getOffsetWithDefault,
4 connectionFromArray,
5 connectionFromArraySlice
6} from 'graphql-relay'
7import { getFields, extendIncludes } from './util'
8
9export function includeRelationships (params, info, fragments = info.fragments) {
10 let fields = getFields(info, fragments)
11 if (info.fieldName !== 'relationships') {
12 if (fields.relationships) {
13 fields = getFields(fields.relationships, fragments)
14 } else {
15 if (fields.edges) {
16 fields = getFields(fields.edges, fragments)
17 if (fields.node) {
18 return includeRelationships(params, fields.node, fragments)
19 }
20 }
21 return params
22 }
23 }
24 if (fields) {
25 const relationships = Object.keys(fields)
26 const includeRels = relationships.map(field => {
27 return `${toDashed(toSingular(field))}-rels`
28 })
29 if (includeRels.length) {
30 params = {
31 ...params,
32 inc: extendIncludes(params.inc, includeRels)
33 }
34 }
35 }
36 return params
37}
38
39export function includeSubqueries (params, info, fragments = info.fragments) {
40 const subqueryIncludes = {
41 aliases: ['aliases'],
42 artistCredit: ['artist-credits'],
43 artistCredits: ['artist-credits'],
44 isrcs: ['isrcs'],
45 media: ['media', 'discids'],
46 rating: ['ratings'],
47 tags: ['tags']
48 }
49 let fields = getFields(info, fragments)
50 const include = []
51 for (const key in subqueryIncludes) {
52 if (fields[key]) {
53 const value = subqueryIncludes[key]
54 include.push(...value)
55 }
56 }
57 params = {
58 ...params,
59 inc: extendIncludes(params.inc, include)
60 }
61 if (fields.edges) {
62 fields = getFields(fields.edges, fragments)
63 if (fields.node) {
64 params = includeSubqueries(params, fields.node, fragments)
65 }
66 }
67 return params
68}
69
70export function resolveLookup (root, { mbid, ...params }, { loaders }, info) {
71 if (!mbid && !params.resource) {
72 throw new Error('Lookups by a field other than MBID must provide: resource')
73 }
74 const entityType = toDashed(info.fieldName)
75 params = includeSubqueries(params, info)
76 params = includeRelationships(params, info)
77 return loaders.lookup.load([entityType, mbid, params])
78}
79
80export function resolveBrowse (root, {
81 first,
82 after,
83 type = [],
84 status = [],
85 discID,
86 isrc,
87 iswc,
88 ...args
89}, { loaders }, info) {
90 const pluralName = toDashed(info.fieldName)
91 const singularName = toSingular(pluralName)
92 let params = {
93 ...args,
94 type,
95 status,
96 limit: first,
97 offset: getOffsetWithDefault(after, -1) + 1 || undefined
98 }
99 params = includeSubqueries(params, info)
100 params = includeRelationships(params, info, info.fragments)
101 const formatParam = value => value.toLowerCase().replace(/ /g, '')
102 params.type = params.type.map(formatParam)
103 params.status = params.status.map(formatParam)
104 let request
105 if (discID) {
106 request = loaders.lookup.load(['discid', discID, params])
107 // If fetching releases by disc ID, they will already include the `media`
108 // and `discids` subqueries, and it is invalid to specify them.
109 if (params.inc) {
110 params.inc = params.inc.filter(value => {
111 return value !== 'media' && value !== 'discids'
112 })
113 }
114 } else if (isrc) {
115 request = loaders.lookup.load(['isrc', isrc, params])
116 } else if (iswc) {
117 request = loaders.lookup.load(['iswc', iswc, params])
118 } else {
119 request = loaders.browse.load([singularName, params])
120 }
121 return request.then(list => {
122 // Grab the list, offet, and count from the response and use them to build
123 // a Relay connection object.
124 const {
125 [pluralName]: arraySlice,
126 [`${singularName}-offset`]: sliceStart = 0,
127 [`${singularName}-count`]: arrayLength = arraySlice.length
128 } = list
129 const meta = { sliceStart, arrayLength }
130 const connection = connectionFromArraySlice(arraySlice, { first, after }, meta)
131 return {
132 nodes: connection.edges.map(edge => edge.node),
133 totalCount: arrayLength,
134 ...connection
135 }
136 })
137}
138
139export function resolveSearch (root, {
140 after,
141 first,
142 query,
143 ...args
144}, { loaders }, info) {
145 const pluralName = toDashed(info.fieldName)
146 const singularName = toSingular(pluralName)
147 let params = {
148 ...args,
149 limit: first,
150 offset: getOffsetWithDefault(after, -1) + 1 || undefined
151 }
152 params = includeSubqueries(params, info)
153 return loaders.search.load([singularName, query, params]).then(list => {
154 const {
155 [pluralName]: arraySlice,
156 offset: sliceStart,
157 count: arrayLength
158 } = list
159 const meta = { sliceStart, arrayLength }
160 const connection = connectionFromArraySlice(arraySlice, { first, after }, meta)
161 // Move the `score` field up to the edge object and make sure it's a
162 // number (MusicBrainz returns a string).
163 const edges = connection.edges.map(edge => ({ ...edge, score: +edge.node.score }))
164 const connectionWithExtras = {
165 nodes: edges.map(edge => edge.node),
166 totalCount: arrayLength,
167 ...connection,
168 edges
169 }
170 return connectionWithExtras
171 })
172}
173
174export function resolveRelationship (rels, args, context, info) {
175 const targetType = toDashed(toSingular(info.fieldName)).replace('-', '_')
176 let matches = rels.filter(rel => rel['target-type'] === targetType)
177 // There's no way to filter these at the API level, so do it here.
178 if (args.direction != null) {
179 matches = matches.filter(rel => rel.direction === args.direction)
180 }
181 if (args.type != null) {
182 matches = matches.filter(rel => rel.type === args.type)
183 }
184 if (args.typeID != null) {
185 matches = matches.filter(rel => rel['type-id'] === args.typeID)
186 }
187 const connection = connectionFromArray(matches, args)
188 return {
189 nodes: connection.edges.map(edge => edge.node),
190 totalCount: matches.length,
191 ...connection
192 }
193}
194
195export function resolveLinked (entity, args, context, info) {
196 const parentEntity = toDashed(info.parentType.name)
197 args = { ...args, [parentEntity]: entity.id }
198 return resolveBrowse(entity, args, context, info)
199}
200
201/**
202 * If we weren't smart enough or weren't able to include the `inc` parameter
203 * for a particular field that's being requested, make another request to grab
204 * it (after making sure it isn't already available).
205 */
206export function createSubqueryResolver ({ inc, key } = {}, handler = value => value) {
207 return (entity, args, { loaders }, info) => {
208 key = key || toDashed(info.fieldName)
209 let promise
210 if (key in entity) {
211 promise = Promise.resolve(entity)
212 } else {
213 const { _type: entityType, id } = entity
214 const params = { inc: [inc || key] }
215 promise = loaders.lookup.load([entityType, id, params])
216 }
217 return promise.then(entity => handler(entity[key], args))
218 }
219}
220
221export function resolveDiscReleases (disc, args, context, info) {
222 const { releases } = disc
223 if (releases != null) {
224 const connection = connectionFromArray(releases, args)
225 return {
226 nodes: connection.edges.map(edge => edge.node),
227 totalCount: releases.length,
228 ...connection
229 }
230 }
231 args = { ...args, discID: disc.id }
232 return resolveBrowse(disc, args, context, info)
233}