elasticgraph-indexer 1.0.2 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96459aae32411712ac8a1fe13289cf2e411a4ac8c7a38d2b868712380149fcf1
4
- data.tar.gz: d73d0c3420d55399b6e4111606ba8ba154a19067efb15eb0b006f30e86f664af
3
+ metadata.gz: 7a8d580572ec508d9df0131448f19398a5850aaee3cd1e36b4530be67bee190f
4
+ data.tar.gz: 7db60416bac8d71edad120569e8abfe55e2884b6844d140109e485b8c9d66d10
5
5
  SHA512:
6
- metadata.gz: ab47eea78b85a3e0e6252eed24cc775402be7527778a8cb15bde35c0b76451af057baab6b37b43c02957ee13138ff70e11078625bcb48834a050c3c1045cddea
7
- data.tar.gz: c47921bbfce1a736c714ad5f696c6692460b59c545a15af23a575a85c192e41ad27ef671fe7e4bb6309a2328f278b5a28a7b96dc781f13ef96adaa9f8c554e7e
6
+ metadata.gz: 14b344d1591976df42dd53257a03911e158f9913974c7c2e606c8ea1222989a6552154f615606d0447e97aebef4097f6710600a0af49f453e821c519c89ee146
7
+ data.tar.gz: 45d6e1a8c27040378857d7b0c7a7411b669c9f9c6e4ba31ee7e3307cfd71f48780c842e1f505d4d3b19e1c63fdfdcdee2db2dfb30b9e5feb40a122796a991dbe
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2024 - 2025 Block, Inc.
3
+ Copyright (c) 2024 - 2026 Block, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -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,9 +8,7 @@
8
8
 
9
9
  require "elastic_graph/constants"
10
10
  require "elastic_graph/errors"
11
- require "elastic_graph/datastore_core/index_config_normalizer"
12
11
  require "elastic_graph/indexer/event_id"
13
- require "elastic_graph/indexer/hash_differ"
14
12
  require "elastic_graph/indexer/indexing_failures_error"
15
13
  require "elastic_graph/support/threading"
16
14
 
@@ -18,38 +16,12 @@ module ElasticGraph
18
16
  class Indexer
19
17
  # Responsible for routing datastore indexing requests to the appropriate cluster and index.
20
18
  class DatastoreIndexingRouter
21
- # In this class, we internally cache the datastore mapping for an index definition, so that we don't have to
22
- # fetch the mapping from the datastore on each call to `bulk`. It rarely changes and ElasticGraph is designed so that
23
- # mapping updates are applied before deploying the indexer with a new mapping.
24
- #
25
- # However, if an engineer forgets to apply a mapping update before deploying, they'll run into "mappings are incomplete"
26
- # errors. They can updated the mapping to fix it, but the use of caching in this class could mean that the fix doesn't
27
- # necessarily work right away. The app would have to be deployed or restarted so that the caches are cleared. That could
28
- # be annoying.
29
- #
30
- # To address this issue, we're adding an expiration on the caching of the index mappings. Re-fetching the index
31
- # mapping once every few minutes is no big deal and will allow the indexer to recover on its own after a mapping
32
- # update has been applied without requiring a deploy or a restart.
33
- #
34
- # The expiration is a range so that, when we have many processes running, and they all started around the same time,
35
- # (say, after a deploy!), they don't all expire their caches in sync, leading to spiky load on the datastore. Instead,
36
- # the random distribution of expiration times will spread out the load.
37
- MAPPING_CACHE_MAX_AGE_IN_MS_RANGE = (5 * 60 * 1000)..(10 * 60 * 1000)
38
-
39
19
  def initialize(
40
20
  datastore_clients_by_name:,
41
- mappings_by_index_def_name:,
42
- monotonic_clock:,
43
21
  logger:
44
22
  )
45
23
  @datastore_clients_by_name = datastore_clients_by_name
46
24
  @logger = logger
47
- @monotonic_clock = monotonic_clock
48
- @cached_mappings = {}
49
-
50
- @mappings_by_index_def_name = mappings_by_index_def_name.transform_values do |mappings|
51
- DatastoreCore::IndexConfigNormalizer.normalize_mappings(mappings)
52
- end
53
25
  end
54
26
 
55
27
  # Proxies `client#bulk` by converting `operations` to their bulk
@@ -70,17 +42,11 @@ module ElasticGraph
70
42
  #
71
43
  # It is the caller's responsibility to deal with any returned failures as this method does not
72
44
  # raise an exception in that case.
