elasticgraph-graphql 0.19.1.1 → 0.19.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/lib/elastic_graph/graphql/aggregation/composite_grouping_adapter.rb +1 -1
  4. data/lib/elastic_graph/graphql/aggregation/computation.rb +1 -1
  5. data/lib/elastic_graph/graphql/aggregation/date_histogram_grouping.rb +1 -1
  6. data/lib/elastic_graph/graphql/aggregation/field_path_encoder.rb +1 -1
  7. data/lib/elastic_graph/graphql/aggregation/field_term_grouping.rb +1 -1
  8. data/lib/elastic_graph/graphql/aggregation/key.rb +1 -1
  9. data/lib/elastic_graph/graphql/aggregation/nested_sub_aggregation.rb +1 -1
  10. data/lib/elastic_graph/graphql/aggregation/non_composite_grouping_adapter.rb +2 -2
  11. data/lib/elastic_graph/graphql/aggregation/path_segment.rb +2 -2
  12. data/lib/elastic_graph/graphql/aggregation/query.rb +1 -1
  13. data/lib/elastic_graph/graphql/aggregation/query_adapter.rb +33 -6
  14. data/lib/elastic_graph/graphql/aggregation/query_optimizer.rb +1 -1
  15. data/lib/elastic_graph/graphql/aggregation/resolvers/aggregated_values.rb +2 -6
  16. data/lib/elastic_graph/graphql/aggregation/resolvers/count_detail.rb +1 -1
  17. data/lib/elastic_graph/graphql/aggregation/resolvers/grouped_by.rb +26 -6
  18. data/lib/elastic_graph/graphql/aggregation/resolvers/node.rb +1 -1
  19. data/lib/elastic_graph/graphql/aggregation/resolvers/relay_connection_builder.rb +5 -6
  20. data/lib/elastic_graph/graphql/aggregation/resolvers/sub_aggregations.rb +10 -8
  21. data/lib/elastic_graph/graphql/aggregation/script_term_grouping.rb +1 -1
  22. data/lib/elastic_graph/graphql/aggregation/term_grouping.rb +2 -2
  23. data/lib/elastic_graph/graphql/client.rb +1 -1
  24. data/lib/elastic_graph/graphql/config.rb +21 -6
  25. data/lib/elastic_graph/graphql/datastore_query/document_paginator.rb +10 -5
  26. data/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb +2 -3
  27. data/lib/elastic_graph/graphql/datastore_query/paginator.rb +1 -1
  28. data/lib/elastic_graph/graphql/datastore_query/routing_picker.rb +2 -3
  29. data/lib/elastic_graph/graphql/datastore_query.rb +66 -74
  30. data/lib/elastic_graph/graphql/datastore_response/document.rb +1 -1
  31. data/lib/elastic_graph/graphql/datastore_response/search_response.rb +83 -9
  32. data/lib/elastic_graph/graphql/datastore_search_router.rb +19 -4
  33. data/lib/elastic_graph/graphql/decoded_cursor.rb +1 -1
  34. data/lib/elastic_graph/graphql/filtering/boolean_query.rb +1 -1
  35. data/lib/elastic_graph/graphql/filtering/field_path.rb +1 -1
  36. data/lib/elastic_graph/graphql/filtering/filter_args_translator.rb +2 -2
  37. data/lib/elastic_graph/graphql/filtering/filter_interpreter.rb +10 -5
  38. data/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb +2 -2
  39. data/lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb +17 -2
  40. data/lib/elastic_graph/graphql/filtering/range_query.rb +1 -1
  41. data/lib/elastic_graph/graphql/http_endpoint.rb +2 -2
  42. data/lib/elastic_graph/graphql/query_adapter/filters.rb +1 -1
  43. data/lib/elastic_graph/graphql/query_adapter/pagination.rb +1 -1
  44. data/lib/elastic_graph/graphql/query_adapter/requested_fields.rb +18 -3
  45. data/lib/elastic_graph/graphql/query_adapter/sort.rb +1 -1
  46. data/lib/elastic_graph/graphql/query_details_tracker.rb +11 -14
  47. data/lib/elastic_graph/graphql/query_executor.rb +10 -16
  48. data/lib/elastic_graph/graphql/resolvers/get_record_field_value.rb +6 -12
  49. data/lib/elastic_graph/graphql/resolvers/graphql_adapter_builder.rb +126 -0
  50. data/lib/elastic_graph/graphql/resolvers/list_records.rb +4 -4
  51. data/lib/elastic_graph/graphql/resolvers/nested_relationships.rb +57 -27
  52. data/lib/elastic_graph/graphql/resolvers/nested_relationships_source.rb +325 -0
  53. data/lib/elastic_graph/graphql/resolvers/object.rb +36 -0
  54. data/lib/elastic_graph/graphql/resolvers/query_adapter.rb +2 -2
  55. data/lib/elastic_graph/graphql/resolvers/query_source.rb +6 -3
  56. data/lib/elastic_graph/graphql/resolvers/relay_connection/array_adapter.rb +1 -1
  57. data/lib/elastic_graph/graphql/resolvers/relay_connection/generic_adapter.rb +1 -1
  58. data/lib/elastic_graph/graphql/resolvers/relay_connection/page_info.rb +1 -1
  59. data/lib/elastic_graph/graphql/resolvers/relay_connection/search_response_adapter_builder.rb +1 -1
  60. data/lib/elastic_graph/graphql/resolvers/relay_connection.rb +2 -2
  61. data/lib/elastic_graph/graphql/resolvers/resolvable_value.rb +2 -7
  62. data/lib/elastic_graph/graphql/scalar_coercion_adapters/cursor.rb +1 -1
  63. data/lib/elastic_graph/graphql/scalar_coercion_adapters/date.rb +1 -1
  64. data/lib/elastic_graph/graphql/scalar_coercion_adapters/date_time.rb +1 -1
  65. data/lib/elastic_graph/graphql/scalar_coercion_adapters/local_time.rb +1 -1
  66. data/lib/elastic_graph/graphql/scalar_coercion_adapters/longs.rb +1 -1
  67. data/lib/elastic_graph/graphql/scalar_coercion_adapters/no_op.rb +1 -1
  68. data/lib/elastic_graph/graphql/scalar_coercion_adapters/time_zone.rb +1 -1
  69. data/lib/elastic_graph/graphql/scalar_coercion_adapters/untyped.rb +1 -1
  70. data/lib/elastic_graph/graphql/scalar_coercion_adapters/valid_time_zones.rb +1 -1
  71. data/lib/elastic_graph/graphql/schema/arguments.rb +1 -1
  72. data/lib/elastic_graph/graphql/schema/enum_value.rb +1 -1
  73. data/lib/elastic_graph/graphql/schema/field.rb +12 -27
  74. data/lib/elastic_graph/graphql/schema/relation_join.rb +17 -9
  75. data/lib/elastic_graph/graphql/schema/type.rb +19 -8
  76. data/lib/elastic_graph/graphql/schema.rb +83 -29
  77. data/lib/elastic_graph/graphql.rb +56 -43
  78. data/script/dump_time_zones +1 -1
  79. metadata +59 -29
  80. data/lib/elastic_graph/graphql/monkey_patches/schema_field.rb +0 -56
  81. data/lib/elastic_graph/graphql/monkey_patches/schema_object.rb +0 -48
  82. data/lib/elastic_graph/graphql/resolvers/graphql_adapter.rb +0 -114
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -63,21 +63,25 @@ module ElasticGraph
63
63
 
