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
@@ -0,0 +1,636 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Valkey
4
+ module Commands
5
+ # This module contains commands related to Redis Streams.
6
+ #
7
+ # @see https://valkey.io/commands/#stream
8
+ #
9
+ module StreamCommands
10
+ # Append a new entry to a stream.
11
+ #
12
+ # @param [String] key stream key
13
+ # @param [Hash, Array] entry field-value pairs
14
+ # @param [Hash] options optional parameters
15
+ # - `:id => String`: entry ID (default: "*" for auto-generated)
16
+ # - `:maxlen => Integer`: maximum length of the stream
17
+ # - `:minid => String`: minimum ID to keep
18
+ # - `:approximate => true`: use approximate trimming
19
+ # - `:nomkstream => true`: do not create stream if it doesn't exist
20
+ # @return [String] entry ID
21
+ #
22
+ # @example Add entry with auto-generated ID
23
+ # valkey.xadd("mystream", { "field1" => "value1", "field2" => "value2" })
24
+ # # => "1234567890-0"
25
+ # @example Add entry with specific ID
26
+ # valkey.xadd("mystream", { "field1" => "value1" }, id: "1234567890-1")
27
+ # # => "1234567890-1"
28
+ # @example Add entry with maxlen trimming
29
+ # valkey.xadd("mystream", { "field1" => "value1" }, maxlen: 1000, approximate: true)
30
+ #
31
+ # @see https://valkey.io/commands/xadd/
32
+ def xadd(key, entry, approximate: nil, maxlen: nil, minid: nil, nomkstream: nil, id: "*")
33
+ args = [key]
34
+
35
+ # Handle maxlen/minid trimming
36
+ if maxlen
37
+ raise ArgumentError, "can't supply both maxlen and minid" if minid
38
+
39
+ args << "MAXLEN"
40
+ args << "~" if approximate
41
+ args << maxlen.to_s
42
+ elsif minid
43
+ args << "MINID"
44
+ args << "~" if approximate
45
+ args << minid
46
+ end
47
+
48
+ args << "NOMKSTREAM" if nomkstream
49
+ args << id
50
+
51
+ # Add field-value pairs
52
+ if entry.is_a?(Hash)
53
+ entry.each { |k, v| args << k.to_s << v.to_s }
54
+ else
55
+ args.concat(Array(entry).flatten)
56
+ end
57
+
58
+ send_command(RequestType::X_ADD, args)
59
+ end
60
+
61
+ # Remove one or more entries from a stream.
62
+ #
63
+ # @param [String] key stream key
64
+ # @param [String, Array<String>] ids entry ID(s) to delete
65
+ # @return [Integer] number of entries deleted
66
+ #
67
+ # @example Delete a single entry
68
+ # valkey.xdel("mystream", "1234567890-0")
69
+ # # => 1
70
+ # @example Delete multiple entries
71
+ # valkey.xdel("mystream", ["1234567890-0", "1234567890-1"])
72
+ # # => 2
73
+ #
74
+ # @see https://valkey.io/commands/xdel/
75
+ def xdel(key, *ids)
76
+ args = [key] + Array(ids).flatten
77
+ send_command(RequestType::X_DEL, args)
78
+ end
79
+
80
+ # Get the length of a stream.
81
+ #
82
+ # @param [String] key stream key
83
+ # @return [Integer] number of entries in the stream
84
+ #
85
+ # @example
86
+ # valkey.xlen("mystream")
87
+ # # => 42
88
+ #
89
+ # @see https://valkey.io/commands/xlen/
90
+ def xlen(key)
91
+ send_command(RequestType::X_LEN, [key])
92
+ end
93
+
94
+ # Read entries from one or more streams.
95
+ #
96
+ # @param [Array<String>] keys stream keys
97
+ # @param [Array<String>] ids last read IDs for each stream
98
+ # @param [Hash] options optional parameters
99
+ # - `:count => Integer`: maximum number of entries per stream
100
+ # - `:block => Integer`: block for specified milliseconds (0 = no timeout)
101
+ # @return [Hash] hash of stream keys to arrays of entries (empty hash on timeout or no data)
102
+ #
103
+ # @example Read from a single stream
104
+ # valkey.xread(["mystream"], ["0"])
105
+ # # => { "mystream" => [["1234567890-0", ["field1", "value1"]]] }
106
+ # @example Read with count and block
107
+ # valkey.xread(["mystream"], ["0"], count: 10, block: 1000)
108
+ #
109
+ # @see https://valkey.io/commands/xread/
110
+ def xread(keys, ids, count: nil, block: nil)
111
+ args = []
112
+
113
+ args << "COUNT" << count.to_s if count
114
+ args << "BLOCK" << block.to_s if block
115
+ args << "STREAMS"
116
+ args.concat(Array(keys))
117
+ args.concat(Array(ids))
118
+
119
+ send_command(RequestType::X_READ, args) do |reply|
120
+ # Backend returns Array format: [stream_name, entries, stream_name2, entries2, ...]
121
+ # Convert to Hash format first
122
+ if reply.nil?
123
+ {}
124
+ elsif reply.is_a?(Array) && !reply.empty?
125
+ stream_hash = reply.each_slice(2).to_h
126
+ Utils::HashifyStreams.call(stream_hash)
127
+ else
128
+ Utils::HashifyStreams.call(reply)
129
+ end
130
+ end
131
+ end
132
+
133
+ # Read entries from streams using a consumer group.
134
+ #
135
+ # @param [String] group consumer group name
136
+ # @param [String] consumer consumer name
137
+ # @param [Array<String>] keys stream keys
138
+ # @param [Array<String>] ids last read IDs for each stream
139
+ # @param [Hash] options optional parameters
140
+ # - `:count => Integer`: maximum number of entries per stream
141
+ # - `:block => Integer`: block for specified milliseconds (0 = no timeout)
142
+ # - `:noack => true`: do not add messages to pending list
143
+ # @return [Hash] hash of stream keys to arrays of entries (empty hash on timeout or no data)
144
+ #
145
+ # @example Read from consumer group
146
+ # valkey.xreadgroup("mygroup", "consumer1", ["mystream"], [">"])
147
+ #
148
+ # @see https://valkey.io/commands/xreadgroup/
149
+ def xreadgroup(group, consumer, keys, ids, count: nil, block: nil, noack: false)
150
+ args = ["GROUP", group, consumer]
151
+
152
+ args << "COUNT" << count.to_s if count
153
+ args << "BLOCK" << block.to_s if block
154
+ args << "NOACK" if noack
155
+ args << "STREAMS"
156
+ args.concat(Array(keys))
157
+ args.concat(Array(ids))
158
+
159
+ send_command(RequestType::X_READ_GROUP, args) do |reply|
160
+ # Backend returns Array format: [stream_name, entries, stream_name2, entries2, ...]
161
+ # Convert to Hash format first
162
+ if reply.nil?
163
+ {}
164
+ elsif reply.is_a?(Array) && !reply.empty?
165
+ stream_hash = reply.each_slice(2).to_h
166
+ Utils::HashifyStreams.call(stream_hash)
167
+ else
168
+ Utils::HashifyStreams.call(reply)
169
+ end
170
+ end
171
+ end
172
+
173
+ # Get entries from a stream within a range of IDs.
174
+ #
175
+ # @param [String] key stream key
176
+ # @param [String] start start ID ("-" for beginning, "+" for end)
177
+ # @param [String] end_id end ID ("-" for beginning, "+" for end)
178
+ # @param [Hash] options optional parameters
179
+ # - `:count => Integer`: maximum number of entries to return
180
+ # @return [Array] array of [id, [field, value, ...]] entries
181
+ #
182
+ # @example Get all entries
183
+ # valkey.xrange("mystream", "-", "+")
184
+ # @example Get entries with count limit
185
+ # valkey.xrange("mystream", "-", "+", count: 10)
186
+ #
187
+ # @see https://valkey.io/commands/xrange/
188
+ def xrange(key, start, end_id, **options)
189
+ args = [key, start, end_id]
190
+ args << "COUNT" << options[:count].to_s if options[:count]
191
+ send_command(RequestType::X_RANGE, args) do |reply|
192
+ Utils::HashifyStreamEntries.call(reply)
193
+ end
194
+ end
195
+
196
+ # Get entries from a stream within a range of IDs in reverse order.
197
+ #
198
+ # @param [String] key stream key
199
+ # @param [String] end_id end ID ("+" for end, "-" for beginning) - higher bound
200
+ # @param [String] start start ID ("-" for beginning, "+" for end) - lower bound
201
+ # @param [Hash] options optional parameters
202
+ # - `:count => Integer`: maximum number of entries to return
203
+ # @return [Array] array of [id, [field, value, ...]] entries in reverse order
204
+ #
205
+ # @example Get last 10 entries
206
+ # valkey.xrevrange("mystream", "+", "-", count: 10)
207
+ #
208
+ # @see https://valkey.io/commands/xrevrange/
209
+ def xrevrange(key, end_id = "+", start = "-", count: nil)
210
+ args = [key, end_id, start]
211
+ args << "COUNT" << count.to_s if count
212
+ send_command(RequestType::X_REV_RANGE, args) do |reply|
213
+ Utils::HashifyStreamEntries.call(reply)
214
+ end
215
+ end
216
+
217
+ # Trim a stream to a maximum length.
218
+ #
219
+ # @param [String] key stream key
220
+ # @param [Integer] maxlen maximum length of the stream
221
+ # @param [Hash] options trimming options
222
+ # - `:strategy => String`: trimming strategy (default: "MAXLEN")
223
+ # - `:approximate => true`: use approximate trimming (default: true)
224
+ # @return [Integer] number of entries removed
225
+ #
226
+ # @example Trim to maximum length
227
+ # valkey.xtrim("mystream", 1000)
228
+ # @example Trim with exact count
229
+ # valkey.xtrim("mystream", 1000, approximate: false)
230
+ #
231
+ # @see https://valkey.io/commands/xtrim/
232
+ def xtrim(key, maxlen, strategy: "MAXLEN", approximate: true)
233
+ args = [key, strategy]
234
+ args << "~" if approximate
235
+ args << maxlen.to_s
236
+ send_command(RequestType::X_TRIM, args)
237
+ end
238
+
239
+ # Manage consumer groups (dispatcher method).
240
+ #
241
+ # @param [Symbol, String] subcommand subcommand (:create, :setid, :destroy, :createconsumer, :delconsumer)
242
+ # @param [String] key stream key
243
+ # @param [String] group consumer group name
244
+ # @param [Array] args additional arguments depending on subcommand
245
+ # @return [String, Integer] depends on subcommand
246
+ #
247
+ # @example Create group
248
+ # valkey.xgroup(:create, "mystream", "mygroup", "0")
249
+ # @example Create group with mkstream
250
+ # valkey.xgroup(:create, "mystream", "mygroup", "0", mkstream: true)
251
+ # @example Set group ID
252
+ # valkey.xgroup(:setid, "mystream", "mygroup", "1234567890-0")
253
+ # @example Destroy group
254
+ # valkey.xgroup(:destroy, "mystream", "mygroup")
255
+ # @example Create consumer
256
+ # valkey.xgroup(:createconsumer, "mystream", "mygroup", "consumer1")
257
+ # @example Delete consumer
258
+ # valkey.xgroup(:delconsumer, "mystream", "mygroup", "consumer1")
259
+ #
260
+ # @see https://valkey.io/commands/xgroup/
261
+ def xgroup(subcommand, key, group, *args, **options)
262
+ subcommand = subcommand.to_s.downcase
263
+ case subcommand
264
+ when "create"
265
+ xgroup_create(key, group, args[0], **options)
266
+ when "setid"
267
+ xgroup_setid(key, group, args[0])
268
+ when "destroy"
269
+ xgroup_destroy(key, group)
270
+ when "createconsumer"
271
+ xgroup_createconsumer(key, group, args[0])
272
+ when "delconsumer"
273
+ xgroup_delconsumer(key, group, args[0])
274
+ else
275
+ raise ArgumentError, "Unknown XGROUP subcommand: #{subcommand}"
276
+ end
277
+ end
278
+
279
+ private
280
+
281
+ def xgroup_create_impl(key, group, id, **options)
282
+ args = [key, group, id]
283
+ args << "MKSTREAM" if options[:mkstream]
284
+ send_command(RequestType::X_GROUP_CREATE, args)
285
+ end
286
+
287
+ public
288
+
289
+ # Create a consumer group for a stream.
290
+ #
291
+ # @param [String] key stream key
292
+ # @param [String] group consumer group name
293
+ # @param [String] id starting ID ("0" for beginning, "$" for end)
294
+ # @param [Hash] options optional parameters
295
+ # - `:mkstream => true`: create stream if it doesn't exist
296
+ # @return [String] "OK"
297
+ #
298
+ # @example Create group from beginning
299
+ # valkey.xgroup_create("mystream", "mygroup", "0")
300
+ # @example Create group from end and create stream if needed
301
+ # valkey.xgroup_create("mystream", "mygroup", "$", mkstream: true)
302
+ #
303
+ # @see https://valkey.io/commands/xgroup-create/
304
+ def xgroup_create(key, group, id, **options)
305
+ xgroup_create_impl(key, group, id, **options)
306
+ end
307
+
308
+ # Create a consumer in a consumer group.
309
+ #
310
+ # @param [String] key stream key
311
+ # @param [String] group consumer group name
312
+ # @param [String] consumer consumer name
313
+ # @return [Integer] number of pending messages for the consumer (0 for new consumer)
314
+ #
315
+ # @example
316
+ # valkey.xgroup_createconsumer("mystream", "mygroup", "consumer1")
317
+ # # => 0
318
+ #
319
+ # @see https://valkey.io/commands/xgroup-createconsumer/
320
+ def xgroup_createconsumer(key, group, consumer)
321
+ send_command(RequestType::X_GROUP_CREATE_CONSUMER, [key, group, consumer]) do |reply|
322
+ # Convert boolean to integer if needed (backend may return boolean)
323
+ if reply.is_a?(TrueClass)
324
+ 1
325
+ elsif reply.is_a?(FalseClass)
326
+ 0
327
+ else
328
+ reply
329
+ end
330
+ end
331
+ end
332
+
333
+ # Set the last-delivered ID for a consumer group.
334
+ #
335
+ # @param [String] key stream key
336
+ # @param [String] group consumer group name
337
+ # @param [String] id entry ID
338
+ # @return [String] "OK"
339
+ #
340
+ # @example
341
+ # valkey.xgroup_setid("mystream", "mygroup", "1234567890-0")
342
+ #
343
+ # @see https://valkey.io/commands/xgroup-setid/
344
+ def xgroup_setid(key, group, id)
345
+ send_command(RequestType::X_GROUP_SET_ID, [key, group, id])
346
+ end
347
+
348
+ # Destroy a consumer group.
349
+ #
350
+ # @param [String] key stream key
351
+ # @param [String] group consumer group name
352
+ # @return [Integer] number of pending messages (if any)
353
+ #
354
+ # @example
355
+ # valkey.xgroup_destroy("mystream", "mygroup")
356
+ # # => 0
357
+ #
358
+ # @see https://valkey.io/commands/xgroup-destroy/
359
+ def xgroup_destroy(key, group)
360
+ send_command(RequestType::X_GROUP_DESTROY, [key, group]) do |reply|
361
+ # Convert boolean to integer if needed (backend may return boolean)
362
+ if reply.is_a?(TrueClass)
363
+ 1
364
+ elsif reply.is_a?(FalseClass)
365
+ 0
366
+ else
367
+ reply
368
+ end
369
+ end
370
+ end
371
+
372
+ # Remove a consumer from a consumer group.
373
+ #
374
+ # @param [String] key stream key
375
+ # @param [String] group consumer group name
376
+ # @param [String] consumer consumer name
377
+ # @return [Integer] number of pending messages for the consumer
378
+ #
379
+ # @example
380
+ # valkey.xgroup_delconsumer("mystream", "mygroup", "consumer1")
381
+ # # => 5
382
+ #
383
+ # @see https://valkey.io/commands/xgroup-delconsumer/
384
+ def xgroup_delconsumer(key, group, consumer)
385
+ send_command(RequestType::X_GROUP_DEL_CONSUMER, [key, group, consumer])
386
+ end
387
+
388
+ # Acknowledge one or more messages in a consumer group.
389
+ #
390
+ # @param [String] key stream key
391
+ # @param [String] group consumer group name
392
+ # @param [String, Array<String>] ids entry ID(s) to acknowledge
393
+ # @return [Integer] number of messages acknowledged
394
+ #
395
+ # @example Acknowledge a single message
396
+ # valkey.xack("mystream", "mygroup", "1234567890-0")
397
+ # # => 1
398
+ # @example Acknowledge multiple messages
399
+ # valkey.xack("mystream", "mygroup", ["1234567890-0", "1234567890-1"])
400
+ # # => 2
401
+ #
402
+ # @see https://valkey.io/commands/xack/
403
+ def xack(key, group, *ids)
404
+ args = [key, group] + Array(ids).flatten
405
+ send_command(RequestType::X_ACK, args)
406
+ end
407
+
408
+ # Get information about pending messages in a consumer group.
409
+ #
410
+ # @param [String] key stream key
411
+ # @param [String] group consumer group name
412
+ # @param [Array] args optional arguments (start, end, count, consumer)
413
+ # @param [Hash] options optional parameters
414
+ # - `:idle => Integer`: filter by minimum idle time in milliseconds
415
+ # @return [Hash, Array] pending information
416
+ # - Without args: summary hash with keys 'size', 'min_entry_id', 'max_entry_id', 'consumers'
417
+ # - With start/end/count: array of Hashes with keys 'entry_id', 'consumer', 'elapsed', and 'count'
418
+ #
419
+ # @example Get summary
420
+ # valkey.xpending("mystream", "mygroup")
421
+ # # => {"size" => 5, "min_entry_id" => "1234567890-0",
422
+ # # "max_entry_id" => "1234567890-4", "consumers" => {"consumer1" => 3, "consumer2" => 2}}
423
+ # @example Get detailed pending entries
424
+ # valkey.xpending("mystream", "mygroup", "-", "+", 10)
425
+ #
426
+ # @see https://valkey.io/commands/xpending/
427
+ def xpending(key, group, *args, idle: nil)
428
+ cmd_args = [key, group]
429
+ cmd_args.concat(args)
430
+ cmd_args << "IDLE" << idle.to_s if idle
431
+
432
+ send_command(RequestType::X_PENDING, cmd_args) do |reply|
433
+ # If args provided (start, end, count), return detailed format
434
+ # Otherwise return summary format
435
+ if args.length >= 2
436
+ Utils::HashifyStreamPendingDetails.call(reply)
437
+ else
438
+ Utils::HashifyStreamPendings.call(reply)
439
+ end
440
+ end
441
+ end
442
+
443
+ # Claim ownership of pending messages in a consumer group.
444
+ #
445
+ # @param [String] key stream key
446
+ # @param [String] group consumer group name
447
+ # @param [String] consumer consumer name
448
+ # @param [Integer] min_idle_time minimum idle time in milliseconds
449
+ # @param [String, Array<String>] ids entry ID(s) to claim
450
+ # @param [Hash] options optional parameters
451
+ # - `:idle => Integer`: set idle time in milliseconds
452
+ # - `:time => Integer`: set time in milliseconds (Unix timestamp)
453
+ # - `:retrycount => Integer`: set retry count
454
+ # - `:force => true`: claim even if already assigned
455
+ # - `:justid => true`: return only IDs
456
+ # @return [Array] array of claimed entries or IDs
457
+ #
458
+ # @example Claim pending messages
459
+ # valkey.xclaim("mystream", "mygroup", "consumer2", 3600000, ["1234567890-0"])
460
+ #
461
+ # @see https://valkey.io/commands/xclaim/
462
+ def xclaim(key, group, consumer, min_idle_time, ids, **options)
463
+ args = [key, group, consumer, min_idle_time.to_s]
464
+ args.concat(Array(ids).flatten)
465
+
466
+ args << "IDLE" << options[:idle].to_s if options[:idle]
467
+ args << "TIME" << options[:time].to_s if options[:time]
468
+ args << "RETRYCOUNT" << options[:retrycount].to_s if options[:retrycount]
469
+ args << "FORCE" if options[:force]
470
+ args << "JUSTID" if options[:justid]
471
+
472
+ send_command(RequestType::X_CLAIM, args) do |reply|
473
+ if options[:justid]
474
+ reply
475
+ else
476
+ Utils::HashifyStreamEntries.call(reply)
477
+ end
478
+ end
479
+ end
480
+
481
+ # Automatically claim pending messages that have been idle for a specified time.
482
+ #
483
+ # @param [String] key stream key
484
+ # @param [String] group consumer group name
485
+ # @param [String] consumer consumer name
486
+ # @param [Integer] min_idle_time minimum idle time in milliseconds
487
+ # @param [String] start start ID for scanning
488
+ # @param [Hash] options optional parameters
489
+ # - `:count => Integer`: maximum number of entries to claim
490
+ # - `:idle => Integer`: set idle time in milliseconds
491
+ # - `:time => Integer`: set time in milliseconds (Unix timestamp)
492
+ # - `:retrycount => Integer`: set retry count
493
+ # - `:justid => true`: return only IDs
494
+ # @return [Hash] hash with 'next' key for next cursor ID and 'entries' key for array of claimed entries
495
+ #
496
+ # @example Auto-claim pending messages
497
+ # valkey.xautoclaim("mystream", "mygroup", "consumer2", 3600000, "0-0")
498
+ # # => { 'next' => "1234567890-5", 'entries' => [["1234567890-0", ["field1", "value1"]]] }
499
+ #
500
+ # @see https://valkey.io/commands/xautoclaim/
501
+ def xautoclaim(key, group, consumer, min_idle_time, start, **options)
502
+ args = [key, group, consumer, min_idle_time.to_s, start]
503
+
504
+ args << "COUNT" << options[:count].to_s if options[:count]
505
+ args << "IDLE" << options[:idle].to_s if options[:idle]
506
+ args << "TIME" << options[:time].to_s if options[:time]
507
+ args << "RETRYCOUNT" << options[:retrycount].to_s if options[:retrycount]
508
+ args << "JUSTID" if options[:justid]
509
+
510
+ send_command(RequestType::X_AUTO_CLAIM, args) do |reply|
511
+ return { 'next' => '0-0', 'entries' => [] } if reply.nil? || !reply.is_a?(Array)
512
+
513
+ if options[:justid]
514
+ Utils::HashifyStreamAutoclaimJustId.call(reply)
515
+ else
516
+ Utils::HashifyStreamAutoclaim.call(reply)
517
+ end
518
+ end
519
+ end
520
+
521
+ # Get information about streams, groups, and consumers (dispatcher method).
522
+ #
523
+ # @param [Symbol, String] subcommand subcommand (:stream, :groups, :consumers)
524
+ # @param [String] key stream key
525
+ # @param [String] group optional consumer group name (required for :consumers)
526
+ # @param [Hash] options optional parameters (for :stream)
527
+ # - `:full => true`: return full information including entries
528
+ # - `:count => Integer`: limit number of entries (requires :full)
529
+ # @return [Hash, Array] depends on subcommand
530
+ #
531
+ # @example Get stream info
532
+ # valkey.xinfo(:stream, "mystream")
533
+ # @example Get stream info with full details
534
+ # valkey.xinfo(:stream, "mystream", full: true, count: 10)
535
+ # @example Get groups info
536
+ # valkey.xinfo(:groups, "mystream")
537
+ # @example Get consumers info
538
+ # valkey.xinfo(:consumers, "mystream", "mygroup")
539
+ #
540
+ # @see https://valkey.io/commands/xinfo/
541
+ def xinfo(subcommand, key, group = nil, **options)
542
+ subcommand = subcommand.to_s.downcase
543
+ case subcommand
544
+ when "stream"
545
+ args = [key]
546
+ if options[:full]
547
+ args << "FULL"
548
+ args << "COUNT" << options[:count].to_s if options[:count]
549
+ end
550
+ send_command(RequestType::X_INFO_STREAM, args)
551
+ when "groups"
552
+ send_command(RequestType::X_INFO_GROUPS, [key])
553
+ when "consumers"
554
+ raise ArgumentError, "Group name required for XINFO CONSUMERS" unless group
555
+
556
+ send_command(RequestType::X_INFO_CONSUMERS, [key, group])
557
+ else
558
+ raise ArgumentError, "Unknown XINFO subcommand: #{subcommand}"
559
+ end
560
+ end
561
+
562
+ # Get information about a stream.
563
+ #
564
+ # @param [String] key stream key
565
+ # @param [Hash] options optional parameters
566
+ # - `:full => true`: return full information including entries
567
+ # - `:count => Integer`: limit number of entries (requires :full)
568
+ # @return [Array] stream information as flat array of key-value pairs
569
+ #
570
+ # @example Get basic stream info
571
+ # valkey.xinfo_stream("mystream")
572
+ # # => ["length", 42, "radix-tree-keys", 1, ...]
573
+ # @example Get full info with entries
574
+ # valkey.xinfo_stream("mystream", full: true, count: 10)
575
+ #
576
+ # @see https://valkey.io/commands/xinfo-stream/
577
+ def xinfo_stream(key, **options)
578
+ xinfo(:stream, key, **options)
579
+ end
580
+
581
+ # Get information about consumer groups of a stream.
582
+ #
583
+ # @param [String] key stream key
584
+ # @return [Array] array of consumer group information hashes
585
+ #
586
+ # @example
587
+ # valkey.xinfo_groups("mystream")
588
+ # # => [{"name" => "mygroup", "consumers" => 2, "pending" => 5, ...}]
589
+ #
590
+ # @see https://valkey.io/commands/xinfo-groups/
591
+ def xinfo_groups(key)
592
+ xinfo(:groups, key)
593
+ end
594
+
595
+ # Get information about consumers in a consumer group.
596
+ #
597
+ # @param [String] key stream key
598
+ # @param [String] group consumer group name
599
+ # @return [Array] array of consumer information hashes
600
+ #
601
+ # @example
602
+ # valkey.xinfo_consumers("mystream", "mygroup")
603
+ # # => [{"name" => "consumer1", "pending" => 3, "idle" => 12345, ...}]
604
+ #
605
+ # @see https://valkey.io/commands/xinfo-consumers/
606
+ def xinfo_consumers(key, group)
607
+ xinfo(:consumers, key, group)
608
+ end
609
+
610
+ # TODO: Implement xsetid command after enabling in glide-core
611
+ # Set the ID of the last entry in a stream.
612
+ #
613
+ # @param [String] key stream key
614
+ # @param [String] id entry ID
615
+ # @param [Hash] options optional parameters
616
+ # - `:entries_added => Integer`: set entries-added counter
617
+ # - `:max_deleted_id => String`: set max-deleted-id
618
+ # @return [String] "OK"
619
+ #
620
+ # @example
621
+ # valkey.xsetid("mystream", "1234567890-0")
622
+ # @example With additional options
623
+ # valkey.xsetid("mystream", "1234567890-0", entries_added: 100, max_deleted_id: "1234567890-50")
624
+ #
625
+ # @see https://valkey.io/commands/xsetid/
626
+ # def xsetid(key, id, **options)
627
+ # args = [key, id]
628
+
629
+ # args << "ENTRIESADDED" << options[:entries_added].to_s if options[:entries_added]
630
+ # args << "MAXDELETEDID" << options[:max_deleted_id] if options[:max_deleted_id]
631
+
632
+ # send_command(RequestType::X_SET_ID, args)
633
+ # end
634
+ end
635
+ end
636
+ end