├── Sources
└── TinyGraphQL
│ ├── OperationType.swift
│ ├── QueryOperation.swift
│ ├── Builder.swift
│ ├── MutationOperation.swift
│ ├── Extensions
│ └── Encodable+Arguments.swift
│ ├── Mutation.swift
│ ├── Query.swift
│ ├── ArgumentListRepresentable.swift
│ ├── ArgumentRepresentable.swift
│ ├── FieldRepresentable.swift
│ ├── Operation.swift
│ ├── GraphQL.swift
│ └── Field.swift
├── Tests
├── LinuxMain.swift
└── TinyGraphQLTests
│ ├── XCTestManifests.swift
│ └── TinyGraphQLTests.swift
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .gitignore
├── .github
└── workflows
│ └── test.yml
├── LICENSE
├── Package.swift
├── TinyGraphQL.podspec
└── README.md
/Sources/TinyGraphQL/OperationType.swift:
--------------------------------------------------------------------------------
1 | public enum OperationType: String {
2 | case query
3 | case mutation
4 | }
5 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import TinyGraphQLTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += TinyGraphQLTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/QueryOperation.swift:
--------------------------------------------------------------------------------
1 | public protocol QueryOperation: Operation {
2 |
3 | }
4 |
5 | extension QueryOperation {
6 | public var type: OperationType { .query }
7 | }
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tests/TinyGraphQLTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(TinyGraphQLTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/Builder.swift:
--------------------------------------------------------------------------------
1 | @_functionBuilder
2 | public final class Builder {
3 | public typealias Closure = () -> [T]
4 | public static func buildBlock(_ children: T...) -> [T] {
5 | children
6 | }
7 | }
8 |
9 | public typealias FieldBuilder = Builder
10 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/MutationOperation.swift:
--------------------------------------------------------------------------------
1 | protocol MutationOperation: Operation {
2 |
3 | }
4 |
5 | extension MutationOperation {
6 | public var type: OperationType { .mutation }
7 |
8 | public var description: String {
9 | return "mutation { \(parseBody()) }"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/Extensions/Encodable+Arguments.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Encodable {
4 | var arguments: [String: Encodable & ArgumentRepresentable]? {
5 | guard let data = try? JSONEncoder().encode(self) else { return nil }
6 | return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap {
7 | $0 as? [String: Any]
8 | }?.compactMapValues { $0 as? String }.mapValues { $0 as Encodable & ArgumentRepresentable }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/Mutation.swift:
--------------------------------------------------------------------------------
1 | public struct Mutation: MutationOperation {
2 | public typealias Arguments = [String: ArgumentRepresentable]
3 |
4 | public var name: String
5 | public var arguments: Arguments
6 | public var fields: [Field]
7 |
8 | public init(
9 | _ name: String,
10 | _ arguments: Arguments,
11 | @FieldBuilder fields: Builder.Closure
12 | ) {
13 | self.name = name
14 | self.arguments = arguments
15 | self.fields = fields().map { $0.fieldRepresentation }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/Query.swift:
--------------------------------------------------------------------------------
1 | public struct Query: QueryOperation {
2 | public typealias Arguments = [String: ArgumentRepresentable]
3 |
4 | public let name: String
5 | public let arguments: Arguments
6 | public let fields: [Field]
7 |
8 | public init(
9 | _ name: String,
10 | _ arguments: Arguments,
11 | @Builder fields: Builder.Closure
12 | ) {
13 | self.name = name
14 | self.arguments = arguments
15 | self.fields = fields().map { $0.fieldRepresentation }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/ArgumentListRepresentable.swift:
--------------------------------------------------------------------------------
1 | public protocol ArgumentListRepresentable {
2 | var argumentListRepresentation: [String: ArgumentRepresentable] { get }
3 | }
4 |
5 | extension Dictionary: ArgumentListRepresentable where Key == String, Value == ArgumentRepresentable {
6 | public var argumentListRepresentation: [String: ArgumentRepresentable] {
7 | return self
8 | }
9 | }
10 |
11 | extension Encodable where Self: ArgumentListRepresentable {
12 | public var argumentListRepresentation: [String: ArgumentRepresentable] {
13 | return self.arguments ?? [:]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/ArgumentRepresentable.swift:
--------------------------------------------------------------------------------
1 | public protocol ArgumentRepresentable {
2 | var argumentRepresentation: String { get }
3 | }
4 |
5 | extension String: ArgumentRepresentable {
6 | public var argumentRepresentation: String {
7 | return "\"\(self)\""
8 | }
9 | }
10 |
11 | extension Dictionary: ArgumentRepresentable where Key == String, Value == String {
12 | public var argumentRepresentation: String {
13 | var result = "{"
14 | result += self.map { "\($0.key): \"\($0.value)\"" }.joined(separator: ", ")
15 | result += "}"
16 | return result
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/FieldRepresentable.swift:
--------------------------------------------------------------------------------
1 | public protocol FieldRepresentable: CustomStringConvertible {
2 | var fieldRepresentation: Field { get }
3 | }
4 |
5 | extension FieldRepresentable {
6 | var description: String {
7 | return fieldRepresentation.description
8 | }
9 |
10 | public var field: FieldRepresentable {
11 | return self
12 | }
13 | }
14 |
15 | extension Field: FieldRepresentable {
16 | public var fieldRepresentation: Field {
17 | return self
18 | }
19 | }
20 |
21 | extension String: FieldRepresentable {
22 | public var fieldRepresentation: Field {
23 | return Field(self)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
7 | # OS X
8 | .DS_Store
9 |
10 | # Xcode
11 | build/
12 | *.pbxuser
13 | !default.pbxuser
14 | *.mode1v3
15 | !default.mode1v3
16 | *.mode2v3
17 | !default.mode2v3
18 | *.perspectivev3
19 | !default.perspectivev3
20 | xcuserdata/
21 | *.xccheckout
22 | profile
23 | *.moved-aside
24 | DerivedData
25 | *.hmap
26 | *.ipa
27 |
28 | # Bundler
29 | .bundle
30 |
31 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
32 | # Carthage/Checkouts
33 |
34 | Carthage/Build
35 |
36 | # We recommend against adding the Pods directory to your .gitignore. However
37 | # you should judge for yourself, the pros and cons are mentioned at:
38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
39 | #
40 | # Note: if you ignore the Pods directory, make sure to uncomment
41 | # `pod install` in .travis.yml
42 | #
43 | # Pods/
44 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | push:
4 | branches: [ main ]
5 | pull_request:
6 | branches: [ main ]
7 | jobs:
8 | linux:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | image:
14 | - swift:5.3-xenial
15 | container: ${{ matrix.image }}
16 | env:
17 | LOG_LEVEL: info
18 | steps:
19 | - name: Checkout TinyGraphQL
20 | uses: actions/checkout@v2
21 | - name: Run base tests with Thread Sanitizer
22 | run: swift test --enable-test-discovery --sanitize=thread
23 |
24 | macOS_iOS:
25 | env:
26 | LOG_LEVEL: info
27 | runs-on: macos-latest
28 | steps:
29 | - name: Checkout
30 | uses: actions/checkout@v2
31 | - name: Build and Test (iOS)
32 | run: xcodebuild -scheme TinyGraphQL test -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 12'
33 | - name: Build and Test (macOS)
34 | run: swift test
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 cardoso
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "TinyGraphQL",
8 | products: [
9 | // Products define the executables and libraries a package produces, and make them visible to other packages.
10 | .library(
11 | name: "TinyGraphQL",
12 | targets: ["TinyGraphQL"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
21 | .target(
22 | name: "TinyGraphQL",
23 | dependencies: []),
24 | .testTarget(
25 | name: "TinyGraphQLTests",
26 | dependencies: ["TinyGraphQL"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/Operation.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol Operation: CustomStringConvertible {
4 | associatedtype Arguments: ArgumentListRepresentable
5 |
6 | var type: OperationType { get }
7 | var name: String { get }
8 | var arguments: Arguments { get }
9 | var fields: [Field] { get }
10 |
11 | var description: String { get }
12 | }
13 |
14 | extension Operation {
15 | public var description: String {
16 | parseBody()
17 | }
18 |
19 | func parseBody() -> String {
20 | var result = name
21 |
22 | let arguments = self.arguments.argumentListRepresentation
23 |
24 | if !arguments.isEmpty {
25 | result += "("
26 | result += arguments.map { "\($0.key): \($0.value.argumentRepresentation)" }.joined(separator: ", ")
27 | result += ")"
28 | }
29 |
30 | let fields = self.fields.map { $0.field }
31 |
32 | if !fields.isEmpty {
33 | result += "{"
34 | result += fields.map { $0.description }.joined(separator: ", ")
35 | result += "}"
36 | }
37 |
38 | return result
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/GraphQL.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if canImport(FoundationNetworking)
3 | import FoundationNetworking
4 | #endif
5 |
6 | public struct GraphQL {
7 | public var url: URL
8 | public var headers: [String: String]
9 |
10 | public init(url: URL, headers: [String: String] = [:]) {
11 | self.url = url
12 | self.headers = headers
13 | }
14 |
15 | public func request(for operation: O) -> URLRequest {
16 | switch operation.type {
17 | case .query:
18 | let query = "?query={\(operation)}".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
19 | let url = URL(string: "\(self.url.absoluteString)\(query)")!
20 | var urlRequest = URLRequest(url: url)
21 | urlRequest.allHTTPHeaderFields = headers
22 | return urlRequest
23 | case .mutation:
24 | var urlRequest = URLRequest(url: url)
25 | urlRequest.httpMethod = "POST"
26 | urlRequest.httpBody = """
27 | {
28 | "query": "\(operation.description.replacingOccurrences(of: "\"", with: "\\\""))"
29 | }
30 | """.data(using: .utf8)
31 | urlRequest.allHTTPHeaderFields = headers
32 | return urlRequest
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/TinyGraphQL/Field.swift:
--------------------------------------------------------------------------------
1 | public struct Field {
2 | public var name: String
3 | public var arguments: [String: ArgumentRepresentable]
4 | public var fields: [Field]
5 |
6 | public var description: String {
7 | var result = name
8 |
9 | if !arguments.isEmpty {
10 | result += "("
11 | result += arguments.map { "\($0.key): \"\($0.value)\"" }.joined(separator: ", ")
12 | result += ")"
13 | }
14 |
15 | if !fields.isEmpty {
16 | result += "{"
17 | result += fields.map { $0.description }.joined(separator: ", ")
18 | result += "}"
19 | }
20 |
21 | return result
22 | }
23 |
24 | public init(
25 | _ name: String,
26 | _ arguments: [String: ArgumentRepresentable] = [:],
27 | @FieldBuilder _ fields: FieldBuilder.Closure = { [] }
28 | ) {
29 | self.name = name
30 | self.arguments = arguments
31 | self.fields = fields().map { $0.fieldRepresentation }
32 | }
33 |
34 | init(
35 | name: String,
36 | arguments: [String: ArgumentRepresentable] = [:],
37 | fields: [Field]
38 | ) {
39 | self.name = name
40 | self.arguments = arguments
41 | self.fields = fields
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TinyGraphQL.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint TinyGraphQL.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'TinyGraphQL'
11 | s.version = '1.0.0'
12 | s.summary = 'A lightweight and easy-to-use GraphQL client for Swift'
13 |
14 | s.swift_version = '5.2'
15 |
16 | # This description is used to generate tags and improve search results.
17 | # * Think: What does it do? Why did you write it? What is the focus?
18 | # * Try to keep it short, snappy and to the point.
19 | # * Write the description between the DESC delimiters below.
20 | # * Finally, don't worry about the indent, CocoaPods strips it!
21 |
22 | s.description = <<-DESC
23 | A lightweight and easy-to-use GraphQL client for Swift
24 | - with 💘 from @GetStream
25 | DESC
26 |
27 | s.homepage = 'https://github.com/GetStream/TinyGraphQL'
28 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
29 | s.license = { :type => 'MIT', :file => 'LICENSE' }
30 | s.author = { 'cardoso' => 'matheus@getstream.io' }
31 | s.source = { :git => 'https://github.com/GetStream/TinyGraphQL.git', :tag => s.version.to_s }
32 | s.social_media_url = 'https://twitter.com/getstream_io'
33 |
34 | s.ios.deployment_target = '11.0'
35 |
36 | s.source_files = 'Sources/TinyGraphQL/**/*'
37 | end
38 |
--------------------------------------------------------------------------------
/Tests/TinyGraphQLTests/TinyGraphQLTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import TinyGraphQL
3 |
4 | final class TinyGraphQLTests: XCTestCase {
5 | let graphQL = GraphQL(
6 | url: URL(string: "https://combase.app/graphql")!,
7 | headers: ["combase-organization": "orgid", "Content-Type": "application/json"]
8 | )
9 |
10 | func testInit() {
11 | XCTAssertEqual(graphQL.url.absoluteString, "https://combase.app/graphql")
12 | XCTAssertEqual(graphQL.headers["combase-organization"], "orgid")
13 | XCTAssertEqual(graphQL.headers["Content-Type"], "application/json")
14 | }
15 |
16 | func testQuery() {
17 | let operation = Query("hello", ["hey": "sup"]) {
18 | "string_outside"
19 | Field("hey", ["hey": "sup"]) {
20 | Field("hey", ["lala": "lala"])
21 | "string_inside_a_field"
22 | }
23 | }
24 |
25 | let request = graphQL.request(for: operation)
26 |
27 | XCTAssertEqual(request.httpMethod, "GET")
28 |
29 | guard let queryString = request.url?.query else {
30 | XCTFail("HTTP request url doesn't contain a query string")
31 | return
32 | }
33 |
34 | let expectedBody = "query=%7Bhello(hey:%20%22sup%22)%7Bstring_outside,%20hey(hey:%20%22sup%22)%7Bhey(lala:%20%22lala%22),%20string_inside_a_field%7D%7D%7D"
35 |
36 | XCTAssertEqual(queryString, expectedBody)
37 | }
38 |
39 | func testMutation() {
40 | let operation = Mutation("hello", ["hey": "sup"]) {
41 | "name"
42 | Field("hey", ["hey": "sup"]) {
43 | Field("hey", ["lala": "lala"])
44 | }
45 | "byebye"
46 | }
47 |
48 | let request = graphQL.request(for: operation)
49 |
50 | XCTAssertEqual(request.httpMethod, "POST")
51 |
52 | guard
53 | let httpBody = request.httpBody,
54 | let bodyString = String(data: httpBody, encoding: .utf8)
55 | else {
56 | XCTFail("HTTP request doesn't contain a body")
57 | return
58 | }
59 |
60 | let expectedBody = "{\n \"query\": \"mutation { hello(hey: \\\"sup\\\"){name, hey(hey: \\\"sup\\\"){hey(lala: \\\"lala\\\")}, byebye} }\"\n}"
61 |
62 | XCTAssertEqual(bodyString, expectedBody)
63 | }
64 |
65 | func testQueryInheritance() {
66 | struct UserQuery: QueryOperation {
67 | struct Arguments: ArgumentListRepresentable, Encodable {
68 | let login: String
69 | }
70 |
71 | enum Fields: String {
72 | case name
73 | case id
74 | }
75 |
76 | var name: String = "user"
77 | var arguments: Arguments
78 | var fields: [Field]
79 |
80 | init(login: String, @Builder fields: Builder.Closure) {
81 | self.arguments = .init(login: login)
82 | self.fields = fields().map { $0.rawValue.fieldRepresentation }
83 | }
84 | }
85 |
86 | let operation = UserQuery(login: "cardoso") {
87 | UserQuery.Fields.name
88 | UserQuery.Fields.id
89 | }
90 |
91 | let request = graphQL.request(for: operation)
92 |
93 | XCTAssertEqual(request.httpMethod, "GET")
94 |
95 | guard let queryString = request.url?.query else {
96 | XCTFail("HTTP request url doesn't contain a query string")
97 | return
98 | }
99 |
100 | XCTAssertEqual(queryString, "query=%7Buser(login:%20%22cardoso%22)%7Bname,%20id%7D%7D")
101 | }
102 |
103 | static var allTests = [
104 | ("testInit", testInit),
105 | ]
106 | }
107 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [![Swift][swift-badge]][swift-url]
4 | [![CI Status][ci-badge]][ci-url]
5 | [![Cocoapods][cocoapods-badge]][cocoapods-url]
6 | 
7 | ![SPM & Carthage compatible][spm-carthage-badge]
8 | [![License][license-badge]][license-url]
9 | [![Twitter Follow][follow-badge]][follow-url]
10 | [![Twitter][tweet-badge]][tweet-url]
11 |
12 | TinyGraphQL is a simple and lightweight query builder for the Swift language with zero dependencies. It provides a syntax close to that of GraphQL while preventing you from making time-consuming syntax mistakes.
13 |
14 | Made with 💘 by the folks @ [Stream](https://getstream.io).
15 |
16 |
17 |
18 | ## Table of Contents
19 |
20 | - [Snippets](#snippets)
21 | * [Initialization](#initialization)
22 | * [Query](#query)
23 | + [Raw GraphQL](#raw-graphql)
24 | + [TinyGraphQL](#tinygraphql)
25 | * [Mutation](#mutation)
26 | + [Raw GraphQL](#raw-graphql-1)
27 | + [TinyGraphQL](#tinygraphql-1)
28 | * [Request](#request)
29 | + [Raw GraphQL + URLSession](#raw-graphql---urlsession)
30 | + [TinyGraphQL + URLSession](#tinygraphql---urlsession)
31 | - [Installation](#installation)
32 | * [SPM](#spm)
33 | * [CocoaPods](#cocoapods)
34 | * [Carthage](#carthage)
35 | - [Adopters](#adopters)
36 |
37 | ## Snippets
38 |
39 | ### Initialization
40 |
41 | TinyGraphQL is also a container for pre-configuring your requests. In the initialization step, you should specify the URL for the GraphQL endpoint and any HTTP headers you'd need for the requests.
42 |
43 | ```swift
44 | let graphQL = TinyGraphQL(
45 | url: URL(string: "https://api.my.app/graphql")!,
46 | headers: ["Content-Type": "application/json", "combase-organization": "5fd7ecb251b33b10c380977b"]
47 | )
48 | ```
49 |
50 | ### Query
51 |
52 | See below a comparison between a regular GraphQL query and how to generate a similar query using TinyGraphQL. Note that it's possible to have multiple levels of fields like in regular GraphQL.
53 |
54 | #### Raw GraphQL
55 |
56 | ```graphql
57 | query {
58 | organizationById(_id: "(id)") {
59 | name
60 | stream {
61 | key
62 | }
63 | agentCount
64 | }
65 | }
66 | ```
67 |
68 | #### TinyGraphQL
69 |
70 | ```swift
71 | Query("organizationById", ["_id": id]) {
72 | "name"
73 | Field("stream") {
74 | "key"
75 | }
76 | "agentCount"
77 | }
78 | ```
79 |
80 | ### Mutation
81 |
82 | Mutations work pretty much the same as queries, except behind the scenes where it becomes a `POST` request instead of `GET`.
83 |
84 | #### Raw GraphQL
85 |
86 | ```graphql
87 | mutation {
88 | getOrCreateUser(record: { name: "(name)", email: "(email)" }) {
89 | _id
90 | name
91 | streamToken
92 | }
93 | }
94 | ```
95 |
96 | #### TinyGraphQL
97 |
98 | ```swift
99 | Mutation("getOrCreateUser", ["record": ["name": name, "email": email]]) {
100 | "_id"
101 | "name"
102 | "streamToken"
103 | }
104 | ```
105 |
106 | ### Request
107 |
108 | Making a request without TinyGraphQL is quite tedious and error prone. With TinyGraphQL, it's safe and easy. Look below at a comparison between building the request yourself and having TinyGraphQL do it for you.
109 |
110 | #### Raw GraphQL + URLSession
111 |
112 | ```swift
113 | var urlRequest = URLRequest(url: url)
114 | urlRequest.httpMethod = "POST"
115 | urlRequest.setValue("5fd7ecb251b33b10c380977b", forHTTPHeaderField: "combase-organization")
116 | urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
117 | urlRequest.httpBody = """
118 | {
119 | "query": "mutation { createTicket(message: \\"\(message)\\", user: \\"\(userId)\\") { _id }}"
120 | }
121 | """.data(using: .utf8)
122 |
123 | URLSession.shared.dataTask(with: urlRequest, completionHandler: { data, response, error in
124 | // handle response
125 | }
126 | ```
127 |
128 | #### TinyGraphQL + URLSession
129 |
130 | ```swift
131 | let query = Query("organizationById", ["_id": id]) {
132 | "name"
133 | Field("stream") {
134 | "key"
135 | }
136 | "agentCount"
137 | }
138 |
139 | let urlRequest = graphQL.request(for: query)
140 |
141 | URLSession.shared.dataTask(with: urlRequest, completionHandler: { data, response, error in
142 | // handle response
143 | }
144 | ```
145 |
146 | ## Installation
147 |
148 | TinyGraphQL supports all three major dependency managers (SPM, CocoaPods, and Carthage)
149 |
150 | ### SPM
151 |
152 | ```swift
153 | .package(name: "TinyGraphQL", url: "https://github.com/getstream/TinyGraphQL", from: "0.0.2")
154 | ```
155 |
156 | ### CocoaPods
157 |
158 | ```ruby
159 | pod 'TinyGraphQL', '~> 1.0'
160 | ```
161 |
162 | ### Carthage
163 |
164 | ```
165 | github "getstream/TinyGraphQL" ~> 1.0
166 | ```
167 |
168 | ## Adopters
169 |
170 | - [Combase][combase-url]: Combase is an Open Source white-label chat solution that provides everything you need to instantly spin up a powerful, real-time customer support dashboard. TinyGraphQL is used by the [Combase Swift SDK][combase-swift-url].
171 |
172 | If you use TinyGraphQL, you can show your support by [opening a PR](https://github.com/GetStream/TinyGraphQL/edit/main/README.md) and including your project or company in this list!
173 |
174 | [swift-badge]: https://img.shields.io/badge/Swift-5.2-orange.svg?style=flat
175 | [swift-url]: https://swift.org
176 |
177 | [ci-badge]: https://img.shields.io/github/workflow/status/getstream/tinygraphql/CI
178 | [ci-url]: https://github.com/GetStream/TinyGraphQL/actions?query=workflow%3ACI
179 |
180 | [cocoapods-badge]: https://img.shields.io/cocoapods/v/TinyGraphQL
181 | [cocoapods-url]: https://cocoapods.org/pods/TinyGraphQL
182 |
183 | [spm-carthage-badge]: https://img.shields.io/badge/SPM%20%26%20Carthage-compatible-green
184 |
185 | [license-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat
186 | [license-url]: https://tldrlegal.com/license/mit-license
187 |
188 | [follow-badge]: https://img.shields.io/twitter/follow/getstream_io?style=social
189 | [follow-url]: https://twitter.com/intent/follow?screen_name=getstream_io
190 |
191 | [tweet-badge]: https://img.shields.io/twitter/url?url=https%3A%2F%2Fgithub.com%2FGetStream%2FStreamoji
192 | [tweet-url]: https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2FGetStream%2FTinyGraphQL
193 |
194 | [combase-url]: https://comba.se
195 | [combase-swift-url]: https://github.com/getstream/combase-swift
196 |
--------------------------------------------------------------------------------