diff --git a/README.md b/README.md index 4682f83..270d85c 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ type Area implements Entity { statuses: [ReleaseStatus]): [Release] } -type AreaPage { +type AreaConnection { count: Int! offset: Int! created: Date @@ -80,7 +80,7 @@ type ArtistCredit { joinPhrase: String } -type ArtistPage { +type ArtistConnection { count: Int! offset: Int! created: Date @@ -94,23 +94,23 @@ type BrowseQuery { recording: MBID, release: MBID, releaseGroup: MBID, - work: MBID): ArtistPage + work: MBID): ArtistConnection events(limit: Int, offset: Int, area: MBID, artist: MBID, - place: MBID): EventPage + place: MBID): EventConnection labels(limit: Int, offset: Int, area: MBID, - release: MBID): LabelPage + release: MBID): LabelConnection places(limit: Int, offset: Int, - area: MBID): PlacePage + area: MBID): PlaceConnection recordings(limit: Int, offset: Int, artist: MBID, - release: MBID): RecordingPage + release: MBID): RecordingConnection releases(limit: Int, offset: Int, area: MBID, @@ -119,17 +119,17 @@ type BrowseQuery { track: MBID, trackArtist: MBID, recording: MBID, - releaseGroup: MBID): ReleasePage + releaseGroup: MBID): ReleaseConnection releaseGroups(limit: Int, offset: Int, artist: MBID, - release: MBID): ReleaseGroupPage + release: MBID): ReleaseGroupConnection works(limit: Int, offset: Int, - artist: MBID): WorkPage + artist: MBID): WorkConnection urls(limit: Int, offset: Int, - resource: URLString): URLPage + resource: URLString): URLConnection } type Coordinates { @@ -157,7 +157,7 @@ type Event implements Entity { typeID: MBID } -type EventPage { +type EventConnection { count: Int! offset: Int! created: Date @@ -195,7 +195,7 @@ type Label implements Entity { statuses: [ReleaseStatus]): [Release] } -type LabelPage { +type LabelConnection { count: Int! offset: Int! created: Date @@ -237,7 +237,7 @@ type Place implements Entity { events(limit: Int, offset: Int): [Event] } -type PlacePage { +type PlaceConnection { count: Int! offset: Int! created: Date @@ -261,7 +261,7 @@ type Recording implements Entity { relations: Relations } -type RecordingPage { +type RecordingConnection { count: Int! offset: Int! created: Date @@ -394,7 +394,7 @@ type ReleaseGroup implements Entity { relations: Relations } -type ReleaseGroupPage { +type ReleaseGroupConnection { count: Int! offset: Int! created: Date @@ -420,7 +420,7 @@ enum ReleaseGroupType { NAT } -type ReleasePage { +type ReleaseConnection { count: Int! offset: Int! created: Date @@ -441,14 +441,14 @@ type RootQuery { } type SearchQuery { - areas(query: String!, limit: Int, offset: Int): AreaPage - artists(query: String!, limit: Int, offset: Int): ArtistPage - labels(query: String!, limit: Int, offset: Int): LabelPage - places(query: String!, limit: Int, offset: Int): PlacePage - recordings(query: String!, limit: Int, offset: Int): RecordingPage - releases(query: String!, limit: Int, offset: Int): ReleasePage - releaseGroups(query: String!, limit: Int, offset: Int): ReleaseGroupPage - works(query: String!, limit: Int, offset: Int): WorkPage + areas(query: String!, limit: Int, offset: Int): AreaConnection + artists(query: String!, limit: Int, offset: Int): ArtistConnection + labels(query: String!, limit: Int, offset: Int): LabelConnection + places(query: String!, limit: Int, offset: Int): PlaceConnection + recordings(query: String!, limit: Int, offset: Int): RecordingConnection + releases(query: String!, limit: Int, offset: Int): ReleaseConnection + releaseGroups(query: String!, limit: Int, offset: Int): ReleaseGroupConnection + works(query: String!, limit: Int, offset: Int): WorkConnection } scalar Time @@ -459,7 +459,7 @@ type URL implements Entity { relations: Relations } -type URLPage { +type URLConnection { count: Int! offset: Int! created: Date @@ -480,7 +480,7 @@ type Work implements Entity { relations: Relations } -type WorkPage { +type WorkConnection { count: Int! offset: Int! created: Date diff --git a/package.json b/package.json index c4ba3e6..8cd727b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "pascalcase": "^0.1.1", "qs": "^6.2.1", "request": "^2.74.0", - "retry": "^0.9.0" + "retry": "^0.10.0" }, "devDependencies": { "babel-cli": "^6.11.4", @@ -59,7 +59,7 @@ "mocha": "^3.0.1", "nodemon": "^1.10.2", "snazzy": "^4.0.1", - "standard": "^7.1.2" + "standard": "^8.0.0" }, "standard": { "parser": "babel-eslint" diff --git a/schema.json b/schema.json index 480a5de..b69a33f 100644 --- a/schema.json +++ b/schema.json @@ -12,6 +12,33 @@ "name": "RootQuery", "description": null, "fields": [ + { + "name": "node", + "description": "Fetches an object given its ID", + "args": [ + { + "name": "id", + "description": "The ID of an object", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "lookup", "description": null, @@ -54,6 +81,99 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INTERFACE", + "name": "Node", + "description": "An object with an ID", + "fields": [ + { + "name": "id", + "description": "The id of the object.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "Area", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Artist", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Recording", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Release", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Label", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "ReleaseGroup", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Work", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Event", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Place", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Instrument", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "URL", + "ofType": null + } + ] + }, { "kind": "OBJECT", "name": "LookupQuery", @@ -362,16 +482,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "SCALAR", - "name": "ID", - "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "Area", @@ -711,89 +821,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "INTERFACE", - "name": "Node", - "description": "An object with an ID", - "fields": [ - { - "name": "id", - "description": "The id of the object.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": [ - { - "kind": "OBJECT", - "name": "Area", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Artist", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Recording", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Release", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Label", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "ReleaseGroup", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Work", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Event", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Place", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Instrument", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "URL", - "ofType": null - } - ] - }, { "kind": "INTERFACE", "name": "Entity", @@ -4771,7 +4798,7 @@ ], "type": { "kind": "OBJECT", - "name": "ArtistPage", + "name": "ArtistConnection", "ofType": null }, "isDeprecated": false, @@ -4834,7 +4861,7 @@ ], "type": { "kind": "OBJECT", - "name": "EventPage", + "name": "EventConnection", "ofType": null }, "isDeprecated": false, @@ -4887,7 +4914,7 @@ ], "type": { "kind": "OBJECT", - "name": "LabelPage", + "name": "LabelConnection", "ofType": null }, "isDeprecated": false, @@ -4930,7 +4957,7 @@ ], "type": { "kind": "OBJECT", - "name": "PlacePage", + "name": "PlaceConnection", "ofType": null }, "isDeprecated": false, @@ -4983,7 +5010,7 @@ ], "type": { "kind": "OBJECT", - "name": "RecordingPage", + "name": "RecordingConnection", "ofType": null }, "isDeprecated": false, @@ -5086,7 +5113,7 @@ ], "type": { "kind": "OBJECT", - "name": "ReleasePage", + "name": "ReleaseConnection", "ofType": null }, "isDeprecated": false, @@ -5139,7 +5166,7 @@ ], "type": { "kind": "OBJECT", - "name": "ReleaseGroupPage", + "name": "ReleaseGroupConnection", "ofType": null }, "isDeprecated": false, @@ -5182,7 +5209,7 @@ ], "type": { "kind": "OBJECT", - "name": "WorkPage", + "name": "WorkConnection", "ofType": null }, "isDeprecated": false, @@ -5225,7 +5252,7 @@ ], "type": { "kind": "OBJECT", - "name": "URLPage", + "name": "URLConnection", "ofType": null }, "isDeprecated": false, @@ -5239,19 +5266,19 @@ }, { "kind": "OBJECT", - "name": "ArtistPage", - "description": "A page of Artist results from browsing or searching.", + "name": "ArtistConnection", + "description": "A connection to a list of items.", "fields": [ { - "name": "count", - "description": null, + "name": "pageInfo", + "description": "Information to aid in pagination.", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "PageInfo", "ofType": null } }, @@ -5259,52 +5286,20 @@ "deprecationReason": null }, { - "name": "offset", - "description": null, + "name": "edges", + "description": "A list of edges.", "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "ArtistEdge", "ofType": null } }, "isDeprecated": false, "deprecationReason": null - }, - { - "name": "created", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "Date", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "results", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Artist", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null } ], "inputFields": null, @@ -5314,19 +5309,19 @@ }, { "kind": "OBJECT", - "name": "EventPage", - "description": "A page of Event results from browsing or searching.", + "name": "PageInfo", + "description": "Information about pagination in a connection.", "fields": [ { - "name": "count", - "description": null, + "name": "hasNextPage", + "description": "When paginating forwards, are there more items?", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Boolean", "ofType": null } }, @@ -5334,15 +5329,15 @@ "deprecationReason": null }, { - "name": "offset", - "description": null, + "name": "hasPreviousPage", + "description": "When paginating backwards, are there more items?", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Boolean", "ofType": null } }, @@ -5350,32 +5345,63 @@ "deprecationReason": null }, { - "name": "created", - "description": null, + "name": "startCursor", + "description": "When paginating backwards, the cursor to continue.", "args": [], "type": { "kind": "SCALAR", - "name": "Date", + "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "results", - "description": null, + "name": "endCursor", + "description": "When paginating forwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ArtistEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Artist", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Event", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null } }, "isDeprecated": false, @@ -5389,19 +5415,19 @@ }, { "kind": "OBJECT", - "name": "LabelPage", - "description": "A page of Label results from browsing or searching.", + "name": "EventConnection", + "description": "A connection to a list of items.", "fields": [ { - "name": "count", - "description": null, + "name": "pageInfo", + "description": "Information to aid in pagination.", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "PageInfo", "ofType": null } }, @@ -5409,52 +5435,20 @@ "deprecationReason": null }, { - "name": "offset", - "description": null, + "name": "edges", + "description": "A list of edges.", "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "EventEdge", "ofType": null } }, "isDeprecated": false, "deprecationReason": null - }, - { - "name": "created", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "Date", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "results", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Label", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null } ], "inputFields": null, @@ -5464,68 +5458,32 @@ }, { "kind": "OBJECT", - "name": "PlacePage", - "description": "A page of Place results from browsing or searching.", + "name": "EventEdge", + "description": "An edge in a connection.", "fields": [ { - "name": "count", - "description": null, + "name": "node", + "description": "The item at the end of the edge", "args": [], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "offset", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "created", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "Date", + "kind": "OBJECT", + "name": "Event", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "results", - "description": null, + "name": "cursor", + "description": "A cursor for use in pagination", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Place", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null } }, "isDeprecated": false, @@ -5539,19 +5497,19 @@ }, { "kind": "OBJECT", - "name": "RecordingPage", - "description": "A page of Recording results from browsing or searching.", + "name": "LabelConnection", + "description": "A connection to a list of items.", "fields": [ { - "name": "count", - "description": null, + "name": "pageInfo", + "description": "Information to aid in pagination.", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "PageInfo", "ofType": null } }, @@ -5559,52 +5517,20 @@ "deprecationReason": null }, { - "name": "offset", - "description": null, + "name": "edges", + "description": "A list of edges.", "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "LabelEdge", "ofType": null } }, "isDeprecated": false, "deprecationReason": null - }, - { - "name": "created", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "Date", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "results", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Recording", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null } ], "inputFields": null, @@ -5614,68 +5540,32 @@ }, { "kind": "OBJECT", - "name": "ReleasePage", - "description": "A page of Release results from browsing or searching.", + "name": "LabelEdge", + "description": "An edge in a connection.", "fields": [ { - "name": "count", - "description": null, + "name": "node", + "description": "The item at the end of the edge", "args": [], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "offset", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "created", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "Date", + "kind": "OBJECT", + "name": "Label", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "results", - "description": null, + "name": "cursor", + "description": "A cursor for use in pagination", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Release", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null } }, "isDeprecated": false, @@ -5689,19 +5579,19 @@ }, { "kind": "OBJECT", - "name": "ReleaseGroupPage", - "description": "A page of ReleaseGroup results from browsing or searching.", + "name": "PlaceConnection", + "description": "A connection to a list of items.", "fields": [ { - "name": "count", - "description": null, + "name": "pageInfo", + "description": "Information to aid in pagination.", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "PageInfo", "ofType": null } }, @@ -5709,52 +5599,20 @@ "deprecationReason": null }, { - "name": "offset", - "description": null, + "name": "edges", + "description": "A list of edges.", "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "PlaceEdge", "ofType": null } }, "isDeprecated": false, "deprecationReason": null - }, - { - "name": "created", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "Date", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "results", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "ReleaseGroup", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null } ], "inputFields": null, @@ -5764,68 +5622,32 @@ }, { "kind": "OBJECT", - "name": "WorkPage", - "description": "A page of Work results from browsing or searching.", + "name": "PlaceEdge", + "description": "An edge in a connection.", "fields": [ { - "name": "count", - "description": null, + "name": "node", + "description": "The item at the end of the edge", "args": [], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "offset", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "created", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "Date", + "kind": "OBJECT", + "name": "Place", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "results", - "description": null, + "name": "cursor", + "description": "A cursor for use in pagination", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Work", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null } }, "isDeprecated": false, @@ -5839,19 +5661,19 @@ }, { "kind": "OBJECT", - "name": "URLPage", - "description": "A page of URL results from browsing or searching.", + "name": "RecordingConnection", + "description": "A connection to a list of items.", "fields": [ { - "name": "count", - "description": null, + "name": "pageInfo", + "description": "Information to aid in pagination.", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "PageInfo", "ofType": null } }, @@ -5859,48 +5681,383 @@ "deprecationReason": null }, { - "name": "offset", - "description": null, + "name": "edges", + "description": "A list of edges.", "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "RecordingEdge", "ofType": null } }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "RecordingEdge", + "description": "An edge in a connection.", + "fields": [ { - "name": "created", - "description": null, + "name": "node", + "description": "The item at the end of the edge", "args": [], "type": { - "kind": "SCALAR", - "name": "Date", + "kind": "OBJECT", + "name": "Recording", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "results", - "description": null, + "name": "cursor", + "description": "A cursor for use in pagination", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "URL", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ReleaseConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ReleaseEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ReleaseEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Release", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ReleaseGroupConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ReleaseGroupEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ReleaseGroupEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "ReleaseGroup", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "WorkConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "WorkEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "WorkEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Work", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "URLConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "URLEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "URLEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "URL", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null } }, "isDeprecated": false, @@ -5919,7 +6076,7 @@ "fields": [ { "name": "areas", - "description": "Search for Area entities.", + "description": null, "args": [ { "name": "query", @@ -5936,17 +6093,17 @@ "defaultValue": null }, { - "name": "limit", + "name": "after", "description": null, "type": { "kind": "SCALAR", - "name": "Int", + "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "offset", + "name": "first", "description": null, "type": { "kind": "SCALAR", @@ -5958,7 +6115,7 @@ ], "type": { "kind": "OBJECT", - "name": "AreaPage", + "name": "AreaConnection", "ofType": null }, "isDeprecated": false, @@ -5966,7 +6123,7 @@ }, { "name": "artists", - "description": "Search for Artist entities.", + "description": null, "args": [ { "name": "query", @@ -5983,17 +6140,17 @@ "defaultValue": null }, { - "name": "limit", + "name": "after", "description": null, "type": { "kind": "SCALAR", - "name": "Int", + "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "offset", + "name": "first", "description": null, "type": { "kind": "SCALAR", @@ -6005,7 +6162,7 @@ ], "type": { "kind": "OBJECT", - "name": "ArtistPage", + "name": "ArtistConnection", "ofType": null }, "isDeprecated": false, @@ -6013,7 +6170,7 @@ }, { "name": "labels", - "description": "Search for Label entities.", + "description": null, "args": [ { "name": "query", @@ -6030,17 +6187,17 @@ "defaultValue": null }, { - "name": "limit", + "name": "after", "description": null, "type": { "kind": "SCALAR", - "name": "Int", + "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "offset", + "name": "first", "description": null, "type": { "kind": "SCALAR", @@ -6052,7 +6209,7 @@ ], "type": { "kind": "OBJECT", - "name": "LabelPage", + "name": "LabelConnection", "ofType": null }, "isDeprecated": false, @@ -6060,7 +6217,7 @@ }, { "name": "places", - "description": "Search for Place entities.", + "description": null, "args": [ { "name": "query", @@ -6077,17 +6234,17 @@ "defaultValue": null }, { - "name": "limit", + "name": "after", "description": null, "type": { "kind": "SCALAR", - "name": "Int", + "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "offset", + "name": "first", "description": null, "type": { "kind": "SCALAR", @@ -6099,7 +6256,7 @@ ], "type": { "kind": "OBJECT", - "name": "PlacePage", + "name": "PlaceConnection", "ofType": null }, "isDeprecated": false, @@ -6107,7 +6264,7 @@ }, { "name": "recordings", - "description": "Search for Recording entities.", + "description": null, "args": [ { "name": "query", @@ -6124,17 +6281,17 @@ "defaultValue": null }, { - "name": "limit", + "name": "after", "description": null, "type": { "kind": "SCALAR", - "name": "Int", + "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "offset", + "name": "first", "description": null, "type": { "kind": "SCALAR", @@ -6146,7 +6303,7 @@ ], "type": { "kind": "OBJECT", - "name": "RecordingPage", + "name": "RecordingConnection", "ofType": null }, "isDeprecated": false, @@ -6154,7 +6311,7 @@ }, { "name": "releases", - "description": "Search for Release entities.", + "description": null, "args": [ { "name": "query", @@ -6171,17 +6328,17 @@ "defaultValue": null }, { - "name": "limit", + "name": "after", "description": null, "type": { "kind": "SCALAR", - "name": "Int", + "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "offset", + "name": "first", "description": null, "type": { "kind": "SCALAR", @@ -6193,7 +6350,7 @@ ], "type": { "kind": "OBJECT", - "name": "ReleasePage", + "name": "ReleaseConnection", "ofType": null }, "isDeprecated": false, @@ -6201,7 +6358,7 @@ }, { "name": "releaseGroups", - "description": "Search for ReleaseGroup entities.", + "description": null, "args": [ { "name": "query", @@ -6218,17 +6375,17 @@ "defaultValue": null }, { - "name": "limit", + "name": "after", "description": null, "type": { "kind": "SCALAR", - "name": "Int", + "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "offset", + "name": "first", "description": null, "type": { "kind": "SCALAR", @@ -6240,7 +6397,7 @@ ], "type": { "kind": "OBJECT", - "name": "ReleaseGroupPage", + "name": "ReleaseGroupConnection", "ofType": null }, "isDeprecated": false, @@ -6248,7 +6405,7 @@ }, { "name": "works", - "description": "Search for Work entities.", + "description": null, "args": [ { "name": "query", @@ -6265,17 +6422,17 @@ "defaultValue": null }, { - "name": "limit", + "name": "after", "description": null, "type": { "kind": "SCALAR", - "name": "Int", + "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "offset", + "name": "first", "description": null, "type": { "kind": "SCALAR", @@ -6287,7 +6444,7 @@ ], "type": { "kind": "OBJECT", - "name": "WorkPage", + "name": "WorkConnection", "ofType": null }, "isDeprecated": false, @@ -6301,19 +6458,19 @@ }, { "kind": "OBJECT", - "name": "AreaPage", - "description": "A page of Area results from browsing or searching.", + "name": "AreaConnection", + "description": "A connection to a list of items.", "fields": [ { - "name": "count", - "description": null, + "name": "pageInfo", + "description": "Information to aid in pagination.", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "PageInfo", "ofType": null } }, @@ -6321,48 +6478,55 @@ "deprecationReason": null }, { - "name": "offset", - "description": null, + "name": "edges", + "description": "A list of edges.", "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Int", + "kind": "OBJECT", + "name": "AreaEdge", "ofType": null } }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "AreaEdge", + "description": "An edge in a connection.", + "fields": [ { - "name": "created", - "description": null, + "name": "node", + "description": "The item at the end of the edge", "args": [], "type": { - "kind": "SCALAR", - "name": "Date", + "kind": "OBJECT", + "name": "Area", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "results", - "description": null, + "name": "cursor", + "description": "A cursor for use in pagination", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Area", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null } }, "isDeprecated": false, diff --git a/src/queries/browse.js b/src/queries/browse.js index 40194fe..84fdf4a 100644 --- a/src/queries/browse.js +++ b/src/queries/browse.js @@ -2,15 +2,15 @@ import { GraphQLObjectType, GraphQLInt } from 'graphql' import { MBID, URLString, - ArtistPage, - EventPage, - LabelPage, - PlacePage, - RecordingPage, - ReleasePage, - ReleaseGroupPage, - URLPage, - WorkPage + ArtistConnection, + EventConnection, + LabelConnection, + PlaceConnection, + RecordingConnection, + ReleaseConnection, + ReleaseGroupConnection, + URLConnection, + WorkConnection } from '../types' import { browseResolver } from '../resolvers' @@ -21,7 +21,7 @@ export default new GraphQLObjectType({ 'to another entity.', fields: { artists: { - type: ArtistPage, + type: ArtistConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, @@ -34,7 +34,7 @@ export default new GraphQLObjectType({ resolve: browseResolver() }, events: { - type: EventPage, + type: EventConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, @@ -45,7 +45,7 @@ export default new GraphQLObjectType({ resolve: browseResolver() }, labels: { - type: LabelPage, + type: LabelConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, @@ -55,7 +55,7 @@ export default new GraphQLObjectType({ resolve: browseResolver() }, places: { - type: PlacePage, + type: PlaceConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, @@ -64,7 +64,7 @@ export default new GraphQLObjectType({ resolve: browseResolver() }, recordings: { - type: RecordingPage, + type: RecordingConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, @@ -74,7 +74,7 @@ export default new GraphQLObjectType({ resolve: browseResolver() }, releases: { - type: ReleasePage, + type: ReleaseConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, @@ -89,7 +89,7 @@ export default new GraphQLObjectType({ resolve: browseResolver() }, releaseGroups: { - type: ReleaseGroupPage, + type: ReleaseGroupConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, @@ -99,7 +99,7 @@ export default new GraphQLObjectType({ resolve: browseResolver() }, works: { - type: WorkPage, + type: WorkConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, @@ -108,7 +108,7 @@ export default new GraphQLObjectType({ resolve: browseResolver() }, urls: { - type: URLPage, + type: URLConnection, args: { limit: { type: GraphQLInt }, offset: { type: GraphQLInt }, diff --git a/src/queries/search.js b/src/queries/search.js index 584b718..6e28f26 100644 --- a/src/queries/search.js +++ b/src/queries/search.js @@ -1,13 +1,13 @@ import { GraphQLObjectType } from 'graphql' import { - AreaPage, - ArtistPage, - LabelPage, - PlacePage, - RecordingPage, - ReleasePage, - ReleaseGroupPage, - WorkPage + AreaConnection, + ArtistConnection, + LabelConnection, + PlaceConnection, + RecordingConnection, + ReleaseConnection, + ReleaseGroupConnection, + WorkConnection } from '../types' import { searchQuery } from '../types/helpers' @@ -17,13 +17,13 @@ export default new GraphQLObjectType({ 'Search queries provide a way to search for MusicBrainz entities using ' + 'Lucene query syntax.', fields: { - areas: searchQuery(AreaPage), - artists: searchQuery(ArtistPage), - labels: searchQuery(LabelPage), - places: searchQuery(PlacePage), - recordings: searchQuery(RecordingPage), - releases: searchQuery(ReleasePage), - releaseGroups: searchQuery(ReleaseGroupPage), - works: searchQuery(WorkPage) + areas: searchQuery(AreaConnection), + artists: searchQuery(ArtistConnection), + labels: searchQuery(LabelConnection), + places: searchQuery(PlaceConnection), + recordings: searchQuery(RecordingConnection), + releases: searchQuery(ReleaseConnection), + releaseGroups: searchQuery(ReleaseGroupConnection), + works: searchQuery(WorkConnection) } }) diff --git a/src/resolvers.js b/src/resolvers.js index 2c518b6..2bee11e 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -1,4 +1,5 @@ -import dashify from 'dashify' +import { toEntityType } from './types/helpers' +import { getOffsetWithDefault, connectionFromArraySlice } from 'graphql-relay' import { getFields, extendIncludes } from './util' export function includeRelations (params, info) { @@ -12,7 +13,7 @@ export function includeRelations (params, info) { } if (fields) { const relations = Object.keys(fields) - const includeRels = relations.map(key => `${dashify(key)}-rels`) + const includeRels = relations.map(key => `${toEntityType(key)}-rels`) if (includeRels.length) { params = { ...params, @@ -37,14 +38,14 @@ export function includeSubqueries (params, info) { export function lookupResolver (entityType, extraParams = {}) { return (root, { id }, { lookupLoader }, info) => { const params = includeRelations(extraParams, info) - entityType = entityType || dashify(info.fieldName) + entityType = entityType || toEntityType(info.fieldName) return lookupLoader.load([entityType, id, params]) } } export function browseResolver () { return (source, args, { browseLoader }, info) => { - const pluralName = dashify(info.fieldName) + const pluralName = toEntityType(info.fieldName) let singularName = pluralName if (pluralName.endsWith('s')) { singularName = pluralName.slice(0, -1) @@ -56,13 +57,23 @@ export function browseResolver () { export function searchResolver () { return (source, args, { searchLoader }, info) => { - const pluralName = dashify(info.fieldName) + const pluralName = toEntityType(info.fieldName) let singularName = pluralName if (pluralName.endsWith('s')) { singularName = pluralName.slice(0, -1) } - const { query, ...params } = args - return searchLoader.load([singularName, query, params]) + const { query, first, after, ...params } = args + params.limit = first + params.offset = getOffsetWithDefault(after, 0) + return searchLoader.load([singularName, query, params]).then(list => { + const { + [pluralName]: arraySlice, + offset: sliceStart, + count: arrayLength + } = list + const meta = { sliceStart, arrayLength } + return connectionFromArraySlice(arraySlice, { first, after }, meta) + }) } } @@ -72,7 +83,7 @@ export function relationResolver () { direction, type, typeID }, { lookupLoader }, info) => { - const targetType = dashify(info.fieldName).replace('-', '_') + const targetType = toEntityType(info.fieldName).replace('-', '_') return source.filter(relation => { if (relation['target-type'] !== targetType) { return false @@ -93,12 +104,12 @@ export function relationResolver () { export function linkedResolver () { return (source, args, { browseLoader }, info) => { - const pluralName = dashify(info.fieldName) + const pluralName = toEntityType(info.fieldName) let singularName = pluralName if (pluralName.endsWith('s')) { singularName = pluralName.slice(0, -1) } - const parentEntity = dashify(info.parentType.name) + const parentEntity = toEntityType(info.parentType.name) let params = { [parentEntity]: source.id, type: [], diff --git a/src/types/helpers.js b/src/types/helpers.js index b4c13c2..51f6ad4 100644 --- a/src/types/helpers.js +++ b/src/types/helpers.js @@ -7,7 +7,7 @@ import { GraphQLList, GraphQLNonNull } from 'graphql' -import { globalIdField } from 'graphql-relay' +import { globalIdField, forwardConnectionArgs } from 'graphql-relay' import { MBID } from './scalars' import { ReleaseGroupType, ReleaseStatus } from './enums' import ArtistCredit from './artist-credit' @@ -85,22 +85,22 @@ export function lookupQuery (entity, params) { } } -export function searchQuery (entityPage) { - const entity = entityPage.getFields().results.type.ofType.ofType +export function searchQuery (connectionType) { return { - type: entityPage, - description: `Search for ${entity.name} entities.`, + type: connectionType, args: { query: { type: new GraphQLNonNull(GraphQLString) }, - limit: { type: GraphQLInt }, - offset: { type: GraphQLInt } + ...forwardConnectionArgs }, resolve: searchResolver() } } export const id = globalIdField() -export const mbid = { type: new GraphQLNonNull(MBID) } +export const mbid = { + type: new GraphQLNonNull(MBID), + resolve: source => source.id +} export const name = { type: GraphQLString } export const sortName = { type: GraphQLString, resolve: getHyphenated } export const title = { type: GraphQLString } diff --git a/src/types/index.js b/src/types/index.js index 93a3460..b306b19 100644 --- a/src/types/index.js +++ b/src/types/index.js @@ -2,15 +2,15 @@ export { MBID, DateType, IPI, URLString } from './scalars' export { ReleaseGroupType, ReleaseStatus } from './enums' export { default as Node } from './node' export { default as Entity } from './entity' -export { default as Area, AreaPage } from './area' -export { default as Artist, ArtistPage } from './artist' -export { default as Event, EventPage } from './event' +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 Label, LabelPage } from './label' -export { default as Place, PlacePage } from './place' -export { default as Recording, RecordingPage } from './recording' -export { default as Release, ReleasePage } from './release' -export { default as ReleaseGroup, ReleaseGroupPage } from './release-group' -export { default as Series, SeriesPage } from './series' -export { default as URL, URLPage } from './url' -export { default as Work, WorkPage } from './work' +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 URL, URLConnection } from './url' +export { default as Work, WorkConnection } from './work' diff --git a/src/types/node.js b/src/types/node.js index c44b147..a14eaf7 100644 --- a/src/types/node.js +++ b/src/types/node.js @@ -10,8 +10,9 @@ const { nodeInterface, nodeField } = nodeDefinitions( }, (obj) => { try { - return require(`./${obj.entityType}`) + return require(`./${obj.entityType}`).default } catch (err) { + console.error(err) return null } }