73
- #
74
- # Note: before any operations are attempted, the datastore indices are validated for consistency
75
- # with the mappings we expect, meaning that no bulk operations will be attempted if that is not up-to-date.
76
45
  def bulk(operations, refresh: false)
77
- # Before writing these operations, verify their destination index mapping are consistent.
78
- validate_mapping_completeness_of!(:accessible_cluster_names_to_index_into, *operations.map(&:destination_index_def).uniq)
79
-
80
46
  ops_by_client = ::Hash.new { |h, k| h[k] = [] } # : ::Hash[DatastoreCore::_Client, ::Array[_Operation]]
81
47
  unsupported_ops = ::Set.new # : ::Set[_Operation]
82
48
 
83
- operations.reject { |op| op.to_datastore_bulk.empty? }.each do |op|
49
+ operations.each do |op|
84
50
  # Note: this intentionally does not use `accessible_cluster_names_to_index_into`.
85
51
  # We want to fail with clear error if any clusters are inaccessible instead of silently ignoring
86
52
  # the named cluster. The `IndexingFailuresError` provides a clear error.
@@ -290,108 +256,6 @@ module ElasticGraph
290
256
  "for document versions:\n\n#{failures.join("\n")}"
291
257
  end
292
258
  end
293
-
294
- # Queries the datastore mapping(s) for the given index definition(s) to verify that they are up-to-date
295
- # with our schema artifacts, raising an error if the datastore mappings are missing fields that we
296
- # expect. (Extra fields are allowed, though--we'll just ignore them).
297
- #
298
- # This is intended for use when you want a strong guarantee before proceeding that the indices are current,
299
- # such as before indexing data, or after applying index updates (to "prove" that everything is how it should
300
- # be).
301
- #
302
- # This correctly queries the datastore clusters specified via `index_into_clusters` in config,
303
- # but ignores clusters specified via `query_cluster` (since this isn't intended to be used as part
304
- # of the query flow).
305
- #
306
- # For a rollover template, this takes care of verifying the template itself and also any indices that originated
307
- # from the template.
308
- #
309
- # Note also that this caches the datastore mappings, since this is intended to be used to verify an index
310
- # before we index data into it, and we do not want to impose a huge performance penalty on that process (requiring
311
- # multiple datastore requests before we index each document...). In general, the index mapping only changes
312
- # when we make it change, and we deploy and restart ElasticGraph after any index mapping changes, so we do not
313
- # need to worry about it mutating during the lifetime of a single process (particularly given the expense of doing
314
- # so).
315
- def validate_mapping_completeness_of!(index_cluster_name_method, *index_definitions)
316
- diffs_by_cluster_and_index_name =
317
- index_definitions.reduce({}) do |accum, index_def| # $ ::Hash[[::String, ::String], ::String]
318
- accum.merge(mapping_diffs_for(index_def, index_cluster_name_method))
319
- end
320
-
321
- if diffs_by_cluster_and_index_name.any?
322
- formatted_diffs = diffs_by_cluster_and_index_name.map do |(cluster_name, index_name), diff|
323
- <<~EOS
324
- On cluster `#{cluster_name}` and index/template `#{index_name}`:
325
- #{diff}
326
- EOS
327
- end.join("\n\n")
328
-
329
- raise Errors::ConfigError, "Datastore index mappings are incomplete compared to the current schema. " \
330
- "The diff below uses the datastore index mapping as the base, and shows the expected mapping as a diff. " \
331
- "\n\n#{formatted_diffs}"
332
- end
333
- end
334
-
335
- private
336
-
337
- def mapping_diffs_for(index_definition, index_cluster_name_method)
338
- expected_mapping = @mappings_by_index_def_name.fetch(index_definition.name)
339
-
340
- index_definition.public_send(index_cluster_name_method).flat_map do |cluster_name|
341
- datastore_client = datastore_client_named(cluster_name)
342
-
343
- cached_mappings_for(index_definition, datastore_client).filter_map do |index, mapping_in_index|
344
- if (diff = HashDiffer.diff(mapping_in_index, expected_mapping, ignore_ops: [:-]))
345
- [[cluster_name, index.name], diff]
346
- end
347
- end
348
- end.to_h
349
- end
350
-
351
- def cached_mappings_for(index_definition, datastore_client)
352
- key = [datastore_client, index_definition] # : [DatastoreCore::_Client, DatastoreCore::indexDefinition]
353
- cached_mapping = @cached_mappings[key] ||= new_cached_mapping(fetch_mappings_from_datastore(index_definition, datastore_client))
354
-
355
- return cached_mapping.mappings if @monotonic_clock.now_in_ms < cached_mapping.expires_at
356
-
357
- begin
358
- fetch_mappings_from_datastore(index_definition, datastore_client).tap do |mappings|
359
- @logger.info "Mapping cache expired for #{index_definition.name}; cleared it from the cache and re-fetched the mapping."
360
- @cached_mappings[key] = new_cached_mapping(mappings)
361
- end
362
- rescue => e
363
- @logger.warn <<~EOS
364
- Mapping cache expired for #{index_definition.name}; attempted to re-fetch it but got an error[1]. Will continue using expired mapping information for now.
365
-
366
- [1] #{e.class}: #{e.message}
367
- #{e.backtrace.join("\n")}
368
- EOS
369
-
370
- # Update the cached mapping so that the expiration is reset.
371
- @cached_mappings[key] = new_cached_mapping(cached_mapping.mappings)
372
-
373
- cached_mapping.mappings
374
- end
375
- end
376
-
377
- def fetch_mappings_from_datastore(index_definition, datastore_client)
378
- # We need to also check any related indices...
379
- indices_to_check = [index_definition] + index_definition.related_rollover_indices(datastore_client)
380
-
381
- indices_to_check.to_h do |index|
382
- [index, index.mappings_in_datastore(datastore_client)]
383
- end
384
- end
385
-
386
- def new_cached_mapping(mappings)
387
- CachedMapping.new(mappings, @monotonic_clock.now_in_ms + rand(MAPPING_CACHE_MAX_AGE_IN_MS_RANGE).to_i)
388
- end
389
-
390
- def datastore_client_named(name)
391
- @datastore_clients_by_name.fetch(name)
392
- end
393
-
394
- CachedMapping = ::Data.define(:mappings, :expires_at)
395
259
  end
