Skip to content

Commit 6c89e60

Browse files
refactor(perf): Adds fieldCache and interfaceCache
Improves benchmarked `graphql` time by 22%
1 parent 2a1fead commit 6c89e60

1 file changed

Lines changed: 96 additions & 22 deletions

File tree

Sources/GraphQL/Type/Definition.swift

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,37 @@ extension GraphQLScalarType: Hashable {
275275
public final class GraphQLObjectType: @unchecked Sendable {
276276
public let name: String
277277
public let description: String?
278+
278279
// While technically not sendable, fields and interfaces should not be mutated after schema
279280
// creation.
280-
public var fields: () throws -> GraphQLFieldMap
281-
public var interfaces: () throws -> [GraphQLInterfaceType]
281+
public var fields: () throws -> GraphQLFieldMap {
282+
get {
283+
fieldFunc
284+
}
285+
set {
286+
fieldFunc = newValue
287+
// Clear the cache when setting a new function
288+
fieldCache = nil
289+
}
290+
}
291+
292+
private var fieldFunc: () throws -> GraphQLFieldMap
293+
private var fieldCache: GraphQLFieldDefinitionMap?
294+
295+
public var interfaces: () throws -> [GraphQLInterfaceType] {
296+
get {
297+
interfaceFunc
298+
}
299+
set {
300+
interfaceFunc = newValue
301+
// Clear the cache when setting a new function
302+
interfaceCache = nil
303+
}
304+
}
305+
306+
private var interfaceFunc: () throws -> [GraphQLInterfaceType]
307+
private var interfaceCache: [GraphQLInterfaceType]?
308+
282309
public let isTypeOf: GraphQLIsTypeOf?
283310
public let astNode: ObjectTypeDefinition?
284311
public let extensionASTNodes: [TypeExtensionDefinition]
@@ -296,8 +323,8 @@ public final class GraphQLObjectType: @unchecked Sendable {
296323
try assertValid(name: name)
297324
self.name = name
298325
self.description = description
299-
self.fields = { fields }
300-
self.interfaces = { interfaces }
326+
fieldFunc = { fields }
327+
interfaceFunc = { interfaces }
301328
self.isTypeOf = isTypeOf
302329
self.astNode = astNode
303330
self.extensionASTNodes = extensionASTNodes
@@ -315,22 +342,32 @@ public final class GraphQLObjectType: @unchecked Sendable {
315342
try assertValid(name: name)
316343
self.name = name
317344
self.description = description
318-
self.fields = fields
319-
self.interfaces = interfaces
345+
fieldFunc = fields
346+
interfaceFunc = interfaces
320347
self.isTypeOf = isTypeOf
321348
self.astNode = astNode
322349
self.extensionASTNodes = extensionASTNodes
323350
}
324351

325352
func getFields() throws -> GraphQLFieldDefinitionMap {
326-
try defineFieldMap(
327-
name: name,
328-
fields: fields()
329-
)
353+
// Cache on the first call
354+
return try fieldCache ?? {
355+
let fields = try defineFieldMap(
356+
name: name,
357+
fields: fields()
358+
)
359+
self.fieldCache = fields
360+
return fields
361+
}()
330362
}
331363

332364
func getInterfaces() throws -> [GraphQLInterfaceType] {
333-
return try interfaces()
365+
// Cache on the first call
366+
return try interfaceCache ?? {
367+
let interfaces = try interfaces()
368+
self.interfaceCache = interfaces
369+
return interfaces
370+
}()
334371
}
335372
}
336373

@@ -657,10 +694,37 @@ public final class GraphQLInterfaceType: @unchecked Sendable {
657694
public let name: String
658695
public let description: String?
659696
public let resolveType: GraphQLTypeResolve?
697+
660698
// While technically not sendable, fields and interfaces should not be mutated after schema
661699
// creation.
662-
public var fields: () throws -> GraphQLFieldMap
663-
public var interfaces: () throws -> [GraphQLInterfaceType]
700+
public var fields: () throws -> GraphQLFieldMap {
701+
get {
702+
fieldFunc
703+
}
704+
set {
705+
fieldFunc = newValue
706+
// Clear the cache when setting a new function
707+
fieldCache = nil
708+
}
709+
}
710+
711+
private var fieldFunc: () throws -> GraphQLFieldMap
712+
private var fieldCache: GraphQLFieldDefinitionMap?
713+
714+
public var interfaces: () throws -> [GraphQLInterfaceType] {
715+
get {
716+
interfaceFunc
717+
}
718+
set {
719+
interfaceFunc = newValue
720+
// Clear the cache when setting a new function
721+
interfaceCache = nil
722+
}
723+
}
724+
725+
private var interfaceFunc: () throws -> [GraphQLInterfaceType]
726+
private var interfaceCache: [GraphQLInterfaceType]?
727+
664728
public let astNode: InterfaceTypeDefinition?
665729
public let extensionASTNodes: [InterfaceExtensionDefinition]
666730
public let kind: TypeKind = .interface
@@ -677,8 +741,8 @@ public final class GraphQLInterfaceType: @unchecked Sendable {
677741
try assertValid(name: name)
678742
self.name = name
679743
self.description = description
680-
self.fields = { fields }
681-
self.interfaces = { interfaces }
744+
fieldFunc = { fields }
745+
interfaceFunc = { interfaces }
682746
self.resolveType = resolveType
683747
self.astNode = astNode
684748
self.extensionASTNodes = extensionASTNodes
@@ -696,22 +760,32 @@ public final class GraphQLInterfaceType: @unchecked Sendable {
696760
try assertValid(name: name)
697761
self.name = name
698762
self.description = description
699-
self.fields = fields
700-
self.interfaces = interfaces
763+
fieldFunc = fields
764+
interfaceFunc = interfaces
701765
self.resolveType = resolveType
702766
self.astNode = astNode
703767
self.extensionASTNodes = extensionASTNodes
704768
}
705769

706770
func getFields() throws -> GraphQLFieldDefinitionMap {
707-
try defineFieldMap(
708-
name: name,
709-
fields: fields()
710-
)
771+
// Cache on the first call
772+
return try fieldCache ?? {
773+
let fields = try defineFieldMap(
774+
name: name,
775+
fields: fields()
776+
)
777+
self.fieldCache = fields
778+
return fields
779+
}()
711780
}
712781

713782
func getInterfaces() throws -> [GraphQLInterfaceType] {
714-
return try interfaces()
783+
// Cache on the first call
784+
return try interfaceCache ?? {
785+
let interfaces = try interfaces()
786+
self.interfaceCache = interfaces
787+
return interfaces
788+
}()
715789
}
716790
}
717791

0 commit comments

Comments
 (0)