Giter Club home page Giter Club logo

graphql's Introduction

GraphQL

Swift License GitHub Actions Codebeat

The Swift implementation for GraphQL, a query language for APIs created by Facebook.

Looking for help? Find resources from the community.

Usage

Schema Definition

The GraphQLSchema object can be used to define GraphQL Schemas and Types. These schemas are made up of types, fields, arguments, and resolver functions. Below is an example:

let schema = try GraphQLSchema(
    query: GraphQLObjectType(                   // Defines the special "query" type
        name: "Query",
        fields: [
            "hello": GraphQLField(              // Users may query 'hello'
                type: GraphQLString,            // The result is a string type
                resolve: { _, _, _, _ in
                    "world"                     // The result of querying 'hello' is "world"
                }
            )
        ]
    )
)

For more complex schema examples see the test files.

This repo only contains the core GraphQL implementation and does not focus on the ease of schema creation. For a better experience when creating your GraphQL schema use Graphiti.

Execution

Once a schema has been defined queries may be executed against it using the global graphql function:

let result = try await graphql(
    schema: schema,
    request: "{ hello }",
    eventLoopGroup: eventLoopGroup
)

The result of this query is a GraphQLResult that encodes to the following JSON:

{ "hello": "world" }

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:

let schema = try GraphQLSchema(
    subscribe: GraphQLObjectType(
        name: "Subscribe",
        fields: [
            "hello": GraphQLField(              
                type: GraphQLString,
                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
                        let timer = Timer.scheduledTimer(
                            withTimeInterval: 3,
                            repeats: true,
                        ) {
                            continuation.yield("world")     // Emits "world" every 3 seconds
                        }
                    }
                    return ConcurrentEventStream<String>(asyncStream)
                }
            )
        ]
    )
)

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 {
    print(result)
}

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

Encoding Results

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. To preserve this order, GraphQLResult should be encoded using the GraphQLJSONEncoder provided by this package.

Contributing

If you think you have found a security vulnerability, please follow the Security guidelines.

Those contributing to this package are expected to follow the Swift Code of Conduct, the Swift API Design Guidelines, and the SSWG Technical Best Practices.

This repo uses SwiftFormat, and includes lint checks to enforce these formatting standards. To format your code, install swiftformat and run:

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 missing, looking at the original code and "translating" it to Swift works, most of the time. For example:

Swift

/Sources/GraphQL/Language/AST.swift

Javascript/Typescript

/src/language/ast.js

License

This project is released under the MIT license. See LICENSE for details.

graphql's People

Contributors

5t111111 avatar adam-fowler avatar alexsteinerde avatar bradlarson avatar d-exclaimation avatar eliotfowler avatar haikusw avatar igor-palaguta avatar jbttn avatar jseibert avatar kimdv avatar lgaches avatar needleinajaystack avatar nicholaslythallyf avatar paulofaria avatar randop avatar samisuteria avatar sevki avatar sportlabsmike avatar williambailey avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql's Issues

SDL support

Question

Support executable schema based on schema definition language.

Compilation warnings with Swift 3.0.2

Compiling .Package(url: "https://github.com/GraphQLSwift/GraphQL.git", majorVersion: 0) on Mac.

swift --version
Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)
Target: x86_64-apple-macosx10.9

Calling swift build is able to build binary, but produces these warnings:

