noiseless 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/lib/noiseless/adapter.rb +2 -2
- data/lib/noiseless/adapters/execution_modules/http_transport.rb +38 -2
- data/lib/noiseless/adapters/execution_modules/postgresql_execution.rb +55 -63
- data/lib/noiseless/adapters/execution_modules/typesense_execution.rb +1 -2
- data/lib/noiseless/connection_manager.rb +5 -3
- data/lib/noiseless/query_builder.rb +3 -3
- data/lib/noiseless/response/empty.rb +1 -1
- data/lib/noiseless/response/results.rb +3 -1
- data/lib/noiseless/version.rb +1 -1
- data/lib/noiseless.rb +3 -1
- data/lib/tasks/release.rake +1 -3
- metadata +23 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b661712a5930c3da3a455f6e70c2d99cfbc424cf4e007c58abd587ab170854f
|
|
4
|
+
data.tar.gz: 547b60b50d5efb9b32da878523d96d5a58c5a7a645ab8bed13ebec40269370b7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17a625f20fb2c879f427d429c82ecb4b9c2b338ffec03bd69de83ee9c2b800800c57eaef0bb6f4d2e32876e91497c5b12bdf389a2f1edcb055bc228764b03473
|
|
7
|
+
data.tar.gz: 4799c4ba712b8d097b30553ac800caa798349d70c0ecbc7ca901b3f54752a9ed48ce2fcb4d3784d76117c0b7598801efc6f9e3685c5c945e34639740970a8d2e
|
data/lib/noiseless/adapter.rb
CHANGED
|
@@ -359,8 +359,8 @@ module Noiseless
|
|
|
359
359
|
end
|
|
360
360
|
|
|
361
361
|
def build_aggregations_hash(aggregations)
|
|
362
|
-
aggregations.
|
|
363
|
-
|
|
362
|
+
aggregations.to_h do |agg|
|
|
363
|
+
[agg.name, build_single_aggregation(agg)]
|
|
364
364
|
end
|
|
365
365
|
end
|
|
366
366
|
|
|
@@ -30,11 +30,27 @@ module Noiseless
|
|
|
30
30
|
# +timeout:+.
|
|
31
31
|
DEFAULT_TIMEOUT = 5
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# Default wall-clock deadline (seconds) for a complete round-trip:
|
|
34
|
+
# request write, response headers, and full body. The idle timeout
|
|
35
|
+
# above never trips against a backend that keeps trickling bytes —
|
|
36
|
+
# each read makes "progress" — which leaves callers blocked in
|
|
37
|
+
# Sync { ... .wait } indefinitely. This caps total duration instead.
|
|
38
|
+
# Override per-connection with +request_timeout:+; nil disables the
|
|
39
|
+
# deadline (long bulk imports may need a higher value or nil).
|
|
40
|
+
DEFAULT_REQUEST_TIMEOUT = 30
|
|
41
|
+
|
|
42
|
+
BufferedResponse = Struct.new(:status, :body) do
|
|
43
|
+
def read = body
|
|
44
|
+
def success? = (200..299).cover?(status)
|
|
45
|
+
def close = nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(hosts: [], timeout: DEFAULT_TIMEOUT, request_timeout: DEFAULT_REQUEST_TIMEOUT, **connection_params)
|
|
34
49
|
# Ensure we always have at least one host
|
|
35
50
|
hosts_array = Array(hosts)
|
|
36
51
|
@hosts = hosts_array.empty? ? ["http://localhost:#{default_port}"] : hosts_array
|
|
37
52
|
@timeout = timeout
|
|
53
|
+
@request_timeout = request_timeout
|
|
38
54
|
@connection_params = connection_params
|
|
39
55
|
|
|
40
56
|
# Initialize HTTP clients for each host. The endpoint timeout makes a
|
|
@@ -95,7 +111,27 @@ module Noiseless
|
|
|
95
111
|
host = @hosts.sample
|
|
96
112
|
client = @clients[host]
|
|
97
113
|
|
|
98
|
-
wrap_transport_errors(host: host)
|
|
114
|
+
wrap_transport_errors(host: host) do
|
|
115
|
+
with_request_deadline(host: host) do
|
|
116
|
+
buffer_response(yield(client))
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def with_request_deadline(host:, &)
|
|
122
|
+
task = @request_timeout && Async::Task.current?
|
|
123
|
+
return yield unless task
|
|
124
|
+
|
|
125
|
+
task.with_timeout(@request_timeout, &)
|
|
126
|
+
rescue Async::TimeoutError
|
|
127
|
+
raise Noiseless::ConnectionError,
|
|
128
|
+
"search backend at #{host} exceeded request_timeout (#{@request_timeout}s wall-clock)"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def buffer_response(response)
|
|
132
|
+
BufferedResponse.new(response.status, response.read)
|
|
133
|
+
ensure
|
|
134
|
+
response.close
|
|
99
135
|
end
|
|
100
136
|
|
|
101
137
|
def parse_json_response!(response, error_class: Noiseless::RequestError, context: nil)
|
|
@@ -24,9 +24,10 @@ module Noiseless
|
|
|
24
24
|
return execute_vector_search(model, query_hash) if query_hash[:vector]
|
|
25
25
|
|
|
26
26
|
scope = build_search_scope(model, query_hash)
|
|
27
|
-
|
|
27
|
+
total = scope.except(:order, :limit, :offset).count
|
|
28
|
+
records = apply_pagination(scope, query_hash[:paginate]).to_a
|
|
28
29
|
|
|
29
|
-
format_as_search_response(records, model)
|
|
30
|
+
format_as_search_response(records, model, total: total)
|
|
30
31
|
rescue StandardError => e
|
|
31
32
|
error_response(e)
|
|
32
33
|
end
|
|
@@ -39,7 +40,7 @@ module Noiseless
|
|
|
39
40
|
scope = model.all
|
|
40
41
|
|
|
41
42
|
# Apply any filters first
|
|
42
|
-
scope = apply_filter_clauses(scope, query_hash[:bool]&.filter || [])
|
|
43
|
+
scope = apply_filter_clauses(scope, query_hash[:bool]&.filter || [], model)
|
|
43
44
|
|
|
44
45
|
# Apply vector search
|
|
45
46
|
scope = vector_search(
|
|
@@ -104,43 +105,20 @@ module Noiseless
|
|
|
104
105
|
false
|
|
105
106
|
end
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
record.save!
|
|
114
|
-
|
|
115
|
-
{ "_index" => index, "_id" => id, "result" => record.previously_new_record? ? "created" : "updated" }
|
|
116
|
-
rescue StandardError => e
|
|
117
|
-
{ "_index" => index, "_id" => id, "result" => "error", "error" => e.message }
|
|
108
|
+
# Document writes are no-ops: the table IS the index, so queries always
|
|
109
|
+
# see current data. Writing indexed documents (which may be transformed
|
|
110
|
+
# by mappings) back into source rows would corrupt them, and deleting a
|
|
111
|
+
# record because its index entry was removed inverts ownership.
|
|
112
|
+
def execute_index_document(index, id, _document, **)
|
|
113
|
+
{ "_index" => index, "_id" => id, "result" => "noop" }
|
|
118
114
|
end
|
|
119
115
|
|
|
120
|
-
def execute_update_document(index, id,
|
|
121
|
-
|
|
122
|
-
return { "_id" => id, "result" => "error", "error" => "Model not found" } unless model
|
|
123
|
-
|
|
124
|
-
record = model.find(id)
|
|
125
|
-
record.update!(changes.slice(*model.column_names))
|
|
126
|
-
|
|
127
|
-
{ "_index" => index, "_id" => id, "result" => "updated" }
|
|
128
|
-
rescue ActiveRecord::RecordNotFound
|
|
129
|
-
{ "_index" => index, "_id" => id, "result" => "not_found" }
|
|
130
|
-
rescue StandardError => e
|
|
131
|
-
{ "_index" => index, "_id" => id, "result" => "error", "error" => e.message }
|
|
116
|
+
def execute_update_document(index, id, _changes, **)
|
|
117
|
+
{ "_index" => index, "_id" => id, "result" => "noop" }
|
|
132
118
|
end
|
|
133
119
|
|
|
134
120
|
def execute_delete_document(index, id, **)
|
|
135
|
-
|
|
136
|
-
return { "_id" => id, "result" => "error", "error" => "Model not found" } unless model
|
|
137
|
-
|
|
138
|
-
model.destroy(id)
|
|
139
|
-
{ "_index" => index, "_id" => id, "result" => "deleted" }
|
|
140
|
-
rescue ActiveRecord::RecordNotFound
|
|
141
|
-
{ "_index" => index, "_id" => id, "result" => "not_found" }
|
|
142
|
-
rescue StandardError => e
|
|
143
|
-
{ "_index" => index, "_id" => id, "result" => "error", "error" => e.message }
|
|
121
|
+
{ "_index" => index, "_id" => id, "result" => "noop" }
|
|
144
122
|
end
|
|
145
123
|
|
|
146
124
|
def execute_document_exists?(index, id)
|
|
@@ -175,13 +153,10 @@ module Noiseless
|
|
|
175
153
|
scope = apply_must_clauses(scope, query_hash[:bool]&.must || [], model)
|
|
176
154
|
|
|
177
155
|
# Apply filter clauses (exact matches)
|
|
178
|
-
scope = apply_filter_clauses(scope, query_hash[:bool]&.filter || [])
|
|
179
|
-
|
|
180
|
-
# Apply sorting
|
|
181
|
-
scope = apply_sorting(scope, query_hash[:sort] || [])
|
|
156
|
+
scope = apply_filter_clauses(scope, query_hash[:bool]&.filter || [], model)
|
|
182
157
|
|
|
183
|
-
# Apply pagination
|
|
184
|
-
|
|
158
|
+
# Apply sorting (pagination is applied by the caller, after counting)
|
|
159
|
+
apply_sorting(scope, query_hash[:sort] || [], model)
|
|
185
160
|
end
|
|
186
161
|
|
|
187
162
|
def apply_must_clauses(scope, must_nodes, model)
|
|
@@ -211,6 +186,10 @@ module Noiseless
|
|
|
211
186
|
field = node.field.to_s
|
|
212
187
|
value = node.value.to_s
|
|
213
188
|
|
|
189
|
+
# Mapping-only fields (denormalized into a search index, not columns)
|
|
190
|
+
# cannot be satisfied here; fail closed rather than matching everything.
|
|
191
|
+
return scope.none unless column?(model, field)
|
|
192
|
+
|
|
214
193
|
# Use pg_trgm similarity for fuzzy matching with unaccent
|
|
215
194
|
if trgm_available? && text_column?(model, field)
|
|
216
195
|
scope.where(
|
|
@@ -227,7 +206,10 @@ module Noiseless
|
|
|
227
206
|
|
|
228
207
|
def apply_multi_match(scope, node, model)
|
|
229
208
|
query = node.query.to_s
|
|
230
|
-
fields
|
|
209
|
+
# Drop mapping-only fields; if none of the requested fields are real
|
|
210
|
+
# columns the query cannot be satisfied — fail closed.
|
|
211
|
+
fields = node.fields.map(&:to_s).select { |field| column?(model, field) }
|
|
212
|
+
return scope.none if fields.empty?
|
|
231
213
|
|
|
232
214
|
conditions = fields.map do |field|
|
|
233
215
|
if trgm_available? && text_column?(model, field)
|
|
@@ -272,7 +254,7 @@ module Noiseless
|
|
|
272
254
|
scope.where("#{quoted_column(node.field.to_s)} ILIKE ?", "#{sanitize_like(node.value)}%")
|
|
273
255
|
end
|
|
274
256
|
|
|
275
|
-
def apply_filter_clauses(scope, filter_nodes)
|
|
257
|
+
def apply_filter_clauses(scope, filter_nodes, model = nil)
|
|
276
258
|
return scope if filter_nodes.empty?
|
|
277
259
|
|
|
278
260
|
filter_nodes.each do |node|
|
|
@@ -280,6 +262,10 @@ module Noiseless
|
|
|
280
262
|
|
|
281
263
|
scope = if value.is_a?(Hash) && value[:geo_distance]
|
|
282
264
|
apply_geo_filter(scope, node)
|
|
265
|
+
elsif model && !column?(model, node.field.to_s)
|
|
266
|
+
# A filter on a mapping-only field cannot be enforced;
|
|
267
|
+
# silently dropping it would broaden results, so fail closed.
|
|
268
|
+
scope.none
|
|
283
269
|
else
|
|
284
270
|
scope.where(node.field => value)
|
|
285
271
|
end
|
|
@@ -310,13 +296,19 @@ module Noiseless
|
|
|
310
296
|
scope
|
|
311
297
|
end
|
|
312
298
|
|
|
313
|
-
def apply_sorting(scope, sort_nodes)
|
|
299
|
+
def apply_sorting(scope, sort_nodes, model = nil)
|
|
314
300
|
return scope if sort_nodes.empty?
|
|
315
301
|
|
|
316
|
-
order_clauses = sort_nodes.
|
|
302
|
+
order_clauses = sort_nodes.filter_map do |node|
|
|
303
|
+
field = node.field.to_s
|
|
304
|
+
# Sorting on a mapping-only field is cosmetic — drop it instead of
|
|
305
|
+
# erroring the whole query.
|
|
306
|
+
next if model && !column?(model, field)
|
|
307
|
+
|
|
317
308
|
direction = node.direction.to_s.upcase == "DESC" ? "DESC" : "ASC"
|
|
318
|
-
"#{quoted_column(
|
|
309
|
+
"#{quoted_column(field)} #{direction}"
|
|
319
310
|
end
|
|
311
|
+
return scope if order_clauses.empty?
|
|
320
312
|
|
|
321
313
|
scope.order(Arel.sql(order_clauses.join(", ")))
|
|
322
314
|
end
|
|
@@ -332,9 +324,7 @@ module Noiseless
|
|
|
332
324
|
|
|
333
325
|
# Response formatting
|
|
334
326
|
|
|
335
|
-
def format_as_search_response(records, model)
|
|
336
|
-
total = records.size
|
|
337
|
-
|
|
327
|
+
def format_as_search_response(records, model, total: records.size)
|
|
338
328
|
hits = records.map do |record|
|
|
339
329
|
{
|
|
340
330
|
"_index" => model.table_name,
|
|
@@ -386,12 +376,15 @@ module Noiseless
|
|
|
386
376
|
# Helper methods
|
|
387
377
|
|
|
388
378
|
def resolve_model(indexes, model_class = nil)
|
|
389
|
-
|
|
379
|
+
# The standard Model#execute path passes the Noiseless::Model search
|
|
380
|
+
# class here; only an ActiveRecord model can back a PG search, so
|
|
381
|
+
# fall through to index-name resolution for anything else.
|
|
382
|
+
return model_class if active_record_model?(model_class)
|
|
390
383
|
|
|
391
384
|
index_name = indexes&.first
|
|
392
385
|
return nil unless index_name
|
|
393
386
|
|
|
394
|
-
# Try cached model first
|
|
387
|
+
# Try cached model first (populated via register_model)
|
|
395
388
|
return @model_class_cache[index_name] if @model_class_cache&.key?(index_name)
|
|
396
389
|
|
|
397
390
|
# Try to infer model from index name
|
|
@@ -401,6 +394,10 @@ module Noiseless
|
|
|
401
394
|
nil
|
|
402
395
|
end
|
|
403
396
|
|
|
397
|
+
def active_record_model?(klass)
|
|
398
|
+
klass.is_a?(Class) && klass < ActiveRecord::Base
|
|
399
|
+
end
|
|
400
|
+
|
|
404
401
|
def trgm_available?
|
|
405
402
|
@trgm_available ||= available_extensions.include?("pg_trgm")
|
|
406
403
|
end
|
|
@@ -409,9 +406,13 @@ module Noiseless
|
|
|
409
406
|
@unaccent_available ||= available_extensions.include?("unaccent")
|
|
410
407
|
end
|
|
411
408
|
|
|
409
|
+
def column?(model, field)
|
|
410
|
+
model.columns_hash.key?(field.to_s)
|
|
411
|
+
end
|
|
412
|
+
|
|
412
413
|
def text_column?(model, field)
|
|
413
414
|
column = model.columns_hash[field.to_s]
|
|
414
|
-
column && %i[string text].include?(column.type)
|
|
415
|
+
column && %i[string text citext].include?(column.type) && !column.array
|
|
415
416
|
end
|
|
416
417
|
|
|
417
418
|
def quoted_column(field)
|
|
@@ -439,18 +440,9 @@ module Noiseless
|
|
|
439
440
|
|
|
440
441
|
def process_bulk_action(action)
|
|
441
442
|
if action[:index]
|
|
442
|
-
index
|
|
443
|
-
id = action[:index][:_id]
|
|
444
|
-
data = action[:index][:data]
|
|
445
|
-
|
|
446
|
-
result = execute_index_document(index, id, data)
|
|
447
|
-
{ "index" => result }
|
|
443
|
+
{ "index" => execute_index_document(action[:index][:_index], action[:index][:_id], nil) }
|
|
448
444
|
elsif action[:delete]
|
|
449
|
-
|
|
450
|
-
id = action[:delete][:_id]
|
|
451
|
-
|
|
452
|
-
result = execute_delete_document(index, id)
|
|
453
|
-
{ "delete" => result }
|
|
445
|
+
{ "delete" => execute_delete_document(action[:delete][:_index], action[:delete][:_id]) }
|
|
454
446
|
else
|
|
455
447
|
{ "error" => "Unknown action type" }
|
|
456
448
|
end
|
|
@@ -402,7 +402,7 @@ module Noiseless
|
|
|
402
402
|
headers
|
|
403
403
|
end
|
|
404
404
|
|
|
405
|
-
# rubocop:disable Lint/DuplicateBranch
|
|
405
|
+
# rubocop:disable-next Lint/DuplicateBranch
|
|
406
406
|
def map_type_to_typesense(elasticsearch_type)
|
|
407
407
|
# Map Elasticsearch types to Typesense types
|
|
408
408
|
case elasticsearch_type
|
|
@@ -418,7 +418,6 @@ module Noiseless
|
|
|
418
418
|
"string" # Default to string for unknown types
|
|
419
419
|
end
|
|
420
420
|
end
|
|
421
|
-
# rubocop:enable Lint/DuplicateBranch
|
|
422
421
|
end
|
|
423
422
|
end
|
|
424
423
|
end
|
|
@@ -7,9 +7,10 @@ module Noiseless
|
|
|
7
7
|
@configs = {}
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
# Register a named client statically from YAML (boot-time only)
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
# Register a named client statically from YAML (boot-time only).
|
|
11
|
+
# request_timeout: :default uses the adapter default, nil disables the deadline.
|
|
12
|
+
def register(name, adapter:, hosts:, timeout: nil, request_timeout: :default)
|
|
13
|
+
@configs[name.to_sym] = { adapter: adapter, hosts: hosts, timeout: timeout, request_timeout: request_timeout }
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
# Retrieve a client; defaults to :primary
|
|
@@ -21,6 +22,7 @@ module Noiseless
|
|
|
21
22
|
config = @configs.fetch(name) { raise "Unknown connection: #{name}" }
|
|
22
23
|
params = { hosts: config[:hosts] }
|
|
23
24
|
params[:timeout] = config[:timeout] unless config[:timeout].nil?
|
|
25
|
+
params[:request_timeout] = config[:request_timeout] unless config[:request_timeout] == :default
|
|
24
26
|
Adapters.lookup(config[:adapter], **params)
|
|
25
27
|
end
|
|
26
28
|
end
|
|
@@ -221,13 +221,13 @@ module Noiseless
|
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
def to_ast
|
|
224
|
-
filter_nodes = @nodes.
|
|
225
|
-
vector_nodes = @nodes.
|
|
224
|
+
filter_nodes = @nodes.grep(AST::Filter)
|
|
225
|
+
vector_nodes = @nodes.grep(AST::Vector)
|
|
226
226
|
must_nodes = @nodes.reject do |n|
|
|
227
227
|
n.is_a?(AST::Filter) || n.is_a?(AST::Sort) || n.is_a?(AST::Paginate) || n.is_a?(AST::Vector)
|
|
228
228
|
end
|
|
229
229
|
bool_node = AST::Bool.new(must: must_nodes, filter: filter_nodes)
|
|
230
|
-
sort_nodes = @nodes.
|
|
230
|
+
sort_nodes = @nodes.grep(AST::Sort)
|
|
231
231
|
paginate_node = @nodes.find { |n| n.is_a?(AST::Paginate) }
|
|
232
232
|
AST::Root.new(
|
|
233
233
|
indexes: @indexes,
|
|
@@ -12,7 +12,9 @@ module Noiseless
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def sources
|
|
15
|
-
|
|
15
|
+
# Indifferent access so callers can pluck(:id) or read ["id"] regardless
|
|
16
|
+
# of which adapter produced the hit.
|
|
17
|
+
@sources ||= hits.map { |hit| (hit["_source"] || {}).with_indifferent_access }
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
alias records sources
|
data/lib/noiseless/version.rb
CHANGED
data/lib/noiseless.rb
CHANGED
|
@@ -98,7 +98,9 @@ module Noiseless
|
|
|
98
98
|
config.connections_config.each do |name, params|
|
|
99
99
|
adapter_name = params[:adapter]
|
|
100
100
|
hosts = params[:hosts] || []
|
|
101
|
-
|
|
101
|
+
register_params = { adapter: adapter_name, hosts: hosts, timeout: params[:timeout] }
|
|
102
|
+
register_params[:request_timeout] = params[:request_timeout] if params.key?(:request_timeout)
|
|
103
|
+
connections.register(name, **register_params)
|
|
102
104
|
end
|
|
103
105
|
end
|
|
104
106
|
|
data/lib/tasks/release.rake
CHANGED
|
@@ -11,9 +11,7 @@ namespace :release do
|
|
|
11
11
|
version_path = File.expand_path("../../lib/noiseless/version", __dir__)
|
|
12
12
|
require version_path
|
|
13
13
|
|
|
14
|
-
if spec.version.to_s != Noiseless::VERSION
|
|
15
|
-
raise "Version mismatch: gemspec=#{spec.version} lib=#{Noiseless::VERSION}"
|
|
16
|
-
end
|
|
14
|
+
raise "Version mismatch: gemspec=#{spec.version} lib=#{Noiseless::VERSION}" if spec.version.to_s != Noiseless::VERSION
|
|
17
15
|
|
|
18
16
|
ruby = Shellwords.escape(RbConfig.ruby)
|
|
19
17
|
sh "#{ruby} -Ilib -e 'require \"noiseless\"; abort(\"version mismatch\") unless Noiseless::VERSION == \"#{Noiseless::VERSION}\"'"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: noiseless
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Abdelkader Boudih
|
|
@@ -29,42 +29,42 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '2.
|
|
32
|
+
version: '2.45'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '2.
|
|
39
|
+
version: '2.45'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: async-http
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0.
|
|
46
|
+
version: '0.103'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0.
|
|
53
|
+
version: '0.103'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: async-pool
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.
|
|
60
|
+
version: '0.11'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0.
|
|
67
|
+
version: '0.11'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: railties
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -85,14 +85,14 @@ dependencies:
|
|
|
85
85
|
requirements:
|
|
86
86
|
- - "~>"
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '2.
|
|
88
|
+
version: '2.8'
|
|
89
89
|
type: :runtime
|
|
90
90
|
prerelease: false
|
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
92
|
requirements:
|
|
93
93
|
- - "~>"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '2.
|
|
95
|
+
version: '2.8'
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
97
|
name: async-safe
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -109,6 +109,20 @@ dependencies:
|
|
|
109
109
|
version: '0.5'
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: minitest
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '6.0'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '6.0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: minitest-mock
|
|
112
126
|
requirement: !ruby/object:Gem::Requirement
|
|
113
127
|
requirements:
|
|
114
128
|
- - "~>"
|