Skip to content

Commit 51a5981

Browse files
Initial Commit
0 parents  commit 51a5981

File tree

8 files changed

+1477
-0
lines changed

8 files changed

+1477
-0
lines changed

GraphQLRxSwift/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2021 Jay Herron
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

GraphQLRxSwift/Package.resolved

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"object": {
3+
"pins": [
4+
{
5+
"package": "CRuntime",
6+
"repositoryURL": "https://github.com/wickwirew/CRuntime.git",
7+
"state": {
8+
"branch": null,
9+
"revision": "95f911318d8c885f6fc05e971471f94adfd39405",
10+
"version": "2.1.2"
11+
}
12+
},
13+
{
14+
"package": "GraphQL",
15+
"repositoryURL": "https://github.com/GraphQLSwift/GraphQL.git",
16+
"state": {
17+
"branch": null,
18+
"revision": "21104b805ad7de5a223b0d78e0cf5d9d49465ac4",
19+
"version": "1.1.8"
20+
}
21+
},
22+
{
23+
"package": "Runtime",
24+
"repositoryURL": "https://github.com/wickwirew/Runtime.git",
25+
"state": {
26+
"branch": null,
27+
"revision": "61c9776f47d2951bdc562486ad348e5e1ca2e180",
28+
"version": "2.1.1"
29+
}
30+
},
31+
{
32+
"package": "RxSwift",
33+
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
34+
"state": {
35+
"branch": null,
36+
"revision": "7e01c05f25c025143073eaa3be3532f9375c614b",
37+
"version": "6.1.0"
38+
}
39+
},
40+
{
41+
"package": "swift-nio",
42+
"repositoryURL": "https://github.com/apple/swift-nio.git",
43+
"state": {
44+
"branch": null,
45+
"revision": "6d3ca7e54e06a69d0f2612c2ce8bb8b7319085a4",
46+
"version": "2.26.0"
47+
}
48+
}
49+
]
50+
},
51+
"version": 1
52+
}

GraphQLRxSwift/Package.swift

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// swift-tools-version:5.3
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "GraphQLRxSwift",
6+
products: [
7+
.library(name: "GraphQLRxSwift", targets: ["GraphQLRxSwift"]),
8+
],
9+
dependencies: [
10+
.package(url: "https://github.com/GraphQLSwift/GraphQL.git", .upToNextMajor(from: "1.1.7")),
11+
.package(url: "https://github.com/GraphQLSwift/Graphiti.git", .upToNextMinor(from: "0.22.0")),
12+
.package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.1.0"))
13+
],
14+
targets: [
15+
.target(name: "GraphQLRxSwift", dependencies: ["GraphQL", "Graphiti", "RxSwift"]),
16+
.testTarget(name: "GraphQLRxSwiftTests",dependencies: ["GraphQLRxSwift"]),
17+
]
18+
)