64
64
  # Normalizes the given documents, ensuring it has the expected cardinality.
65
65
  def normalize_documents(response, &handle_warning)
66
- doc_cardinality.normalize(response, handle_warning: handle_warning, &:id)
66
+ doc_cardinality.normalize(response, handle_warning: handle_warning) { |doc| (_ = doc).id }
67
67
  end
68
68
 
69
69
  private
70
70
 
71
71
  def normalize_ids(id_or_ids, &handle_warning)
72
- id_cardinality.normalize(id_or_ids, handle_warning: handle_warning, &:itself)
72
+ id_cardinality.normalize(id_or_ids, handle_warning: handle_warning) { |id| id }
73
73
  end
74
74
 
75
75
  module Cardinality
76
76
  module Many
77
77
  def self.normalize(list_or_scalar, handle_warning:)
78
- return list_or_scalar if list_or_scalar.is_a?(Enumerable)
79
- handle_warning.call("scalar instead of a list")
80
- Array(list_or_scalar)
78
+ case list_or_scalar
79
+ when ::Enumerable
80
+ list_or_scalar
81
+ else
82
+ handle_warning.call("scalar instead of a list")
83
+ Array(list_or_scalar)
84
+ end
81
85
  end
82
86
 
83
87
  def self.blank_value
@@ -87,9 +91,13 @@ module ElasticGraph
87
91
 
