Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
714a5b6
feat: Adds basic generator.
NeedleInAJayStack Dec 24, 2025
d7ecea6
feat: Implements schema builder
NeedleInAJayStack Dec 24, 2025
703de4d
feat: Removes ContentType support
NeedleInAJayStack Dec 24, 2025
a42af76
feat: Reduces public scope
NeedleInAJayStack Dec 24, 2025
1ef93bc
feat: Conforms all types to sendable
NeedleInAJayStack Dec 24, 2025
d2f6b01
feat: Improves code reuse
NeedleInAJayStack Dec 24, 2025
4e5c012
fix: Improve schema indentation
NeedleInAJayStack Dec 24, 2025
09300c6
fix: Fixes argument decoding
NeedleInAJayStack Dec 25, 2025
a79fcee
fix: Fixes referencing enums/recursive types
NeedleInAJayStack Dec 25, 2025
b01ade5
feat: Adds GraphQLResolveInfo support
NeedleInAJayStack Dec 25, 2025
f0257b9
feat: Adds interface generation
NeedleInAJayStack Dec 25, 2025
0f92712
feat: Builds result example in HelloWorldServer
NeedleInAJayStack Dec 26, 2025
10cd536
refactor: Use TypeMap protocol to collect typealiases
NeedleInAJayStack Dec 26, 2025
581b6c8
feat: Adds interface type support
NeedleInAJayStack Dec 26, 2025
f8ecb5d
feat: Adds union type support
NeedleInAJayStack Dec 26, 2025
beaf0ba
feat: Adds input objects and mutations
NeedleInAJayStack Dec 26, 2025
50e176d
feat: Adds scalar support
NeedleInAJayStack Dec 26, 2025
09de839
feat: Splits out Query & Mutation as types
NeedleInAJayStack Dec 27, 2025
eb588ef
feat: Changes TypeMap to Resolvers
NeedleInAJayStack Dec 27, 2025
94f160e
feat: Loosens type restrictions
NeedleInAJayStack Dec 27, 2025
c228bfb
refactor: Schema generic naming improvements
NeedleInAJayStack Dec 27, 2025
995aa13
feat: Combine Resolvers and Types
NeedleInAJayStack Dec 27, 2025
2af6ef9
feat: Adds complete type generator
NeedleInAJayStack Dec 27, 2025
8734422
feat: Schema updates
NeedleInAJayStack Dec 27, 2025
e4fba9a
feat: Fully working HelloWorldServer example
NeedleInAJayStack Dec 28, 2025
a389c8c
chore: Deletes AI cruft
NeedleInAJayStack Dec 28, 2025
c6d872a
test: Makes HelloWorldServer example testable
NeedleInAJayStack Dec 28, 2025
09a0777
feat: Revise Scalar and remove default impl
NeedleInAJayStack Dec 28, 2025
1944d7a
feat: Centralize decoder
NeedleInAJayStack Dec 28, 2025
bd23d6c
docs: Adds design details for each type
NeedleInAJayStack Dec 29, 2025
36c16c6
feat: Adds default value support
NeedleInAJayStack Dec 29, 2025
0e4881d
feat: Adds subscription support
NeedleInAJayStack Dec 29, 2025
5268a81
docs: Documentation updates
NeedleInAJayStack Dec 29, 2025
6769f2d
feat: Adds MIT license
NeedleInAJayStack Dec 29, 2025
ad54a71
feat: Adds CI
NeedleInAJayStack Dec 29, 2025
3d78151
refactor: Formatting
NeedleInAJayStack Dec 29, 2025
287e412
fix: Fixes trailing commas for Swift < v6.1
NeedleInAJayStack Dec 29, 2025
cb732e7
ci: Disable android because it has caching trouble
NeedleInAJayStack Dec 29, 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
19 changes: 19 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
lint:
uses: graphqlswift/ci/.github/workflows/lint.yaml@main
test:
uses: graphqlswift/ci/.github/workflows/test.yaml@main
with:
include_android: false
test-example:
uses: graphqlswift/ci/.github/workflows/test.yaml@main
with:
package_path: "Examples/HelloWorldServer"
include_android: false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
/.vscode
9 changes: 9 additions & 0 deletions Examples/HelloWorldServer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.vscode
33 changes: 33 additions & 0 deletions Examples/HelloWorldServer/Package.resolved

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

33 changes: 33 additions & 0 deletions Examples/HelloWorldServer/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// swift-tools-version: 6.0

import PackageDescription

