valkey-glide-rb 0.9.0 → 0.9.2

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.
@@ -41,10 +41,29 @@ end
41
41
  client.del("otel_key", "otel_pipe_1")
42
42
  client.close
43
43
 
44
+ # Distributed tracing: register a parent_span_context_provider so command spans
45
+ # become children of the app's current trace instead of independent root spans.
46
+ # Here we hand-roll a fake W3C trace context (no dependency on the real
47
+ # opentelemetry-ruby gem); in a real app this would read from
48
+ # ::OpenTelemetry::Trace.current_span.context (see README).
49
+ fake_trace_id = "4bf92f3577b34da6a3ce929d0e0e4736"
50
+ fake_span_id = "00f067aa0ba902b7"
51
+
52
+ Valkey::OpenTelemetry.set_parent_span_context_provider do
53
+ { trace_id: fake_trace_id, span_id: fake_span_id, trace_flags: 1, tracestate: nil }
54
+ end
55
+
56
+ client = Valkey.new(host: host, port: port)
57
+ client.set("otel_traced_key", "value") # span will be a child of fake_trace_id/fake_span_id
58
+ client.close
59
+
60
+ Valkey::OpenTelemetry.set_parent_span_context_provider(nil)
61
+
44
62
  # Allow exporter flush
45
63
  sleep 2
46
64
 
47
65
  puts "OpenTelemetry initialized: #{Valkey::OpenTelemetry.initialized?}"
48
66
  puts "Traces file: #{TRACES_FILE} (#{File.size?(TRACES_FILE) || 0} bytes)"
49
67
  puts "Metrics file: #{METRICS_FILE} (#{File.size?(METRICS_FILE) || 0} bytes)"
50
- puts "Done."
68
+ puts "Done. Inspect #{TRACES_FILE} - the SET span for otel_traced_key should have " \
69
+ "trace_id=#{fake_trace_id} and parent_span_id=#{fake_span_id}."
@@ -4,6 +4,23 @@ class Valkey
4
4
  module Bindings
5
5
  extend FFI::Library
6
6
 
7
+ # Detect whether the current Linux system uses musl libc (e.g., Alpine Linux).
8
+ # Uses a three-check cascade — any single match is sufficient.
9
+ def self.musl_libc?
10
+ return false unless FFI::Platform::OS == "linux"
11
+
12
+ # Check 1: RUBY_PLATFORM contains 'musl' (e.g., Alpine-built Ruby)
13
+ return true if RUBY_PLATFORM.include?("musl")
14
+
15
+ # Check 2: /etc/alpine-release exists (Alpine Linux indicator)
16
+ return true if File.exist?("/etc/alpine-release")
17
+
18
+ # Check 3: RbConfig target_os contains 'musl'
19
+ return true if RbConfig::CONFIG["target_os"].to_s.include?("musl")
20
+
21
+ false
22
+ end
23
+
7
24
  # Determine platform-specific library extension and directory name
8
25
  def self.platform_info
9
26
  os = if FFI::Platform.mac?
@@ -40,7 +57,7 @@ class Valkey
40
57
  when "darwin"
41
58
  "#{arch}-apple-darwin"
42
59
  when "linux"
43
- "#{arch}-unknown-linux-gnu"
60
+ musl_libc? ? "#{arch}-unknown-linux-musl" : "#{arch}-unknown-linux-gnu"
44
61
  when "windows"
45
62
  "#{arch}-pc-windows-msvc"
46
63
  end
@@ -106,6 +123,33 @@ class Valkey
106
123
  )
107
124
  end
108
125
 
126
+ # Mirrors Rust's `RouteType` enum (valkey-glide/ffi/src/lib.rs)
127
+ RouteType = enum(
128
+ :all_nodes, 0,
129
+ :all_primaries,
130
+ :random,
131
+ :slot_id,
132
+ :slot_key,
133
+ :by_address
134
+ )
135
+
136
+ # Mirrors Rust's `SlotType` enum (a mirror of `SlotAddr`)
137
+ SlotType = enum(
138
+ :primary, 0,
139
+ :replica
140
+ )
141
+
142
+ class RouteInfo < FFI::Struct
143
+ layout(
144
+ :route_type, RouteType,
145
+ :slot_id, :int32, # slot number (for SlotId route)
146
+ :slot_key, :pointer, # *const c_char (for SlotKey route; NULL otherwise)
147
+ :slot_type, SlotType,
148
+ :hostname, :pointer, # *const c_char (for ByAddress route; NULL otherwise)
149
+ :port, :int32 # port number (for ByAddress route)
150
+ )
151
+ end
152
+
109
153
  class BatchOptionsInfo < FFI::Struct