88
92
  module One
89
93
  def self.normalize(list_or_scalar, handle_warning:, &deterministic_comparator)
90
- return list_or_scalar unless list_or_scalar.is_a?(Enumerable)
91
- handle_warning.call("list of more than one item instead of a scalar") if list_or_scalar.size > 1
92
- list_or_scalar.min_by(&deterministic_comparator)
94
+ case list_or_scalar
95
+ when ::Enumerable
96
+ handle_warning.call("list of more than one item instead of a scalar") if (_ = list_or_scalar).size > 1
97
+ list_or_scalar.min_by(&deterministic_comparator)
98
+ else
99
+ list_or_scalar
100
+ end
93
101
  end
94
102
 
95
103
  def self.blank_value
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -24,7 +24,8 @@ module ElasticGraph
24
24
  graphql_type,
25
25
  index_definitions,
26
26
  object_runtime_metadata,
27
- enum_runtime_metadata
27
+ enum_runtime_metadata,
28
+ resolvers_needing_lookahead
28
29
  )
29
30
  @schema = schema
30
31
  @graphql_type = graphql_type
@@ -37,6 +38,7 @@ module ElasticGraph
37
38
  @elasticgraph_category = object_runtime_metadata&.elasticgraph_category
38
39
  @graphql_only_return_type = object_runtime_metadata&.graphql_only_return_type
39
40
  @enum_runtime_metadata = enum_runtime_metadata
41
+ @resolvers_needing_lookahead = resolvers_needing_lookahead
40
42
  @enum_value_names_by_original_name = (enum_runtime_metadata&.values_by_name || {}).to_h do |name, value|
41
43
  [value.alternate_original_name || name, name]
42
44
  end
@@ -45,7 +47,7 @@ module ElasticGraph
45
47
  end
46
48
 
47
49
  def name
48
- @name ||= @graphql_type.to_type_signature.to_sym
50
+ @name ||= @graphql_type.to_type_signature
49
51
  end
50
52
 
51
53
  # List of index definitions that should be searched for this type.
@@ -110,11 +112,14 @@ module ElasticGraph
110
112
  # Returns the subtypes of this type, if it has any. This is like `#possible_types` provided by the
111
113
  # GraphQL gem, but that includes a type itself when you ask for the possible types of a non-abstract type.
112
114
  def subtypes
113
- @subtypes ||= @schema.graphql_schema.possible_types(graphql_type).map { |t| @schema.type_from(t) } - [self]
115
+ @subtypes ||= @schema
116
+ .graphql_schema
117
+ .possible_types(graphql_type, visibility_profile: :boot)
118
+ .map { |t| @schema.type_from(t) } - [self]
114
119
  end
115
120
 
116
121
  def field_named(field_name)
117
- @fields_by_name.fetch(field_name.to_s)
122
+ @fields_by_name.fetch(field_name)
118
123
  rescue KeyError => e
119
124
  msg = "No field named #{field_name} (on type #{name}) could be found"
120
125
  msg += "; Possible alternatives: [#{e.corrections.join(", ").delete('"')}]." if e.corrections.any?
@@ -122,7 +127,7 @@ module ElasticGraph
122
127
  end
123
128
 
124
129
  def enum_value_named(enum_value_name)
125
- @enum_values_by_name[enum_value_name.to_s]
130
+ @enum_values_by_name[enum_value_name]
126
131
  end
127
132
 
128
133
  def coerce_result(result)
@@ -226,7 +231,7 @@ module ElasticGraph
226
231
  graphql_enum_value = @graphql_type.values.fetch(enum_value_name)
227
232
 
228
233
  EnumValue.new(
229
- name: graphql_enum_value.graphql_name.to_sym,
234
+ name: graphql_enum_value.graphql_name,
230
235
  type: self,
231
236
  runtime_metadata: @enum_runtime_metadata&.values_by_name&.dig(enum_value_name)
232
237
  )
@@ -250,7 +255,13 @@ module ElasticGraph
250
255
  # Eagerly fan out and instantiate all `Field` objects so that the :extras
251
256
  # get added to each field as require before we execute the first query
252
257
  fields_hash.each_with_object({}) do |(name, field), hash|
