valkey-rb 1.0.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +58 -0
  3. data/.rubocop_todo.yml +22 -0
  4. data/README.md +95 -0
  5. data/Rakefile +23 -0
  6. data/lib/valkey/bindings.rb +224 -0
  7. data/lib/valkey/commands/bitmap_commands.rb +86 -0
  8. data/lib/valkey/commands/cluster_commands.rb +259 -0
  9. data/lib/valkey/commands/connection_commands.rb +318 -0
  10. data/lib/valkey/commands/function_commands.rb +255 -0
  11. data/lib/valkey/commands/generic_commands.rb +525 -0
  12. data/lib/valkey/commands/geo_commands.rb +87 -0
  13. data/lib/valkey/commands/hash_commands.rb +587 -0
  14. data/lib/valkey/commands/hyper_log_log_commands.rb +51 -0
  15. data/lib/valkey/commands/json_commands.rb +389 -0
  16. data/lib/valkey/commands/list_commands.rb +348 -0
  17. data/lib/valkey/commands/module_commands.rb +125 -0
  18. data/lib/valkey/commands/pubsub_commands.rb +237 -0
  19. data/lib/valkey/commands/scripting_commands.rb +286 -0
  20. data/lib/valkey/commands/server_commands.rb +961 -0
  21. data/lib/valkey/commands/set_commands.rb +220 -0
  22. data/lib/valkey/commands/sorted_set_commands.rb +971 -0
  23. data/lib/valkey/commands/stream_commands.rb +636 -0
  24. data/lib/valkey/commands/string_commands.rb +359 -0
  25. data/lib/valkey/commands/transaction_commands.rb +175 -0
  26. data/lib/valkey/commands/vector_search_commands.rb +271 -0
  27. data/lib/valkey/commands.rb +68 -0
  28. data/lib/valkey/errors.rb +41 -0
  29. data/lib/valkey/libglide_ffi.so +0 -0
  30. data/lib/valkey/opentelemetry.rb +207 -0
  31. data/lib/valkey/pipeline.rb +20 -0
  32. data/lib/valkey/protobuf/command_request_pb.rb +51 -0
  33. data/lib/valkey/protobuf/connection_request_pb.rb +51 -0
  34. data/lib/valkey/protobuf/response_pb.rb +39 -0
  35. data/lib/valkey/pubsub_callback.rb +10 -0
  36. data/lib/valkey/request_error_type.rb +10 -0
  37. data/lib/valkey/request_type.rb +436 -0
  38. data/lib/valkey/response_type.rb +20 -0
  39. data/lib/valkey/utils.rb +253 -0
  40. data/lib/valkey/version.rb +5 -0
  41. data/lib/valkey.rb +551 -0
  42. metadata +119 -0
