Skip to content

Commit 397c0f4

Browse files
Merge pull request #174 from NeedleInAJayStack/fix/mutable-field-resolvers
Field resolvers are mutable
2 parents de08923 + eb48dc8 commit 397c0f4

File tree

1 file changed

+51
-15
lines changed

1 file changed

+51
-15
lines changed

Sources/GraphQL/Type/Definition.swift

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@ public final class GraphQLObjectType: @unchecked Sendable {
276276
public let name: String
277277
public let description: String?
278278

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

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

506508
public typealias GraphQLFieldMap = OrderedDictionary<String, GraphQLField>
507509

508-
public struct GraphQLField: Sendable {
510+
public final class GraphQLField: @unchecked Sendable {
509511
public let type: GraphQLOutputType
510512
public let args: GraphQLArgumentConfigMap
511513
public let deprecationReason: String?
512514
public let description: String?
513-
public let resolve: GraphQLFieldResolve?
514-
public let subscribe: GraphQLFieldResolve?
515+
516+
public var resolve: GraphQLFieldResolve? {
517+
get {
518+
fieldPropertyQueue.sync { _resolve }
519+
}
520+
set {
521+
fieldPropertyQueue.sync(flags: .barrier) { _resolve = newValue }
522+
}
523+
}
524+
525+
private var _resolve: GraphQLFieldResolve?
526+
527+
public var subscribe: GraphQLFieldResolve? {
528+
get {
529+
fieldPropertyQueue.sync { _subscribe }
530+
}
531+
set {
532+
fieldPropertyQueue.sync(flags: .barrier) { _subscribe = newValue }
533+
}
534+
}
535+
536+
private var _subscribe: GraphQLFieldResolve?
537+
515538
public let astNode: FieldDefinition?
516539

517540
public init(
@@ -526,8 +549,8 @@ public struct GraphQLField: Sendable {
526549
self.deprecationReason = deprecationReason
527550
self.description = description
528551
self.astNode = astNode
529-
resolve = nil
530-
subscribe = nil
552+
_resolve = nil
553+
_subscribe = nil
531554
}
532555

533556
public init(
@@ -544,8 +567,8 @@ public struct GraphQLField: Sendable {
544567
self.deprecationReason = deprecationReason
545568
self.description = description
546569
self.astNode = astNode
547-
self.resolve = resolve
548-
self.subscribe = subscribe
570+
_resolve = resolve
571+
_subscribe = subscribe
549572
}
550573

551574
public init(
@@ -562,11 +585,11 @@ public struct GraphQLField: Sendable {
562585
self.description = description
563586
self.astNode = astNode
564587

565-
self.resolve = { source, args, context, info in
588+
_resolve = { source, args, context, info in
566589
let result = try resolve(source, args, context, info)
567590
return result
568591
}
569-
subscribe = nil
592+
_subscribe = nil
570593
}
571594
}
572595

@@ -711,8 +734,8 @@ public final class GraphQLInterfaceType: @unchecked Sendable {
711734
public let description: String?
712735
public let resolveType: GraphQLTypeResolve?
713736

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

755+
/// The interfaces that the interface conforms to. This may be mutated during setup, but should
756+
/// not be modified once the schema is being used for execution.
732757
public var interfaces: () throws -> [GraphQLInterfaceType] {
733758
get {
734759
interfaceFunc
@@ -869,7 +894,9 @@ public final class GraphQLUnionType: @unchecked Sendable {
869894
public let name: String
870895
public let description: String?
871896
public let resolveType: GraphQLTypeResolve?
872-
// While technically not sendable, types should not be mutated after schema creation.
897+
898+
/// The types that belong to the union. This may be mutated during setup, but must not be
899+
/// modified once the schema is being used for execution.
873900
public internal(set) var types: () throws -> [GraphQLObjectType]
874901
public let possibleTypeNames: [String: Bool]
875902
let extensions: [GraphQLUnionTypeExtensions]
@@ -1164,7 +1191,9 @@ public struct GraphQLEnumValueDefinition: Sendable {
11641191
public final class GraphQLInputObjectType: @unchecked Sendable {
11651192
public let name: String
11661193
public let description: String?
1167-
// While technically not sendable, this should not be mutated after schema creation.
1194+
1195+
/// The fields that the input has. This may be mutated during setup, but must not be modified
1196+
/// once the schema is being used for execution.
11681197
public var fields: () throws -> InputObjectFieldMap
11691198
public let astNode: InputObjectTypeDefinition?
11701199
public let extensionASTNodes: [InputObjectExtensionDefinition]
@@ -1422,3 +1451,10 @@ private let cacheQueue = DispatchQueue(
14221451
label: "graphql.objecttype.cache",
14231452
attributes: .concurrent
14241453
)
1454+
1455+
/// Shared queue for field property access
1456+
/// Uses reader/writer pattern for read-heavy workload
1457+
private let fieldPropertyQueue = DispatchQueue(
1458+
label: "graphql.field.properties",
1459+
attributes: .concurrent
1460+
)

0 commit comments

Comments
 (0)