elasticgraph-schema_definition 0.19.2.2 → 0.19.3.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/lib/elastic_graph/schema_definition/api.rb +12 -5
  4. data/lib/elastic_graph/schema_definition/factory.rb +4 -4
  5. data/lib/elastic_graph/schema_definition/indexing/derived_fields/append_only_set.rb +1 -1
  6. data/lib/elastic_graph/schema_definition/indexing/derived_fields/field_initializer_support.rb +1 -1
  7. data/lib/elastic_graph/schema_definition/indexing/derived_fields/immutable_value.rb +1 -1
  8. data/lib/elastic_graph/schema_definition/indexing/derived_fields/min_or_max_value.rb +1 -1
  9. data/lib/elastic_graph/schema_definition/indexing/derived_indexed_type.rb +1 -1
  10. data/lib/elastic_graph/schema_definition/indexing/event_envelope.rb +1 -1
  11. data/lib/elastic_graph/schema_definition/indexing/field.rb +1 -1
  12. data/lib/elastic_graph/schema_definition/indexing/field_reference.rb +1 -1
  13. data/lib/elastic_graph/schema_definition/indexing/field_type/enum.rb +1 -1
  14. data/lib/elastic_graph/schema_definition/indexing/field_type/object.rb +1 -1
  15. data/lib/elastic_graph/schema_definition/indexing/field_type/scalar.rb +1 -1
  16. data/lib/elastic_graph/schema_definition/indexing/field_type/union.rb +1 -1
  17. data/lib/elastic_graph/schema_definition/indexing/index.rb +1 -1
  18. data/lib/elastic_graph/schema_definition/indexing/json_schema_field_metadata.rb +1 -1
  19. data/lib/elastic_graph/schema_definition/indexing/json_schema_with_metadata.rb +1 -1
  20. data/lib/elastic_graph/schema_definition/indexing/list_counts_mapping.rb +1 -1
  21. data/lib/elastic_graph/schema_definition/indexing/relationship_resolver.rb +1 -1
  22. data/lib/elastic_graph/schema_definition/indexing/rollover_config.rb +1 -1
  23. data/lib/elastic_graph/schema_definition/indexing/update_target_factory.rb +1 -1
  24. data/lib/elastic_graph/schema_definition/indexing/update_target_resolver.rb +1 -1
  25. data/lib/elastic_graph/schema_definition/json_schema_pruner.rb +1 -1
  26. data/lib/elastic_graph/schema_definition/mixins/can_be_graphql_only.rb +1 -1
  27. data/lib/elastic_graph/schema_definition/mixins/has_derived_graphql_type_customizations.rb +1 -1
  28. data/lib/elastic_graph/schema_definition/mixins/has_directives.rb +1 -1
  29. data/lib/elastic_graph/schema_definition/mixins/has_documentation.rb +1 -1
  30. data/lib/elastic_graph/schema_definition/mixins/has_indices.rb +17 -4
  31. data/lib/elastic_graph/schema_definition/mixins/has_readable_to_s_and_inspect.rb +1 -1
  32. data/lib/elastic_graph/schema_definition/mixins/has_subtypes.rb +1 -1
  33. data/lib/elastic_graph/schema_definition/mixins/has_type_info.rb +1 -1
  34. data/lib/elastic_graph/schema_definition/mixins/implements_interfaces.rb +13 -2
  35. data/lib/elastic_graph/schema_definition/mixins/supports_default_value.rb +1 -1
  36. data/lib/elastic_graph/schema_definition/mixins/supports_filtering_and_aggregation.rb +7 -7
  37. data/lib/elastic_graph/schema_definition/mixins/verifies_graphql_name.rb +1 -1
  38. data/lib/elastic_graph/schema_definition/rake_tasks.rb +1 -1
  39. data/lib/elastic_graph/schema_definition/results.rb +5 -5
  40. data/lib/elastic_graph/schema_definition/schema_artifact_manager.rb +1 -1
  41. data/lib/elastic_graph/schema_definition/schema_elements/argument.rb +1 -1
  42. data/lib/elastic_graph/schema_definition/schema_elements/built_in_types.rb +113 -5
  43. data/lib/elastic_graph/schema_definition/schema_elements/deprecated_element.rb +1 -1
  44. data/lib/elastic_graph/schema_definition/schema_elements/directive.rb +1 -1
  45. data/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb +1 -1
  46. data/lib/elastic_graph/schema_definition/schema_elements/enum_value.rb +1 -1
  47. data/lib/elastic_graph/schema_definition/schema_elements/enum_value_namer.rb +1 -1
  48. data/lib/elastic_graph/schema_definition/schema_elements/enums_for_indexed_types.rb +1 -1
  49. data/lib/elastic_graph/schema_definition/schema_elements/field.rb +94 -13
  50. data/lib/elastic_graph/schema_definition/schema_elements/field_path.rb +1 -1
  51. data/lib/elastic_graph/schema_definition/schema_elements/field_source.rb +1 -1
  52. data/lib/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rb +1 -1
  53. data/lib/elastic_graph/schema_definition/schema_elements/input_field.rb +1 -1
  54. data/lib/elastic_graph/schema_definition/schema_elements/input_type.rb +1 -1
  55. data/lib/elastic_graph/schema_definition/schema_elements/interface_type.rb +1 -1
  56. data/lib/elastic_graph/schema_definition/schema_elements/list_counts_state.rb +1 -1
  57. data/lib/elastic_graph/schema_definition/schema_elements/object_type.rb +1 -1
  58. data/lib/elastic_graph/schema_definition/schema_elements/relationship.rb +1 -1
  59. data/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb +1 -1
  60. data/lib/elastic_graph/schema_definition/schema_elements/sort_order_enum_value.rb +1 -1
  61. data/lib/elastic_graph/schema_definition/schema_elements/sub_aggregation_path.rb +1 -1
  62. data/lib/elastic_graph/schema_definition/schema_elements/type_namer.rb +1 -1
  63. data/lib/elastic_graph/schema_definition/schema_elements/type_reference.rb +10 -7
  64. data/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb +26 -7
  65. data/lib/elastic_graph/schema_definition/schema_elements/union_type.rb +1 -1
  66. data/lib/elastic_graph/schema_definition/scripting/file_system_repository.rb +1 -1
  67. data/lib/elastic_graph/schema_definition/scripting/script.rb +1 -1
  68. data/lib/elastic_graph/schema_definition/state.rb +1 -1
  69. data/lib/elastic_graph/schema_definition/test_support.rb +1 -1
  70. metadata +26 -26
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -152,6 +152,15 @@ module ElasticGraph
152
152
  # GeoLocation
