Skip to content

BREAKING: Converts to Swift Concurrency #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
# Migration

## 2.0 to 3.0
## 3 to 4

### NIO removal

All NIO-based arguments and return types were removed, including all `EventLoopGroup` and `EventLoopFuture` parameters.

As such, all `execute` and `subscribe` calls should have the `eventLoopGroup` argument removed, and the `await` keyword should be used.

Also, all resolver closures must remove the `eventLoopGroup` argument, and all that return an `EventLoopFuture` should be converted to an `async` function.

The documentation here will be very helpful in the conversion: https://www.swift.org/documentation/server/guides/libraries/concurrency-adoption-guidelines.html

### `ConcurrentDispatchFieldExecutionStrategy`

This was changed to `ConcurrentFieldExecutionStrategy`, and takes no parameters.

### EventStream removal

The `EventStream` abstraction used to provide pre-concurrency subscription support has been removed. This means that `graphqlSubscribe(...).stream` will now be an `AsyncThrowingStream<GraphQLResult, Error>` type, instead of an `EventStream` type, and that downcasting to `ConcurrentEventStream` is no longer necessary.

### SubscriptionResult removal

The `SubscriptionResult` type was removed, and `graphqlSubscribe` now returns a true Swift `Result` type.

### Instrumentation removal

The `Instrumentation` type has been removed, with anticipated support for tracing using [`swift-distributed-tracing`](https://github.com/apple/swift-distributed-tracing). `instrumentation` arguments must be removed from `graphql` and `graphqlSubscribe` calls.

## 2 to 3

### TypeReference removal

Expand Down Expand Up @@ -73,4 +101,4 @@ The following type properties were changed from arrays to closures. To get the a

### GraphQL type codability

With GraphQL type definitions now including closures, many of the objects in [Definition](https://github.com/GraphQLSwift/GraphQL/blob/main/Sources/GraphQL/Type/Definition.swift) are no longer codable. If you are depending on codability, you can conform the type appropriately in your downstream package.
With GraphQL type definitions now including closures, many of the objects in [Definition](https://github.com/GraphQLSwift/GraphQL/blob/main/Sources/GraphQL/Type/Definition.swift) are no longer codable. If you are depending on codability, you can conform the type appropriately in your downstream package.
27 changes: 0 additions & 27 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ import PackageDescription

let package = Package(
name: "GraphQL",
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)],
products: [
.library(name: "GraphQL", targets: ["GraphQL"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.10.1")),
.package(url: "https://github.com/apple/swift-collections", .upToNextMajor(from: "1.0.0")),
],
targets: [
.target(
name: "GraphQL",
dependencies: [
.product(name: "NIO", package: "swift-nio"),
.product(name: "OrderedCollections", package: "swift-collections"),
]
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe enable strict concurrency as well

    swiftLanguageVersions: [.v5, .version("6")]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NeedleInAJayStack have you tried enabling strict concurrency?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has a quick look at this. The fact that you throw Any around a lot is going to make it very difficult to enable strict concurrency

Expand Down
37 changes: 14 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ Once a schema has been defined queries may be executed against it using the glob
```swift
let result = try await graphql(
schema: schema,
request: "{ hello }",
eventLoopGroup: eventLoopGroup
request: "{ hello }"
)
```

Expand All @@ -58,33 +57,29 @@ The result of this query is a `GraphQLResult` that encodes to the following JSON

### Subscription

This package supports GraphQL subscription, but until the integration of `AsyncSequence` in Swift 5.5 the standard Swift library did not
provide an event-stream construct. For historical reasons and backwards compatibility, this library implements subscriptions using an
`EventStream` protocol that nearly every asynchronous stream implementation can conform to.

To create a subscription field in a GraphQL schema, use the `subscribe` resolver that returns an `EventStream`. You must also provide a
`resolver`, which defines how to process each event as it occurs and must return the field result type. Here is an example:
This package supports GraphQL subscription. To create a subscription field in a GraphQL schema, use the `subscribe`
resolver that returns any type that conforms to `AsyncSequence`. You must also provide a `resolver`, which defines how
to process each event as it occurs and must return the field result type. Here is an example:

```swift
let schema = try GraphQLSchema(
subscribe: GraphQLObjectType(
name: "Subscribe",
fields: [
"hello": GraphQLField(
"hello": GraphQLField(
type: GraphQLString,
resolve: { eventResult, _, _, _, _ in // Defines how to transform each event when it occurs
resolve: { eventResult, _, _, _ in // Defines how to transform each event when it occurs
return eventResult
},
subscribe: { _, _, _, _, _ in // Defines how to construct the event stream
let asyncStream = AsyncThrowingStream<String, Error> { continuation in
subscribe: { _, _, _, _ in // Defines how to construct the event stream
return AsyncThrowingStream<String, Error> { continuation in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that we only support AsyncThrowingStream streams or do we also support any AsyncThrowingSequence?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question! No, this just happens to be the example - within the subscription resolver, we support any AsyncSequence (see here for the subscription type), but the return type of a subscribe() call will always return an AsyncThrowingStream<GraphQLResult, Error>.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! I spent some time looking for that and couldn’t find it, haha. That was my main concern. Please update the README example making that point clear. Other than that, I’m happy with the PR. 🙂

Actually.. I have a follow up question, haha. Why AsyncSequence and not AsyncThrowingSequence?

Copy link
Member Author

@NeedleInAJayStack NeedleInAJayStack Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the README example making that point clear

Good idea. I've done that in this commit

Why AsyncSequence and not AsyncThrowingSequence?

This is because AsyncSequence is the top-level protocol that all AsyncXSequence and AsyncXStream types conform to (including AsyncThrowingStream, and so on).

let timer = Timer.scheduledTimer(
withTimeInterval: 3,
repeats: true,
) {
continuation.yield("world") // Emits "world" every 3 seconds
continuation.yield("world") // Emits "world" every 3 seconds
}
}
return ConcurrentEventStream<String>(asyncStream)
}
)
]
Expand All @@ -98,9 +93,8 @@ To execute a subscription use the `graphqlSubscribe` function:
let subscriptionResult = try await graphqlSubscribe(
schema: schema,
)
// Must downcast from EventStream to concrete type to use in 'for await' loop below
let concurrentStream = subscriptionResult.stream! as! ConcurrentEventStream
for try await result in concurrentStream.stream {
let stream = subscriptionResult.get()
for try await result in stream {
print(result)
}
```
Expand All @@ -111,18 +105,15 @@ The code above will print the following JSON every 3 seconds:
{ "hello": "world" }
```

The example above assumes that your environment has access to Swift Concurrency. If that is not the case, try using
[GraphQLRxSwift](https://github.com/GraphQLSwift/GraphQLRxSwift)

## Encoding Results

If you encode a `GraphQLResult` with an ordinary `JSONEncoder`, there are no guarantees that the field order will match the query,
If you encode a `GraphQLResult` with an ordinary `JSONEncoder`, there are no guarantees that the field order will match the query,
violating the [GraphQL spec](https://spec.graphql.org/June2018/#sec-Serialized-Map-Ordering). To preserve this order, `GraphQLResult`
should be encoded using the `GraphQLJSONEncoder` provided by this package.

## Support

This package supports Swift versions in [alignment with Swift NIO](https://github.com/apple/swift-nio?tab=readme-ov-file#swift-versions).
This package aims to support the previous three Swift versions.

For details on upgrading to new major versions, see [MIGRATION](MIGRATION.md).

Expand All @@ -140,7 +131,7 @@ To format your code, install `swiftformat` and run:

```bash
swiftformat .
```
```

Most of this repo mirrors the structure of
(the canonical GraphQL implementation written in Javascript/Typescript)[https://github.com/graphql/graphql-js]. If there is any feature
Expand Down
Loading