Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 51 additions & 15 deletions Sources/GraphQL/Type/Definition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ public final class GraphQLObjectType: @unchecked Sendable {
public let name: String
public let description: String?

// While technically not sendable, fields and interfaces should not be mutated after schema
// creation.
/// The fields that the object defines. These may be mutated during setup, but should not be
/// modified once the schema is being used for execution.
public var fields: () throws -> GraphQLFieldMap {
get {
fieldFunc
Expand All @@ -294,6 +294,8 @@ public final class GraphQLObjectType: @unchecked Sendable {
private var fieldFunc: () throws -> GraphQLFieldMap
private var fieldCache: GraphQLFieldDefinitionMap?

/// The interfaces that the object conforms to. These may be mutated during setup, but should
/// not be modified once the schema is being used for execution.
public var interfaces: () throws -> [GraphQLInterfaceType] {
get {
interfaceFunc
Expand Down Expand Up @@ -505,13 +507,34 @@ public struct GraphQLResolveInfo: Sendable {

public typealias GraphQLFieldMap = OrderedDictionary<String, GraphQLField>

public struct GraphQLField: Sendable {
public final class GraphQLField: @unchecked Sendable {
public let type: GraphQLOutputType
public let args: GraphQLArgumentConfigMap
public let deprecationReason: String?
public let description: String?
public let resolve: GraphQLFieldResolve?
public let subscribe: GraphQLFieldResolve?

public var resolve: GraphQLFieldResolve? {
get {
fieldPropertyQueue.sync { _resolve }
}
set {
fieldPropertyQueue.sync(flags: .barrier) { _resolve = newValue }
}
}

private var _resolve: GraphQLFieldResolve?

public var subscribe: GraphQLFieldResolve? {
get {
fieldPropertyQueue.sync { _subscribe }
}
set {
fieldPropertyQueue.sync(flags: .barrier) { _subscribe = newValue }
}
}

private var _subscribe: GraphQLFieldResolve?

public let astNode: FieldDefinition?

public init(
Expand All @@ -526,8 +549,8 @@ public struct GraphQLField: Sendable {
self.deprecationReason = deprecationReason
self.description = description
self.astNode = astNode
resolve = nil
subscribe = nil
_resolve = nil
_subscribe = nil
}

public init(
Expand All @@ -544,8 +567,8 @@ public struct GraphQLField: Sendable {
self.deprecationReason = deprecationReason
self.description = description
self.astNode = astNode
self.resolve = resolve
self.subscribe = subscribe
_resolve = resolve
_subscribe = subscribe
}

public init(
Expand All @@ -562,11 +585,11 @@ public struct GraphQLField: Sendable {
self.description = description
self.astNode = astNode

self.resolve = { source, args, context, info in
_resolve = { source, args, context, info in
let result = try resolve(source, args, context, info)
return result
}
subscribe = nil
_subscribe = nil
}
}

Expand Down Expand Up @@ -711,8 +734,8 @@ public final class GraphQLInterfaceType: @unchecked Sendable {
public let description: String?
public let resolveType: GraphQLTypeResolve?

// While technically not sendable, fields and interfaces should not be mutated after schema
// creation.
/// The fields that the interface defines. These may be mutated during setup, but should not be
/// modified once the schema is being used for execution.
public var fields: () throws -> GraphQLFieldMap {
get {
fieldFunc
Expand All @@ -729,6 +752,8 @@ public final class GraphQLInterfaceType: @unchecked Sendable {
private var fieldFunc: () throws -> GraphQLFieldMap
private var fieldCache: GraphQLFieldDefinitionMap?

/// The interfaces that the interface conforms to. This may be mutated during setup, but should
/// not be modified once the schema is being used for execution.
public var interfaces: () throws -> [GraphQLInterfaceType] {
get {
interfaceFunc
Expand Down Expand Up @@ -869,7 +894,9 @@ public final class GraphQLUnionType: @unchecked Sendable {
public let name: String
public let description: String?
public let resolveType: GraphQLTypeResolve?
// While technically not sendable, types should not be mutated after schema creation.

/// The types that belong to the union. This may be mutated during setup, but must not be
/// modified once the schema is being used for execution.
public internal(set) var types: () throws -> [GraphQLObjectType]
public let possibleTypeNames: [String: Bool]
let extensions: [GraphQLUnionTypeExtensions]
Expand Down Expand Up @@ -1164,7 +1191,9 @@ public struct GraphQLEnumValueDefinition: Sendable {
public final class GraphQLInputObjectType: @unchecked Sendable {
public let name: String
public let description: String?
// While technically not sendable, this should not be mutated after schema creation.

/// The fields that the input has. This may be mutated during setup, but must not be modified
/// once the schema is being used for execution.
public var fields: () throws -> InputObjectFieldMap
public let astNode: InputObjectTypeDefinition?
public let extensionASTNodes: [InputObjectExtensionDefinition]
Expand Down Expand Up @@ -1422,3 +1451,10 @@ private let cacheQueue = DispatchQueue(
label: "graphql.objecttype.cache",
attributes: .concurrent
)

/// Shared queue for field property access
/// Uses reader/writer pattern for read-heavy workload
private let fieldPropertyQueue = DispatchQueue(
label: "graphql.field.properties",
attributes: .concurrent
)