153
153
  # : Geographic coordinates representing a location on the Earth's surface.
154
154
  #
155
+ # GeoTile
156
+ # : A tile in the Web Mercator XYZ grid. Its coordinates identify a cell, not a fixed ground distance.
157
+ #
158
+ # GeoTileBounds
159
+ # : The geographic bounds of a Web Mercator XYZ tile. Tiles do not cross the antimeridian.
160
+ #
161
+ # GeoTileCorners
162
+ # : The geographic locations of the four corners of a Web Mercator XYZ tile.
163
+ #
155
164
  # PageInfo
156
165
  # : Provides information about the specific fetched page. This implements the
157
166
  # `PageInfo` specification from the [Relay GraphQL Cursor Connections
@@ -180,6 +189,7 @@ module ElasticGraph
180
189
  register_custom_elastic_graph_scalars
181
190
  register_enum_types
182
191
  register_date_and_time_grouped_by_types
192
+ register_geo_grouped_by_types
183
193
  register_standard_elastic_graph_types
184
194
  register_standard_graphql_resolvers
185
195
  end
@@ -394,7 +404,7 @@ module ElasticGraph
394
404
  # to go from non-null to null, but is not a breaking change to make it non-null
395
405
  # in the future.
396
406
  register_framework_object_type "PageInfo" do |t|
397
- t.default_graphql_resolver = :object_without_lookahead
407
+ t.resolve_fields_with :object_without_lookahead
398
408
 
399
409
  t.documentation <<~EOS
400
410
  Provides information about the specific fetched page. This implements the `PageInfo`
@@ -512,7 +522,7 @@ module ElasticGraph
512
522
 
513
523
  register_framework_object_type "AggregationCountDetail" do |t|
514
524
  t.documentation "Provides detail about an aggregation `#{names.count}`."
515
- t.default_graphql_resolver = :object_without_lookahead
525
+ t.resolve_fields_with :object_without_lookahead
516
526
 
517
527
  t.field names.approximate_value, "JsonSafeLong!", graphql_only: true do |f|
518
528
  f.documentation <<~EOS