Compile Swift Module 'GraphQL' (62 sources)
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:759:32: warning: will never be executed
                        string += ","
                               ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:756:24: note: condition always evaluates to true
                    if _isDebugAssertConfiguration() {
                       ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:768:31: warning: will never be executed
                return string + "]"
                              ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:764:16: note: condition always evaluates to true
            if _isDebugAssertConfiguration() {
               ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:786:28: warning: will never be executed
                    string += escape(key) + ":" + serialize(map: value)
                           ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:781:20: note: condition always evaluates to true
                if _isDebugAssertConfiguration() {
                   ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:793:32: warning: will never be executed
                        string += ","
                               ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:790:24: note: condition always evaluates to true
                    if _isDebugAssertConfiguration() {
                       ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:804:31: warning: will never be executed
                return string + "}"
                              ^
/Users/maximveksler/Project/StreamstagerOrg/Collection/Packages/GraphQL-0.2.8/Sources/GraphQL/Map/Map.swift:800:16: note: condition always evaluates to true
            if _isDebugAssertConfiguration() {
               ^

Schema File

It would be great to have the ability to provide a schema file to the server and start it with the file content.
Or is this already possible just not listed in the readme?
Thanks
Alex

False positive with no unused variable rule

Variables contained within objects are not detected by the "no unused variable" rule:

query Foo($a: String) {
  field(object: { a: $a })
}

"Variable "$a" is never used in operation "Foo"."

GraphQL parser does not seem to have support for the '&' character

This is discussed in the graphql specification here.

I noticed when using the GitHub API to test the changes I made for blockstring and descriptions. This API uses these quite a bit. Here's a little test I wrote to show the issue:

import XCTest
import GraphQL

final class graphQLTests: XCTestCase {
  func testParseAmpersand() throws {
    let source = #"""
    """
    Represents a Git blob.
    """
    type Blob implements Node & GitObject {
      """
      An abbreviated version of the Git object ID
      """
      abbreviatedOid: String!

      """
      Byte size of Blob object
      """
      byteSize: Int!

      # other fields omitted
    }
    """#
    let doc = try parse(instrumentation: NoOpInstrumentation, source: Source(body: source))
    for def in doc.definitions {
    print( def.kind )
    }
  }
}

Outputs the error:

/Users/.../graphqltoolTests.swift:46: error: -[testParseAmpersand] : failed: caught error: "Syntax Error GraphQL (4:27) Unexpected character &.

 3: """
 4: type Blob implements Node & GitObject {
                              ^
 5:   """
"

I will likely try to add support for this, but I don't think I'll have time this week.

JSON result

Hi,

I am having trouble converting returned Map object to JSON. There seems to be no method to simply convert the result to simple dictionary convertible to JSON or any other obvious way.
Or at least simple string valid as HTTP response body.

Custom Schema Directives?

Does this library support putting custom directives into your schema?

I think the answer is no, but I wanted to make sure.

There are some directives that come standard with most GraphQL implementations, like skip, include and deprecated. But there are also other custom ones for implementations like Apollo Federation that aren't part of the GraphQL spec, but you may want to use for the schema you are defining on your server.

Async Resolving

Async resolving would be the corner stone to use Swift GraphQL as an aggregation layer. We would need to introduce some async task handling.

Create queries/mutations from GraphQL schema

Hello Everyone ๐Ÿ‘‹

I would like to use a Graphite for the server endpoints but also use the same schema object to create client requests, would you mind pointing me in the right direction on how to do this or what I need to contribute to enable this?

Thank you for creating this library

Export schema to JSON

It is nice feature in apollo-ios to import JSON schema. So it would be great to add schema export to JSON.

GraphQL parser does not support optional `Description`s.

Simple test - uncomment out either comment line to watch it fail with a syntax error.

	func testParseDescription() throws {
		let sourceText = """
			# "Foo description text"
			type Foo {
			  # "foo field description text"
			  fooType: Int
			}
			"""

		let doc = try parse(instrumentation: NoOpInstrumentation, source: Source(body: sourceText))
		for def in doc.definitions {
			print( def.kind )
		}
	}

For this first one (object StypeSystemDefinition" the error appears to be on line 158 where it expects (requires) to find the name instead of checking for the optional description string.

Presumably the others are similar.

The list of nodes that can have a description according to the GraphQL Spec (working draft) seems to be:

via parseTypeSystemDefinition:

  • SchemaDefinition
  • ScalarTypeDefintion
  • ObjectTypeDefinition
  • InterfaceTypeDefinition
  • UnionTypeDefinition
  • EnumTypeDefinition
  • InputObjectTypeDefinition
  • DirectiveDefinition

others:

  • EnumValueDefinition
  • FieldDefinition
  • InputValueDefinition

I have a PR adding support for this that I'll post shortly.

Question regarding the public API

I was wondering if you had any objection to exposing more to the outside world? For example most of the classes and structs in Language and Type could become public along with their let properties with hopefully little impact.

The more I play with this project the more I find that I need to set things as public so that it's available for use outside.

Top level fields in mutations should execute in SERIES not parallel

The GraphQL spec specifies that the top level fields in mutations should execute serially.

http://spec.graphql.org/June2018/#sec-Mutation

When I run a Mutation like the following using Graphiti, I get race conditions and unexpected results. The only explanation I have for this is that they are running in parallel. Am I missing something? Thanks!

mutation Test {
    addLike(input: {id: 1}) {
       ....
    }
    removeLike(input: {id: 1} ) {
       ....
    }
    addLike2: addLike(input: {id: 1} ) {
       ....
    }
}

Missing GraphQLFieldConfig in Definition.swift

As the title says I can't find the GraphQLFieldConfig in the definitions file.

export type GraphQLFieldConfig<
  TSource,
  TContext,
  TArgs = { [argument: string]: any },
> = {|
  type: GraphQLOutputType,
  args?: GraphQLFieldConfigArgumentMap,
  resolve?: GraphQLFieldResolver<TSource, TContext, TArgs>,
  subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs>,
  deprecationReason?: ?string,
  description?: ?string,
  astNode?: ?FieldDefinitionNode,
|};

I'm currently looking into build a SwiftRelay for pagination, and I ran into this type and can't fint it.

Introspection error with latest CLI

Version 0.13.0 of Apollo iOS was just released, and it includes an update to the CLI from 1.9 to 2.16. Unfortunately, there's a breaking change around @directives.

When I run the code gen build script, I get a CLIError: Error in "Loading schema for Unnamed Project": Error: Introspection result missing directive locations: { onField: true, onOperation: false, args: [[Object]], name: "include", description: "Directs the executor to include this field or fragment only when the ifargument is true.", onFragment: true }

I haven't delved into it too much, but the error message was added in graphql/graphql-js#1429, in response to dotansimha/graphql-code-generator#556 (comment).

(I opened a ticket on the Apollo GitHub as well: apollographql/apollo-ios#665.)

Any thoughts?

MapInitializable initialiser problems with new Reflection library dependency.

Hi,

Since the change to https://github.com/wickwirew/Runtime.git for reflection there's a problem with the default MapInitializable initialiser.

In the call self = try createInstance(), the new reflection library tries to eagerly create an instance and fill in all the properties with 'default' values. However it doesn't support enums, so in the case where the type contains an Enum type property it blows up.

The old implementation of the MapInitializable initialiser didn't suffer form this since the construct reflection method it used took a (Property.Description) throws -> Any parameter that was used to supply all the values as it build the instance (thus it never attempted to create default ones itself)

This means any InputTypes (or Arguments when using Graphiti) that contain enum properties are now broken.

Any ideas for a solution much appreciated...

0 default value for argument is interpreted as false and can not be used

We have in application argument with default value 0 "offset": GraphQLArgument(type: GraphQLInt, defaultValue: 0),

If this argument is not passed, then instead of integer 0 boolean false is used. So arguments["offset"].int returns nil

In the screenshot you can see that Any 0 is casted to Bool and is interpreted as false

It happens as try JSONSerialization.jsonObject(with: data, options: .allowFragments) returns NSNumber, and 0 is interpreted as false (https://developer.apple.com/documentation/foundation/nsnumber/1410865-boolvalue)

Screenshot 2020-10-09 at 01 04 55

GraphQL 1.1.7
Xcode Version 12.0 (12A7209)

GraphQL doesn't support "Blockstrings" which are often used in Descriptions.

The GraphQL specification includes specification of "Blockstrings" of the form:

"""
Hello
This is a multiline block string
"""

and these are commonly used in the optional Description supported by a number of graphql objects.

I have a PR adding Blockstring support that I'll be posting shortly but I wanted an issue to attach it to.

Error "Must be leaf type" in listValue

Describe the bug
Getting error "must be leaf type" in list value for input

Query:
query listQueryByRange($fromDT: String!, $toDT: String!) {
listValueQuery(startAt: {range: [$fromDT, $toDT]}}) {

}}

Declaration in Graphiti:
InputField("startAt", at: \.startAt, as: TypeReference<SearchableDateFilterInput>.self)

extension SearchableDateFilterInput:Codable  {

    enum CodingKeys: String, CodingKey {
        case range
    }
    public func encode(to encoder: Encoder) throws {
        try container.encode(range, forKey: .range)
    }

    public init(from decoder: Decoder) throws {
        self.init(range: nil)
        range = try values.decodeIfPresent([String].self, forKey: .range)
        
    }
}

To Reproduce
Steps to reproduce the behavior:

  1. Apply below query
    query listQueryByRange($fromDT: String!, $toDT: String!) {
    listValueQuery(startAt: {range: [$fromDT, $toDT]}}) {

}}

Expected behavior
Should execute the query and must not throw the error "must be leaf type"

Disabling introspection e.g. in production

Please correct me if I'm wrong. But as I understand correctly, introspection is currently always enabled and cannot be disabled.
If that's true I would like to create a PR for this.
But maybe I'm missing something.

Unable to install via SPM

Hi, I'm not able to install v0.11.0 of this package through SPM (guessing the package name from the Package.swift file from this repo) within a Vapor 3.0.0 application. Here's a sample of my Package.swift:

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "myApp",
    products: [
        .library(name: "myApp", targets: ["App"]),
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
        .package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0"),
        .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.6.0"),
        .package(url: "https://github.com/GraphQLSwift/GraphQL.git", from: "0.11.0")
    ],
    targets: [
        .target(name: "App", dependencies: ["FluentSQLite", "Vapor", "SwiftProtobuf", "GraphQL"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)

When I try to swift package update my computer goes into overdrive, and hits 99% CPU usage until a force quit the process. I've tried this multiple times with the same result. If I remove the GraphQL dependency, everything installs as expected.

Is there a way to validate a document with custom validation rules?

Hey, I am maintaining a GraphQL server library that uses this package. I am planning to allow the user of my library to add additional custom validation rules which will be used to validate incoming requests before they are executed. My library is heavily inspired by apollo-server and looking at their approach, they are using the graphql-js validate function here.

I figured this package should also have the same function, but the only validate function that is public doesn't accept any custom rules.

I found out that the primary entry point function already perform validation but it also doesn't accept custom validation rules.

let validationErrors = validate(instrumentation: instrumentation, schema: schema, ast: documentAST)

It seemed like this library already have the function and struct needed for custom validation, but they are just not public. I am not sure if this is a intentional decision.

Preserve result order

7.2.2 Serialized Map Ordering

Since the result of evaluating a selection set is ordered, the serialized Map of results should preserve this order by writing the map entries in the same order as those fields were requested as defined by query execution. Producing a serialized response where fields are represented in the same order in which they appear in the request improves human readability during debugging and enables more efficient parsing of responses if the order of properties can be anticipated.

For example, if the request was { name, age }, a GraphQL service responding in JSON should respond with { "name": "Mark", "age": 30 } and should not respond with { "age": 30, "name": "Mark" }.

Upgrade Swift-NIO dependency to 2.0.0+

The newer version is updated to Swift 5 and includes some API changes. We should upgrade our dependency version to the latest possible.

I'm going to start taking this on and will comment here with any issues I come across.

GraphQL doesn't compile on Linux Swift 5.4

Using Dockerfile

FROM swift:5.4 as build

WORKDIR /build
COPY ./Package.* ./
RUN swift package resolve

# Copy entire repo into container
COPY . .

RUN swift test --enable-test-discovery

I get these errors

#8 8.305 /build/Sources/GraphQL/Map/AnyCoder.swift:2058:22: error: '_cfTypeID' is inaccessible due to 'internal' protection level
#8 8.305         guard number._cfTypeID == CFBooleanGetTypeID() else {
#8 8.305                      ^~~~~~~~~
#8 8.305 Foundation.NSNumber (internal):45:27: note: '_cfTypeID' declared here
#8 8.305     override internal var _cfTypeID: CFTypeID { get }
#8 8.305                           ^
#8 8.305 [148/184] Compiling GraphQL Parser.swift
#8 8.305 /build/Sources/GraphQL/Map/AnyCoder.swift:2058:22: error: '_cfTypeID' is inaccessible due to 'internal' protection level
#8 8.305         guard number._cfTypeID == CFBooleanGetTypeID() else {
#8 8.305                      ^~~~~~~~~
#8 8.305 Foundation.NSNumber (internal):45:27: note: '_cfTypeID' declared here
#8 8.305     override internal var _cfTypeID: CFTypeID { get }
#8 8.305                           ^
#8 8.305 [149/184] Compiling GraphQL Source.swift
#8 8.305 /build/Sources/GraphQL/Map/AnyCoder.swift:2058:22: error: '_cfTypeID' is inaccessible due to 'internal' protection level
#8 8.305         guard number._cfTypeID == CFBooleanGetTypeID() else {
#8 8.305                      ^~~~~~~~~
#8 8.305 Foundation.NSNumber (internal):45:27: note: '_cfTypeID' declared here
#8 8.305     override internal var _cfTypeID: CFTypeID { get }
#8 8.305                           ^
#8 8.305 [150/184] Compiling GraphQL Visitor.swift
#8 8.305 /build/Sources/GraphQL/Map/AnyCoder.swift:2058:22: error: '_cfTypeID' is inaccessible due to 'internal' protection level
...

Is this library single-threaded only?

It looks like all the tests in this package use MultiThreadedEventLoopGroup(numberOfThreads: 1). However, if the number of threads is changed to > 1, then a number of the tests become unstable and will occasionally cause errors or deadlocks. The Graphiti tests use MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) and I'm finding the same instability there.

Specifically, I found that the following tests become unstable:

  • FieldExecutionStrategyTests
    • testSerialFieldExecutionStrategyWithMultipleFields (deadlock)
    • testSerialFieldExecutionStrategyWithMultipleFieldErrors (deadlock)
    • testConcurrentDispatchFieldExecutionStrategyWithMultipleFields (NIO+Extensions.swift, ln 42: EXC_BAD_ACCESS)
    • testConcurrentDispatchFieldExecutionStrategyWithMultipleFieldErrors (deadlock) (NIO+Extensions.swift, ln 42: EXC_BAD_ACCESS)
  • StarWarsIntrospectionTests
    • testIntrospectionDroidKindQuery (deadlock)
    • testIntrospectionCharacterKindQuery (deadlock)
  • StarWarsQueryTests
    • testDuplicateFieldsQuery (deadlock)
    • testUseFragmentQuery (deadlock)
    • testCheckTypeOfLukeQuery (deadlock)
    • testFetchLukeAndLeiaAliasedQuery (deadlock)

Is it expected that the input EventLoopGroup should always only have one thread?

kCFBoolean casting failed in Swift 5.1

I got errors when swift test on ubuntu 18.04 with official Swift 5.1 docker image.
It seem like they are all related to kCFBoolean.

There are plenty of similar errors, I just paste a few of them.

/NextBookServer/.build/checkouts/GraphQL/Sources/GraphQL/Map/AnyCoder.swift:2066:27: error: cannot convert value of type 'CFBoolean?' to type 'NSNumber' in coercion
            if number === kCFBooleanTrue as NSNumber {
                          ^~~~~~~~~~~~~~
/NextBookServer/.build/checkouts/GraphQL/Sources/GraphQL/Map/AnyCoder.swift:2068:34: error: cannot convert value of type 'CFBoolean?' to type 'NSNumber' in coercion
            } else if number === kCFBooleanFalse as NSNumber {

Crash. Validation not properly works with fragments and then it crashes

Added one more type

struct Planet {
    let name: String
    let distance: Int
}

let PlanetType = try! GraphQLObjectType(
    name: "Planet",
    description: "A humanoid creature in the Star Wars universe.",
    fields: [
        "name": GraphQLField(
            type: GraphQLNonNull(GraphQLString),
            description: "The planet name."
        ),
        "distance": GraphQLField(
            type: GraphQLNonNull(GraphQLInt),
            description: "Distance to planet."
        )
    ]
)

Validation test passes, but really should fail. As planet fields are not related to hero

    func testNestedQueryWithFragment() throws {
        let query = "query NestedQueryWithFragment {" +
                    "    hero {" +
                    "        ...PlanetFields" +
                    "    }" +
                    "}" +
                    "fragment PlanetFields on Planet {" +
                    "    name distance" +
                    "}"

        XCTAssert(try validationErrors(query: query).isEmpty)
    }

And during execution such query it crashes here:

return parentType.fields[fieldName]!

    // we know this field exists because we passed validation before execution
    return parentType.fields[fieldName]!

How to use the api with vapor 3

I'm trying to get this to work with vapor 3's async api, but I cannot figure out how to do this without the resolver accepting a Future instead of the map (synchronously). I couldn't even hack it through wait() because it's not allowed for EventLoopFutures.

resolve: { src, arguments, context, info in
  guard let connectable = context as? DatabaseConnectable else { throw MissingContext() }
  return try MessageController().getById(1, on: connectable) //.wait()
}

AsyncThrowingStream.mapStream produces leaks and loses error during transformation

The new addition ConcurrentEventStream utilizes the .mapStream to transform elements of an AsyncThrowingStream. However, the method .mapStream produces 3 issues in 3 different scenarios.

Transformed stream never ended

After transforming the base stream using .mapStream, the transformed stream never ended even if the base stream has ended.

// Base stream emits values and end
let base = AsyncThrowingStream(Int.self) { con in
    let task = Task {
        for i in 1...5 {
            con.yield(i)
        }
        con.finish()
    }

    con.onTermination = { _ in
        task.cancel()
    }
}

// Transform using the `.mapStream`
let transform = base0.mapStream { "Received from 0: \($0)" }


let task = Task {
    for try await _ in transform {
    }
    print("Done!!")
}
  • When: base ended with continuation.finish()
  • Expected: transform ended and "Done!!" is printed
  • Output: "Done!!" is not printed and transform never ended

Leaks

After transforming the base stream using .mapStream, the base stream is not closed/cancelled/disposed of when the transformed stream is cancelled. Therefore, the base stream will keep emitting values even after the transformed one is no longer consumed.

// Base stream should emit value every second
let base0 = AsyncThrowingStream(Int.self) { con in
    let task = Task {
        for i in 1...5 {
            print("Sending for 0: \(i)")
            con.yield(i)
            try await Task.sleep(nanoseconds: 1000_000_000)
        }
        con.finish()
    }

    con.onTermination = { _ in
        task.cancel()
    }
}

// Transform using the `.mapStream`
let transform0 = base0.mapStream { "Received from 0: \($0)" }


let task0 = Task {
    for try await msg in transform0 {
        print(msg)
    }
}

// Cancel the transformed stream after 1 second (before the base stream ended)
Thread.sleep(forTimeInterval: 1)
task0.cancel()
  • When: transform0 is cancelled but base0 still can emit more values
  • Expected: base0 is cancelled and stops emitting any more values (preventing leaks)
  • Output: base0 keeps emitting values

Error lost in transformation

After transforming the base stream using .mapStream and if the base stream finish with an error, that error is not sent to the transformed stream and is lost in the process.

struct CustomError: Error {}

// Base stream will throw an error when finished
let base1 = AsyncThrowingStream(Int.self) { con in
    let task = Task {
        for i in 1...5 {
            print("Sending for 1: \(i)")
            con.yield(i)
        }
        print("Sending for 1: CustomError")
        con.finish(throwing: CustomError())
    }

    con.onTermination = { _ in
        task.cancel()
    }
}


let transform1 = base1.mapStream { "Received from 1: \($0)" } 

// Should catch error thrown by base stream
Task {
    do {
        for try await msg in transform1 {
            print(msg)
        }
        return
    } catch {
    }
    print("Done!!")
}
  • When: When base1 ended with an error
  • Expected:, transform1 should do the same, and an error should be caught ("Done!!" is printed)
  • Output: transform1 does not end, the error is not caught

Solution

#102

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.