110
154
  layout(
111
155
  :retry_server_error, :bool,
@@ -209,6 +253,23 @@ class Valkey
209
253
  :pointer # *mut ConnectionResponse
210
254
  ], :void
211
255
 
256
+ attach_function :free_command_result, [
257
+ :pointer # *mut CommandResult
258
+ ], :void
259
+
260
+ attach_function :free_script_hash_buffer, [
261
+ :pointer # *mut ScriptHashBuffer
262
+ ], :void
263
+
264
+ attach_function :drop_script, [
265
+ :pointer, # *mut u8 (hash bytes)
266
+ :ulong # usize (hash length)
267
+ ], :pointer # returns *mut c_char (null on success, error string on failure)
268
+
269
+ attach_function :free_drop_script_error, [
270
+ :pointer # *mut c_char (error from drop_script)
271
+ ], :void
272
+
212
273
  attach_function :close_client, [
213
274
  :pointer # client_adapter_ptr
214
275
  ], :void
@@ -225,6 +286,19 @@ class Valkey
225
286
  :ulong # span_ptr (u64)
226
287
  ], :pointer, blocking: true # returns *mut CommandResult, releases GVL during I/O
227
288
 
289
+ attach_function :command_with_route_info, [
290
+ :pointer, # client_adapter_ptr
291
+ :ulong, # request_id
292
+ :int, # command_type (RequestType)
293
+ :ulong, # arg_count
294
+ :pointer, # args (pointer to usize[])
295
+ :pointer, # args_len (pointer to c_ulong[])
296
+ :pointer, # route_info (*const RouteInfo, or NULL for no route)
297
+ :pointer, # response_buf (NULL = normal response path)
298
+ :ulong, # response_buf_len (0 if response_buf is NULL)
299
+ :ulong # span_ptr (u64)
300
+ ], :pointer, blocking: true # returns *mut CommandResult, releases GVL during I/O
301
+
228
302
  attach_function :batch, [
229
303
  :pointer, # client_ptr
230
304
  :ulong, # callback_index
@@ -309,6 +383,21 @@ class Valkey
309
383
 
310
384
  attach_function :create_batch_otel_span, [], :uint64 # returns span pointer (u64) or 0 on failure
311
385
 
386
+ attach_function :create_otel_span_with_trace_context, [
387
+ :int, # request_type (RequestType enum value)
388
+ :string, # trace_id (32-char lowercase hex, or nil for an independent span)
389
+ :string, # span_id (16-char lowercase hex, or nil for an independent span)
390
+ :uint8, # trace_flags
391
+ :string # trace_state (W3C tracestate header, or nil)
392
+ ], :uint64 # returns span pointer (u64); falls back to an independent span on invalid context
393
+
394
+ attach_function :create_batch_otel_span_with_trace_context, [
395
+ :string, # trace_id (32-char lowercase hex, or nil for an independent span)
396
+ :string, # span_id (16-char lowercase hex, or nil for an independent span)
397
+ :uint8, # trace_flags
398
+ :string # trace_state (W3C tracestate header, or nil)
399
+ ], :uint64 # returns span pointer (u64); falls back to an independent batch span on invalid context
400
+
312
401
  attach_function :drop_otel_span, [
313
402
  :uint64 # span_ptr to close
314
403
  ], :void
@@ -71,13 +71,30 @@ class Valkey
71
71
  send_command(RequestType::CLUSTER_DEL_SLOTS_RANGE, [start_slot, end_slot])
72
72
  end
73
73
 
74
+ # Valid modes for CLUSTER FAILOVER: FORCE, TAKEOVER.
75
+ CLUSTER_FAILOVER_MODES = %i[force takeover].freeze
76
+
74
77
  # Force a failover of the cluster.
75
78
  #
76
- # @param [String] force force the failover
79
+ # Must be sent to a replica node. Without a mode, performs a coordinated
80
+ # failover (the primary is asked to pause clients and hand over). +:force+
81
+ # promotes the replica without coordinating with the primary (useful when
82
+ # the primary is unreachable); +:takeover+ promotes it without any cluster
83
+ # consensus (most aggressive — risks data loss / split brain).
84
+ #
85
+ # @param [Symbol, nil] mode optional failover mode: +:force+ or +:takeover+
77
86
  # @return [String] `"OK"`
78
- def cluster_failover(force = nil)
87
+ # @raise [ArgumentError] if +mode+ is not nil, +:force+, or +:takeover+
88
+ # @see https://valkey.io/commands/cluster-failover/
89
+ def cluster_failover(mode = nil)
79
90
  args = []
80
- args << "FORCE" if force
91
+ unless mode.nil?
92
+ unless CLUSTER_FAILOVER_MODES.include?(mode)
93
+ raise ArgumentError, "invalid CLUSTER FAILOVER mode #{mode.inspect}: expected :force or :takeover"
94
+ end
95
+
96
+ args << mode.to_s.upcase
97
+ end
81
98
  send_command(RequestType::CLUSTER_FAILOVER, args)
82
99
  end
83
100
 
@@ -107,10 +124,15 @@ class Valkey
107
124
 
108
125
  # Get information about the cluster.
109
126
  #
127
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
110
128
  # @return [Hash<String, String>] cluster information
111
- def cluster_info
112
- send_command(RequestType::CLUSTER_INFO) do |reply|
113
- Utils::HashifyInfo.call(reply)
129
+ def cluster_info(route: nil)
130
+ send_command(RequestType::CLUSTER_INFO, [], route: route) do |reply|
131
+ if reply.is_a?(Hash)
132
+ reply.transform_values { |v| Utils::HashifyInfo.call(v) }
133
+ else
134
+ Utils::HashifyInfo.call(reply)
135
+ end
114
136
  end
115
137
  end
116
138
 
@@ -124,9 +146,10 @@ class Valkey
124
146
 
125
147
  # Get information about cluster links.
126
148
  #
149
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
127
150
  # @return [Array<Hash>] array of link information
128
- def cluster_links
129
- send_command(RequestType::CLUSTER_LINKS)
151
+ def cluster_links(route: nil)
152
+ send_command(RequestType::CLUSTER_LINKS, [], route: route)
130
153
  end
131
154
 
132
155
  # Meet another node in the cluster.
@@ -140,24 +163,31 @@ class Valkey
140
163
 
141
164
  # Get the ID of the current node.
142
165
  #
166
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
143
167
  # @return [String] node ID
144
- def cluster_myid
145
- send_command(RequestType::CLUSTER_MY_ID)
168
+ def cluster_myid(route: nil)
169
+ send_command(RequestType::CLUSTER_MY_ID, [], route: route)
146
170
  end
147
171
 
148
172
  # Get the shard ID of the current node.
149
173
  #
174
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
150
175
  # @return [String] shard ID
151
- def cluster_myshardid
152
- send_command(RequestType::CLUSTER_MY_SHARD_ID)
176
+ def cluster_myshardid(route: nil)
177
+ send_command(RequestType::CLUSTER_MY_SHARD_ID, [], route: route)
153
178
  end
154
179
 
155
180
  # Get information about all nodes in the cluster.
156
181
  #
182
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
157
183
  # @return [Array<Hash>] array of node information
158
- def cluster_nodes
159
- send_command(RequestType::CLUSTER_NODES) do |reply|
160
- Utils::HashifyClusterNodes.call(reply)
184
+ def cluster_nodes(route: nil)
185
+ send_command(RequestType::CLUSTER_NODES, [], route: route) do |reply|
186
+ if reply.is_a?(Hash)
187
+ reply.transform_values { |v| Utils::HashifyClusterNodes.call(v) }
188
+ else
189
+ Utils::HashifyClusterNodes.call(reply)
190
+ end
161
191
  end
162
192
  end
163
193
 
@@ -218,9 +248,10 @@ class Valkey
218
248
 
219
249
  # Get information about cluster shards.
220
250
  #
251
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
221
252
  # @return [Array<Hash>] array of shard information
222
- def cluster_shards
223
- send_command(RequestType::CLUSTER_SHARDS)
253
+ def cluster_shards(route: nil)
254
+ send_command(RequestType::CLUSTER_SHARDS, [], route: route)
224
255
  end
225
256
 
226
257
  # Get information about slave nodes (deprecated, use cluster_replicas).
@@ -18,18 +18,25 @@ class Valkey
18
18
 
19
19
  # Ping the server.
20
20
  #
21
- # @param [optional, String] message
22
- # @return [String] `PONG`
23
- def ping(message = nil)
24
- send_command(RequestType::PING, [message].compact)
21
+ # @param message [String, nil] optional message to echo back
22
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
23
+ # @return [String]
24
+ #
25
+ # @example
26
+ # ping #=> "PONG"
27
+ # ping("hello") #=> "hello"
28
+ # ping(route: Valkey::Route.all_nodes) #=> Hash (multi-node)
29
+ def ping(message = nil, route: nil)
30
+ send_command(RequestType::PING, [message].compact, route: route)
25
31
  end
26
32
 
27
33
  # Echo the given string.
28
34
  #
29
- # @param [String] value
35
+ # @param value [String]
36
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
30
37
  # @return [String]
31
- def echo(value)
32
- send_command(RequestType::ECHO, [value])
38
+ def echo(value, route: nil)
39
+ send_command(RequestType::ECHO, [value], route: route)
33
40
  end
34
41
 
35
42
  # Change the selected database for the current connection.
@@ -94,14 +101,15 @@ class Valkey
94
101
  # client(:set_name, "my_app") # => "OK"
95
102
  # client(:list) # => [{"id" => "1", ...}, ...]
96
103
  def client(subcommand, *args)
97
- send("client_#{subcommand.to_s.downcase}", *args)
104
+ public_send("client_#{subcommand.to_s.downcase}", *args)
98
105
  end
99
106
 
100
107
  # Get the current client's ID.
101
108
  #
102
- # @return [Integer] Unique client ID
103
- def client_id
104
- send_command(RequestType::CLIENT_ID)
109
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
110
+ # @return [Integer]
111
+ def client_id(route: nil)
112
+ send_command(RequestType::CLIENT_ID, [], route: route)
105
113
  end
106
114
 
107
115
  # Get the current client's name.
@@ -136,8 +144,10 @@ class Valkey
136
144
 
137
145
  send_command(RequestType::CLIENT_LIST, args) do |reply|
138
146
  reply.lines.map do |line|
139
- entries = line.chomp.split(/[ =]/)
140
- entries.each_slice(2).to_a.to_h
147
+ line.chomp.split.each_with_object({}) do |pair, hash|
148
+ key, value = pair.split("=", 2)
149
+ hash[key] = value || ""
150
+ end
141
151
  end
142
152
  end
143
153
  end
@@ -203,9 +213,10 @@ class Valkey
203
213
 
204
214
  # Unpause client processing.
205
215
  #
216
+ # @param route [Valkey::Route, nil] cluster routing.
206
217
  # @return [String] `OK`
207
- def client_unpause
208
- send_command(RequestType::CLIENT_UNPAUSE)
218
+ def client_unpause(route: nil)
219
+ send_command(RequestType::CLIENT_UNPAUSE, [], route: route)
209
220
  end
210
221
 
211
222
  # Configure client reply mode.
@@ -290,6 +301,12 @@ class Valkey
290
301
  send_command(RequestType::CLIENT_NO_TOUCH, [mode.to_s.upcase])
291
302
  end
292
303
 
304
+ # Client-side caching commands are intentionally private.
305
+ #
306
+ # GLIDE's managed client-side caching is not yet implemented in the Ruby client
307
+ # These commands are not meant to be called directly and will be kept private.
308
+ private :client_tracking, :client_caching, :client_tracking_info, :client_getredir
309
+
293
310
  private
294
311
 
295
312
  def build_client_tracking_args(options)
@@ -299,8 +316,9 @@ class Valkey
299
316
  when :redirect
300
317
  args << "REDIRECT" << value.to_s
301
318
  when :prefix
302
- args << "PREFIX"
303
- Array(value).each { |prefix| args << prefix }
319
+ # Valkey requires the PREFIX keyword before each prefix value,
320
+ # e.g. PREFIX foo PREFIX bar.
321
+ Array(value).each { |prefix| args << "PREFIX" << prefix }
304
322
  when :bcast
305
323
  args << "BCAST" if value
306
324
  when :optin
@@ -14,11 +14,12 @@ class Valkey
14
14
  # # => "OK"
15
15
  #
16
16
  # @param [String] library_name the library name to delete
17
+ # @param route [Valkey::Route, nil] cluster routing.
17
18
  # @return [String] "OK"
18
19
  #
19
20
  # @see https://valkey.io/commands/function-delete/
20
- def function_delete(library_name)
21
- send_command(RequestType::FUNCTION_DELETE, [library_name])
21
+ def function_delete(library_name, route: nil)
22
+ send_command(RequestType::FUNCTION_DELETE, [library_name], route: route)
22
23
  end
23
24
 
24
25
  # Return the serialized payload of loaded libraries.
@@ -27,11 +28,12 @@ class Valkey
27
28
  # valkey.function_dump
28
29
  # # => <binary string>
29
30
  #
31
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
30
32
  # @return [String] the serialized payload
31
33
  #
32
34
  # @see https://valkey.io/commands/function-dump/
33
- def function_dump
34
- send_command(RequestType::FUNCTION_DUMP)
35
+ def function_dump(route: nil)
36
+ send_command(RequestType::FUNCTION_DUMP, [], route: route)
35
37
  end
36
38
 
37
39
  # Delete all libraries.
@@ -48,10 +50,11 @@ class Valkey
48
50
  #
49
51
  # @param [Boolean] async flush asynchronously
50
52
  # @param [Boolean] sync flush synchronously
53
+ # @param route [Valkey::Route, nil] cluster routing.
51
54
  # @return [String] "OK"
52
55
  #
53
56
  # @see https://valkey.io/commands/function-flush/
54
- def function_flush(async: false, sync: false)
57
+ def function_flush(async: false, sync: false, route: nil)
55
58
  args = []
56
59
 
57
60
  if async
@@ -60,7 +63,7 @@ class Valkey
60
63
  args << "SYNC"
61
64
  end
62
65
 
63
- send_command(RequestType::FUNCTION_FLUSH, args)
66
+ send_command(RequestType::FUNCTION_FLUSH, args, route: route)
64
67
  end
65
68
 
66
69
  # Kill a function that is currently executing.
@@ -69,11 +72,12 @@ class Valkey
69
72
  # valkey.function_kill
70
73
  # # => "OK"
71
74
  #
75
+ # @param route [Valkey::Route, nil] cluster routing.
72
76
  # @return [String] "OK"
73
77
  #
74
78
  # @see https://valkey.io/commands/function-kill/
75
- def function_kill
76
- send_command(RequestType::FUNCTION_KILL)
79
+ def function_kill(route: nil)
80
+ send_command(RequestType::FUNCTION_KILL, [], route: route)
77
81
  end
78
82
 
79
83
  # Return information about the functions and libraries.
@@ -90,10 +94,11 @@ class Valkey
90
94
  #
91
95
  # @param [String] library_name filter by library name pattern
92
96
  # @param [Boolean] with_code include the library code in the response
97
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
93
98
  # @return [Array<Hash>] array of library information
94
99
  #
95
100
  # @see https://valkey.io/commands/function-list/
96
- def function_list(library_name: nil, with_code: false)
101
+ def function_list(library_name: nil, with_code: false, route: nil)
97
102
  args = []
98
103
 
99
104
  if library_name
@@ -103,7 +108,7 @@ class Valkey
103
108
 
104
109
  args << "WITHCODE" if with_code
105
110
 
106
- send_command(RequestType::FUNCTION_LIST, args)
111
+ send_command(RequestType::FUNCTION_LIST, args, route: route)
107
112
  end
108
113
 
109
114
  # Load a library to Valkey.
@@ -118,15 +123,16 @@ class Valkey
118
123
  #
119
124
  # @param [String] function_code the source code
120
125
  # @param [Boolean] replace replace the library if it exists
126
+ # @param route [Valkey::Route, nil] cluster routing.
121
127
  # @return [String] the library name that was loaded
122
128
  #
123
129
  # @see https://valkey.io/commands/function-load/
124
- def function_load(function_code, replace: false)
130
+ def function_load(function_code, replace: false, route: nil)
125
131
  args = []
126
132
  args << "REPLACE" if replace
127
133
  args << function_code
128
134
 
129
- send_command(RequestType::FUNCTION_LOAD, args)
135
+ send_command(RequestType::FUNCTION_LOAD, args, route: route)
130
136
  end
131
137
 
132
138
  # Restore libraries from a payload.
@@ -147,15 +153,16 @@ class Valkey
147
153
  #
148
154
  # @param [String] serialized_value the serialized payload from FUNCTION DUMP
149
155
  # @param [String] policy the restore policy: "FLUSH", "APPEND", or "REPLACE"
156
+ # @param route [Valkey::Route, nil] cluster routing.
150
157
  # @return [String] "OK"
151
158
  #
152
159
  # @see https://valkey.io/commands/function-restore/
153
- def function_restore(serialized_value, policy: nil)
160
+ def function_restore(serialized_value, policy: nil, route: nil)
154
161
  args = [serialized_value]
155
162
 
156
163
  args << policy.to_s.upcase if policy
157
164
 
158
- send_command(RequestType::FUNCTION_RESTORE, args)
165
+ send_command(RequestType::FUNCTION_RESTORE, args, route: route)
159
166
  end
160
167
 
161
168
  # Return information about the function that's currently running.
@@ -164,11 +171,12 @@ class Valkey
164
171
  # valkey.function_stats
165
172
  # # => {"running_script" => {...}, "engines" => {...}}
166
173
  #
174
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
167
175
  # @return [Hash] function execution statistics
168
176
  #
169
177
  # @see https://valkey.io/commands/function-stats/
170
- def function_stats
171
- send_command(RequestType::FUNCTION_STATS)
178
+ def function_stats(route: nil)
179
+ send_command(RequestType::FUNCTION_STATS, [], route: route)
172
180
  end
173
181
 
174
182
  # Invoke a function.
@@ -183,12 +191,13 @@ class Valkey
183
191
  # @param [String] function the function name
184
192
  # @param [Array<String>] keys the keys to pass to the function
185
193
  # @param [Array<String>] args the arguments to pass to the function
194
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
186
195
  # @return [Object] the function result
187
196
  #
188
197
  # @see https://valkey.io/commands/fcall/
189
- def fcall(function, keys: [], args: [])
198
+ def fcall(function, keys: [], args: [], route: nil)
190
199
  command_args = [function, keys.size] + keys + args
191
- send_command(RequestType::FCALL, command_args)
200
+ send_command(RequestType::FCALL, command_args, route: route)
192
201
  end
193
202
 
194
203
  # Invoke a read-only function.
@@ -200,12 +209,13 @@ class Valkey
200
209
  # @param [String] function the function name
201
210
  # @param [Array<String>] keys the keys to pass to the function
202
211
  # @param [Array<String>] args the arguments to pass to the function
212
+ # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
203
213
  # @return [Object] the function result
204
214
  #
205
215
  # @see https://valkey.io/commands/fcall_ro/
206
- def fcall_ro(function, keys: [], args: [])
216
+ def fcall_ro(function, keys: [], args: [], route: nil)
207
217
  command_args = [function, keys.size] + keys + args
208
- send_command(RequestType::FCALL_READ_ONLY, command_args)
218
+ send_command(RequestType::FCALL_READ_ONLY, command_args, route: route)
209
219
  end
210
220
 
211
221
  # Control function registry (convenience method).