@@ -1294,7 +1304,7 @@ module ElasticGraph
1294
1304
  date = schema_def_state.type_ref("Date")
1295
1305
  register_framework_object_type date.as_grouped_by.name do |t|
1296
1306
  t.documentation "Allows for grouping `Date` values based on the desired return type."
1297
- t.default_graphql_resolver = :object_with_lookahead
1307
+ t.resolve_fields_with :object_with_lookahead
1298
1308
  t.override_runtime_metadata(elasticgraph_category: :date_grouped_by_object)
1299
1309
 
1300
1310
  t.field names.as_date, "Date", graphql_only: true do |f|
@@ -1312,7 +1322,7 @@ module ElasticGraph
1312
1322
  date_time = schema_def_state.type_ref("DateTime")
1313
1323
  register_framework_object_type date_time.as_grouped_by.name do |t|
1314
1324
  t.documentation "Allows for grouping `DateTime` values based on the desired return type."
1315
- t.default_graphql_resolver = :object_with_lookahead
1325
+ t.resolve_fields_with :object_with_lookahead
1316
1326
  t.override_runtime_metadata(elasticgraph_category: :date_grouped_by_object)
1317
1327
 
1318
1328
  t.field names.as_date_time, "DateTime", graphql_only: true do |f|
@@ -1369,7 +1379,105 @@ module ElasticGraph
1369
1379
  end
1370
1380
  end
1371
1381
 
1382
+ # Registers the geographic grouping operation and its tile, bounds, and corner output types.
1383
+ def register_geo_grouped_by_types
1384
+ register_framework_object_type schema_def_state.type_ref("GeoLocation").as_grouped_by.name do |t|
1385
+ t.documentation "Offers geographic grouping representations for a location."
1386
+ t.resolve_fields_with :object_with_lookahead
1387
+ t.override_runtime_metadata(elasticgraph_category: :geo_location_grouped_by_object)
1388
+
1389
+ t.field names.as_geo_tile, "GeoTile", graphql_only: true do |f|
1390
+ f.resolve_with :as_geo_tile
1391
+
1392
+ f.documentation <<~EOS
1393
+ Groups locations into Web Mercator [XYZ tiles](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames). Missing locations produce a `null` tile.
1394
+ Points beyond ±85.0511° latitude are grouped into the northernmost or southernmost row,
1395
+ so a point can be outside the bounds of the tile it is grouped into.
1396
+ Boundary points are assigned to the tile to the east or south, subject to datastore coordinate encoding.
1397
+ EOS
1398
+
1399
+ f.argument names.zoom_level, "Int!" do |a|
1400
+ a.documentation <<~EOS
1401
+ The Web Mercator zoom level, from #{GEO_TILE_ZOOM_LEVELS.min} through #{GEO_TILE_ZOOM_LEVELS.max}.
1402
+ At zoom level z, the map has 2^z rows and 2^z columns. Increasing the zoom level by 1
1403
+ doubles both, producing four times as many tiles.
1404
+ EOS
1405
+ a.default 7
1406
+ end
1407
+ end
1408
+ end
1409
+
1410
+ register_framework_object_type "GeoTile" do |t|
1411
+ t.documentation "A tile in the Web Mercator XYZ grid. Its coordinates identify a cell, not a fixed ground distance."
1412
+ t.resolve_fields_with :object_without_lookahead
1413
+
1414
+ t.field names.zoom_level, "Int!", graphql_only: true do |f|
1415
+ f.documentation "The zoom level of the tile, from #{GEO_TILE_ZOOM_LEVELS.min} through #{GEO_TILE_ZOOM_LEVELS.max}."
1416
+ end
1417
+
1418
+ {names.x => "west to east", names.y => "north to south"}.each do |name, direction|
1419
+ t.field name, "Int!", graphql_only: true do |f|
1420
+ f.documentation "The zero-based tile coordinate, counted #{direction}, from 0 through 2^zoomLevel - 1."
1421
+ end
1422
+ end
1423
+
1424
+ t.field names.map_center_point, "GeoLocation!", graphql_only: true do |f|
1425
+ f.documentation <<~EOS
1426
+ The center of the tile as drawn on a Web Mercator map, not the average record location or the midpoint
1427
+ of the tile's latitude bounds. These differ noticeably only at low zoom levels: for tile 1/0/0,
1428
+ the map center point is at about 66.51° latitude, but the bounds midpoint is at about 42.53°.
1429
+ EOS
1430
+ end
1431
+
1432
+ t.field names.bounds, "GeoTileBounds!", graphql_only: true do |f|
1433
+ f.documentation "The geographic bounds of the tile. Web Mercator excludes the poles and stops at approximately ±85.0511°."
1434
+ end
1435
+
1436
+ t.field names.corners, "GeoTileCorners!", graphql_only: true do |f|
1437
+ f.documentation "The four corners of the tile, calculated from its geographic bounds."
1438
+ end
1439
+ end
1440
+
1441
+ register_framework_object_type "GeoTileBounds" do |t|
1442
+ t.resolve_fields_with :object_without_lookahead
1443
+ t.documentation "The geographic bounds of a Web Mercator XYZ tile. Tiles do not cross the antimeridian."
1444
+
1445
+ {
1446
+ names.north => "latitude of the northern",
1447
+ names.south => "latitude of the southern",
1448
+ names.east => "longitude of the eastern",
1449
+ names.west => "longitude of the western"
1450
+ }.each do |name, description|
1451
+ t.field name, "Float!", graphql_only: true do |f|
1452
+ f.documentation "The #{description} boundary of the tile."
1453
+ end
1454
+ end
1455
+ end
1456
+
1457
+ register_framework_object_type "GeoTileCorners" do |t|
1458
+ t.resolve_fields_with :object_without_lookahead
1459
+ t.documentation "The geographic locations of the four corners of a Web Mercator XYZ tile."
1460
+
1461
+ {
1462
+ names.northwest => "northwest",
1463
+ names.northeast => "northeast",
1464
+ names.southwest => "southwest",
1465
+ names.southeast => "southeast"
1466
+ }.each do |name, description|
1467
+ t.field name, "GeoLocation!", graphql_only: true do |f|
1468
+ f.documentation "The #{description} corner of the tile."
1469
+ end
1470
+ end
1471
+ end
1472
+ end
1473
+
1372
1474
  def register_standard_graphql_resolvers