let package = Package(
name: "HelloWorldServer",
platforms: [
.macOS(.v13),
],
dependencies: [
.package(name: "graphql-generator", path: "../.."),
.package(url: "https://github.com/GraphQLSwift/GraphQL.git", from: "4.0.0"),
],
targets: [
.target(
name: "HelloWorldServer",
dependencies: [
.product(name: "GraphQL", package: "GraphQL"),
.product(name: "GraphQLGeneratorRuntime", package: "graphql-generator"),
],
plugins: [
.plugin(name: "GraphQLGeneratorPlugin", package: "graphql-generator"),
]
),
.testTarget(
name: "HelloWorldServerTests",
dependencies: [
"HelloWorldServer",
.product(name: "GraphQL", package: "GraphQL"),
]
),
]
)
186 changes: 186 additions & 0 deletions Examples/HelloWorldServer/Sources/HelloWorldServer/Resolvers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import Foundation
import GraphQL
import GraphQLGeneratorRuntime

// Must be created by user and named `Context`.
public class Context: @unchecked Sendable {
// User can choose structure
var users: [String: User]
var posts: [String: Post]
var onTriggerWatch: () -> Void = {}

init(
users: [String: User],
posts: [String: Post]
) {
self.users = users
self.posts = posts
}

func triggerWatch() {
onTriggerWatch()
}
}

// Scalars must be represented by a Swift type of the same name, conforming to the Scalar protocol
public struct EmailAddress: Scalar {
let email: String

init(email: String) {
self.email = email
}

// Codability conformance. Required for usage in InputObject
public init(from decoder: any Decoder) throws {
email = try decoder.singleValueContainer().decode(String.self)
}

public func encode(to encoder: any Encoder) throws {
try email.encode(to: encoder)
}

// Scalar conformance. Not necessary, but default methods are very inefficient.
public static func serialize(this: Self) throws -> Map {
return .string(this.email)
}

public static func parseValue(map: Map) throws -> Map {
switch map {
case .string:
return map
default:
throw GraphQLError(message: "EmailAddress cannot represent non-string value: \(map)")
}
}

public static func parseLiteral(value: any Value) throws -> Map {
guard let ast = value as? StringValue else {
throw GraphQLError(
message: "EmailAddress cannot represent non-string value: \(print(ast: value))",
nodes: [value]
)
}
return .string(ast.value)
}
}

// Now create types that conform to the expected protocols
struct Resolvers: ResolversProtocol {
typealias Query = HelloWorldServer.Query
typealias Mutation = HelloWorldServer.Mutation
typealias Subscription = HelloWorldServer.Subscription
}

struct User: UserProtocol {
// User can choose structure
let id: String
let name: String
let email: String
let age: Int?
let role: Role?

// Required implementations
func id(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
return id
}

func name(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
return name
}

func email(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> EmailAddress {
return EmailAddress(email: email)
}

func age(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> Int? {
return age
}

func role(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> Role? {
return role
}
}

struct Contact: ContactProtocol {
// User can choose structure
let email: String

// Required implementations
func email(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> EmailAddress {
return EmailAddress(email: email)
}
}

struct Post: PostProtocol {
// User can choose structure
let id: String
let title: String
let content: String
let authorId: String

// Required implementations
func id(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
return id
}

func title(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
return title
}

func content(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
return content
}

func author(context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> any UserProtocol {
return context.users[authorId]!
}
}

struct Query: QueryProtocol {
// Required implementations
static func user(id: String, context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> (any UserProtocol)? {
return context.users[id]
}

static func users(context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> [any UserProtocol] {
return context.users.values.map { $0 as any UserProtocol }
}

static func post(id: String, context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> (any PostProtocol)? {
return context.posts[id]
}

static func posts(limit _: Int?, context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> [any PostProtocol] {
return context.posts.values.map { $0 as any PostProtocol }
}

static func userOrPost(id: String, context: Context, info _: GraphQLResolveInfo) async throws -> (any UserOrPostUnion)? {
return context.users[id] ?? context.posts[id]
}
}

struct Mutation: MutationProtocol {
// Required implementations
static func upsertUser(userInfo: UserInfoInput, context: Context, info _: GraphQLResolveInfo) -> any UserProtocol {
let user = User(
id: userInfo.id,
name: userInfo.name,
email: userInfo.email.email,
age: userInfo.age,
role: userInfo.role
)
context.users[userInfo.id] = user
return user
}
}

struct Subscription: SubscriptionProtocol {
// Required implementations
static func watchUser(id: String, context: Context, info _: GraphQLResolveInfo) async throws -> AnyAsyncSequence<(any UserProtocol)?> {
return AsyncStream<(any UserProtocol)?> { continuation in
context.onTriggerWatch = { [weak context] in
continuation.yield(context?.users[id])
}
}.any()
}
}
Loading