396
260
  end
397
261
  end
@@ -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
@@ -0,0 +1,41 @@
1
+ # Copyright 2024 - 2026 Block, Inc.
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+ #
7
+ # frozen_string_literal: true
8
+
9
+ require "elastic_graph/constants"
10
+ require "time"
11
+
12
+ module ElasticGraph
13
+ class Indexer
14
+ module IndexingPreparers
15
+ class DateTime
16
+ # Here we normalize DateTime strings to consistent 3-digit millisecond precision.
17
+ # This is critical for min/max value tracking which uses string comparison via
18
+ # Painless's `.compareTo()` method.
19
+ #
20
+ # Without consistent precision, string comparison produces incorrect results:
21
+ # "2025-12-19T04:15:47.53Z" vs "2025-12-19T04:15:47.531Z"
22
+ # ^ ^
23
+ # 'Z' (ASCII 90) > '1' (ASCII 49)
24
+ #
25
+ # This would incorrectly determine that `.53Z > .531Z`.
26
+ #
27
+ # By normalizing all DateTime values to 3-digit precision, we ensure:
28
+ # "2025-12-19T04:15:47.530Z" < "2025-12-19T04:15:47.531Z"
29
+ def self.prepare_for_indexing(value)
30
+ return value if value.nil?
31
+ time = ::Time.iso8601(value)
32
+ time.getutc.iso8601(DATE_TIME_PRECISION)
33
+ rescue ::ArgumentError, ::TypeError
34
+ # If the value is not a valid ISO8601 string, return it as-is
35
+ # and let the datastore reject it if necessary.
36
+ value
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -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
@@ -47,8 +47,6 @@ module ElasticGraph
47
47
  require "elastic_graph/indexer/datastore_indexing_router"
48
48
  DatastoreIndexingRouter.new(
49
49
  datastore_clients_by_name: datastore_core.clients_by_name,
50
- mappings_by_index_def_name: schema_artifacts.index_mappings_by_index_def_name,
51
- monotonic_clock: monotonic_clock,
52
50
  logger: datastore_core.logger
53
51
  )
54
52
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticgraph-indexer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Myron Marston
@@ -17,42 +17,42 @@ dependencies:
17
17
  requirements:
18
18
  - - '='
19
19
  - !ruby/object:Gem::Version
20
- version: 1.0.2
20
+ version: 1.1.0
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '='
26
26
  - !ruby/object:Gem::Version
27
- version: 1.0.2
27
+ version: 1.1.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: elasticgraph-schema_artifacts
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - '='
33
33
  - !ruby/object:Gem::Version
34
- version: 1.0.2
34
+ version: 1.1.0
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - '='
40
40
  - !ruby/object:Gem::Version