253
- hash[name] = Field.new(schema, self, field, @object_runtime_metadata&.graphql_fields_by_name&.dig(name))
258
+ hash[name] = Field.new(
259
+ schema,
260
+ self,
261
+ field,
262
+ @object_runtime_metadata&.graphql_fields_by_name&.dig(name),
263
+ @resolvers_needing_lookahead
264
+ )
254
265
  end
255
266
  end
256
267
 
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -6,16 +6,11 @@
6
6
  #
7
7
  # frozen_string_literal: true
8
8
 
9
- require "digest/md5"
10
- require "forwardable"
11
9
  require "graphql"
12
10
  require "elastic_graph/constants"
13
11
  require "elastic_graph/errors"
14
- require "elastic_graph/graphql/monkey_patches/schema_field"
15
- require "elastic_graph/graphql/monkey_patches/schema_object"
16
12
  require "elastic_graph/graphql/schema/field"
17
13
  require "elastic_graph/graphql/schema/type"
18
- require "elastic_graph/support/hash_util"
19
14
 
20
15
  module ElasticGraph
21
16
  # Wraps a GraphQL::Schema object in order to provide higher-level, more convenient APIs
@@ -29,19 +24,27 @@ module ElasticGraph
29
24
  scalar_types.to_set.union(introspection_types)
30
25
  )
31
26
 
32
- attr_reader :element_names, :config, :graphql_schema, :runtime_metadata
27
+ attr_reader :element_names, :config, :logger, :graphql_schema, :runtime_metadata
33
28
 
34
29
  def initialize(
35
30
  graphql_schema_string:,
36
31
  config:,
32
+ logger:,
37
33
  runtime_metadata:,
34
+ datastore_search_router:,
38
35
  index_definitions_by_graphql_type:,
39
36
  graphql_gem_plugins:,
40
- &build_resolver
37
+ graphql_adapter:
41
38
  )
42
39
  @element_names = runtime_metadata.schema_element_names
43
40
  @config = config
41
+ @logger = logger
44
42
  @runtime_metadata = runtime_metadata
43
+ @datastore_search_router = datastore_search_router
44
+
45
+ resolvers_needing_lookahead = runtime_metadata.graphql_resolvers_by_name.filter_map do |name, resolver|
46
+ name if resolver.needs_lookahead
47
+ end.to_set
45
48
 
46
49
  @types_by_graphql_type = Hash.new do |hash, key|
47
50
  hash[key] = Type.new(
@@ -49,25 +52,55 @@ module ElasticGraph
49
52
  key,
50
53
  index_definitions_by_graphql_type[key.graphql_name] || [],
51
54
  runtime_metadata.object_types_by_name[key.graphql_name],
52
- runtime_metadata.enum_types_by_name[key.graphql_name]
55
+ runtime_metadata.enum_types_by_name[key.graphql_name],
56
+ resolvers_needing_lookahead
53
57
  )
54
58
  end
55
59
 
56
- @build_resolver = build_resolver
57
-
58
60
  # Note: as part of loading the schema, the GraphQL gem may use the resolver (such
59
61
  # when a directive has a custom scalar) so we must wait to instantiate the schema
60
62
  # as late as possible here. If we do this before initializing some of the instance
61
63
  # variables above we'll get `NoMethodError` on `nil`.
62
64
  @graphql_schema = ::GraphQL::Schema.from_definition(
63
65
  graphql_schema_string,
64
- default_resolve: LazyResolverAdapter.new(method(:resolver)),
66
+ base_types: {object: build_base_object_class},
67
+ default_resolve: graphql_adapter,
65
68
  using: graphql_gem_plugins
66
69
  )
67
70
 
68
71
  # Pre-load all defined types so that all field extras can get configured as part
69
72
  # of loading the schema, before we execute the first query.
70
73
  @types_by_name = build_types_by_name
74
+ @graphql_schema.visibility.preload
75
+
76
+ log_hidden_types
77
+ end
78
+
79
+ def new_graphql_query(
80
+ query_string,
81
+ operation_name: nil,
82
+ variables: {},
83
+ context: {},
84
+ document: nil,
85
+ validate: true
86
+ )
87
+ ::GraphQL::Query.new(
88
+ graphql_schema,
89
+ query_string,
90
+ variables: variables,
91
+ operation_name: operation_name,
92
+ document: document,
93
+ validate: validate,
94
+ context: context.merge({
95
+ datastore_search_router: @datastore_search_router,
96
+ elastic_graph_schema: self,
97
+ visibility_profile: VISIBILITY_PROFILE
98
+ })
99
+ )
100
+ end
101
+
102
+ def graphql_query_context
103
+ @graphql_query_context ||= new_graphql_query(nil).context
71
104
  end