1475
+ require(require_path = "elastic_graph/graphql/aggregation/resolvers/as_geo_tile")
1476
+ schema_def_api.register_graphql_resolver :as_geo_tile,
1477
+ GraphQL::Aggregation::Resolvers::AsGeoTile,
1478
+ defined_at: require_path,
1479
+ built_in: true
1480
+
1373
1481
  require(require_path = "elastic_graph/graphql/resolvers/get_record_field_value")
1374
1482
  schema_def_api.register_graphql_resolver :get_record_field_value,
1375
1483
  GraphQL::Resolvers::GetRecordFieldValue,
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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 "delegate"
10
10
  require "elastic_graph/constants"
11
+ require "elastic_graph/schema_artifacts/runtime_metadata/configured_graphql_resolver"
11
12
  require "elastic_graph/schema_definition/indexing/field"
12
13
  require "elastic_graph/schema_definition/indexing/field_reference"
13
14
  require "elastic_graph/schema_definition/mixins/has_directives"
@@ -80,8 +81,6 @@ module ElasticGraph
80
81
  # @private
81
82
  # @!attribute [rw] non_nullable_in_json_schema
82
83
  # @private
83
- # @!attribute [rw] backing_indexing_field
84
- # @private
85
84
  # @!attribute [rw] as_input
86
85
  # @private
87
86
  # @!attribute [rw] legacy_grouping_schema
@@ -91,7 +90,7 @@ module ElasticGraph
91
90
  :filter_customizations, :grouped_by_customizations, :sub_aggregations_customizations,
92
91
  :aggregated_values_customizations, :sort_order_enum_value_customizations,
93
92
  :args, :sortable, :filterable, :aggregatable, :groupable, :graphql_only, :source, :runtime_field_script, :relationship, :singular_name,
94
- :computation_detail, :non_nullable_in_json_schema, :backing_indexing_field, :as_input,
93
+ :computation_detail, :non_nullable_in_json_schema, :as_input,
95
94
  :legacy_grouping_schema, :name_in_index, :resolver
96
95
  )
97
96
  include Mixins::HasDocumentation
@@ -105,7 +104,7 @@ module ElasticGraph
105
104
  accuracy_confidence: :high, name_in_index: name,
106
105
  type_for_derived_types: nil, graphql_only: nil, singular: nil,
