omq 0.7.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2e17e1a12c0f44bec5af914b8df2f810029e75c7da17e79e862428a7b0aa99d
4
- data.tar.gz: 4a8d06116bad67adc4411cb3281302fd6190ecce46ceb47f21cc15a32c3e9006
3
+ metadata.gz: a14af693117ccfb193f54e9bc2a4faae9c2bf6396e7769abee97f279fd62fdba
4
+ data.tar.gz: 1f5e99ae3b61402d2b9e506c84fdb07d2003695515d3f736159cf2c373cc39d0
5
5
  SHA512:
6
- metadata.gz: 1dad86f1ad0648690bc5a01d3b7258b89f301d4768bac0831495d27f0a0e3c84f5a30af049732f8cc6885bcd6105654d41708d519da69c30a62739ca58a27418
7
- data.tar.gz: a71f54aa26dd3e7dea3b249d027c03aeff896c7ded7b0581067fbda609d114c976c8d35a5f7f7e630cbbc34fbb839c7d43f6d445e8f2bb50f1348cb46f8d0e51
6
+ metadata.gz: 76539e2a9fbdcbe59590e1fa499e300b8f7ad3d527255af6f94665139c3c0a60151f0b82202df372df1cdbc16e90f46369aa7b159342f951cf52babc897b837c
7
+ data.tar.gz: dba241d3283a0a0330ba0d12081c08f3ae074acaa8be7b1e9292dee9c19a483d822f896a4442ec585005dbeb36efa704b87f51c53e07c1de0ede54a85150f55c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 — 2026-03-31
4
+
5
+ ### Breaking
6
+
7
+ - **CLI extracted into omq-cli gem** — the `omq` executable, all CLI
8
+ code (`lib/omq/cli/`), tests, and `CLI.md` have moved to the
9
+ [omq-cli](https://github.com/paddor/omq-cli) gem. `gem install omq`
10
+ no longer provides the `omq` command — use `gem install omq-cli`.
11
+ - **`OMQ.outgoing` / `OMQ.incoming`** registration API moved to omq-cli.
12
+ Library-only users are unaffected (these were CLI-specific).
13
+
14
+ ### Changed
15
+
16
+ - **Gemspec is library-only** — no `exe/`, no `bindir`, no `executables`.
17
+ - **README** — restored title, replaced inline CLI section with a
18
+ pointer to omq-cli, fixed ZMTP attribution for protocol-zmtp.
19
+ - **DESIGN.md** — acknowledged protocol-zmtp, clarified transient
20
+ task / linger interaction, removed ZMTP wire protocol section (now in
21
+ protocol-zmtp), simplified inproc description, removed CLI section.
22
+
23
+ ## 0.8.0 — 2026-03-31
24
+
25
+ ### Breaking
26
+
27
+ - **CURVE mechanism moved to protocol-zmtp** — `OMQ::ZMTP::Mechanism::Curve`
28
+ is now `Protocol::ZMTP::Mechanism::Curve` with a required `crypto:` parameter.
29
+ Pass `crypto: RbNaCl` (libsodium) or `crypto: Nuckle` (pure Ruby). The
30
+ omq-curve and omq-kurve gems are superseded.
31
+
32
+ ```ruby
33
+ # Before (omq-curve)
34
+ require "omq/curve"
35
+ rep.mechanism = OMQ::Curve.server(pub, sec)
36
+
37
+ # After (protocol-zmtp + any NaCl backend)
38
+ require "protocol/zmtp/mechanism/curve"
39
+ require "nuckle" # or: require "rbnacl"
40
+ rep.mechanism = Protocol::ZMTP::Mechanism::Curve.server(pub, sec, crypto: Nuckle)
41
+ ```
42
+
43
+ ### Changed
44
+
45
+ - **Protocol layer extracted into protocol-zmtp gem** — Codec (Frame,
46
+ Greeting, Command), Connection, Mechanism::Null, Mechanism::Curve,
47
+ ValidPeers, and Z85 now live in the
48
+ [protocol-zmtp](https://github.com/paddor/protocol-zmtp) gem. OMQ
49
+ re-exports them under `OMQ::ZMTP::` for backwards compatibility.
50
+ protocol-zmtp has zero runtime dependencies.
51
+ - **Unified CURVE mechanism** — one implementation with a pluggable
52
+ `crypto:` backend replaces the two near-identical copies in omq-curve
53
+ (RbNaCl) and omq-kurve (Nuckle). 1,088 → 467 lines (57% reduction).
54
+ - **Heartbeat ownership** — `Connection#start_heartbeat` removed.
55
+ Connection tracks timestamps only; the engine drives the PING/PONG loop.
56
+ - **CI no longer needs libsodium** — CURVE tests use
57
+ [nuckle](https://github.com/paddor/nuckle) (pure Ruby) by default.
58
+ Cross-backend interop tests run when rbnacl is available.
59
+
3
60
  ## 0.7.0 — 2026-03-30
4
61
 
5
62
  ### Breaking
@@ -502,4 +559,4 @@ Initial release. Pure Ruby implementation of ZMTP 3.1 (ZeroMQ) using Async.
502
559
  - Linger on close (drain send queue before closing)
503
560
  - `max_message_size` enforcement
504
561
  - Works inside Async reactors or standalone (shared IO thread)
505
- - Optional CURVE encryption via the [omq-curve](https://github.com/zeromq/omq-curve) gem
562
+ - Optional CURVE encryption via the [protocol-zmtp](https://github.com/paddor/protocol-zmtp) gem
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # OMQ — ZeroMQ in pure Ruby
1
+ # OMQ — Where did the C dependency go!?
2
2
 
3
3
  [![CI](https://github.com/zeromq/omq/actions/workflows/ci.yml/badge.svg)](https://github.com/zeromq/omq/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://img.shields.io/gem/v/omq?color=e9573f)](https://rubygems.org/gems/omq)
@@ -7,7 +7,7 @@
7
7
 
8
8
  `gem install omq` — that's it. No libzmq, no compiler, no system packages. Just Ruby.
9
9
 
10
- OMQ implements the [ZMTP 3.1](https://rfc.zeromq.org/spec/23/) wire protocol from scratch using [Async](https://github.com/socketry/async) fibers. It speaks native ZeroMQ on the wire and interoperates with libzmq, pyzmq, CZMQ, and everything else in the ZMQ ecosystem.
10
+ OMQ builds ZeroMQ socket patterns on top of [protocol-zmtp](https://github.com/paddor/protocol-zmtp) (a pure Ruby [ZMTP 3.1](https://rfc.zeromq.org/spec/23/) codec) using [Async](https://github.com/socketry/async) fibers. It speaks native ZeroMQ on the wire and interoperates with libzmq, pyzmq, CZMQ, and everything else in the ZMQ ecosystem.
11
11
 
12
12
  > **234k msg/s** inproc | **49k msg/s** ipc | **36k msg/s** tcp
13
13
  >
@@ -31,12 +31,12 @@ See [GETTING_STARTED.md](GETTING_STARTED.md) for a ~30 min walkthrough of all ma
31
31
 
32
32
  - **Zero dependencies on C** — no extensions, no FFI, no libzmq. `gem install` just works everywhere
33
33
  - **Fast** — YJIT-optimized hot paths, batched sends, 234k msg/s inproc with 12 µs latency
34
- - **`omq` CLI** — pipe, filter, and transform messages from the terminal with Ruby eval, Ractor parallelism, and [script handlers](CLI.md#script-handlers--r)
34
+ - **[`omq` CLI](https://github.com/paddor/omq-cli)** — `gem install omq-cli` for a command-line tool with Ruby eval, Ractor parallelism, and script handlers
35
35
  - **Every socket pattern** — req/rep, pub/sub, push/pull, dealer/router, xpub/xsub, pair, and all draft types
36
36
  - **Every transport** — tcp, ipc (Unix domain sockets), inproc (in-process queues)
37
37
  - **Async-native** — built on fibers, non-blocking from the ground up. A shared IO thread handles sockets outside of Async — no reactor needed for simple scripts
38
38
  - **Wire-compatible** — interoperates with libzmq, pyzmq, CZMQ over tcp and ipc
39
- - **Bind/connect order doesn't matter** — connect before bind, bind before connect, peers come and go. ZeroMQ reconnects and requeues automatically
39
+ - **Bind/connect order doesn't matter** — connect before bind, bind before connect, peers come and go. ZeroMQ reconnects automatically and queued messages drain when peers arrive
40
40
 
41
41
  For architecture internals, see [DESIGN.md](DESIGN.md).
42
42
 
@@ -150,58 +150,18 @@ All sockets are thread-safe. Default HWM is 1000 messages per socket. Classes li
150
150
  | **PEER** | Routing-ID | Fair-queue | Block |
151
151
  | **CHANNEL** | Exclusive 1-to-1 | Exclusive 1-to-1 | Block |
152
152
 
153
- ## omq — CLI tool
153
+ ## CLI
154
154
 
155
- `omq` is a command-line tool for sending and receiving messages on any OMQ socket. Like `nngcat` from libnng, but with Ruby superpowers.
155
+ Install [omq-cli](https://github.com/paddor/omq-cli) for a command-line tool that sends, receives, pipes, and transforms ZeroMQ messages from the terminal:
156
156
 
157
157
  ```sh
158
- # Echo server
159
- omq rep -b tcp://:5555 --echo
160
-
161
- # Upcase server — -e evals Ruby on each incoming message
162
- omq rep -b tcp://:5555 -e '$F.map(&:upcase)'
158
+ gem install omq-cli
163
159
 
164
- # Client
160
+ omq rep -b tcp://:5555 --echo
165
161
  echo "hello" | omq req -c tcp://localhost:5555
166
- # => HELLO
167
-
168
- # PUB/SUB
169
- omq sub -b tcp://:5556 -s "weather." &
170
- echo "weather.nyc 72F" | omq pub -c tcp://localhost:5556 -d 0.3
171
-
172
- # Pipeline with filtering
173
- tail -f /var/log/syslog | omq push -c tcp://collector:5557
174
- omq pull -b tcp://:5557 -e 'next unless /error/; $F'
175
-
176
- # Transform outgoing messages with -E
177
- echo hello | omq push -c tcp://localhost:5557 -E '$F.map(&:upcase)'
178
-
179
- # REQ: transform request and reply independently
180
- echo hello | omq req -c tcp://localhost:5555 \
181
- -E '$F.map(&:upcase)' -e '$F.map(&:reverse)'
182
-
183
- # Pipe: PULL → eval → PUSH in one process
184
- omq pipe -c ipc://@work -c ipc://@sink -e '$F.map(&:upcase)'
185
-
186
- # Pipe with Ractor workers for CPU parallelism (-P = all CPUs)
187
- omq pipe -c ipc://@work -c ipc://@sink -P -r./fib -e 'fib(Integer($_)).to_s'
188
- ```
189
-
190
- `-e` (recv-eval) transforms incoming messages, `-E` (send-eval) transforms outgoing messages. `$F` is the message parts array, `$_` is the first part. Use `-r` to require gems or load scripts that register handlers via `OMQ.incoming` / `OMQ.outgoing`:
191
-
192
- ```ruby
193
- # my_handler.rb
194
- db = DB.connect("postgres://localhost/app")
195
-
196
- OMQ.incoming { db.query($F.first) }
197
- at_exit { db.close }
198
- ```
199
-
200
- ```sh
201
- omq pull -b tcp://:5557 -r./my_handler.rb
202
162
  ```
203
163
 
204
- See [CLI.md](CLI.md) for full documentation, or `omq --help` / `omq --examples`.
164
+ See the [omq-cli README](https://github.com/paddor/omq-cli) for full documentation.
205
165
 
206
166
  ## Development
207
167
 
data/lib/omq/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OMQ
4
- VERSION = "0.7.0"
4
+ VERSION = "0.9.0"
5
5
  end
@@ -432,17 +432,14 @@ module OMQ
432
432
  def setup_connection(io, as_server:, endpoint: nil, done: nil)
433
433
  conn = Connection.new(
434
434
  io,
435
- socket_type: @socket_type.to_s,
436
- identity: @options.identity,
437
- as_server: as_server,
438
- mechanism: @options.mechanism,
439
- heartbeat_interval: @options.heartbeat_interval,
440
- heartbeat_ttl: @options.heartbeat_ttl,
441
- heartbeat_timeout: @options.heartbeat_timeout,
442
- max_message_size: @options.max_message_size,
435
+ socket_type: @socket_type.to_s,
436
+ identity: @options.identity,
437
+ as_server: as_server,
438
+ mechanism: @options.mechanism&.dup,
439
+ max_message_size: @options.max_message_size,
443
440
  )
444
441
  conn.handshake!
445
- conn.start_heartbeat
442
+ start_heartbeat(conn)
446
443
  @connections << conn
447
444
  @connection_endpoints[conn] = endpoint if endpoint
448
445
  @connection_promises[conn] = done if done
@@ -454,6 +451,35 @@ module OMQ
454
451
  end
455
452
 
456
453
 
454
+ # Spawns a heartbeat task for the connection.
455
+ # The connection only tracks timestamps — the engine drives the loop.
456
+ #
457
+ # @param conn [Connection]
458
+ # @return [void]
459
+ #
460
+ def start_heartbeat(conn)
461
+ interval = @options.heartbeat_interval
462
+ return unless interval
463
+
464
+ ttl = @options.heartbeat_ttl || interval
465
+ timeout = @options.heartbeat_timeout || interval
466
+ conn.touch_heartbeat
467
+
468
+ @tasks << Reactor.spawn_pump(annotation: "heartbeat") do
469
+ loop do
470
+ sleep interval
471
+ conn.send_command(Codec::Command.ping(ttl: ttl, context: "".b))
472
+ if conn.heartbeat_expired?(timeout)
473
+ conn.close
474
+ break
475
+ end
476
+ end
477
+ rescue *CONNECTION_LOST
478
+ # connection closed
479
+ end
480
+ end
481
+
482
+
457
483
  # Spawns a background task that reconnects to the given endpoint
458
484
  # with exponential back-off based on the reconnect_interval option.
459
485
  #
data/lib/omq/zmtp.rb CHANGED
@@ -1,13 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "protocol/zmtp"
4
+ require "io/stream"
5
+
3
6
  module OMQ
4
7
  # ZMTP 3.1 protocol internals.
5
8
  #
6
- # These classes implement the wire protocol, transports, and routing
7
- # strategies. They are not part of the public API.
9
+ # The wire protocol (codec, connection, mechanisms) lives in the
10
+ # protocol-zmtp gem. This module re-exports those classes under the
11
+ # OMQ::ZMTP namespace and adds the transport/routing/engine layers.
8
12
  #
9
13
  module ZMTP
10
- require "io/stream"
14
+ # Re-export protocol-zmtp classes
15
+ Codec = Protocol::ZMTP::Codec
16
+ Connection = Protocol::ZMTP::Connection
17
+ ProtocolError = Protocol::ZMTP::Error
18
+ VALID_PEERS = Protocol::ZMTP::VALID_PEERS
19
+
20
+ module Mechanism
21
+ Null = Protocol::ZMTP::Mechanism::Null
22
+ Curve = Protocol::ZMTP::Mechanism::Curve if defined?(Protocol::ZMTP::Mechanism::Curve)
23
+ end
11
24
 
12
25
  # Errors raised when a peer disconnects or resets the connection.
13
26
  CONNECTION_LOST = [
@@ -32,24 +45,14 @@ module OMQ
32
45
  end
33
46
  end
34
47
 
35
- # Constants
36
- require_relative "zmtp/valid_peers"
37
-
38
- # Codec
39
- require_relative "zmtp/codec"
40
-
41
48
  # Transport
42
49
  require_relative "zmtp/transport/inproc"
43
50
  require_relative "zmtp/transport/tcp"
44
51
  require_relative "zmtp/transport/ipc"
45
52
 
46
- # Mechanisms
47
- require_relative "zmtp/mechanism/null"
48
-
49
53
  # Core
50
54
  require_relative "zmtp/reactor"
51
55
  require_relative "zmtp/options"
52
- require_relative "zmtp/connection"
53
56
  require_relative "zmtp/routing"
54
57
  require_relative "zmtp/routing/round_robin"
55
58
  require_relative "zmtp/routing/fan_out"
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
8
- bindir: exe
8
+ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: protocol-zmtp
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: async
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -37,40 +51,21 @@ dependencies:
37
51
  - - "~>"
38
52
  - !ruby/object:Gem::Version
39
53
  version: '0.11'
40
- description: Pure Ruby implementation of the ZMTP 3.1 wire protocol (ZeroMQ) with
41
- all socket types (REQ/REP, PUB/SUB, PUSH/PULL, DEALER/ROUTER, and draft types) and
42
- TCP/IPC/inproc transports. Includes an `omq` CLI for composable message pipelines
43
- — pipe, filter, and transform across processes with Ruby eval, multiple formats
44
- (JSON, msgpack, marshal), Ractor parallelism, and compression. No native libraries
54
+ description: Pure Ruby implementation of ZeroMQ with all socket types (REQ/REP, PUB/SUB,
55
+ PUSH/PULL, DEALER/ROUTER, and draft types) and TCP/IPC/inproc transports. Built
56
+ on protocol-zmtp (ZMTP 3.1 wire protocol) and Async fibers. No native libraries
45
57
  required.
46
58
  email:
47
59
  - paddor@gmail.com
48
- executables:
49
- - omq
60
+ executables: []
50
61
  extensions: []
51
62
  extra_rdoc_files: []
52
63
  files:
53
64
  - CHANGELOG.md
54
65
  - LICENSE
55
66
  - README.md
56
- - exe/omq
57
67
  - lib/omq.rb
58
68
  - lib/omq/channel.rb
59
- - lib/omq/cli.rb
60
- - lib/omq/cli/base_runner.rb
61
- - lib/omq/cli/channel.rb
62
- - lib/omq/cli/client_server.rb
63
- - lib/omq/cli/config.rb
64
- - lib/omq/cli/formatter.rb
65
- - lib/omq/cli/pair.rb
66
- - lib/omq/cli/peer.rb
67
- - lib/omq/cli/pipe.rb
68
- - lib/omq/cli/pub_sub.rb
69
- - lib/omq/cli/push_pull.rb
70
- - lib/omq/cli/radio_dish.rb
71
- - lib/omq/cli/req_rep.rb
72
- - lib/omq/cli/router_dealer.rb
73
- - lib/omq/cli/scatter_gather.rb
74
69
  - lib/omq/client_server.rb
75
70
  - lib/omq/pair.rb
76
71
  - lib/omq/peer.rb
@@ -83,13 +78,7 @@ files:
83
78
  - lib/omq/socket.rb
84
79
  - lib/omq/version.rb
85
80
  - lib/omq/zmtp.rb
86
- - lib/omq/zmtp/codec.rb
87
- - lib/omq/zmtp/codec/command.rb
88
- - lib/omq/zmtp/codec/frame.rb
89
- - lib/omq/zmtp/codec/greeting.rb
90
- - lib/omq/zmtp/connection.rb
91
81
  - lib/omq/zmtp/engine.rb
92
- - lib/omq/zmtp/mechanism/null.rb
93
82
  - lib/omq/zmtp/options.rb
94
83
  - lib/omq/zmtp/reactor.rb
95
84
  - lib/omq/zmtp/readable.rb
@@ -119,7 +108,6 @@ files:
119
108
  - lib/omq/zmtp/transport/inproc.rb
120
109
  - lib/omq/zmtp/transport/ipc.rb
121
110
  - lib/omq/zmtp/transport/tcp.rb
122
- - lib/omq/zmtp/valid_peers.rb
123
111
  - lib/omq/zmtp/writable.rb
124
112
  homepage: https://github.com/zeromq/omq
125
113
  licenses:
@@ -141,5 +129,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
129
  requirements: []
142
130
  rubygems_version: 4.0.6
143
131
  specification_version: 4
144
- summary: Pure Ruby ZMQ library + CLI
132
+ summary: Pure Ruby ZMQ library
145
133
  test_files: []
data/exe/omq DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
- Warning[:experimental] = false
4
-
5
- require_relative "../lib/omq/cli"
6
- OMQ::CLI.run