Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a06a119
feat: transparent leader hint reconnect on consume
vieiralucas Mar 22, 2026
2658c53
fix: rubocop offenses in client.rb
vieiralucas Mar 24, 2026
42afd7c
fix: reduce class length to pass rubocop
vieiralucas Mar 24, 2026
55cafab
fix: trim class length to pass rubocop 100-line limit
vieiralucas Mar 24, 2026
11aeaf2
fix: trim more doc comments to pass rubocop class length
vieiralucas Mar 24, 2026
ae180a3
fix: trim consume doc comment to pass rubocop class length
vieiralucas Mar 24, 2026
d77b777
fix: trim enqueue doc comments to pass rubocop class length
vieiralucas Mar 24, 2026
7601fb1
fix: condense build_channel_credentials to pass rubocop class length
vieiralucas Mar 24, 2026
9f31a58
Merge pull request #2 from faiscadev/feat/leader-hint-reconnect
vieiralucas Mar 24, 2026
f570f47
chore: bump version to 0.2.0
vieiralucas Mar 24, 2026
1510b29
feat: add batch enqueue, delivery batching, and smart batch modes (#3)
vieiralucas Mar 24, 2026
8bb8a09
feat: unified api surface for story 30.2
vieiralucas Mar 25, 2026
88a334a
fix: address cubic finding and rubocop lint offenses
vieiralucas Mar 25, 2026
b482907
feat: replace grpc with fibp binary protocol
vieiralucas Apr 4, 2026
6f9a966
fix: resolve rubocop lint offenses and test helper timeout
vieiralucas Apr 4, 2026
4a3e0cf
fix: remaining rubocop offenses (line length, constant scoping, modif…
vieiralucas Apr 4, 2026
fc57ce6
fix: add last error detail to server startup failure message
vieiralucas Apr 4, 2026
301ff1c
fix: address 10 cubic review findings
vieiralucas Apr 4, 2026
eb4d575
fix: correct opcode values to match server, split decode_stats_result…
vieiralucas Apr 4, 2026
2da74e7
fix: resolve cyclomatic complexity in consume_with_redirect
vieiralucas Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ AllCops:
NewCops: enable
SuggestExtensions: false
Exclude:
- 'lib/fila/proto/**/*'
- 'vendor/**/*'

Metrics/ClassLength:
Max: 300

Metrics/MethodLength:
Max: 25
Exclude:
Expand Down
137 changes: 86 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# fila-ruby

Ruby client SDK for the [Fila](https://github.com/faisca/fila) message broker.
Ruby client SDK for the [Fila](https://github.com/faisca/fila) message broker using the FIBP binary protocol.

## Installation

Expand Down Expand Up @@ -44,21 +44,9 @@ end
client.close
```

### TLS (system trust store)

```ruby
require "fila"

# TLS using the OS system trust store (e.g., server uses a public CA).
client = Fila::Client.new("localhost:5555", tls: true)
```

### TLS (custom CA)

```ruby
require "fila"

# TLS with an explicit CA certificate (e.g., private/self-signed CA).
client = Fila::Client.new("localhost:5555",
ca_cert: File.read("ca.pem")
)
Expand All @@ -67,16 +55,6 @@ client = Fila::Client.new("localhost:5555",
### mTLS (mutual TLS)

```ruby
require "fila"

# Mutual TLS with system trust store.
client = Fila::Client.new("localhost:5555",
tls: true,
client_cert: File.read("client.pem"),
client_key: File.read("client-key.pem")
)

# Mutual TLS with explicit CA certificate.
client = Fila::Client.new("localhost:5555",
ca_cert: File.read("ca.pem"),
client_cert: File.read("client.pem"),
Expand All @@ -87,9 +65,6 @@ client = Fila::Client.new("localhost:5555",
### API Key Authentication

```ruby
require "fila"

# API key sent as Bearer token on every request.
client = Fila::Client.new("localhost:5555",
api_key: "fila_your_api_key_here"
)
Expand All @@ -98,9 +73,6 @@ client = Fila::Client.new("localhost:5555",
### mTLS + API Key

```ruby
require "fila"

# Full security: mTLS transport + API key authentication.
client = Fila::Client.new("localhost:5555",
ca_cert: File.read("ca.pem"),
client_cert: File.read("client.pem"),
Expand All @@ -109,9 +81,73 @@ client = Fila::Client.new("localhost:5555",
)
```

### Batch Enqueue

```ruby
results = client.enqueue_many([
{ queue: "orders", payload: "order-1", headers: { "tenant" => "acme" } },
{ queue: "orders", payload: "order-2" },
])

results.each do |r|
if r.success?
puts "Enqueued: #{r.message_id}"
else
puts "Failed: #{r.error}"
end
end
```

### Admin Operations

```ruby
# Create a queue.
client.create_queue(name: "my-queue")

# Delete a queue.
client.delete_queue(queue: "my-queue")

# Get queue statistics.
stats = client.get_stats(queue: "my-queue")

# List all queues.
queues = client.list_queues

# Runtime configuration.
client.set_config(key: "queues.my-queue.visibility_timeout_ms", value: "30000")
value = client.get_config(key: "queues.my-queue.visibility_timeout_ms")
entries = client.list_config(prefix: "queues.")

# Redrive DLQ messages.
count = client.redrive(dlq_queue: "my-queue-dlq", count: 100)
```

### Auth Operations

```ruby
# Create an API key.
result = client.create_api_key(name: "my-key", is_superadmin: false)
puts result[:key]

# Revoke an API key.
client.revoke_api_key(key_id: result[:key_id])

# List API keys.
keys = client.list_api_keys

# Set ACL permissions.
client.set_acl(key_id: "key-id", permissions: [
{ kind: "produce", pattern: "orders.*" },
{ kind: "consume", pattern: "orders.*" },
])

# Get ACL permissions.
acl = client.get_acl(key_id: "key-id")
```

## API

### `Fila::Client.new(addr, tls: false, ca_cert: nil, client_cert: nil, client_key: nil, api_key: nil)`
### `Fila::Client.new(addr, ...)`

Connect to a Fila broker at the given address (e.g., `"localhost:5555"`).

Expand All @@ -122,47 +158,46 @@ Connect to a Fila broker at the given address (e.g., `"localhost:5555"`).
| `ca_cert:` | `String` or `nil` | PEM-encoded CA certificate for TLS (implies `tls: true`) |
| `client_cert:` | `String` or `nil` | PEM-encoded client certificate for mTLS |
| `client_key:` | `String` or `nil` | PEM-encoded client private key for mTLS |
| `api_key:` | `String` or `nil` | API key for Bearer token authentication |

When no TLS/auth options are provided, the client connects over plaintext (backward compatible). When `tls: true` is set without `ca_cert:`, the OS system trust store is used for server certificate verification.
| `api_key:` | `String` or `nil` | API key sent during FIBP handshake |
| `batch_mode:` | `Symbol` | `:auto` (default), `:linger`, or `:disabled` |

### `client.enqueue(queue:, headers:, payload:)`
### `client.enqueue(queue:, payload:, headers: nil)`

Enqueue a message. Returns the broker-assigned message ID (UUIDv7).

### `client.enqueue_many(messages)`

Enqueue multiple messages in a single request. Returns an array of `Fila::EnqueueResult`.

### `client.consume(queue:) { |msg| ... }`

Open a streaming consumer. Yields `Fila::ConsumeMessage` objects as they become available. If no block is given, returns an `Enumerator`. Nacked messages are redelivered on the same stream.
Open a streaming consumer. Yields `Fila::ConsumeMessage` objects. If no block is given, returns an `Enumerator`.

### `client.ack(queue:, msg_id:)`

Acknowledge a successfully processed message. The message is permanently removed.
Acknowledge a successfully processed message.

### `client.nack(queue:, msg_id:, error:)`

Negatively acknowledge a failed message. The message is requeued or routed to the dead-letter queue based on the queue's configuration.
Negatively acknowledge a failed message.

### `client.close`

Close the underlying gRPC channel.
Drain pending batches and close the TCP connection.

## Error Handling

Per-operation error classes are raised for specific failure modes:

```ruby
begin
client.enqueue(queue: "missing-queue", payload: "test")
rescue Fila::QueueNotFoundError => e
# handle queue not found
end

begin
client.ack(queue: "my-queue", msg_id: "missing-id")
rescue Fila::MessageNotFoundError => e
# handle message not found
end
```
| Error Class | Description |
|---|---|
| `Fila::QueueNotFoundError` | Queue does not exist |
| `Fila::MessageNotFoundError` | Message not found or not leased |
| `Fila::QueueAlreadyExistsError` | Queue already exists |
| `Fila::AuthenticationError` | Missing or invalid API key |
| `Fila::ForbiddenError` | Insufficient permissions |
| `Fila::NotLeaderError` | Not the leader (includes leader hint) |
| `Fila::RPCError` | Transport or protocol failure |

## License

Expand Down
6 changes: 2 additions & 4 deletions fila-client.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ Gem::Specification.new do |spec|
spec.version = Fila::VERSION
spec.authors = ['Faisca']
spec.summary = 'Ruby client SDK for the Fila message broker'
spec.description = "Idiomatic Ruby client wrapping Fila's gRPC API for enqueue, consume, ack, and nack operations."
spec.description = 'Idiomatic Ruby client for the Fila message broker using the FIBP binary protocol.'
spec.homepage = 'https://github.com/faiscadev/fila-ruby'
spec.license = 'AGPL-3.0-or-later'
spec.required_ruby_version = '>= 3.1'

spec.files = Dir['lib/**/*.rb', 'proto/**/*.proto', 'LICENSE', 'README.md']
spec.files = Dir['lib/**/*.rb', 'LICENSE', 'README.md']
spec.require_paths = ['lib']

spec.add_dependency 'google-protobuf', '~> 4.0'
spec.add_dependency 'grpc', '~> 1.60'
spec.metadata['rubygems_mfa_required'] = 'true'
end
5 changes: 5 additions & 0 deletions lib/fila.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@
require_relative 'fila/version'
require_relative 'fila/errors'
require_relative 'fila/consume_message'
require_relative 'fila/enqueue_result'
require_relative 'fila/fibp/opcodes'
require_relative 'fila/fibp/codec'
require_relative 'fila/fibp/connection'
require_relative 'fila/batcher'
require_relative 'fila/client'
Loading
Loading