Closed cshadek closed 1 year ago
You also need to define the connection type in the schema.
ConnectionType(User.self),
I'll add tests for connections and link here soon.
Thanks @paulofaria for getting back to me so quickly and I saw you have already started improving connections.
I tried a few things based on your suggestion.
When I add ConnectionType(User.self)
before the Type definition for User.self
I get this error: Cannot use type "User" for field "node". Type does not map to a GraphQL type.
If I add ConnectionType(User.self)
after the Type definition for User.self
I get this error: Cannot use type "Connection<User>" for field "followersConnection". Type does not map to a GraphQL type.
I have also tried using ConnectionType(TypeReference<User>.self)
but TypeReference<User>
is not encodable.
Any updates on this? I wasn't able to find any tests or example code that show how Connection
and ConnectionType
can be used, or am I missing something?
Hey, sorry! I'll add an example this weekend.
Here's an example of the schema definition.
import Foundation
import Entities
import Graphiti
public protocol GraphQLContext {
var app: GraphQLApp { get }
var accessToken: AccessToken? { get }
}
public struct GraphQLAPI: API {
public let resolver: GraphQLResolver
public let schema: Schema<GraphQLResolver, GraphQLContext>
public init(resolver: GraphQLResolver) throws {
self.resolver = resolver
self.schema = try Schema<GraphQLResolver, GraphQLContext> {
DateScalar(formatter: ISO8601DateFormatter())
Scalar(URL.self)
Scalar(UUID.self)
Type(AccessToken.self) {
Field("token", at: \.token)
}
Type(Channel.self) {
Field("id", at: \.id)
Field("name", at: \.name)
}
Type(Group.self) {
Field("id", at: \.id)
Field("name", at: \.name)
}
Type(Property.self) {
Field("id", at: \.id)
Field("name", at: \.name)
Field("value", at: \.value)
}
Type(Topic.self) {
Field("id", at: \.id)
Field("parentId", at: \.parentID)
Field("title", at: \.title)
Field("imageURL", at: \.imageURL)
Field("groups", at: \.groups )
Field("intensity", at: \.intensity)
Field("weight", at: \.weight)
}
Type(Keyword.self) {
Field("id", at: \.id)
Field("name", at: \.name)
Field("occurrence", at: \.occurrence)
}
Type(Feed.self) {
Field("id", at: \.id)
Field("type", at: \.type)
Field("url", at: \.url)
Field("displayName", at: \.displayName)
Field("topics", at: \.topics)
Field("channels", at: \.channels)
}
Type(FeedItem.self) {
Field("id", at: \.id)
Field("name", at: \.name)
Field("imageURL", at: \.imageURL)
Field("summary", at: \.summary)
Field("published", at: \.published)
Field("readTime", at: \.readTime)
Field("url", at: \.url)
Field("topics", at: \.topics)
Field("keywords", at: \.keywords)
Field("properties", at: \.properties)
Field("locale", at: \.locale)
Field("feed", at: \.feed)
Field("source", at: \.source)
Field("displaySource", at: \.displaySource)
}
ConnectionType(FeedItem.self)
Query {
Field("topics", at: GraphQLResolver.topics) {
Argument("groups", at: \.groups)
.defaultValue([])
}
Field("feeds", at: GraphQLResolver.feeds) {
Argument("channels", at: \.channels)
.defaultValue([])
}
Field("channels", at: GraphQLResolver.channels)
Field("properties", at: GraphQLResolver.properties)
Field("groups", at: GraphQLResolver.groups)
Field("feedItems", at: GraphQLResolver.feedItems) {
Argument("first", at: \.first)
Argument("after", at: \.after)
Argument("notIn", at: \.notIn)
.defaultValue([])
Argument("topics", at: \.topics)
.defaultValue([])
Argument("topicsNotIn", at: \.topicsNotIn)
.defaultValue([])
Argument("feedURLIn", at: \.feedURLIn)
.defaultValue([])
Argument("feedURLNotIn", at: \.feedURLNotIn)
.defaultValue([])
Argument("channels", at: \.channels)
.defaultValue([])
Argument("feeds", at: \.feeds)
.defaultValue([])
Argument("beforeDate", at: \.beforeDate)
Argument("afterDate", at: \.afterDate)
Argument("locales", at: \.locales)
.defaultValue([])
}
}
}
}
}
Here's an example of the resolver for the schema above:
import Entities
import NIO
import Graphiti
import Foundation
public struct GraphQLResolver {
public init() {}
}
// MARK: Topic Queries
extension GraphQLResolver {
struct TopicsArguments: Decodable {
let groups: [String]
}
func topics(context: GraphQLContext, arguments: TopicsArguments) -> EventLoopFuture<[Topic]> {
context.app.getTopics(groups: arguments.groups)
}
}
// MARK: Group Queries
extension GraphQLResolver {
func groups(context: GraphQLContext, arguments: NoArguments) -> EventLoopFuture<[Group]> {
context.app.getGroups()
}
}
// MARK: Channel Queries
extension GraphQLResolver {
func channels(context: GraphQLContext, arguments: NoArguments) -> EventLoopFuture<[Entities.Channel]> {
context.app.getChannels()
}
}
// MARK: Property Queries
extension GraphQLResolver {
func properties(context: GraphQLContext, arguments: NoArguments) -> EventLoopFuture<[Property]> {
context.app.getProperties()
}
}
// MARK: Feed Queries
extension GraphQLResolver {
struct FeedsArguments: Decodable {
let channels: [String]
}
func feeds(context: GraphQLContext, arguments: FeedsArguments) -> EventLoopFuture<[Feed]> {
context.app.getFeeds(channels: arguments.channels, accessToken: context.accessToken)
}
}
// MARK: Feed Item Queries
extension GraphQLResolver {
struct FeedItemsArguments: ForwardPaginatable {
let first: Int?
let after: String?
let notIn: [Int]
let topics: [String]
let topicsNotIn: [String]
let feedURLIn: [String]
let feedURLNotIn: [String]
let channels: [String]
let feeds: [Int]
let beforeDate: Date?
let afterDate: Date?
let locales: [String]
}
func feedItems(context: GraphQLContext, arguments: FeedItemsArguments) -> EventLoopFuture<Connection<FeedItem>> {
context.app
.getFeedItems(
first: arguments.first,
after: arguments.after.flatMap({ FeedItem.Cursor(base64EncodedString: $0) }),
notIn: arguments.notIn,
topics: arguments.topics,
topicsNotIn: arguments.topicsNotIn,
feedURLIn: arguments.feedURLIn,
feedURLNotIn: arguments.feedURLNotIn,
channels: arguments.channels,
feeds: arguments.feeds,
beforeDate: arguments.beforeDate,
afterDate: arguments.afterDate,
locales: arguments.locales
)
.connection(from: arguments, makeCursor: FeedItem.makeCursor)
}
}
In the resolver above we pass a makeCursor
function that creates a cursor for FeedItem
. If the type you want to create a connection for is type that conforms to Identifiable
, you don't need to provide this function. However, you might want to create a custom cursor to add any data you want.
Awesome, thanks!
@cshadek your issue is harder to solve. We would need to do a double pass when reading the schema. It is possible. However, I don't have much time, these days, to implement that. Feel free to send a PR working on this and I can help with pointers and such.
I believe this is no longer an issue after #84. Maybe this should be closed? We could add an example of connections to the Usage Guide.
Closing because #108 adds documentation to the Usage Guide to show how to use connections. Furthermore, other recent improvements that were introduced alongside PartialSchema
should solve the double pass issue.
I noticed that @paulofaria added connections in an earlier commit. I was trying to use them in my code but have not succeeded in getting them working.
Here is what I have been trying. What am I missing here?
Inside the Schema Definition:
In another file:
I get this error:
Thanks!