UNPKG

7.25 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(
81 root,
82 { first, after, type = [], status = [], discID, isrc, iswc, ...args },
83 { loaders },
84 info
85) {
86 const pluralName = toDashed(info.fieldName)
87 const singularName = toSingular(pluralName)
88 let params = {
89 ...args,
90 type,
91 status,
92 limit: first,
93 offset: getOffsetWithDefault(after, -1) + 1 || undefined
94 }
95 params = includeSubqueries(params, info)
96 params = includeRelationships(params, info, info.fragments)
97 const formatParam = value => value.toLowerCase().replace(/ /g, '')
98 params.type = params.type.map(formatParam)
99 params.status = params.status.map(formatParam)
100 let request
101 if (discID) {
102 request = loaders.lookup.load(['discid', discID, params])
103 // If fetching releases by disc ID, they will already include the `media`
104 // and `discids` subqueries, and it is invalid to specify them.
105 if (params.inc) {
106 params.inc = params.inc.filter(value => {
107 return value !== 'media' && value !== 'discids'
108 })
109 }
110 } else if (isrc) {
111 request = loaders.lookup.load(['isrc', isrc, params])
112 } else if (iswc) {
113 request = loaders.lookup.load(['iswc', iswc, params])
114 } else {
115 request = loaders.browse.load([singularName, params])
116 }
117 return request.then(list => {
118 // Grab the list, offet, and count from the response and use them to build
119 // a Relay connection object.
120 const {
121 [pluralName]: arraySlice,
122 [`${singularName}-offset`]: sliceStart = 0,
123 [`${singularName}-count`]: arrayLength = arraySlice.length
124 } = list
125 const meta = { sliceStart, arrayLength }
126 const connection = connectionFromArraySlice(
127 arraySlice,
128 { first, after },
129 meta
130 )
131 return {
132 nodes: connection.edges.map(edge => edge.node),
133 totalCount: arrayLength,
134 ...connection
135 }
136 })
137}
138
139export function resolveSearch(
140 root,
141 { after, first, query, ...args },
142 { loaders },
143 info
144) {
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(
161 arraySlice,
162 { first, after },
163 meta
164 )
165 // Move the `score` field up to the edge object and make sure it's a
166 // number (MusicBrainz returns a string).
167 const edges = connection.edges.map(edge => ({
168 ...edge,
169 score: +edge.node.score
170 }))
171 const connectionWithExtras = {
172 nodes: edges.map(edge => edge.node),
173 totalCount: arrayLength,
174 ...connection,
175 edges
176 }
177 return connectionWithExtras
178 })
179}
180
181export function resolveRelationship(rels, args, context, info) {
182 const targetType = toDashed(toSingular(info.fieldName)).replace('-', '_')
183 let matches = rels.filter(rel => rel['target-type'] === targetType)
184 // There's no way to filter these at the API level, so do it here.
185 if (args.direction != null) {
186 matches = matches.filter(rel => rel.direction === args.direction)
187 }
188 if (args.type != null) {
189 matches = matches.filter(rel => rel.type === args.type)
190 }
191 if (args.typeID != null) {
192 matches = matches.filter(rel => rel['type-id'] === args.typeID)
193 }
194 const connection = connectionFromArray(matches, args)
195 return {
196 nodes: connection.edges.map(edge => edge.node),
197 totalCount: matches.length,
198 ...connection
199 }
200}
201
202export function resolveLinked(entity, args, context, info) {
203 const parentEntity = toDashed(info.parentType.name)
204 args = { ...args, [parentEntity]: entity.id }
205 return resolveBrowse(entity, args, context, info)
206}
207
208/**
209 * If we weren't smart enough or weren't able to include the `inc` parameter
210 * for a particular field that's being requested, make another request to grab
211 * it (after making sure it isn't already available).
212 */
213export function createSubqueryResolver(
214 { inc, key } = {},
215 handler = value => value
216) {
217 return (entity, args, { loaders }, info) => {
218 key = key || toDashed(info.fieldName)
219 let promise
220 if (key in entity) {
221 promise = Promise.resolve(entity)
222 } else {
223 const { _type: entityType, id } = entity
224 const params = { inc: [inc || key] }
225 promise = loaders.lookup.load([entityType, id, params])
226 }
227 return promise.then(entity => handler(entity[key], args))
228 }
229}
230
231export function resolveDiscReleases(disc, args, context, info) {
232 const { releases } = disc
233 if (releases != null) {
234 const connection = connectionFromArray(releases, args)
235 return {
236 nodes: connection.edges.map(edge => edge.node),
237 totalCount: releases.length,
238 ...connection
239 }
240 }
241 args = { ...args, discID: disc.id }
242 return resolveBrowse(disc, args, context, info)
243}