107
106
  sortable: nil, filterable: nil, aggregatable: nil, groupable: nil,
108
- backing_indexing_field: nil, as_input: false, legacy_grouping_schema: false, resolver: nil
107
+ as_input: false, legacy_grouping_schema: false, resolver: nil
109
108
  )
110
109
  type_ref = schema_def_state.type_ref(type)
111
110
  super(
@@ -134,20 +133,33 @@ module ElasticGraph
134
133
  singular_name: singular,
135
134
  name_in_index: name_in_index,
136
135
  non_nullable_in_json_schema: false,
137
- backing_indexing_field: backing_indexing_field,
138
136
  as_input: as_input,
139
137
  legacy_grouping_schema: legacy_grouping_schema,
140
138
  resolver: resolver
141
139
  )
142
140
 
143
- if name != name_in_index && name_in_index.include?(".") && !graphql_only
144
- raise Errors::SchemaError, "#{self} has an invalid `name_in_index`: #{name_in_index.inspect}. Only `graphql_only: true` fields can have a `name_in_index` that references a child field."
141
+ if name != name_in_index
142
+ if graphql_only
143
+ schema_def_state.after_user_definition_complete do
144
+ unless backing_indexing_field
145
+ raise Errors::SchemaError,
146
+ "GraphQL-only field `#{parent_type.name}.#{name}` has a `name_in_index` (#{name_in_index}) which does not reference an " \
147
+ "existing indexing field. To proceed, remove `graphql_only: true` or update `name_in_index` to match an existing indexing field."
148
+ end
149
+ end
150
+ elsif name_in_index.include?(".")
151
+ raise Errors::SchemaError,
152
+ "#{self} has an invalid `name_in_index`: #{name_in_index.inspect}. " \
153
+ "Only `graphql_only: true` fields can have a `name_in_index` that references a child field."
154
+ end
145
155
  end
146
156
 
147
157
  schema_def_state.register_user_defined_field(self)
148
158
  yield self if block_given?
149
159
  end
150
160
 
161
+ private :resolver=
162
+
151
163
  # @private
152
164
  @@initialize_param_names = instance_method(:initialize).parameters.map(&:last).to_set
153
165
 
@@ -448,6 +460,46 @@ module ElasticGraph
448
460
  )
449
461
  end
450
462
 
463
+ # Configures the GraphQL resolver used to resolve this field. If not set, the resolver configured on the parent type
464
+ # via {Mixins::HasIndices#resolve_fields_with} will be used.
465
+ #
466
+ # @param resolver_name [Symbol] name of the GraphQL resolver
467
+ # @param config [Hash<Symbol, Object>] configuration parameters for the resolver
468
+ # @return [void]
469
+ # @see API#register_graphql_resolver
470
+ #
471
+ # @example Use a custom resolver for a custom `Query` field
472
+ # # In `add_resolver.rb`:
473
+ # class AddResolver
474
+ # def initialize(elasticgraph_graphql:, config:)
475
+ # @multiplier = config.fetch(:multiplier, 1)
476
+ # end
477
+ #
478
+ # def resolve(field:, object:, args:, context:)
479
+ # sum = args.fetch("x") + args.fetch("y")
480
+ # sum * @multiplier
481
+ # end
482
+ # end
483
+ #
484
+ # # In `config/schema.rb`:
485
+ # ElasticGraph.define_schema do |schema|
486
+ # require(resolver_path = "add_resolver")
487
+ # schema.register_graphql_resolver :add, AddResolver, defined_at: resolver_path
488
+ #
489
+ # schema.on_root_query_type do |t|
490
+ # t.field "add", "Int" do |f|
491
+ # f.argument "x", "Int!"
492
+ # f.argument "y", "Int!"
493
+ #
494
+ # # Extra args (`multiplier: 2`, in this example) are passed to the resolver within `config`.
495
+ # f.resolve_with :add, multiplier: 2
496
+ # end
497
+ # end
498
+ # end
499
+ def resolve_with(resolver_name, **config)
500
+ self.resolver = resolver_name&.then { |name| SchemaArtifacts::RuntimeMetadata::ConfiguredGraphQLResolver.new(name, config) }
501
+ end
502
+
451
503
  # @private
452
504
  def runtime_script(script)
453
505
  self.runtime_field_script = script
@@ -557,13 +609,14 @@ module ElasticGraph
557
609
  # instead.
