Add a nodes field to every connection (#41)

* Add a nodes field to every connection
* Document nodes field
This commit is contained in:
Brian Beck 2017-10-06 19:43:15 -07:00 committed by GitHub
parent bc2a5655d8
commit b5d0dcce91
11 changed files with 583 additions and 17 deletions

View file

@ -324,9 +324,13 @@ schema. The schema was originally designed to be more user-friendly, but in the
end I decided that being compatible with Relay was a worthwhile feature. I
agree, its ugly.
Dont forget, though, that you can use [GraphQL aliases][aliases] to rename
fields to your liking. For example, the following query renames `edges`, `node`,
and `mbid` to `results`, `releaseGroup`, and `id`, respectively:
The GraphBrainz schema includes an extra `nodes` field on every connection type.
If you only want the nodes and no other fields on `edges`, you can use `nodes`
as a shortcut.
Dont forget that you can also use [GraphQL aliases][aliases] to rename fields
to your liking. For example, the following query renames `edges`, `node`, and
`mbid` to `results`, `releaseGroup`, and `id`, respectively:
```graphql
query ChristmasAlbums {

View file

@ -99,6 +99,10 @@ type AreaConnection {
# A list of edges.
edges: [AreaEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Area]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -233,6 +237,10 @@ type ArtistConnection {
# A list of edges.
edges: [ArtistEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Artist]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -557,6 +565,10 @@ type CollectionConnection {
# A list of edges.
edges: [CollectionEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Collection]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -755,6 +767,10 @@ type EventConnection {
# A list of edges.
edges: [EventEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Event]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -824,6 +840,10 @@ type InstrumentConnection {
# A list of edges.
edges: [InstrumentEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Instrument]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -952,6 +972,10 @@ type LabelConnection {
# A list of edges.
edges: [LabelEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Label]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -1193,6 +1217,10 @@ type PlaceConnection {
# A list of edges.
edges: [PlaceEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Place]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -1323,6 +1351,10 @@ type RecordingConnection {
# A list of edges.
edges: [RecordingEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Recording]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -1393,6 +1425,10 @@ type RelationshipConnection {
# A list of edges.
edges: [RelationshipEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Relationship]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -1730,6 +1766,10 @@ type ReleaseConnection {
# A list of edges.
edges: [ReleaseEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Release]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -1897,6 +1937,10 @@ type ReleaseGroupConnection {
# A list of edges.
edges: [ReleaseGroupEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [ReleaseGroup]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -2191,6 +2235,10 @@ type SeriesConnection {
# A list of edges.
edges: [SeriesEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Series]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -2228,6 +2276,10 @@ type TagConnection {
# A list of edges.
edges: [TagEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Tag]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int
@ -2327,6 +2379,10 @@ type WorkConnection {
# A list of edges.
edges: [WorkEdge]
# A list of nodes in the connection (without going through the
# `edges` field).
nodes: [Work]
# A count of the total number of items in this connection,
# ignoring pagination.
totalCount: Int

View file

@ -514,6 +514,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#area">Area</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -950,6 +960,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#artist">Artist</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -2053,6 +2073,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#collection">Collection</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -2576,6 +2606,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#event">Event</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -2809,6 +2849,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#instrument">Instrument</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -3136,6 +3186,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#label">Label</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -3870,6 +3930,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#place">Place</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -4241,6 +4311,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#recording">Recording</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -4455,6 +4535,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#relationship">Relationship</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -5594,6 +5684,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#release">Release</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -6102,6 +6202,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#releasegroup">ReleaseGroup</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -6722,6 +6832,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#series">Series</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -6849,6 +6969,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#tag">Tag</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>
@ -7172,6 +7302,16 @@ Information to aid in pagination.
A list of edges.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>nodes</strong></td>
<td valign="top">[<a href="#work">Work</a>]</td>
<td>
A list of nodes in the connection (without going through the
`edges` field).
</td>
</tr>
<tr>

View file

@ -1239,6 +1239,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Artist",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -1999,6 +2015,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Recording",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -2664,6 +2696,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Release",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -3898,6 +3946,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Label",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -5356,6 +5420,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Relationship",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -5621,6 +5701,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Collection",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -6254,6 +6350,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Area",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -6360,6 +6472,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Event",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -6781,6 +6909,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Tag",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -6926,6 +7070,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Instrument",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -7240,6 +7400,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Place",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -7668,6 +7844,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ReleaseGroup",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -8246,6 +8438,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Series",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",
@ -8532,6 +8740,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes in the connection (without going through the\n`edges` field).",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Work",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of items in this connection,\nignoring pagination.",

View file

@ -127,9 +127,11 @@ export function resolveBrowse (root, {
[`${singularName}-count`]: arrayLength = arraySlice.length
} = list
const meta = { sliceStart, arrayLength }
const connection = connectionFromArraySlice(arraySlice, { first, after }, meta)
return {
nodes: connection.edges.map(edge => edge.node),
totalCount: arrayLength,
...connectionFromArraySlice(arraySlice, { first, after }, meta)
...connection
}
})
}
@ -155,14 +157,17 @@ export function resolveSearch (root, {
count: arrayLength
} = list
const meta = { sliceStart, arrayLength }
const connection = {
totalCount: arrayLength,
...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
// number (MusicBrainz returns a string).
connection.edges.forEach(edge => { edge.score = +edge.node.score })
return connection
const edges = connection.edges.map(edge => ({ ...edge, score: +edge.node.score }))
const connectionWithExtras = {
nodes: edges.map(edge => edge.node),
totalCount: arrayLength,
...connection,
edges
}
return connectionWithExtras
})
}
@ -179,9 +184,11 @@ export function resolveRelationship (rels, args, context, info) {
if (args.typeID != null) {
matches = matches.filter(rel => rel['type-id'] === args.typeID)
}
const connection = connectionFromArray(matches, args)
return {
nodes: connection.edges.map(edge => edge.node),
totalCount: matches.length,
...connectionFromArray(matches, args)
...connection
}
}
@ -214,9 +221,11 @@ export function createSubqueryResolver ({ inc, key } = {}, handler = value => va
export function resolveDiscReleases (disc, args, context, info) {
const { releases } = disc
if (releases != null) {
const connection = connectionFromArray(releases, args)
return {
nodes: connection.edges.map(edge => edge.node),
totalCount: releases.length,
...connectionFromArray(releases, args)
...connection
}
}
args = { ...args, discID: disc.id }

View file

@ -263,10 +263,14 @@ export const releaseGroups = linkedQuery(ReleaseGroupConnection, {
})
export const series = linkedQuery(SeriesConnection)
export const tags = linkedQuery(TagConnection, {
resolve: createSubqueryResolver({}, (value = [], args) => ({
totalCount: value.length,
...connectionFromArray(value, args)
}))
resolve: createSubqueryResolver({}, (value = [], args) => {
const connection = connectionFromArray(value, args)
return {
nodes: connection.edges.map(edge => edge.node),
totalCount: value.length,
...connection
}
})
})
export const works = linkedQuery(WorkConnection)
@ -285,7 +289,14 @@ these results were found through a search.`
export function connectionWithExtras (nodeType) {
return connectionDefinitions({
nodeType,
connectionFields: () => ({ totalCount }),
connectionFields: () => ({
nodes: {
type: new GraphQLList(nodeType),
description: `A list of nodes in the connection (without going through the
\`edges\` field).`
},
totalCount
}),
edgeFields: () => ({ score })
}).connectionType
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Sat, 07 Oct 2017 02:16:50 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1052",
"x-ratelimit-reset": "1507342611",
"server": "Plack::Handler::Starlet",
"etag": "W/\"511ce70a5d7fc09685033f0a9cc9363f\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/artist/b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d?inc=artist-rels&fmt=json",
"time": 402,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.0.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Sat, 07 Oct 2017 02:16:49 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "755",
"x-ratelimit-reset": "1507342609",
"server": "Plack::Handler::Starlet",
"etag": "W/\"dfb1258ab1119ada8197e91d3da43002\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/artist?release=2ac3cbf2-f0d0-3678-af5f-b62dcb051bc0&fmt=json",
"time": 659,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.0.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

View file

@ -190,6 +190,70 @@ test('supports deeply nested queries', testData, `
t.is(labels.edges[0].node.releases.edges.length, 1)
})
test('connections have a nodes shortcut field', testData, `
query AppleRecordsMarriages {
search {
labels(query: "Apple Records", first: 1) {
nodes {
name
disambiguation
country
releases(first: 1) {
nodes {
title
date
artists {
nodes {
name
...bandMembers
}
}
}
}
}
}
}
}
fragment bandMembers on Artist {
relationships {
artists(direction: "backward", type: "member of band") {
nodes {
type
target {
... on Artist {
name
...marriages
}
}
}
}
}
}
fragment marriages on Artist {
relationships {
artists(type: "married") {
nodes {
type
direction
begin
end
target {
... on Artist {
name
}
}
}
}
}
}
`, (t, data) => {
const { labels } = data.search
t.true(labels.nodes.length > 0)
t.is(labels.nodes[0].releases.nodes.length, 1)
})
// FIXME: https://github.com/graphql/graphql-js/issues/910
test('throws an error if given a malformed MBID', testThrows, `
{