72
105
 
73
106
  def type_from(graphql_type)
@@ -79,7 +112,7 @@ module ElasticGraph
79
112
  # get type objects for wrapped types, but you need to get it from a field object of that
80
113
  # type.
81
114
  def type_named(type_name)
82
- @types_by_name.fetch(type_name.to_s)
115
+ @types_by_name.fetch(type_name)
83
116
  rescue KeyError => e
84
117
  msg = "No type named #{type_name} could be found"
85
118
  msg += "; Possible alternatives: [#{e.corrections.join(", ").delete('"')}]." if e.corrections.any?
@@ -114,33 +147,44 @@ module ElasticGraph
114
147
  end
115
148
 
116
149
  def to_s
117
- "#<#{self.class.name} 0x#{__id__.to_s(16)} indexed_document_types=#{indexed_document_types.map(&:name).sort.to_s.delete(":")}>"
150
+ "#<#{self.class.name} 0x#{__id__.to_s(16)} indexed_document_types=[#{indexed_document_types.map(&:name).sort.join(", ")}]>"
118
151
  end
119
152
  alias_method :inspect, :to_s
120
153
 
121
154
  private
122
155
 
123
- # Adapter class to allow us to lazily load the resolver instance.
124
- #
125
- # Necessary because the resolver must be provided to `GraphQL::Schema.from_definition`,
126
- # but the resolver logic itself depends upon the loaded schema to know how to resolve.
127
- # To work around the circular dependency, we build the schema with this lazy adapter,
128
- # then build the resolver with the schema, and then the lazy resolver lazily loads the resolver.
129
- LazyResolverAdapter = Struct.new(:builder) do
130
- def resolver
131
- @resolver ||= builder.call
156
+ def build_base_object_class
157
+ schema = self
158
+
159
+ base_field_class = ::Class.new(::GraphQL::Schema::Field) do
160
+ define_method :visible? do |context|
161
+ return super(context) if context[:visibility_profile] == :boot
162
+
163
+ if schema.field_named(owner.graphql_name, graphql_name).hidden_from_queries?
164
+ return false
165
+ end
166
+
167
+ super(context)
168
+ end
132
169
  end
133
170
 
134
- extend Forwardable
135
- def_delegators :resolver, :call, :resolve_type, :coerce_input, :coerce_result
136
- end
171
+ ::Class.new(::GraphQL::Schema::Object) do
172
+ field_class base_field_class
173
+
174
+ define_singleton_method :visible? do |context|
175
+ return super(context) if context[:visibility_profile] == :boot
176
+
177
+ if schema.type_named(graphql_name).hidden_from_queries?
178
+ return false
179
+ end
137
180
 
138
- def resolver
139
- @resolver ||= @build_resolver.call(self)
181
+ super(context)
182
+ end
183
+ end
140
184
  end
141
185
 
142
186
  def build_types_by_name
143
- graphql_schema.types.transform_values do |graphql_type|
187
+ graphql_schema.types(visibility_profile: :boot).transform_values do |graphql_type|
144
188
  @types_by_graphql_type[graphql_type]
145
189
  end
146
190
  end
@@ -156,6 +200,16 @@ module ElasticGraph
156
200
  end
157
201
  end.freeze
158
202
  end
203
+
204
+ def log_hidden_types
205
+ hidden_types = @types_by_name.values.select(&:hidden_from_queries?)
206
+ return if hidden_types.empty?
207
+
208
+ logger.warn(
209
+ "#{hidden_types.size} GraphQL types were hidden from the schema due to their backing indices being " \
210
+ "inaccessible: #{hidden_types.map(&:name).sort.join(", ")}"
211
+ )
212
+ end
159
213
  end
160
214
  end
161
215
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -8,6 +8,7 @@
8
8
 
9
9
  require "elastic_graph/datastore_core"
10
10
  require "elastic_graph/graphql/config"
11
+ require "elastic_graph/constants"
11
12
  require "elastic_graph/support/from_yaml_file"
12
13
 
13
14
  module ElasticGraph
@@ -26,7 +27,7 @@ module ElasticGraph
26
27
  def self.from_parsed_yaml(parsed_yaml, &datastore_client_customization_block)
