Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24080b6
feat!: Uses swift concurrency under the hood
NeedleInAJayStack Jun 23, 2025
d112be0
chore: Removes unnecessary @available
NeedleInAJayStack Jun 24, 2025
9ec313a
feat!: Removes EventStream, replacing with AsyncThrowingStream
NeedleInAJayStack Jun 24, 2025
9b4a6f2
feat!: Removes Instrumentation
NeedleInAJayStack Jun 27, 2025
1bc642c
test: Fixes race condition in test
NeedleInAJayStack Jun 27, 2025
d25254b
test: Moves test off of global dispatch queue
NeedleInAJayStack Jul 1, 2025
631650a
feat!: Switches SubscriptionResult with Result
NeedleInAJayStack Jul 5, 2025
34268d3
docs: Updates subscription docs in README
NeedleInAJayStack Jul 5, 2025
e1c66d9
fix: Avoids unstructured task
NeedleInAJayStack Jul 15, 2025
0d90d04
chore: swiftformat updates
NeedleInAJayStack Jul 15, 2025
2cbc8b9
feat!: Deletes deprecated Node.set func
NeedleInAJayStack Aug 1, 2025
a348434
feat: Makes FieldExecutionStrategy sendable
NeedleInAJayStack Aug 1, 2025
bc807e3
feat!: Enable strict concurrency
NeedleInAJayStack Aug 2, 2025
5c1e0be
feat!: Improves thread safety of unchecked Sendables
NeedleInAJayStack Aug 11, 2025
bf8b942
feat!: Hides execution strategies, applying spec specified ones
NeedleInAJayStack Aug 10, 2025
4f01de2
feat!: Uses specified rules by default
NeedleInAJayStack Aug 10, 2025
dfa0a60
feat!: Validates schema on execute
NeedleInAJayStack Aug 11, 2025
0522fb6
feat: Exposes validateSchema
NeedleInAJayStack Aug 11, 2025
4424f8e
test: Resolve test warnings
NeedleInAJayStack Aug 11, 2025
210fb72
chore: Fixes deprecation warnings
NeedleInAJayStack Aug 11, 2025
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
60 changes: 58 additions & 2 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
# 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

### Swift Concurrency checking

With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).

### `ExecutionStrategy` argument removals