41
- version: 1.0.2
41
+ version: 1.1.0
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: elasticgraph-support
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - '='
47
47
  - !ruby/object:Gem::Version
48
- version: 1.0.2
48
+ version: 1.1.0
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - '='
54
54
  - !ruby/object:Gem::Version
55
- version: 1.0.2
55
+ version: 1.1.0
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: hashdiff
58
58
  requirement: !ruby/object:Gem::Requirement
@@ -79,56 +79,56 @@ dependencies:
79
79
  requirements:
80
80
  - - '='
81
81
  - !ruby/object:Gem::Version
82
- version: 1.0.2
82
+ version: 1.1.0
83
83
  type: :development
84
84
  prerelease: false
85
85
  version_requirements: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - '='
88
88
  - !ruby/object:Gem::Version
89
- version: 1.0.2
89
+ version: 1.1.0
90
90
  - !ruby/object:Gem::Dependency
91
91
  name: elasticgraph-elasticsearch
92
92
  requirement: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - '='
95
95
  - !ruby/object:Gem::Version
96
- version: 1.0.2
96
+ version: 1.1.0
97
97
  type: :development
98
98
  prerelease: false
99
99
  version_requirements: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - '='
102
102
  - !ruby/object:Gem::Version
103
- version: 1.0.2
103
+ version: 1.1.0
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: elasticgraph-opensearch
106
106
  requirement: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - '='
109
109
  - !ruby/object:Gem::Version
110
- version: 1.0.2
110
+ version: 1.1.0
111
111
  type: :development
112
112
  prerelease: false
113
113
  version_requirements: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - '='
116
116
  - !ruby/object:Gem::Version
117
- version: 1.0.2
117
+ version: 1.1.0
118
118
  - !ruby/object:Gem::Dependency
119
119
  name: elasticgraph-schema_definition
120
120
  requirement: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - '='
123
123
  - !ruby/object:Gem::Version
124
- version: 1.0.2
124
+ version: 1.1.0
125
125
  type: :development
126
126
  prerelease: false
127
127
  version_requirements: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - '='
130
130
  - !ruby/object:Gem::Version
131
- version: 1.0.2
131
+ version: 1.1.0
132
132
  email:
133
133
  - myron@squareup.com
134
134
  executables: []
@@ -144,6 +144,7 @@ files:
144
144
  - lib/elastic_graph/indexer/failed_event_error.rb
145
145
  - lib/elastic_graph/indexer/hash_differ.rb
146
146
  - lib/elastic_graph/indexer/indexing_failures_error.rb
147
+ - lib/elastic_graph/indexer/indexing_preparers/date_time.rb
147
148
  - lib/elastic_graph/indexer/indexing_preparers/integer.rb
148
149
  - lib/elastic_graph/indexer/indexing_preparers/no_op.rb
149
150
  - lib/elastic_graph/indexer/indexing_preparers/untyped.rb
@@ -160,10 +161,10 @@ licenses:
160
161
  - MIT
161
162
  metadata:
162
163
  bug_tracker_uri: https://github.com/block/elasticgraph/issues
163
- changelog_uri: https://github.com/block/elasticgraph/releases/tag/v1.0.2
164
- documentation_uri: https://block.github.io/elasticgraph/api-docs/v1.0.2/
164
+ changelog_uri: https://github.com/block/elasticgraph/releases/tag/v1.1.0
165
+ documentation_uri: https://block.github.io/elasticgraph/api-docs/v1.1.0/
165
166
  homepage_uri: https://block.github.io/elasticgraph/
166
- source_code_uri: https://github.com/block/elasticgraph/tree/v1.0.2/elasticgraph-indexer
167
+ source_code_uri: https://github.com/block/elasticgraph/tree/v1.1.0/elasticgraph-indexer
167
168
  gem_category: core
168
169
  rdoc_options: []
169
170
  require_paths:
@@ -175,14 +176,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
175
176
  version: '3.4'
176
177
  - - "<"
177
178
  - !ruby/object:Gem::Version
178
- version: '3.5'
179
+ version: '4.1'
179
180
  required_rubygems_version: !ruby/object:Gem::Requirement
180
181
  requirements:
181
182
  - - ">="
182
183
  - !ruby/object:Gem::Version
183
184
  version: '0'
184
185
  requirements: []
185
- rubygems_version: 3.6.9
186
+ rubygems_version: 4.0.3
186
187
  specification_version: 4
187
188
  summary: Indexes ElasticGraph data into a datastore.
188
189
  test_files: []