GraphQLRxSwift/README.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# GraphQLRxSwift
2+
3+
GraphQLRxSwift is a small Swift GraphQL/Graphiti subscription driver that allows usage of [RxSwift](https://github.com/ReactiveX/RxSwift) observables
4+
as subscription event streams.
5+
6+
[![Swift][swift-badge]][swift-url]
7+
[![License][mit-badge]][mit-url]
8+
9+
Looking for help? Find resources [from the community](http://graphql.org/community/).
10+
11+
12+
## Getting Started
13+
14+
Before using, it is best to familiarize yourself with the Swift [GraphQL](https://github.com/GraphQLSwift/GraphQL) and
15+
[Graphiti](https://github.com/GraphQLSwift/Graphiti) packages.
16+
17+
## Usage
18+
19+
Add GraphQLRxSwift to your `Package.swift`
20+
21+
```swift
22+
import PackageDescription
23+
24+
let package = Package(
25+
dependencies: [
26+
.Package(url: "https://github.com/GraphQLSwift/Graphiti.git", .upToNextMinor(from: "0.20.1")),
27+
]
28+
)
29+
```
30+
31+
GraphQLRxSwift provides an `ObservableEventStream` wrapper class that can be used with an RxSwift `Observable` to satisfy
32+
`EventStream` requirements. A convenience method `toEventStream` is added to all Observable instances.
33+
This class is used when defining subscription resolvers, as shown below:
34+
35+
```
36+
func subscribeUser(context: HelloContext, arguments: NoArguments) -> EventStream<Any> {
37+
PublishSubject<Any>().toEventStream()
38+
}
39+
```
40+
41+
Also provided is a `ObservableSourceEventStream` type alias that can be used to downcast the result of a subscribe execution.
42+
It is guaranteed that the stream returned by any subscription query whose resolver returns an `ObservableEventStream` will be a
43+
`ObservableSourceEventStream`. For example:
44+
45+
```
46+
let subscriptionResult = try graphQLApi.subscribe(
47+
request: request,
48+
context: api.context,
49+
on: group
50+
).wait()
51+
let subscription = subscriptionResult.stream as! ObservableSubscriptionEventStream? // Guaranteed
52+
```
53+
54+
## License
55+
56+
This project is released under the MIT license. See [LICENSE](LICENSE) for details.
57+
58+
[swift-badge]: https://img.shields.io/badge/Swift-5.2-orange.svg?style=flat
59+
[swift-url]: https://swift.org
60+
61+
[mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat
62+
[mit-url]: https://tldrlegal.com/license/mit-license
63+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import GraphQL
2+
import RxSwift
3+
4+
// EventStream wrapper for Observable
5+
public class ObservableEventStream<Element> : EventStream<Element> {
6+
public var observable: Observable<Element>
7+
init(_ observable: Observable<Element>) {
8+
self.observable = observable
9+
}
10+
override open func map<To>(_ closure: @escaping (Element) throws -> To) -> EventStream<To> {
11+
return ObservableEventStream<To>(observable.map(closure))
12+
}
13+
}
14+
// Convenience types
15+
public typealias ObservableSubscriptionEventStream = ObservableEventStream<Future<GraphQLResult>>
16+
17+
extension Observable {
18+
// Convenience method for wrapping Observables in EventStreams
19+
public func toEventStream() -> ObservableEventStream<Element> {
20+
return ObservableEventStream(self)
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import GraphQL
2+
import GraphQLRxSwift
3+
import NIO
4+
import RxSwift
5+
6+
// MARK: Types
7+
struct Email : Encodable {
8+
let from:String
9+
let subject:String
10+
let message:String
11+
let unread:Bool
12+
let priority:Int
13+
14+
init(from:String, subject:String, message:String, unread:Bool, priority:Int = 0) {
15+
self.from = from
16+
self.subject = subject
17+
self.message = message
18+
self.unread = unread
19+
self.priority = priority
20+
}
21+
}
22+
23+
struct Inbox : Encodable {
24+
let emails:[Email]
25+
}
26+
27+
struct EmailEvent : Encodable {
28+
let email:Email
29+
let inbox:Inbox
30+
}
31+
32+
// MARK: Schema
33+
let EmailType = try! GraphQLObjectType(
34+
name: "Email",
35+
fields: [
36+
"from": GraphQLField(
37+
type: GraphQLString
38+
),
39+
"subject": GraphQLField(
40+
type: GraphQLString
41+
),
42+
"message": GraphQLField(
43+
type: GraphQLString
44+
),
45+
"unread": GraphQLField(
46+
type: GraphQLBoolean
47+
),
48+
]
49+
)
50+
let InboxType = try! GraphQLObjectType(
51+
name: "Inbox",
52+
fields: [
53+
"emails": GraphQLField(
54+
type: GraphQLList(EmailType)
55+
),
56+
"total": GraphQLField(
57+
type: GraphQLInt,
58+
resolve: { inbox, _, _, _ in
59+
(inbox as! Inbox).emails.count
60+
}
61+
),
62+
"unread": GraphQLField(
63+
type: GraphQLInt,
64+
resolve: { inbox, _, _, _ in
65+
(inbox as! Inbox).emails.filter({$0.unread}).count
66+
}
67+
),
68+
]
69+
)
70+
let EmailEventType = try! GraphQLObjectType(
71+
name: "EmailEvent",
72+
fields: [
73+
"email": GraphQLField(
74+
type: EmailType
75+
),
76+
"inbox": GraphQLField(
77+
type: InboxType
78+
)
79+
]
80+
)
81+
let EmailQueryType = try! GraphQLObjectType(
82+
name: "Query",
83+
fields: [
84+
"inbox": GraphQLField(
85+
type: InboxType
86+
)
87+
]
88+
)
89+
90+
// MARK: Test Helpers
91+
92+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
93+
94+
class EmailDb {
95+
var emails: [Email]
96+
let publisher: PublishSubject<Any>
97+
let disposeBag: DisposeBag
98+
99+
init() {
100+
emails = [
101+
Email(
102+
from: "joe@graphql.org",
103+
subject: "Hello",
104+
message: "Hello World",
105+
unread: false
106+
)
107+
]
108+
publisher = PublishSubject<Any>()
109+
disposeBag = DisposeBag()
110+
}
111+
112+
/// Adds a new email to the database and triggers all observers
113+
func trigger(email:Email) {
114+
emails.append(email)
115+
publisher.onNext(email)
116+
}
117+
118+
/// Returns the default email schema, with standard resolvers.
119+
func defaultSchema() -> GraphQLSchema {
120+
return emailSchemaWithResolvers(
121+
resolve: {emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture<Any?> in
122+
if let email = emailAny as? Email {
123+
return eventLoopGroup.next().makeSucceededFuture(EmailEvent(
124+
email: email,
125+
inbox: Inbox(emails: self.emails)
126+
))
127+
} else {
128+
throw GraphQLError(message: "\(type(of:emailAny)) is not Email")
129+
}
130+
},
131+
subscribe: {_, args, _, eventLoopGroup, _ throws -> EventLoopFuture<Any?> in
132+
let priority = args["priority"].int ?? 0
133+
let filtered = self.publisher.filter { emailAny throws in
134+
if let email = emailAny as? Email {
135+
return email.priority >= priority
136+
} else {
137+
return true
138+
}
139+
}
140+
return eventLoopGroup.next().makeSucceededFuture(filtered.toEventStream())
141+
}
142+
)
143+
}
144+
145+
/// Generates a subscription to the database using the default schema and resolvers
146+
func subscription (
147+
query:String,
148+
variableValues: [String: Map] = [:]
149+
) throws -> SubscriptionEventStream {
150+
return try createSubscription(schema: defaultSchema(), query: query, variableValues: variableValues)
151+
}
152+
}
153+
154+
/// Generates an email schema with the specified resolve and subscribe methods
155+
func emailSchemaWithResolvers(resolve: GraphQLFieldResolve? = nil, subscribe: GraphQLFieldResolve? = nil) -> GraphQLSchema {
156+
return try! GraphQLSchema(
157+
query: EmailQueryType,
158+
subscription: try! GraphQLObjectType(
159+
name: "Subscription",
160+
fields: [
161+
"importantEmail": GraphQLField(
162+
type: EmailEventType,
163+
args: [
164+
"priority": GraphQLArgument(
165+
type: GraphQLInt
166+
)
167+
],
168+
resolve: resolve,
169+
subscribe: subscribe
170+
)
171+
]
172+
)
173+
)
174+
}
175+
176+
/// Generates a subscription from the given schema and query. It's expected that the resolver/database interactions are configured by the caller.
177+
func createSubscription(
178+
schema: GraphQLSchema,
179+
query: String,
180+
variableValues: [String: Map] = [:]
181+
) throws -> SubscriptionEventStream {
182+
let result = try graphqlSubscribe(
183+
queryStrategy: SerialFieldExecutionStrategy(),
184+
mutationStrategy: SerialFieldExecutionStrategy(),
185+
subscriptionStrategy: SerialFieldExecutionStrategy(),
186+
instrumentation: NoOpInstrumentation,
187+
schema: schema,
188+
request: query,
189+
rootValue: Void(),
190+
context: Void(),
191+
eventLoopGroup: eventLoopGroup,
192+
variableValues: variableValues,
193+
operationName: nil
194+
).wait()
195+
196+
if let stream = result.stream {
197+
return stream
198+
} else {
199+
throw result.errors.first! // We may have more than one...
200+
}
201+
}

0 commit comments

Comments
 (0)