27
28
  new(
28
29
  config: GraphQL::Config.from_parsed_yaml(parsed_yaml),
29
- datastore_core: DatastoreCore.from_parsed_yaml(parsed_yaml, for_context: :graphql, &datastore_client_customization_block)
30
+ datastore_core: DatastoreCore.from_parsed_yaml(parsed_yaml, &datastore_client_customization_block)
30
31
  )
31
32
  end
32
33
 
@@ -55,7 +56,7 @@ module ElasticGraph
55
56
 
56
57
  # Apply any extension modules that have been configured.
57
58
  @config.extension_modules.each { |mod| extend mod }
58
- @runtime_metadata.graphql_extension_modules.each { |ext_mod| extend ext_mod.extension_class }
59
+ @runtime_metadata.graphql_extension_modules.each { |ext_mod| extend ext_mod.load_extension.extension_class }
59
60
  end
60
61
 
61
62
  # @private
@@ -78,8 +79,7 @@ module ElasticGraph
78
79
  schema: schema,
79
80
  monotonic_clock: monotonic_clock,
80
81
  logger: logger,
81
- slow_query_threshold_ms: @config.slow_query_latency_warning_threshold_in_ms,
82
- datastore_search_router: datastore_search_router
82
+ slow_query_threshold_ms: @config.slow_query_latency_warning_threshold_in_ms
83
83
  )
84
84
  end
85
85
  end
@@ -88,26 +88,16 @@ module ElasticGraph
88
88
  def schema
89
89
  @schema ||= begin
90
90
  require "elastic_graph/graphql/schema"
91
-
92
91
  Schema.new(
93
92
  graphql_schema_string: graphql_schema_string,
94
93
  config: config,
94
+ logger: logger,
95
95
  runtime_metadata: runtime_metadata,
96
+ datastore_search_router: datastore_search_router,
96
97
  index_definitions_by_graphql_type: @datastore_core.index_definitions_by_graphql_type,
97
- graphql_gem_plugins: graphql_gem_plugins
98
- ) do |schema|
99
- @graphql_adapter || begin
100
- @schema = schema # assign this so that `#schema` returns the schema when `datastore_query_adapters` is called below
101
- require "elastic_graph/graphql/resolvers/graphql_adapter"
102
- Resolvers::GraphQLAdapter.new(
103
- schema: schema,
104
- datastore_query_builder: datastore_query_builder,
105
- datastore_query_adapters: datastore_query_adapters,
106
- runtime_metadata: runtime_metadata,
107
- resolvers: graphql_resolvers
108
- )
109
- end
110
- end
98
+ graphql_gem_plugins: graphql_gem_plugins,
99
+ graphql_adapter: graphql_adapter
100
+ )
111
101
  end
112
102
  end
113
103
 
@@ -128,7 +118,7 @@ module ElasticGraph
128
118
  def datastore_query_builder
129
119
  @datastore_query_builder ||= begin
130
120
  require "elastic_graph/graphql/datastore_query"