The `queryStrategy`, `mutationStrategy`, and `subscriptionStrategy` arguments have been removed from `graphql` and `graphqlSubscribe`. Instead Queries and Subscriptions are executed in parallel and Mutations are executed serially, [as required by the spec](https://spec.graphql.org/October2021/#sec-Mutation).

### `validationRules` argument reorder

The `validationRules` argument has been moved from the beginning of `graphql` and `graphqlSubscribe` to the end to better reflect its relative importance:


```swift
// Before
let result = try await graphql(
validationRules: [ruleABC],
schema: schema,
...
)
// After
let result = try await graphql(
schema: schema,
...
validationRules: [ruleABC]
)
```

### 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 `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.

### 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.

### AST Node `set`

The deprecated `Node.set(value: Node?, key: String)` function was removed in preference of the `Node.set(value _: NodeResult?, key _: String)`. Change any calls from `node.set(value: node, key: string)` to `node.set(.node(node), string)`.

## 2 to 3

### TypeReference removal

Expand DownExpand Up@@ -73,4 +129,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.

6 changes: 3 additions & 3 deletions Package.swift
Original file line numberDiff line numberDiff 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"),
]
),
Comment thread
NeedleInAJayStack marked this conversation as resolved.
Expand All@@ -26,5 +25,6 @@ let package = Package(
.copy("LanguageTests/schema-kitchen-sink.graphql"),
]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)
37 changes: 14 additions & 23 deletions README.md
Original file line numberDiff line numberDiff 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
Comment thread
NeedleInAJayStack marked this conversation as resolved.
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
4 changes: 2 additions & 2 deletions Sources/GraphQL/Error/GraphQLError.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {

// MARK: IndexPath

public struct IndexPath: Codable {
public struct IndexPath: Codable, Sendable {
public let elements: [IndexPathValue]

public init(_ elements: [IndexPathElement] = []) {
Expand DownExpand Up@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
}
}

public enum IndexPathValue: Codable, Equatable {
public enum IndexPathValue: Codable, Equatable, Sendable {
case index(Int)
case key(String)

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24080b6
feat!: Uses swift concurrency under the hood
NeedleInAJayStack Jun 23, 2025
d112be0
chore: Removes unnecessary @available
NeedleInAJayStack Jun 24, 2025
9ec313a
feat!: Removes EventStream, replacing with AsyncThrowingStream
NeedleInAJayStack Jun 24, 2025
9b4a6f2
feat!: Removes Instrumentation
NeedleInAJayStack Jun 27, 2025
1bc642c
test: Fixes race condition in test
NeedleInAJayStack Jun 27, 2025
d25254b
test: Moves test off of global dispatch queue
NeedleInAJayStack Jul 1, 2025
631650a
feat!: Switches SubscriptionResult with Result
NeedleInAJayStack Jul 5, 2025
34268d3
docs: Updates subscription docs in README
NeedleInAJayStack Jul 5, 2025
e1c66d9
fix: Avoids unstructured task
NeedleInAJayStack Jul 15, 2025
0d90d04
chore: swiftformat updates
NeedleInAJayStack Jul 15, 2025
2cbc8b9
feat!: Deletes deprecated Node.set func
NeedleInAJayStack Aug 1, 2025
a348434
feat: Makes FieldExecutionStrategy sendable
NeedleInAJayStack Aug 1, 2025
bc807e3
feat!: Enable strict concurrency
NeedleInAJayStack Aug 2, 2025
5c1e0be
feat!: Improves thread safety of unchecked Sendables
NeedleInAJayStack Aug 11, 2025
bf8b942
feat!: Hides execution strategies, applying spec specified ones
NeedleInAJayStack Aug 10, 2025
4f01de2
feat!: Uses specified rules by default
NeedleInAJayStack Aug 10, 2025
dfa0a60
feat!: Validates schema on execute
NeedleInAJayStack Aug 11, 2025
0522fb6
feat: Exposes validateSchema
NeedleInAJayStack Aug 11, 2025
4424f8e
test: Resolve test warnings
NeedleInAJayStack Aug 11, 2025
210fb72
chore: Fixes deprecation warnings
NeedleInAJayStack Aug 11, 2025
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
60 changes: 58 additions & 2 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
# 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

### Swift Concurrency checking

With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).

### `ExecutionStrategy` argument removals

The `queryStrategy`, `mutationStrategy`, and `subscriptionStrategy` arguments have been removed from `graphql` and `graphqlSubscribe`. Instead Queries and Subscriptions are executed in parallel and Mutations are executed serially, [as required by the spec](https://spec.graphql.org/October2021/#sec-Mutation).

### `validationRules` argument reorder

The `validationRules` argument has been moved from the beginning of `graphql` and `graphqlSubscribe` to the end to better reflect its relative importance:


```swift
// Before
let result = try await graphql(
validationRules: [ruleABC],
schema: schema,
...
)
// After
let result = try await graphql(
schema: schema,
...
validationRules: [ruleABC]
)
```

### 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 `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.

### 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.

### AST Node `set`

The deprecated `Node.set(value: Node?, key: String)` function was removed in preference of the `Node.set(value _: NodeResult?, key _: String)`. Change any calls from `node.set(value: node, key: string)` to `node.set(.node(node), string)`.

## 2 to 3

### TypeReference removal

Expand DownExpand Up@@ -73,4 +129,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.

6 changes: 3 additions & 3 deletions Package.swift
Original file line numberDiff line numberDiff 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"),
]
),
Comment thread
NeedleInAJayStack marked this conversation as resolved.
Expand All@@ -26,5 +25,6 @@ let package = Package(
.copy("LanguageTests/schema-kitchen-sink.graphql"),
]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)
37 changes: 14 additions & 23 deletions README.md
Original file line numberDiff line numberDiff 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
Comment thread
NeedleInAJayStack marked this conversation as resolved.
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
4 changes: 2 additions & 2 deletions Sources/GraphQL/Error/GraphQLError.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {

// MARK: IndexPath

public struct IndexPath: Codable {
public struct IndexPath: Codable, Sendable {
public let elements: [IndexPathValue]

public init(_ elements: [IndexPathElement] = []) {
Expand DownExpand Up@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
}
}

public enum IndexPathValue: Codable, Equatable {
public enum IndexPathValue: Codable, Equatable, Sendable {
case index(Int)
case key(String)

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24080b6
feat!: Uses swift concurrency under the hood
NeedleInAJayStack Jun 23, 2025
d112be0
chore: Removes unnecessary @available
NeedleInAJayStack Jun 24, 2025
9ec313a
feat!: Removes EventStream, replacing with AsyncThrowingStream
NeedleInAJayStack Jun 24, 2025
9b4a6f2
feat!: Removes Instrumentation
NeedleInAJayStack Jun 27, 2025
1bc642c
test: Fixes race condition in test
NeedleInAJayStack Jun 27, 2025
d25254b
test: Moves test off of global dispatch queue
NeedleInAJayStack Jul 1, 2025
631650a
feat!: Switches SubscriptionResult with Result
NeedleInAJayStack Jul 5, 2025
34268d3
docs: Updates subscription docs in README
NeedleInAJayStack Jul 5, 2025
e1c66d9
fix: Avoids unstructured task
NeedleInAJayStack Jul 15, 2025
0d90d04
chore: swiftformat updates
NeedleInAJayStack Jul 15, 2025
2cbc8b9
feat!: Deletes deprecated Node.set func
NeedleInAJayStack Aug 1, 2025
a348434
feat: Makes FieldExecutionStrategy sendable
NeedleInAJayStack Aug 1, 2025
bc807e3
feat!: Enable strict concurrency
NeedleInAJayStack Aug 2, 2025
5c1e0be
feat!: Improves thread safety of unchecked Sendables
NeedleInAJayStack Aug 11, 2025
bf8b942
feat!: Hides execution strategies, applying spec specified ones
NeedleInAJayStack Aug 10, 2025
4f01de2
feat!: Uses specified rules by default
NeedleInAJayStack Aug 10, 2025
dfa0a60
feat!: Validates schema on execute
NeedleInAJayStack Aug 11, 2025
0522fb6
feat: Exposes validateSchema
NeedleInAJayStack Aug 11, 2025
4424f8e
test: Resolve test warnings
NeedleInAJayStack Aug 11, 2025
210fb72
chore: Fixes deprecation warnings
NeedleInAJayStack Aug 11, 2025
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
60 changes: 58 additions & 2 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
# 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

### Swift Concurrency checking

With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).

### `ExecutionStrategy` argument removals

The `queryStrategy`, `mutationStrategy`, and `subscriptionStrategy` arguments have been removed from `graphql` and `graphqlSubscribe`. Instead Queries and Subscriptions are executed in parallel and Mutations are executed serially, [as required by the spec](https://spec.graphql.org/October2021/#sec-Mutation).

### `validationRules` argument reorder

The `validationRules` argument has been moved from the beginning of `graphql` and `graphqlSubscribe` to the end to better reflect its relative importance:


```swift
// Before
let result = try await graphql(
validationRules: [ruleABC],
schema: schema,
...
)
// After
let result = try await graphql(
schema: schema,
...
validationRules: [ruleABC]
)
```

### 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 `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.

### 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.

### AST Node `set`

The deprecated `Node.set(value: Node?, key: String)` function was removed in preference of the `Node.set(value _: NodeResult?, key _: String)`. Change any calls from `node.set(value: node, key: string)` to `node.set(.node(node), string)`.

## 2 to 3

### TypeReference removal

Expand DownExpand Up@@ -73,4 +129,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.

6 changes: 3 additions & 3 deletions Package.swift
Original file line numberDiff line numberDiff 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"),
]
),
Comment thread
NeedleInAJayStack marked this conversation as resolved.
Expand All@@ -26,5 +25,6 @@ let package = Package(
.copy("LanguageTests/schema-kitchen-sink.graphql"),
]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)
37 changes: 14 additions & 23 deletions README.md
Original file line numberDiff line numberDiff 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
Comment thread
NeedleInAJayStack marked this conversation as resolved.
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
4 changes: 2 additions & 2 deletions Sources/GraphQL/Error/GraphQLError.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {

// MARK: IndexPath

public struct IndexPath: Codable {
public struct IndexPath: Codable, Sendable {
public let elements: [IndexPathValue]

public init(_ elements: [IndexPathElement] = []) {
Expand DownExpand Up@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
}
}

public enum IndexPathValue: Codable, Equatable {
public enum IndexPathValue: Codable, Equatable, Sendable {
case index(Int)
case key(String)

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24080b6
feat!: Uses swift concurrency under the hood
NeedleInAJayStack Jun 23, 2025
d112be0
chore: Removes unnecessary @available
NeedleInAJayStack Jun 24, 2025
9ec313a
feat!: Removes EventStream, replacing with AsyncThrowingStream
NeedleInAJayStack Jun 24, 2025
9b4a6f2
feat!: Removes Instrumentation
NeedleInAJayStack Jun 27, 2025
1bc642c
test: Fixes race condition in test
NeedleInAJayStack Jun 27, 2025
d25254b
test: Moves test off of global dispatch queue
NeedleInAJayStack Jul 1, 2025
631650a
feat!: Switches SubscriptionResult with Result
NeedleInAJayStack Jul 5, 2025
34268d3
docs: Updates subscription docs in README
NeedleInAJayStack Jul 5, 2025
e1c66d9
fix: Avoids unstructured task
NeedleInAJayStack Jul 15, 2025
0d90d04
chore: swiftformat updates
NeedleInAJayStack Jul 15, 2025
2cbc8b9
feat!: Deletes deprecated Node.set func
NeedleInAJayStack Aug 1, 2025
a348434
feat: Makes FieldExecutionStrategy sendable
NeedleInAJayStack Aug 1, 2025
bc807e3
feat!: Enable strict concurrency
NeedleInAJayStack Aug 2, 2025
5c1e0be
feat!: Improves thread safety of unchecked Sendables
NeedleInAJayStack Aug 11, 2025
bf8b942
feat!: Hides execution strategies, applying spec specified ones
NeedleInAJayStack Aug 10, 2025
4f01de2
feat!: Uses specified rules by default
NeedleInAJayStack Aug 10, 2025
dfa0a60
feat!: Validates schema on execute
NeedleInAJayStack Aug 11, 2025
0522fb6
feat: Exposes validateSchema
NeedleInAJayStack Aug 11, 2025
4424f8e
test: Resolve test warnings
NeedleInAJayStack Aug 11, 2025
210fb72
chore: Fixes deprecation warnings
NeedleInAJayStack Aug 11, 2025
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
60 changes: 58 additions & 2 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
# 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

### Swift Concurrency checking

With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).

### `ExecutionStrategy` argument removals

The `queryStrategy`, `mutationStrategy`, and `subscriptionStrategy` arguments have been removed from `graphql` and `graphqlSubscribe`. Instead Queries and Subscriptions are executed in parallel and Mutations are executed serially, [as required by the spec](https://spec.graphql.org/October2021/#sec-Mutation).

### `validationRules` argument reorder

The `validationRules` argument has been moved from the beginning of `graphql` and `graphqlSubscribe` to the end to better reflect its relative importance:


```swift
// Before
let result = try await graphql(
validationRules: [ruleABC],
schema: schema,
...
)
// After
let result = try await graphql(
schema: schema,
...
validationRules: [ruleABC]
)
```

### 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 `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.

### 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.

### AST Node `set`

The deprecated `Node.set(value: Node?, key: String)` function was removed in preference of the `Node.set(value _: NodeResult?, key _: String)`. Change any calls from `node.set(value: node, key: string)` to `node.set(.node(node), string)`.

## 2 to 3

### TypeReference removal

Expand DownExpand Up@@ -73,4 +129,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.

6 changes: 3 additions & 3 deletions Package.swift
Original file line numberDiff line numberDiff 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"),
]
),
Comment thread
NeedleInAJayStack marked this conversation as resolved.
Expand All@@ -26,5 +25,6 @@ let package = Package(
.copy("LanguageTests/schema-kitchen-sink.graphql"),
]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)
37 changes: 14 additions & 23 deletions README.md
Original file line numberDiff line numberDiff 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
Comment thread
NeedleInAJayStack marked this conversation as resolved.
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
4 changes: 2 additions & 2 deletions Sources/GraphQL/Error/GraphQLError.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {

// MARK: IndexPath

public struct IndexPath: Codable {
public struct IndexPath: Codable, Sendable {
public let elements: [IndexPathValue]

public init(_ elements: [IndexPathElement] = []) {
Expand DownExpand Up@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
}
}

public enum IndexPathValue: Codable, Equatable {
public enum IndexPathValue: Codable, Equatable, Sendable {
case index(Int)
case key(String)

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24080b6
feat!: Uses swift concurrency under the hood
NeedleInAJayStack Jun 23, 2025
d112be0
chore: Removes unnecessary @available
NeedleInAJayStack Jun 24, 2025
9ec313a
feat!: Removes EventStream, replacing with AsyncThrowingStream
NeedleInAJayStack Jun 24, 2025
9b4a6f2
feat!: Removes Instrumentation
NeedleInAJayStack Jun 27, 2025
1bc642c
test: Fixes race condition in test
NeedleInAJayStack Jun 27, 2025
d25254b
test: Moves test off of global dispatch queue
NeedleInAJayStack Jul 1, 2025
631650a
feat!: Switches SubscriptionResult with Result
NeedleInAJayStack Jul 5, 2025
34268d3
docs: Updates subscription docs in README
NeedleInAJayStack Jul 5, 2025
e1c66d9
fix: Avoids unstructured task
NeedleInAJayStack Jul 15, 2025
0d90d04
chore: swiftformat updates
NeedleInAJayStack Jul 15, 2025
2cbc8b9
feat!: Deletes deprecated Node.set func
NeedleInAJayStack Aug 1, 2025
a348434
feat: Makes FieldExecutionStrategy sendable
NeedleInAJayStack Aug 1, 2025
bc807e3
feat!: Enable strict concurrency
NeedleInAJayStack Aug 2, 2025
5c1e0be
feat!: Improves thread safety of unchecked Sendables
NeedleInAJayStack Aug 11, 2025
bf8b942
feat!: Hides execution strategies, applying spec specified ones
NeedleInAJayStack Aug 10, 2025
4f01de2
feat!: Uses specified rules by default
NeedleInAJayStack Aug 10, 2025
dfa0a60
feat!: Validates schema on execute
NeedleInAJayStack Aug 11, 2025
0522fb6
feat: Exposes validateSchema
NeedleInAJayStack Aug 11, 2025
4424f8e
test: Resolve test warnings
NeedleInAJayStack Aug 11, 2025
210fb72
chore: Fixes deprecation warnings
NeedleInAJayStack Aug 11, 2025
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
60 changes: 58 additions & 2 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
# 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

### Swift Concurrency checking

With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).

### `ExecutionStrategy` argument removals

The `queryStrategy`, `mutationStrategy`, and `subscriptionStrategy` arguments have been removed from `graphql` and `graphqlSubscribe`. Instead Queries and Subscriptions are executed in parallel and Mutations are executed serially, [as required by the spec](https://spec.graphql.org/October2021/#sec-Mutation).

### `validationRules` argument reorder

The `validationRules` argument has been moved from the beginning of `graphql` and `graphqlSubscribe` to the end to better reflect its relative importance:


```swift
// Before
let result = try await graphql(
validationRules: [ruleABC],
schema: schema,
...
)
// After
let result = try await graphql(
schema: schema,
...
validationRules: [ruleABC]
)
```

### 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 `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.

### 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.

### AST Node `set`

The deprecated `Node.set(value: Node?, key: String)` function was removed in preference of the `Node.set(value _: NodeResult?, key _: String)`. Change any calls from `node.set(value: node, key: string)` to `node.set(.node(node), string)`.

## 2 to 3

### TypeReference removal

Expand DownExpand Up@@ -73,4 +129,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.

6 changes: 3 additions & 3 deletions Package.swift
Original file line numberDiff line numberDiff 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"),
]
),
Comment thread
NeedleInAJayStack marked this conversation as resolved.
Expand All@@ -26,5 +25,6 @@ let package = Package(
.copy("LanguageTests/schema-kitchen-sink.graphql"),
]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)
37 changes: 14 additions & 23 deletions README.md
Original file line numberDiff line numberDiff 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
Comment thread
NeedleInAJayStack marked this conversation as resolved.
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
4 changes: 2 additions & 2 deletions Sources/GraphQL/Error/GraphQLError.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {

// MARK: IndexPath

public struct IndexPath: Codable {
public struct IndexPath: Codable, Sendable {
public let elements: [IndexPathValue]

public init(_ elements: [IndexPathElement] = []) {
Expand DownExpand Up@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
}
}

public enum IndexPathValue: Codable, Equatable {
public enum IndexPathValue: Codable, Equatable, Sendable {
case index(Int)
case key(String)

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24080b6
feat!: Uses swift concurrency under the hood
NeedleInAJayStack Jun 23, 2025
d112be0
chore: Removes unnecessary @available
NeedleInAJayStack Jun 24, 2025
9ec313a
feat!: Removes EventStream, replacing with AsyncThrowingStream
NeedleInAJayStack Jun 24, 2025
9b4a6f2
feat!: Removes Instrumentation
NeedleInAJayStack Jun 27, 2025
1bc642c
test: Fixes race condition in test
NeedleInAJayStack Jun 27, 2025
d25254b
test: Moves test off of global dispatch queue
NeedleInAJayStack Jul 1, 2025
631650a
feat!: Switches SubscriptionResult with Result
NeedleInAJayStack Jul 5, 2025
34268d3
docs: Updates subscription docs in README
NeedleInAJayStack Jul 5, 2025
e1c66d9
fix: Avoids unstructured task
NeedleInAJayStack Jul 15, 2025
0d90d04
chore: swiftformat updates
NeedleInAJayStack Jul 15, 2025
2cbc8b9
feat!: Deletes deprecated Node.set func
NeedleInAJayStack Aug 1, 2025
a348434
feat: Makes FieldExecutionStrategy sendable
NeedleInAJayStack Aug 1, 2025
bc807e3
feat!: Enable strict concurrency
NeedleInAJayStack Aug 2, 2025
5c1e0be
feat!: Improves thread safety of unchecked Sendables
NeedleInAJayStack Aug 11, 2025
bf8b942
feat!: Hides execution strategies, applying spec specified ones
NeedleInAJayStack Aug 10, 2025
4f01de2
feat!: Uses specified rules by default
NeedleInAJayStack Aug 10, 2025
dfa0a60
feat!: Validates schema on execute
NeedleInAJayStack Aug 11, 2025
0522fb6
feat: Exposes validateSchema
NeedleInAJayStack Aug 11, 2025
4424f8e
test: Resolve test warnings
NeedleInAJayStack Aug 11, 2025
210fb72
chore: Fixes deprecation warnings
NeedleInAJayStack Aug 11, 2025
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
60 changes: 58 additions & 2 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
# 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

### Swift Concurrency checking

With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).

### `ExecutionStrategy` argument removals

The `queryStrategy`, `mutationStrategy`, and `subscriptionStrategy` arguments have been removed from `graphql` and `graphqlSubscribe`. Instead Queries and Subscriptions are executed in parallel and Mutations are executed serially, [as required by the spec](https://spec.graphql.org/October2021/#sec-Mutation).

### `validationRules` argument reorder

The `validationRules` argument has been moved from the beginning of `graphql` and `graphqlSubscribe` to the end to better reflect its relative importance:


```swift
// Before
let result = try await graphql(
validationRules: [ruleABC],
schema: schema,
...
)
// After
let result = try await graphql(
schema: schema,
...
validationRules: [ruleABC]
)
```

### 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 `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.

### 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.

### AST Node `set`

The deprecated `Node.set(value: Node?, key: String)` function was removed in preference of the `Node.set(value _: NodeResult?, key _: String)`. Change any calls from `node.set(value: node, key: string)` to `node.set(.node(node), string)`.

## 2 to 3

### TypeReference removal

Expand DownExpand Up@@ -73,4 +129,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.

6 changes: 3 additions & 3 deletions Package.swift
Original file line numberDiff line numberDiff 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"),
]
),
Comment thread
NeedleInAJayStack marked this conversation as resolved.
Expand All@@ -26,5 +25,6 @@ let package = Package(
.copy("LanguageTests/schema-kitchen-sink.graphql"),
]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)
37 changes: 14 additions & 23 deletions README.md
Original file line numberDiff line numberDiff 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
Comment thread
NeedleInAJayStack marked this conversation as resolved.
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
4 changes: 2 additions & 2 deletions Sources/GraphQL/Error/GraphQLError.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {

// MARK: IndexPath

public struct IndexPath: Codable {
public struct IndexPath: Codable, Sendable {
public let elements: [IndexPathValue]

public init(_ elements: [IndexPathElement] = []) {
Expand DownExpand Up@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
}
}

public enum IndexPathValue: Codable, Equatable {
public enum IndexPathValue: Codable, Equatable, Sendable {
case index(Int)
case key(String)

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24080b6
feat!: Uses swift concurrency under the hood
NeedleInAJayStack Jun 23, 2025
d112be0
chore: Removes unnecessary @available
NeedleInAJayStack Jun 24, 2025
9ec313a
feat!: Removes EventStream, replacing with AsyncThrowingStream
NeedleInAJayStack Jun 24, 2025
9b4a6f2
feat!: Removes Instrumentation
NeedleInAJayStack Jun 27, 2025
1bc642c
test: Fixes race condition in test
NeedleInAJayStack Jun 27, 2025
d25254b
test: Moves test off of global dispatch queue
NeedleInAJayStack Jul 1, 2025
631650a
feat!: Switches SubscriptionResult with Result
NeedleInAJayStack Jul 5, 2025
34268d3
docs: Updates subscription docs in README
NeedleInAJayStack Jul 5, 2025
e1c66d9
fix: Avoids unstructured task
NeedleInAJayStack Jul 15, 2025
0d90d04
chore: swiftformat updates
NeedleInAJayStack Jul 15, 2025
2cbc8b9
feat!: Deletes deprecated Node.set func
NeedleInAJayStack Aug 1, 2025
a348434
feat: Makes FieldExecutionStrategy sendable
NeedleInAJayStack Aug 1, 2025
bc807e3
feat!: Enable strict concurrency
NeedleInAJayStack Aug 2, 2025
5c1e0be
feat!: Improves thread safety of unchecked Sendables
NeedleInAJayStack Aug 11, 2025
bf8b942
feat!: Hides execution strategies, applying spec specified ones
NeedleInAJayStack Aug 10, 2025
4f01de2
feat!: Uses specified rules by default
NeedleInAJayStack Aug 10, 2025
dfa0a60
feat!: Validates schema on execute
NeedleInAJayStack Aug 11, 2025
0522fb6
feat: Exposes validateSchema
NeedleInAJayStack Aug 11, 2025
4424f8e
test: Resolve test warnings
NeedleInAJayStack Aug 11, 2025
210fb72
chore: Fixes deprecation warnings
NeedleInAJayStack Aug 11, 2025
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
60 changes: 58 additions & 2 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
# 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

### Swift Concurrency checking

With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).

### `ExecutionStrategy` argument removals

The `queryStrategy`, `mutationStrategy`, and `subscriptionStrategy` arguments have been removed from `graphql` and `graphqlSubscribe`. Instead Queries and Subscriptions are executed in parallel and Mutations are executed serially, [as required by the spec](https://spec.graphql.org/October2021/#sec-Mutation).

### `validationRules` argument reorder

The `validationRules` argument has been moved from the beginning of `graphql` and `graphqlSubscribe` to the end to better reflect its relative importance:


```swift
// Before
let result = try await graphql(
validationRules: [ruleABC],
schema: schema,
...
)
// After
let result = try await graphql(
schema: schema,
...
validationRules: [ruleABC]
)
```

### 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 `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.

### 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.

### AST Node `set`

The deprecated `Node.set(value: Node?, key: String)` function was removed in preference of the `Node.set(value _: NodeResult?, key _: String)`. Change any calls from `node.set(value: node, key: string)` to `node.set(.node(node), string)`.

## 2 to 3

### TypeReference removal

Expand DownExpand Up@@ -73,4 +129,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.

6 changes: 3 additions & 3 deletions Package.swift
Original file line numberDiff line numberDiff 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"),
]
),
Comment thread
NeedleInAJayStack marked this conversation as resolved.
Expand All@@ -26,5 +25,6 @@ let package = Package(
.copy("LanguageTests/schema-kitchen-sink.graphql"),
]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)
37 changes: 14 additions & 23 deletions README.md
Original file line numberDiff line numberDiff 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
Comment thread
NeedleInAJayStack marked this conversation as resolved.
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
4 changes: 2 additions & 2 deletions Sources/GraphQL/Error/GraphQLError.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {

// MARK: IndexPath

public struct IndexPath: Codable {
public struct IndexPath: Codable, Sendable {
public let elements: [IndexPathValue]

public init(_ elements: [IndexPathElement] = []) {
Expand DownExpand Up@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
}
}

public enum IndexPathValue: Codable, Equatable {
public enum IndexPathValue: Codable, Equatable, Sendable {
case index(Int)
case key(String)

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24080b6
feat!: Uses swift concurrency under the hood
NeedleInAJayStack Jun 23, 2025
d112be0
chore: Removes unnecessary @available
NeedleInAJayStack Jun 24, 2025
9ec313a
feat!: Removes EventStream, replacing with AsyncThrowingStream
NeedleInAJayStack Jun 24, 2025
9b4a6f2
feat!: Removes Instrumentation
NeedleInAJayStack Jun 27, 2025
1bc642c
test: Fixes race condition in test
NeedleInAJayStack Jun 27, 2025
d25254b
test: Moves test off of global dispatch queue
NeedleInAJayStack Jul 1, 2025
631650a
feat!: Switches SubscriptionResult with Result
NeedleInAJayStack Jul 5, 2025
34268d3
docs: Updates subscription docs in README
NeedleInAJayStack Jul 5, 2025
e1c66d9
fix: Avoids unstructured task
NeedleInAJayStack Jul 15, 2025
0d90d04
chore: swiftformat updates
NeedleInAJayStack Jul 15, 2025
2cbc8b9
feat!: Deletes deprecated Node.set func
NeedleInAJayStack Aug 1, 2025
a348434
feat: Makes FieldExecutionStrategy sendable
NeedleInAJayStack Aug 1, 2025
bc807e3
feat!: Enable strict concurrency
NeedleInAJayStack Aug 2, 2025
5c1e0be
feat!: Improves thread safety of unchecked Sendables
NeedleInAJayStack Aug 11, 2025
bf8b942
feat!: Hides execution strategies, applying spec specified ones
NeedleInAJayStack Aug 10, 2025
4f01de2
feat!: Uses specified rules by default
NeedleInAJayStack Aug 10, 2025
dfa0a60
feat!: Validates schema on execute
NeedleInAJayStack Aug 11, 2025
0522fb6
feat: Exposes validateSchema
NeedleInAJayStack Aug 11, 2025
4424f8e
test: Resolve test warnings
NeedleInAJayStack Aug 11, 2025
210fb72
chore: Fixes deprecation warnings
NeedleInAJayStack Aug 11, 2025
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
60 changes: 58 additions & 2 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
# 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

### Swift Concurrency checking

With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).

### `ExecutionStrategy` argument removals

The `queryStrategy`, `mutationStrategy`, and `subscriptionStrategy` arguments have been removed from `graphql` and `graphqlSubscribe`. Instead Queries and Subscriptions are executed in parallel and Mutations are executed serially, [as required by the spec](https://spec.graphql.org/October2021/#sec-Mutation).

### `validationRules` argument reorder

The `validationRules` argument has been moved from the beginning of `graphql` and `graphqlSubscribe` to the end to better reflect its relative importance:


```swift
// Before
let result = try await graphql(
validationRules: [ruleABC],
schema: schema,
...
)
// After
let result = try await graphql(
schema: schema,
...
validationRules: [ruleABC]
)
```

### 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 `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.

### 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.

### AST Node `set`

The deprecated `Node.set(value: Node?, key: String)` function was removed in preference of the `Node.set(value _: NodeResult?, key _: String)`. Change any calls from `node.set(value: node, key: string)` to `node.set(.node(node), string)`.

## 2 to 3

### TypeReference removal

Expand DownExpand Up@@ -73,4 +129,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.

6 changes: 3 additions & 3 deletions Package.swift
Original file line numberDiff line numberDiff 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"),
]
),
Comment thread
NeedleInAJayStack marked this conversation as resolved.
Expand All@@ -26,5 +25,6 @@ let package = Package(
.copy("LanguageTests/schema-kitchen-sink.graphql"),
]
),
]
],
swiftLanguageVersions: [.v5, .version("6")]
)
37 changes: 14 additions & 23 deletions README.md
Original file line numberDiff line numberDiff 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
Comment thread
NeedleInAJayStack marked this conversation as resolved.
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
4 changes: 2 additions & 2 deletions Sources/GraphQL/Error/GraphQLError.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {

// MARK: IndexPath

public struct IndexPath: Codable {
public struct IndexPath: Codable, Sendable {
public let elements: [IndexPathValue]

public init(_ elements: [IndexPathElement] = []) {
Expand DownExpand Up@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
}
}

public enum IndexPathValue: Codable, Equatable {
public enum IndexPathValue: Codable, Equatable, Sendable {
case index(Int)
case key(String)

Expand Down
Loading
Loading