558
610
  return false if parent_type.indexed? && name == "id"
559
611
 
560
- return false if relationship || type.fully_unwrapped.as_object_type&.does_not_support?(&:groupable?)
612
+ return false if relationship || type_disallows_grouping?
561
613
 
562
614
  # We don't support grouping an entire list of values, but we do support grouping on individual values in a list.
563
615
  # However, we only do so when a `singular_name` has been provided (so that we know what to call the grouped_by field).
564
616
  # The semantics are a little odd (since one document can be duplicated in multiple grouping buckets) so we're ok
565
617
  # with not offering it by default--the user has to opt-in by telling us what to call the field in its singular form.
566
- return list_field_groupable_by_single_values? if type.list? && type.fully_unwrapped.leaf?
618
+ # Types with their own `GroupedBy` type are grouped as individual values, like scalars.
619
+ return list_field_groupable_by_single_values? if type.list? && (type.fully_unwrapped.leaf? || original_type.fully_unwrapped.grouped_by_type_defined?)
567
620
 
568
621
  # Nested fields will be supported through specific nested aggregation support, and do not
569
622
  # work as expected when grouping on the root document type.
@@ -678,11 +731,13 @@ module ElasticGraph
678
731
  end
679
732
  end
680
733
 
734
+ # Returns the GraphQL type used for this field's grouped value.
681
735
  # @private
682
736
  def grouped_by_field_type_name
683
737
  unwrapped_type = type_for_derived_types.fully_unwrapped
684
- if unwrapped_type.scalar_type_needing_grouped_by_object? && !legacy_grouping_schema
685
- unwrapped_type.with_reverted_override.as_grouped_by.name
738
+ original_unwrapped_type = original_type_for_derived_types.fully_unwrapped
739
+ if original_unwrapped_type.grouped_by_type_defined? && !legacy_grouping_schema
740
+ original_unwrapped_type.as_grouped_by.name
686
741
  elsif unwrapped_type.leaf?
687
742
  unwrapped_type.name
688
743
  else
@@ -702,7 +757,7 @@ module ElasticGraph
702
757
  "The `#{name}` field value for this group",
703
758
  list_field_grouped_by_doc_note("the selected subfields of `#{name}`")
704
759
  )
705
- elsif type_for_derived_types.fully_unwrapped.scalar_type_needing_grouped_by_object? && !legacy_grouping_schema
760
+ elsif original_type_for_derived_types.fully_unwrapped.grouped_by_type_defined? && !legacy_grouping_schema
706
761
  derived_documentation("Offers the different grouping options for the `#{name}` value within this group")
707
762
  else
708
763
  derived_documentation("The `#{name}` field value for this group")
@@ -934,6 +989,27 @@ module ElasticGraph
934
989
  )
935
990
  end
936
991
 
992
+ # The alternate field that is backing this field in the datastore index. Will only be non-`nil` for `graphql_only` fields.
993
+ # @return [Field, nil] the field backing this field in the index
994
+ #
995
+ # @private
996
+ def backing_indexing_field
997
+ return nil unless graphql_only
998
+
999
+ type = parent_type
1000
+ field = nil
1001
+
1002
+ name_in_index.split(".").each do |path_part|
1003
+ if (field = type&.indexing_fields_by_name_in_index&.fetch(path_part, nil))
1004
+ type = field.type.fully_unwrapped.as_object_type
1005
+ else
1006
+ return nil
1007
+ end
1008
+ end
1009
+
1010
+ field
1011
+ end
1012
+
937
1013
  private
938
1014
 
939
1015
  def args_sdl(joiner:, after_opening_paren: "", &arg_selector)
@@ -943,6 +1019,11 @@ module ElasticGraph
943
1019
  "(#{after_opening_paren}#{args_sdl})"
944
1020
  end
945
1021
 
1022
+ # Identifies types that disallow grouping, except for types that define their own `GroupedBy` type.
1023
+ def type_disallows_grouping?
1024
+ !original_type.fully_unwrapped.grouped_by_type_defined? && !!type.fully_unwrapped.as_object_type&.does_not_support?(&:groupable?)
1025
+ end
1026
+
946
1027
  # Indicates if the field uses the `text` mapping type.
947
1028
  def text?
948
1029
  mapping_type == "text"
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -153,12 +153,15 @@ module ElasticGraph
153
153
  .sub(/[\]!]+\z/, "") # strip `]` and `!` characters from the end: https://rubular.com/r/pC8C0i7EpvHDbf