131
- DatastoreQuery::Builder.with(
121
+ DatastoreQuery::Builder.new(
132
122
  filter_interpreter:,
133
123
  filter_node_interpreter:,
134
124
  runtime_metadata:,
@@ -143,35 +133,57 @@ module ElasticGraph
143
133
  def graphql_gem_plugins
144
134
  @graphql_gem_plugins ||= begin
145
135
  require "graphql"
136
+ # As per https://graphql-ruby.org/language_tools/c_parser.html, loading the
137
+ # C parser causes the faster parser to be assigned as the `::GraphQL.default_parser`,
138
+ # providing greater efficiency.
139
+ #
140
+ # We load it here since this is where we load the GraphQL gem.
141
+ require "graphql/c_parser"
142
+
146
143
  {
147
144
  # We depend on this to avoid N+1 calls to the datastore.
148
145
  ::GraphQL::Dataloader => {},
149
- # This is new in the graphql-ruby 2.4 release, and will be required in the future.
150
- # We pass `preload: true` because the way we handle the schema depends on it being preloaded.
151
- ::GraphQL::Schema::Visibility => {preload: true}
146
+ # https://graphql-ruby.org/authorization/visibility.html
147
+ ::GraphQL::Schema::Visibility => {
148
+ # The GraphQL gem internally cache the visibility per profile. Ideally, we'd have only a single profile,
149
+ # but that makes for a chicken-and-egg problem: our visibility logic depends on the schema being fully
150
+ # loaded, but the `visibility?` methods are called while loading the schema. To deal with that, we're
151
+ # using tw profiles:
152
+ #
153
+ # - `boot` is used while loading, and skips our normal visibility logic.
154
+ # - `main` is used after that and uses our normal visibility logic.
155
+ profiles: {:boot => {}, VISIBILITY_PROFILE => {}} # : ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
156
+ }
152
157
  }
153
158
  end
154
159
  end
155
160
 
156
- # @private
157
- def graphql_resolvers
158
- @graphql_resolvers ||= begin
159
- require "elastic_graph/graphql/resolvers/get_record_field_value"
160
- require "elastic_graph/graphql/resolvers/list_records"
161
- require "elastic_graph/graphql/resolvers/nested_relationships"
162
-
163
- nested_relationships = Resolvers::NestedRelationships.new(
164
- schema_element_names: runtime_metadata.schema_element_names,
165
- logger: logger
166
- )
167
-
168
- list_records = Resolvers::ListRecords.new
161
+ def graphql_adapter
162
+ @graphql_adapter ||= begin
163
+ require "elastic_graph/graphql/resolvers/graphql_adapter_builder"
164
+ Resolvers::GraphQLAdapterBuilder.new(
165
+ named_resolvers: named_graphql_resolvers,
166
+ query_adapter: resolver_query_adapter,
167
+ runtime_metadata: runtime_metadata
168
+ ).build
169
+ end
170
+ end
169
171
 
170
- get_record_field_value = Resolvers::GetRecordFieldValue.new(
171
- schema_element_names: runtime_metadata.schema_element_names
172
+ def resolver_query_adapter
173
+ @resolver_query_adapter ||= begin
174
+ require "elastic_graph/graphql/resolvers/query_adapter"
175
+ Resolvers::QueryAdapter.new(
176
+ datastore_query_builder: datastore_query_builder,
177
+ datastore_query_adapters: datastore_query_adapters
172
178
  )
179
+ end
180
+ end
173
181
 
174
- [nested_relationships, list_records, get_record_field_value]
182
+ # @private
183
+ def named_graphql_resolvers
184
+ @named_graphql_resolvers ||= runtime_metadata.graphql_resolvers_by_name.transform_values do |resolver|
185
+ ext = resolver.load_resolver
186
+ (_ = ext.extension_class).new(elasticgraph_graphql: self, config: ext.config)
175
187
  end
176
188
  end
177
189
 
@@ -194,14 +206,13 @@ module ElasticGraph
194
206
  filter_node_interpreter: filter_node_interpreter
195
207
  ),
196
208
  GraphQL::QueryAdapter::Sort.new(order_by_arg_name: schema_element_names.order_by),
197
- Aggregation::QueryAdapter.new(
198
- schema: schema,
209
+ Aggregation::QueryAdapter::WithoutSchema.new(
199
210
  config: config,
200
211
  filter_args_translator: filter_args_translator,
201
212
  runtime_metadata: runtime_metadata,
202
213
  sub_aggregation_grouping_adapter: sub_aggregation_grouping_adapter
203
214
  ),
204
- GraphQL::QueryAdapter::RequestedFields.new(schema)
215
+ GraphQL::QueryAdapter::RequestedFields::WithoutSchema.new
205
216
  ]
206
217
  end
207
218
  end
@@ -252,6 +263,8 @@ module ElasticGraph
252
263
  # it's nice to load dependencies when needed.
253
264
  def load_dependencies_eagerly
254
265
  require "graphql"
266
+ require "graphql/c_parser"
267
+
255
268
  ::GraphQL.eager_load!
256
269
 
257
270
  # run a simple GraphQL query to force load any dependencies needed to handle GraphQL queries
@@ -8,7 +8,7 @@ updated_code_filename = "#{__dir__}/../../tmp/updated_valid_time_zones.rb"
8
8
  java_time_zones = `java --source 11 #{__dir__}/dump_time_zones.java`.split("\n")
9
9
 
10
10
  ::File.write(updated_code_filename, <<~EOS)
11
- # Copyright 2024 Block, Inc.
11
+ # Copyright 2024 - 2025 Block, Inc.
12
12
  #
13
13
  # Use of this source code is governed by an MIT-style
14
14
  # license that can be found in the LICENSE file or at