data/lib/valkey.rb ADDED
@@ -0,0 +1,551 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+ require "google/protobuf"
5
+
6
+ require "valkey/version"
7
+ require "valkey/request_type"
8
+ require "valkey/response_type"
9
+ require "valkey/request_error_type"
10
+ require "valkey/protobuf/command_request_pb"
11
+ require "valkey/protobuf/connection_request_pb"
12
+ require "valkey/protobuf/response_pb"
13
+ require "valkey/bindings"
14
+ require "valkey/utils"
15
+ require "valkey/commands"
16
+ require "valkey/errors"
17
+ require "valkey/pubsub_callback"
18
+ require "valkey/pipeline"
19
+ require "valkey/opentelemetry"
20
+
21
+ class Valkey
22
+ include Utils
23
+ include Commands
24
+ include PubSubCallback
25
+
26
+ def pipelined(exception: true)
27
+ pipeline = Pipeline.new
28
+
29
+ yield pipeline
30
+
31
+ return [] if pipeline.commands.empty?
32
+
33
+ send_batch_commands(pipeline.commands, exception: exception)
34
+ end
35
+
36
+ def send_batch_commands(commands, exception: true)
37
+ # WORKAROUND: The underlying Glide FFI backend has stability issues when
38
+ # batching transactional commands like MULTI / EXEC / DISCARD. To avoid
39
+ # native crashes we fall back to issuing those commands sequentially
40
+ # instead of via `Bindings.batch`.
41
+ tx_types = [RequestType::MULTI, RequestType::EXEC, RequestType::DISCARD]
42
+
43
+ if commands.any? { |(command_type, _args, _block)| tx_types.include?(command_type) }
44
+ results = []
45
+
46
+ commands.each do |command_type, command_args, block|
47
+ res = send_command(command_type, command_args)
48
+ res = block.call(res) if block
49
+ results << res
50
+ end
51
+
52
+ return results
53
+ end
54
+
55
+ cmds = []
56
+ blocks = []
57
+ buffers = [] # Keep references to prevent GC
58
+
59
+ commands.each do |command_type, command_args, block|
60
+ arg_ptrs, arg_lens = build_command_args(command_args)
61
+
62
+ cmd = Bindings::CmdInfo.new
63
+ cmd[:request_type] = command_type
64
+ cmd[:args] = arg_ptrs
65
+ cmd[:arg_count] = command_args.size
66
+ cmd[:args_len] = arg_lens
67
+
68
+ cmds << cmd
69
+ blocks << block
70
+ buffers << [arg_ptrs, arg_lens] # Prevent GC
71
+ end
72
+
73
+ # Create array of pointers to CmdInfo structs
74
+ cmd_ptrs = FFI::MemoryPointer.new(:pointer, cmds.size)
75
+ cmds.each_with_index do |cmd, i|
76
+ cmd_ptrs[i].put_pointer(0, cmd.to_ptr)
77
+ end
78
+
79
+ batch_info = Bindings::BatchInfo.new
80
+ batch_info[:cmd_count] = cmds.size
81
+ batch_info[:cmds] = cmd_ptrs
82
+ batch_info[:is_atomic] = false
83
+
84
+ batch_options = Bindings::BatchOptionsInfo.new
85
+ batch_options[:retry_server_error] = true
86
+ batch_options[:retry_connection_error] = true
87
+ batch_options[:has_timeout] = false
88
+ batch_options[:timeout] = 0 # No timeout
89
+ batch_options[:route_info] = FFI::Pointer::NULL
90
+
91
+ # Create OpenTelemetry span for batch operation if sampling is enabled
92
+ span_ptr = 0
93
+ if OpenTelemetry.should_sample?
94
+ begin
95
+ span_ptr = Bindings.create_batch_otel_span
96
+ rescue StandardError => e
97
+ warn "Failed to create OpenTelemetry batch span: #{e.message}"
98
+ span_ptr = 0
99
+ end
100
+ end
101
+
102
+ begin
103
+ res = Bindings.batch(
104
+ @connection,
105
+ 0,
106
+ batch_info,
107
+ exception,
108
+ batch_options.to_ptr,
109
+ span_ptr
110
+ )
111
+
112
+ results = convert_response(res)
113
+ ensure
114
+ # Always drop the span if one was created
115
+ if span_ptr != 0
116
+ begin
117
+ Bindings.drop_otel_span(span_ptr)
118
+ rescue StandardError => e
119
+ warn "Failed to drop OpenTelemetry batch span: #{e.message}"
120
+ end
121
+ end
122
+ end
123
+
124
+ blocks.each_with_index do |block, i|
125
+ results[i] = block.call(results[i]) if block
126
+ end
127
+
128
+ results
129
+ end
130
+
131
+ def build_command_args(command_args)
132
+ arg_ptrs = FFI::MemoryPointer.new(:pointer, command_args.size)
133
+ arg_lens = FFI::MemoryPointer.new(:ulong, command_args.size)
134
+ buffers = []
135
+
136
+ command_args.each_with_index do |arg, i|
137
+ arg = arg.to_s # Ensure we convert to string
138
+
139
+ buf = FFI::MemoryPointer.from_string(arg.to_s)
140
+ buffers << buf # prevent garbage collection
141
+ arg_ptrs.put_pointer(i * FFI::Pointer.size, buf)
142
+ arg_lens.put_ulong(i * 8, arg.bytesize)
143
+ end
144
+
145
+ [arg_ptrs, arg_lens]
146
+ end
147
+
148
+ def convert_response(res, &block)
149
+ result = Bindings::CommandResult.new(res)
150
+
151
+ if result[:response].null?
152
+ error = result[:command_error]
153
+
154
+ case error[:command_error_type]
155
+ when RequestErrorType::EXECABORT, RequestErrorType::UNSPECIFIED
156
+ raise CommandError, error[:command_error_message]
157
+ when RequestErrorType::TIMEOUT
158
+ raise TimeoutError, error[:command_error_message]
159
+ when RequestErrorType::DISCONNECT
160
+ raise ConnectionError, error[:command_error_message]
161
+ else
162
+ raise "Unknown error type: #{error[:command_error_type]}"
163
+ end
164
+ end
165
+
166
+ result = result[:response]
167
+
168
+ convert_response = lambda { |response_item|
169
+ # TODO: handle all types of responses
170
+ case response_item[:response_type]
171
+ when ResponseType::STRING
172
+ response_item[:string_value].read_string(response_item[:string_value_len])
173
+ when ResponseType::INT
174
+ response_item[:int_value]
175
+ when ResponseType::FLOAT
176
+ response_item[:float_value]
177
+ when ResponseType::BOOL
178
+ response_item[:bool_value]
179
+ when ResponseType::ARRAY
180
+ ptr = response_item[:array_value]
181
+ count = response_item[:array_value_len].to_i
182
+
183
+ Array.new(count) do |i|
184
+ item = Bindings::CommandResponse.new(ptr + (i * Bindings::CommandResponse.size))
185
+ convert_response.call(item)
186
+ end
187
+ when ResponseType::MAP
188
+ return nil if response_item[:array_value].null?
189
+
190
+ ptr = response_item[:array_value]
191
+ count = response_item[:array_value_len].to_i
192
+ map = {}
193
+
194
+ Array.new(count) do |i|
195
+ item = Bindings::CommandResponse.new(ptr + (i * Bindings::CommandResponse.size))
196
+
197
+ map_key = convert_response.call(Bindings::CommandResponse.new(item[:map_key]))
198
+ map_value = convert_response.call(Bindings::CommandResponse.new(item[:map_value]))
199
+
200
+ map[map_key] = map_value
201
+ end
202
+
203
+ # technically it has to return a Hash, but as of now we return just one pair
204
+ map.to_a.flatten(1) # Flatten to get pairs
205
+ when ResponseType::SETS
206
+ ptr = response_item[:sets_value]
207
+ count = response_item[:sets_value_len].to_i
208
+
209
+ Array.new(count) do |i|
210
+ item = Bindings::CommandResponse.new(ptr + (i * Bindings::CommandResponse.size))
211
+ convert_response.call(item)
212
+ end
213
+ when ResponseType::NULL
214
+ nil
215
+ when ResponseType::OK
216
+ "OK"
217
+ when ResponseType::ERROR
218
+ # For errors in arrays (like EXEC responses), return an error object
219
+ # instead of raising. The error message is typically in string_value.
220
+ error_msg = if response_item[:string_value].null?
221
+ "Unknown error"
222
+ else
223
+ response_item[:string_value].read_string(response_item[:string_value_len])
224
+ end
225
+ CommandError.new(error_msg)
226
+ else
227
+ raise "Unsupported response type: #{response_item[:response_type]}"
228
+ end
229
+ }
230
+
231
+ response = convert_response.call(result)
232
+
233
+ if block_given?
234
+ block.call(response)
235
+ else
236
+ response
237
+ end
238
+ end
239
+
240
+ def send_command(command_type, command_args = [], &block)
241
+ # Validate connection
242
+ if @connection.nil?
243
+ raise "Connection is nil"
244
+ elsif @connection.null?
245
+ raise "Connection pointer is null"
246
+ elsif @connection.address.zero?
247
+ raise "Connection address is 0"
248
+ end
249
+
250
+ channel = 0
251
+ route = ""
252
+
253
+ route_buf = FFI::MemoryPointer.from_string(route)
254
+
255
+ # Handle empty command_args case
256
+ if command_args.empty?
257
+ arg_ptrs = FFI::MemoryPointer.new(:pointer, 1)
258
+ arg_lens = FFI::MemoryPointer.new(:ulong, 1)
259
+ arg_ptrs.put_pointer(0, FFI::MemoryPointer.new(1))
260
+ arg_lens.put_ulong(0, 0)
261
+ else
262
+ arg_ptrs, arg_lens = build_command_args(command_args)
263
+ end
264
+
265
+ # Create OpenTelemetry span if sampling is enabled
266
+ span_ptr = 0
267
+ if OpenTelemetry.should_sample?
268
+ begin
269
+ span_ptr = Bindings.create_otel_span(command_type)
270
+ rescue StandardError => e
271
+ # Log error but continue execution - tracing is non-critical
272
+ warn "Failed to create OpenTelemetry span: #{e.message}"
273
+ span_ptr = 0
274
+ end
275
+ end
276
+
277
+ begin
278
+ res = Bindings.command(
279
+ @connection,
280
+ channel,
281
+ command_type,
282
+ command_args.size,
283
+ arg_ptrs,
284
+ arg_lens,
285
+ route_buf,
286
+ route.bytesize,
287
+ span_ptr
288
+ )
289
+
290
+ result = convert_response(res, &block)
291
+ ensure
292
+ # Always drop the span if one was created, even if command fails
293
+ if span_ptr != 0
294
+ begin
295
+ Bindings.drop_otel_span(span_ptr)
296
+ rescue StandardError => e
297
+ # Log but don't raise - span cleanup errors shouldn't break command execution
298
+ warn "Failed to drop OpenTelemetry span: #{e.message}"
299
+ end
300
+ end
301
+ end
302
+
303
+ # Track queued commands during MULTI (except for MULTI, EXEC, DISCARD, WATCH, UNWATCH)
304
+ if @in_multi && !@queued_commands.nil?
305
+ tx_commands = [
306
+ RequestType::MULTI, RequestType::EXEC, RequestType::DISCARD,
307
+ RequestType::WATCH, RequestType::UNWATCH
308
+ ]
309
+ @queued_commands << [command_type, command_args.dup] if !tx_commands.include?(command_type) && result == "QUEUED"
310
+ end
311
+
312
+ result
313
+ end
314
+
315
+ def initialize(options = {})
316
+ # Parse URL if provided
317
+ if options[:url]
318
+ url_options = Utils.parse_redis_url(options[:url])
319
+ # Merge URL options, but explicit options take precedence
320
+ options = url_options.merge(options.reject { |k, _v| k == :url })
321
+ end
322
+
323
+ # Extract connection parameters
324
+ host = options[:host] || "127.0.0.1"
325
+ port = options[:port] || 6379
326
+
327
+ nodes = options[:nodes] || [{ host: host, port: port }]
328
+
329
+ cluster_mode_enabled = options[:cluster_mode] || false
330
+
331
+ # Protocol defaults to RESP2
332
+ protocol = case options[:protocol]
333
+ when :resp3, "resp3", 3
334
+ ConnectionRequest::ProtocolVersion::RESP3
335
+ else
336
+ ConnectionRequest::ProtocolVersion::RESP2
337
+ end
338
+
339
+ # TLS/SSL support
340
+ tls_mode = if [true, "true"].include?(options[:ssl])
341
+ ConnectionRequest::TlsMode::SecureTls
342
+ else
343
+ ConnectionRequest::TlsMode::NoTls
344
+ end
345
+
346
+ # SSL parameters - map ssl_params to protobuf root_certs
347
+ root_certs = []
348
+ if options[:ssl_params].is_a?(Hash)
349
+ # ca_file - read CA certificate file (PEM or DER format)
350
+ root_certs << File.binread(options[:ssl_params][:ca_file]) if options[:ssl_params][:ca_file]
351
+
352
+ # cert - client certificate (file path or OpenSSL::X509::Certificate)
353
+ if options[:ssl_params][:cert]
354
+ cert_data = if options[:ssl_params][:cert].is_a?(String)
355
+ File.binread(options[:ssl_params][:cert])
356
+ elsif options[:ssl_params][:cert].respond_to?(:to_pem)
357
+ options[:ssl_params][:cert].to_pem
358
+ elsif options[:ssl_params][:cert].respond_to?(:to_der)
359
+ options[:ssl_params][:cert].to_der
360
+ else
361
+ options[:ssl_params][:cert].to_s
362
+ end
363
+ root_certs << cert_data
364
+ end
365
+
366
+ # key - client key (file path or OpenSSL::PKey)
367
+ if options[:ssl_params][:key]
368
+ key_data = if options[:ssl_params][:key].is_a?(String)
369
+ File.binread(options[:ssl_params][:key])
370
+ elsif options[:ssl_params][:key].respond_to?(:to_pem)
371
+ options[:ssl_params][:key].to_pem
372
+ elsif options[:ssl_params][:key].respond_to?(:to_der)
373
+ options[:ssl_params][:key].to_der
374
+ else
375
+ options[:ssl_params][:key].to_s
376
+ end
377
+ root_certs << key_data
378
+ end
379
+
380
+ # Additional root certificates from ca_path
381
+ if options[:ssl_params][:ca_path]
382
+ Dir.glob(File.join(options[:ssl_params][:ca_path], "*.crt")).each do |cert_file|
383
+ root_certs << File.binread(cert_file)
384
+ end
385
+ Dir.glob(File.join(options[:ssl_params][:ca_path], "*.pem")).each do |cert_file|
386
+ root_certs << File.binread(cert_file)
387
+ end
388
+ end
389
+
390
+ # Direct root_certs array support
391
+ root_certs.concat(options[:ssl_params][:root_certs]) if options[:ssl_params][:root_certs].is_a?(Array)
392
+ end
393
+
394
+ # Authentication support
395
+ authentication_info = nil
396
+ if options[:password] || options[:username]
397
+ authentication_info = ConnectionRequest::AuthenticationInfo.new(
398
+ password: options[:password] || "",
399
+ username: options[:username] || ""
400
+ )
401
+ end
402
+
403
+ # Database selection
404
+ database_id = options[:db] || 0
405
+
406
+ # Client name
407
+ client_name = options[:client_name] || ""
408
+
409
+ # Timeout handling
410
+ # :timeout sets the request timeout (for command execution)
411
+ # :connect_timeout sets the connection establishment timeout
412
+ # Default request timeout is 5.0 seconds
413
+ request_timeout = options[:timeout] || 5.0
414
+
415
+ # Connection timeout (milliseconds) - defaults to 0 (uses system default)
416
+ connection_timeout_ms = if options[:connect_timeout]
417
+ (options[:connect_timeout] * 1000).to_i
418
+ else
419
+ 0
420
+ end
421
+
422
+ # Connection retry strategy
423
+ connection_retry_strategy = nil
424
+ if options[:reconnect_attempts] || options[:reconnect_delay] || options[:reconnect_delay_max]
425
+ number_of_retries = options[:reconnect_attempts] || 1
426
+ base_delay = options[:reconnect_delay] || 0.5
427
+ max_delay = options[:reconnect_delay_max]
428
+ exponent_base = 2
429
+ jitter_percent = 0
430
+
431
+ if max_delay && base_delay.positive? && number_of_retries.positive?
432
+ calculated_base = (max_delay / base_delay)**(1.0 / number_of_retries.to_f)
433
+ exponent_base = [calculated_base.round, 2].max
434
+ end
435
+
436
+ factor_ms = (base_delay * 1000).to_i
437
+
438
+ connection_retry_strategy = ConnectionRequest::ConnectionRetryStrategy.new(
439
+ number_of_retries: number_of_retries,
440
+ factor: factor_ms,
441
+ exponent_base: exponent_base,
442
+ jitter_percent: jitter_percent
443
+ )
444
+ end
445
+
446
+ # Build connection request
447
+ request_params = {
448
+ cluster_mode_enabled: cluster_mode_enabled,
449
+ request_timeout: request_timeout,
450
+ protocol: protocol,
451
+ tls_mode: tls_mode,
452
+ addresses: nodes.map { |node| ConnectionRequest::NodeAddress.new(host: node[:host], port: node[:port]) }
453
+ }
454
+
455
+ # Add optional fields only if they have values
456
+ request_params[:connection_timeout] = connection_timeout_ms if connection_timeout_ms.positive?
457
+ request_params[:database_id] = database_id if database_id.positive?
458
+ request_params[:client_name] = client_name unless client_name.empty?
459
+ request_params[:authentication_info] = authentication_info if authentication_info
460
+ request_params[:root_certs] = root_certs unless root_certs.empty?
461
+ request_params[:connection_retry_strategy] = connection_retry_strategy if connection_retry_strategy
462
+
463
+ request = ConnectionRequest::ConnectionRequest.new(request_params)
464
+
465
+ client_type = Bindings::ClientType.new
466
+ client_type[:tag] = 1 # SyncClient
467
+
468
+ request_str = ConnectionRequest::ConnectionRequest.encode(request)
469
+ request_buf = FFI::MemoryPointer.new(:char, request_str.bytesize)
470
+ request_buf.put_bytes(0, request_str)
471
+
472
+ request_len = request_str.bytesize
473
+
474
+ response_ptr = Bindings.create_client(
475
+ request_buf,
476
+ request_len,
477
+ client_type,
478
+ method(:pubsub_callback)
479
+ )
480
+
481
+ res = Bindings::ConnectionResponse.new(response_ptr)
482
+
483
+ # Check if connection was successful
484
+ if res[:conn_ptr].null?
485
+ error_message = res[:connection_error_message]
486
+ raise CannotConnectError, "Failed to connect to cluster: #{error_message}"
487
+ end
488
+
489
+ @connection = res[:conn_ptr]
490
+
491
+ # Track transactional state for `MULTI` / `EXEC` / `DISCARD` helpers.
492
+ # This avoids Ruby warnings about uninitialised instance variables and
493
+ # gives us a single source of truth for whether we're inside a TX.
494
+ @in_multi = false
495
+ # Track queued commands during MULTI for transaction isolation support
496
+ @queued_commands = []
497
+ # Track if we're inside a multi block (multi { ... }) vs direct multi calls
498
+ @in_multi_block = false
499
+ end
500
+
501
+ def close
502
+ return if @connection.nil? || @connection.null?
503
+
504
+ Bindings.close_client(@connection)
505
+ @connection = nil
506
+ end
507
+
508
+ alias disconnect! close
509
+
510
+ # Retrieves client statistics including connection and compression metrics.
511
+ #
512
+ # This method returns detailed statistics about the client's operations,
513
+ # tracked globally across all clients in the process.
514
+ #
515
+ # @return [Hash] a hash containing statistics with the following keys:
516
+ # - `:total_connections` [Integer] total number of connections opened to Valkey
517
+ # - `:total_clients` [Integer] total number of GLIDE clients
518
+ # - `:total_values_compressed` [Integer] total number of values compressed
519
+ # - `:total_values_decompressed` [Integer] total number of values decompressed
520
+ # - `:total_original_bytes` [Integer] total original bytes before compression
521
+ # - `:total_bytes_compressed` [Integer] total bytes after compression
522
+ # - `:total_bytes_decompressed` [Integer] total bytes after decompression
523
+ # - `:compression_skipped_count` [Integer] number of times compression was skipped
524
+ #
525
+ # @example Get client statistics
526
+ # client = Valkey.new(host: 'localhost', port: 6379)
527
+ # stats = client.statistics
528
+ # puts "Total connections: #{stats[:total_connections]}"
529
+ # puts "Total clients: #{stats[:total_clients]}"
530
+ # puts "Values compressed: #{stats[:total_values_compressed]}"
531
+ #
532
+ # @note Statistics are tracked globally and shared across all clients
533
+ #
534
+ # @return [Hash] statistics hash with integer values
535
+ def statistics
536
+ # Call FFI function to get statistics (returns by value)
537
+ stats = Bindings.get_statistics
538
+
539
+ # Convert to Ruby hash
540
+ {
541
+ total_connections: stats[:total_connections],
542
+ total_clients: stats[:total_clients],
543
+ total_values_compressed: stats[:total_values_compressed],
544
+ total_values_decompressed: stats[:total_values_decompressed],
545
+ total_original_bytes: stats[:total_original_bytes],
546
+ total_bytes_compressed: stats[:total_bytes_compressed],
547
+ total_bytes_decompressed: stats[:total_bytes_decompressed],
548
+ compression_skipped_count: stats[:compression_skipped_count]
549
+ }
550
+ end
551
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: valkey-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Valkey GLIDE Maintainers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.17.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.17.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: google-protobuf
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.23'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 3.23.4
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '3.23'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.23.4
47
+ description: A Ruby client library for Valkey
48
+ email:
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - ".rubocop.yml"
54
+ - ".rubocop_todo.yml"
55
+ - README.md
56
+ - Rakefile
57
+ - lib/valkey.rb
58
+ - lib/valkey/bindings.rb
59
+ - lib/valkey/commands.rb
60
+ - lib/valkey/commands/bitmap_commands.rb
61
+ - lib/valkey/commands/cluster_commands.rb
62
+ - lib/valkey/commands/connection_commands.rb
63
+ - lib/valkey/commands/function_commands.rb
64
+ - lib/valkey/commands/generic_commands.rb
65
+ - lib/valkey/commands/geo_commands.rb
66
+ - lib/valkey/commands/hash_commands.rb
67
+ - lib/valkey/commands/hyper_log_log_commands.rb
68
+ - lib/valkey/commands/json_commands.rb
69
+ - lib/valkey/commands/list_commands.rb
70
+ - lib/valkey/commands/module_commands.rb
71
+ - lib/valkey/commands/pubsub_commands.rb
72
+ - lib/valkey/commands/scripting_commands.rb
73
+ - lib/valkey/commands/server_commands.rb
74
+ - lib/valkey/commands/set_commands.rb
75
+ - lib/valkey/commands/sorted_set_commands.rb
76
+ - lib/valkey/commands/stream_commands.rb
77
+ - lib/valkey/commands/string_commands.rb
78
+ - lib/valkey/commands/transaction_commands.rb
79
+ - lib/valkey/commands/vector_search_commands.rb
80
+ - lib/valkey/errors.rb
81
+ - lib/valkey/libglide_ffi.so
82
+ - lib/valkey/opentelemetry.rb
83
+ - lib/valkey/pipeline.rb
84
+ - lib/valkey/protobuf/command_request_pb.rb
85
+ - lib/valkey/protobuf/connection_request_pb.rb
86
+ - lib/valkey/protobuf/response_pb.rb
87
+ - lib/valkey/pubsub_callback.rb
88
+ - lib/valkey/request_error_type.rb
89
+ - lib/valkey/request_type.rb
90
+ - lib/valkey/response_type.rb
91
+ - lib/valkey/utils.rb
92
+ - lib/valkey/version.rb
93
+ homepage: https://github.com/valkey-io/valkey-glide-ruby
94
+ licenses: []
95
+ metadata:
96
+ homepage_uri: https://github.com/valkey-io/valkey-glide-ruby
97
+ source_code_uri: https://github.com/valkey-io/valkey-glide-ruby
98
+ changelog_uri: https://github.com/valkey-io/valkey-glide-ruby
99
+ rubygems_mfa_required: 'true'
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: 2.6.0
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubygems_version: 3.4.19
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: A Ruby client library for Valkey
119
+ test_files: []