154
154
  end
155
155
 
156
- # Generally speaking, scalar types have `grouped_by` fields which are scalars of the same types,
157
- # and object types have `grouped_by` fields which are special `[object_type]GroupedBy` types.
158
- #
159
- # ...except for some special cases (Date and DateTime), which this predicate detects.
160
- def scalar_type_needing_grouped_by_object?
161
- %w[Date DateTime].include?(type_namer.revert_override_for(name))
156
+ # Indicates if this type opts into specialized grouping by defining its own `[type]GroupedBy` type.
157
+ # Use the original type name, before overrides, since an overridden name may identify another type.
158
+ # Only call this after the user definition is complete, when all grouping types are known.
159
+ def grouped_by_type_defined?
160
+ unless schema_def_state.user_definition_complete
161
+ raise Errors::SchemaError, "Cannot call `grouped_by_type_defined?` until the schema definition is complete."
162
+ end
163
+
164
+ schema_def_state.types_by_name.key?(as_grouped_by.name)
162
165
  end
163
166
 
164
167
  # Returns a new `TypeReference` with any type name overrides reverted (to provide the "original" type name).
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -279,6 +279,12 @@ module ElasticGraph
279
279
  # differently named field in the index.
280
280
  # @param singular [String] indicates what the singular form of a field's name is. When provided, ElasticGraph will define a
281
281
  # `groupedBy` field (using the singular form) allowing clients to group by individual values from the field.
282
+ # @param aggregatable [Boolean] force-enables or disables the ability for aggregation queries to aggregate over this field.
283
+ # When not provided, ElasticGraph will infer field aggregatability based on the field's GraphQL type and mapping type.
284
+ # @param filterable [Boolean] force-enables or disables the ability for queries to filter by this field. When not provided,
285
+ # ElasticGraph will infer field filterability based on the field's GraphQL type and mapping type.
286
+ # @param groupable [Boolean] force-enables or disables the ability for aggregation queries to group by this field. When
287
+ # not provided, ElasticGraph will infer field groupability based on the field's GraphQL type and mapping type.
282
288
  # @yield [Field] the field for further customization
283
289
  # @return [void]
284
290
  #
@@ -295,24 +301,37 @@ module ElasticGraph
295
301
  # t.index "authors"
296
302
  # end
297
303
  # end
298
- def paginated_collection_field(name, element_type, name_in_index: name, singular: nil, &block)
304
+ def paginated_collection_field(
305
+ name,
306
+ element_type,
307
+ name_in_index: name,
308
+ graphql_only: false,
309
+ singular: nil,
310
+ groupable: !!singular,
311
+ filterable: nil,
312
+ aggregatable: nil,
313
+ &block
314
+ )
299
315
  element_type_ref = schema_def_state.type_ref(element_type).to_final_form
300
316
  element_type = element_type_ref.name
301
317
 
302
318
  schema_def_state.paginated_collection_element_types << element_type
303
319
 
304
- backing_indexing_field = field(name, "[#{element_type}!]!", indexing_only: true, name_in_index: name_in_index, &block)
320
+ unless graphql_only
321
+ field(name, "[#{element_type}!]!", indexing_only: true, name_in_index: name_in_index, &block)
322
+ end
305
323
 
306
324
  field(
307
325
  name,
308
326
  element_type_ref.as_connection.name,
309
327
  name_in_index: name_in_index,
310
328
  type_for_derived_types: "[#{element_type}]",
311
- groupable: !!singular,
312
- sortable: false,
313
329
  graphql_only: true,
314
330
  singular: singular,
315
- backing_indexing_field: backing_indexing_field
331
+ groupable: groupable,
332
+ filterable: filterable,
333
+ aggregatable: aggregatable,
334
+ sortable: false
316
335
  ) do |f|
317
336
  f.define_relay_pagination_arguments!
318
337
  block&.call(f)
@@ -536,7 +555,7 @@ module ElasticGraph
536
555
  )
537
556
 
538
557
  field.relationship = relationship
539
- field.resolver = :nested_relationships
558
+ field.resolve_with :nested_relationships
540
559
 
541
560
  yield relationship if block_given?
542
561
 
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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
@@ -1,4 +1,4 @@
1
- # Copyright 2024 - 2025 Block, Inc.
1
+ # Copyright 2024 - 2026 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