omq-cli 0.1.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 +7 -0
- data/LICENSE +15 -0
- data/README.md +464 -0
- data/exe/omq +6 -0
- data/lib/omq/cli/base_runner.rb +477 -0
- data/lib/omq/cli/channel.rb +8 -0
- data/lib/omq/cli/client_server.rb +111 -0
- data/lib/omq/cli/config.rb +55 -0
- data/lib/omq/cli/formatter.rb +75 -0
- data/lib/omq/cli/pair.rb +31 -0
- data/lib/omq/cli/peer.rb +8 -0
- data/lib/omq/cli/pipe.rb +265 -0
- data/lib/omq/cli/pub_sub.rb +14 -0
- data/lib/omq/cli/push_pull.rb +14 -0
- data/lib/omq/cli/radio_dish.rb +27 -0
- data/lib/omq/cli/req_rep.rb +83 -0
- data/lib/omq/cli/router_dealer.rb +76 -0
- data/lib/omq/cli/scatter_gather.rb +14 -0
- data/lib/omq/cli/version.rb +7 -0
- data/lib/omq/cli.rb +544 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a68efaffe40870b21e4fa8d94fb99efb40dddc96cc68fd0ebaa232373073c35b
|
|
4
|
+
data.tar.gz: 0b817664632dbd6a83844562cf79b296493da2438526675f65801e85305add2a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a4c3df6862c4820561ac8f7c8c1f60d89caac4c538a9bce7cbce7daeff2abdf0e33c63520e46a36cad291a8d1255eb934516041a1a723e00893c22a2926ba0f7
|
|
7
|
+
data.tar.gz: ee47b7dc9f0becaf518532a70a724dc998a2df1a59d80bb525bf9cc597e61df2688577c2cf748b0949121e60f5544b4f0ec066f40fdb01e24eccd423a21121d4
|
data/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026, Patrik Wenger
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# omq — ZeroMQ CLI
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/omq-cli)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.ruby-lang.org)
|
|
6
|
+
|
|
7
|
+
Command-line tool for sending and receiving ZeroMQ messages on any socket type. Like `nngcat` from libnng, but with Ruby eval, Ractor parallelism, and message handlers.
|
|
8
|
+
|
|
9
|
+
Built on [omq](https://github.com/zeromq/omq) — pure Ruby ZeroMQ, no C dependencies.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
gem install omq-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
# Echo server
|
|
21
|
+
omq rep -b tcp://:5555 --echo
|
|
22
|
+
|
|
23
|
+
# Client
|
|
24
|
+
echo "hello" | omq req -c tcp://localhost:5555
|
|
25
|
+
|
|
26
|
+
# Upcase server — -e evals Ruby on each incoming message
|
|
27
|
+
omq rep -b tcp://:5555 -e '$F.map(&:upcase)'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Usage: omq TYPE [options]
|
|
32
|
+
|
|
33
|
+
Types: req, rep, pub, sub, push, pull, pair, dealer, router
|
|
34
|
+
Draft: client, server, radio, dish, scatter, gather, channel, peer
|
|
35
|
+
Virtual: pipe (PULL → eval → PUSH)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Connection
|
|
39
|
+
|
|
40
|
+
Every socket needs at least one `--bind` or `--connect`:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
omq pull --bind tcp://:5557 # listen on port 5557
|
|
44
|
+
omq push --connect tcp://host:5557 # connect to host
|
|
45
|
+
omq pull -b ipc:///tmp/feed.sock # IPC (unix socket)
|
|
46
|
+
omq push -c ipc://@abstract # IPC (abstract namespace, Linux)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Multiple endpoints are allowed — `omq pull -b tcp://:5557 -b tcp://:5558` binds both. Pipe requires exactly two (`-c` for pull-side, `-c` for push-side).
|
|
50
|
+
|
|
51
|
+
## Socket types
|
|
52
|
+
|
|
53
|
+
### Unidirectional (send-only / recv-only)
|
|
54
|
+
|
|
55
|
+
| Send | Recv | Pattern |
|
|
56
|
+
|------|------|---------|
|
|
57
|
+
| `push` | `pull` | Pipeline — round-robin to workers |
|
|
58
|
+
| `pub` | `sub` | Publish/subscribe — fan-out with topic filtering |
|
|
59
|
+
| `scatter` | `gather` | Pipeline (draft, single-frame only) |
|
|
60
|
+
| `radio` | `dish` | Group messaging (draft, single-frame only) |
|
|
61
|
+
|
|
62
|
+
Send-only sockets read from stdin (or `--data`/`--file`) and send. Recv-only sockets receive and write to stdout.
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
echo "task" | omq push -c tcp://worker:5557
|
|
66
|
+
omq pull -b tcp://:5557
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Bidirectional (request-reply)
|
|
70
|
+
|
|
71
|
+
| Type | Behavior |
|
|
72
|
+
|------|----------|
|
|
73
|
+
| `req` | Sends a request, waits for reply, prints reply |
|
|
74
|
+
| `rep` | Receives request, sends reply (from `--echo`, `-e`, `--data`, `--file`, or stdin) |
|
|
75
|
+
| `client` | Like `req` (draft, single-frame) |
|
|
76
|
+
| `server` | Like `rep` (draft, single-frame, routing-ID aware) |
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
# echo server
|
|
80
|
+
omq rep -b tcp://:5555 --echo
|
|
81
|
+
|
|
82
|
+
# upcase server
|
|
83
|
+
omq rep -b tcp://:5555 -e '$F.map(&:upcase)'
|
|
84
|
+
|
|
85
|
+
# client
|
|
86
|
+
echo "hello" | omq req -c tcp://localhost:5555
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Bidirectional (concurrent send + recv)
|
|
90
|
+
|
|
91
|
+
| Type | Behavior |
|
|
92
|
+
|------|----------|
|
|
93
|
+
| `pair` | Exclusive 1-to-1 — concurrent send and recv tasks |
|
|
94
|
+
| `dealer` | Like `pair` but round-robin send to multiple peers |
|
|
95
|
+
| `channel` | Like `pair` (draft, single-frame) |
|
|
96
|
+
|
|
97
|
+
These spawn two concurrent tasks: a receiver (prints incoming) and a sender (reads stdin). `-e` transforms incoming, `-E` transforms outgoing.
|
|
98
|
+
|
|
99
|
+
### Routing sockets
|
|
100
|
+
|
|
101
|
+
| Type | Behavior |
|
|
102
|
+
|------|----------|
|
|
103
|
+
| `router` | Receives with peer identity prepended; sends to peer by identity |
|
|
104
|
+
| `server` | Like `router` but draft, single-frame, uses routing IDs |
|
|
105
|
+
| `peer` | Like `server` (draft, single-frame) |
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
# monitor mode — just print what arrives
|
|
109
|
+
omq router -b tcp://:5555
|
|
110
|
+
|
|
111
|
+
# reply to specific peer
|
|
112
|
+
omq router -b tcp://:5555 --target worker-1 -D "reply"
|
|
113
|
+
|
|
114
|
+
# dynamic routing via send-eval (first element = identity)
|
|
115
|
+
omq router -b tcp://:5555 -E '["worker-1", $_.upcase]'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`--target` and `--send-eval` are mutually exclusive on routing sockets.
|
|
119
|
+
|
|
120
|
+
### Pipe (virtual)
|
|
121
|
+
|
|
122
|
+
Pipe creates an internal PULL → eval → PUSH pipeline:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
omq pipe -c ipc://@work -c ipc://@sink -e '$F.map(&:upcase)'
|
|
126
|
+
|
|
127
|
+
# with Ractor workers for CPU parallelism
|
|
128
|
+
omq pipe -c ipc://@work -c ipc://@sink -P 4 -r./fib.rb -e 'fib(Integer($_)).to_s'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The first endpoint is the pull-side (input), the second is the push-side (output). Both must use `-c`.
|
|
132
|
+
|
|
133
|
+
## Eval: -e and -E
|
|
134
|
+
|
|
135
|
+
`-e` (alias `--recv-eval`) runs a Ruby expression for each **incoming** message.
|
|
136
|
+
`-E` (alias `--send-eval`) runs a Ruby expression for each **outgoing** message.
|
|
137
|
+
|
|
138
|
+
### Globals
|
|
139
|
+
|
|
140
|
+
| Variable | Value |
|
|
141
|
+
|----------|-------|
|
|
142
|
+
| `$F` | Message parts (`Array<String>`) |
|
|
143
|
+
| `$_` | First part (`$F.first`) — works in inline expressions |
|
|
144
|
+
|
|
145
|
+
### Return value
|
|
146
|
+
|
|
147
|
+
| Return | Effect |
|
|
148
|
+
|--------|--------|
|
|
149
|
+
| `Array` | Used as the message parts |
|
|
150
|
+
| `String` | Wrapped in `[result]` |
|
|
151
|
+
| `nil` | Message is skipped (filtered) |
|
|
152
|
+
| `self` (the socket) | Signals "I already sent" (REP only) |
|
|
153
|
+
|
|
154
|
+
### Control flow
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
# skip messages matching a pattern
|
|
158
|
+
omq pull -b tcp://:5557 -e 'next if /^#/; $F'
|
|
159
|
+
|
|
160
|
+
# stop on "quit"
|
|
161
|
+
omq pull -b tcp://:5557 -e 'break if /quit/; $F'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### BEGIN/END blocks
|
|
165
|
+
|
|
166
|
+
Like awk — `BEGIN{}` runs once before the message loop, `END{}` runs after:
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
omq pull -b tcp://:5557 -e 'BEGIN{ @sum = 0 } @sum += Integer($_); next END{ puts @sum }'
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Local variables won't work to share state between the blocks. Use `@ivars` instead.
|
|
173
|
+
|
|
174
|
+
### Which sockets accept which flag
|
|
175
|
+
|
|
176
|
+
| Socket | `-E` (send) | `-e` (recv) |
|
|
177
|
+
|--------|-------------|-------------|
|
|
178
|
+
| push, pub, scatter, radio | transforms outgoing | error |
|
|
179
|
+
| pull, sub, gather, dish | error | transforms incoming |
|
|
180
|
+
| req, client | transforms request | transforms reply |
|
|
181
|
+
| rep, server (reply mode) | error | transforms request → return = reply |
|
|
182
|
+
| pair, dealer, channel | transforms outgoing | transforms incoming |
|
|
183
|
+
| router, server, peer (monitor) | routes outgoing (first element = identity) | transforms incoming |
|
|
184
|
+
| pipe | error | transforms in pipeline |
|
|
185
|
+
|
|
186
|
+
### Examples
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
# upcase echo server
|
|
190
|
+
omq rep -b tcp://:5555 -e '$F.map(&:upcase)'
|
|
191
|
+
|
|
192
|
+
# transform before sending
|
|
193
|
+
echo hello | omq push -c tcp://localhost:5557 -E '$F.map(&:upcase)'
|
|
194
|
+
|
|
195
|
+
# filter incoming
|
|
196
|
+
omq pull -b tcp://:5557 -e '$F.first.include?("error") ? $F : nil'
|
|
197
|
+
|
|
198
|
+
# REQ: different transforms per direction
|
|
199
|
+
echo hello | omq req -c tcp://localhost:5555 \
|
|
200
|
+
-E '$F.map(&:upcase)' -e '$F.map(&:reverse)'
|
|
201
|
+
|
|
202
|
+
# generate messages without stdin
|
|
203
|
+
omq pub -c tcp://localhost:5556 -E 'Time.now.to_s' -i 1
|
|
204
|
+
|
|
205
|
+
# use gems
|
|
206
|
+
omq sub -c tcp://localhost:5556 -s "" -rjson -e 'JSON.parse($F.first)["temperature"]'
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Script handlers (-r)
|
|
210
|
+
|
|
211
|
+
For non-trivial transforms, put the logic in a Ruby file and load it with `-r`:
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
# handler.rb
|
|
215
|
+
db = PG.connect("dbname=app")
|
|
216
|
+
|
|
217
|
+
OMQ.outgoing { |msg| msg.map(&:upcase) }
|
|
218
|
+
OMQ.incoming { |msg| db.exec(msg.first).values.flatten }
|
|
219
|
+
|
|
220
|
+
at_exit { db.close }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
omq req -c tcp://localhost:5555 -r./handler.rb
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Registration API
|
|
228
|
+
|
|
229
|
+
| Method | Effect |
|
|
230
|
+
|--------|--------|
|
|
231
|
+
| `OMQ.outgoing { |msg| ... }` | Register outgoing message transform |
|
|
232
|
+
| `OMQ.incoming { |msg| ... }` | Register incoming message transform |
|
|
233
|
+
|
|
234
|
+
- use explicit block variable (like `msg`) instead of `$F`/`$_`
|
|
235
|
+
- Setup: use local variables and closures at the top of the script
|
|
236
|
+
- Teardown: use Ruby's `at_exit { ... }`
|
|
237
|
+
- CLI flags (`-e`/`-E`) override script-registered handlers for the same direction
|
|
238
|
+
- A script can register one direction while the CLI handles the other:
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
# handler.rb registers recv_eval, CLI adds send_eval
|
|
242
|
+
omq req -c tcp://localhost:5555 -r./handler.rb -E '$F.map(&:upcase)'
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Script handler examples
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# count.rb — count messages, print total on exit
|
|
249
|
+
count = 0
|
|
250
|
+
OMQ.incoming { |msg| count += 1; msg }
|
|
251
|
+
at_exit { $stderr.puts "processed #{count} messages" }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
# json_transform.rb — parse JSON, extract field
|
|
256
|
+
require "json"
|
|
257
|
+
OMQ.incoming { |first_part, _| [JSON.parse(first_part)["value"]] }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
# rate_limit.rb — skip messages arriving too fast
|
|
262
|
+
last = 0
|
|
263
|
+
|
|
264
|
+
OMQ.incoming do |msg|
|
|
265
|
+
now = Async::Clock.now # monotonic clock
|
|
266
|
+
|
|
267
|
+
if now - last >= 0.1
|
|
268
|
+
last = now
|
|
269
|
+
msg
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
# enrich.rb — add timestamp to outgoing messages
|
|
276
|
+
OMQ.outgoing { |msg| [*msg, Time.now.iso8601] }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Data sources
|
|
280
|
+
|
|
281
|
+
| Flag | Behavior |
|
|
282
|
+
|------|----------|
|
|
283
|
+
| (stdin) | Read lines from stdin, one message per line |
|
|
284
|
+
| `-D "text"` | Send literal string (one-shot or repeated with `-i`) |
|
|
285
|
+
| `-F file` | Read message from file (`-F -` reads stdin as blob) |
|
|
286
|
+
| `--echo` | Echo received messages back (REP only) |
|
|
287
|
+
|
|
288
|
+
`-D` and `-F` are mutually exclusive.
|
|
289
|
+
|
|
290
|
+
## Formats
|
|
291
|
+
|
|
292
|
+
| Flag | Format |
|
|
293
|
+
|------|--------|
|
|
294
|
+
| `-A` / `--ascii` | Tab-separated frames, non-printable → dots (default) |
|
|
295
|
+
| `-Q` / `--quoted` | C-style escapes, lossless round-trip |
|
|
296
|
+
| `--raw` | Raw ZMTP binary (pipe to `hexdump -C` for debugging) |
|
|
297
|
+
| `-J` / `--jsonl` | JSON Lines — `["frame1","frame2"]` per line |
|
|
298
|
+
| `--msgpack` | MessagePack arrays (binary stream) |
|
|
299
|
+
| `-M` / `--marshal` | Ruby Marshal (binary stream of `Array<String>` objects) |
|
|
300
|
+
|
|
301
|
+
Multipart messages: in ASCII/quoted mode, frames are tab-separated. In JSONL mode, each message is a JSON array.
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
# send multipart via tabs
|
|
305
|
+
printf "key\tvalue" | omq push -c tcp://localhost:5557
|
|
306
|
+
|
|
307
|
+
# JSONL
|
|
308
|
+
echo '["key","value"]' | omq push -c tcp://localhost:5557 -J
|
|
309
|
+
omq pull -b tcp://:5557 -J
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Timing
|
|
313
|
+
|
|
314
|
+
| Flag | Effect |
|
|
315
|
+
|------|--------|
|
|
316
|
+
| `-i SECS` | Repeat send every N seconds (wall-clock aligned) |
|
|
317
|
+
| `-n COUNT` | Max messages to send/receive (0 = unlimited) |
|
|
318
|
+
| `-d SECS` | Delay before first send |
|
|
319
|
+
| `-t SECS` | Send/receive timeout |
|
|
320
|
+
| `-l SECS` | Linger time on close (default 5s) |
|
|
321
|
+
| `--reconnect-ivl` | Reconnect interval: `SECS` or `MIN..MAX` (default 0.1) |
|
|
322
|
+
| `--heartbeat-ivl SECS` | ZMTP heartbeat interval (detects dead peers) |
|
|
323
|
+
|
|
324
|
+
```sh
|
|
325
|
+
# publish a tick every second, 10 times
|
|
326
|
+
omq pub -c tcp://localhost:5556 -D "tick" -i 1 -n 10 -d 1
|
|
327
|
+
|
|
328
|
+
# receive with 5s timeout
|
|
329
|
+
omq pull -b tcp://:5557 -t 5
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Compression
|
|
333
|
+
|
|
334
|
+
Both sides must use `--compress` (`-z`). Requires the `zstd-ruby` gem.
|
|
335
|
+
|
|
336
|
+
```sh
|
|
337
|
+
omq push -c tcp://remote:5557 -z < data.txt
|
|
338
|
+
omq pull -b tcp://:5557 -z
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## CURVE encryption
|
|
342
|
+
|
|
343
|
+
End-to-end encryption using CurveZMQ. Requires a crypto backend:
|
|
344
|
+
- **rbnacl** (recommended) — wraps libsodium, fast and audited. `gem install rbnacl`
|
|
345
|
+
- **nuckle** — pure Ruby, no system dependencies, not audited. `gem install nuckle`
|
|
346
|
+
|
|
347
|
+
By default, `rbnacl` is used if installed. To use `nuckle` explicitly:
|
|
348
|
+
|
|
349
|
+
```sh
|
|
350
|
+
omq rep -b tcp://:5555 --echo --curve-server --curve-crypto nuckle
|
|
351
|
+
# or: OMQ_CURVE_CRYPTO=nuckle omq rep -b tcp://:5555 --echo --curve-server
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
```sh
|
|
355
|
+
# server (prints OMQ_SERVER_KEY=...)
|
|
356
|
+
omq rep -b tcp://:5555 --echo --curve-server
|
|
357
|
+
|
|
358
|
+
# client (paste the key)
|
|
359
|
+
echo "secret" | omq req -c tcp://localhost:5555 \
|
|
360
|
+
--curve-server-key '<key from server>'
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Persistent keys via env vars: `OMQ_SERVER_PUBLIC` + `OMQ_SERVER_SECRET` (server), `OMQ_SERVER_KEY` (client).
|
|
364
|
+
|
|
365
|
+
## Subscription and groups
|
|
366
|
+
|
|
367
|
+
```sh
|
|
368
|
+
# subscribe to topic prefix
|
|
369
|
+
omq sub -b tcp://:5556 -s "weather."
|
|
370
|
+
|
|
371
|
+
# subscribe to all (default)
|
|
372
|
+
omq sub -b tcp://:5556
|
|
373
|
+
|
|
374
|
+
# multiple subscriptions
|
|
375
|
+
omq sub -b tcp://:5556 -s "weather." -s "sports."
|
|
376
|
+
|
|
377
|
+
# RADIO/DISH groups
|
|
378
|
+
omq dish -b tcp://:5557 -j "weather" -j "sports"
|
|
379
|
+
omq radio -c tcp://localhost:5557 -g "weather" -D "72F"
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Identity and routing
|
|
383
|
+
|
|
384
|
+
```sh
|
|
385
|
+
# DEALER with identity
|
|
386
|
+
echo "hello" | omq dealer -c tcp://localhost:5555 --identity worker-1
|
|
387
|
+
|
|
388
|
+
# ROUTER receives identity + message as tab-separated
|
|
389
|
+
omq router -b tcp://:5555
|
|
390
|
+
|
|
391
|
+
# ROUTER sends to specific peer
|
|
392
|
+
omq router -b tcp://:5555 --target worker-1 -D "reply"
|
|
393
|
+
|
|
394
|
+
# ROUTER dynamic routing via -E (first element = routing identity)
|
|
395
|
+
omq router -b tcp://:5555 -E '["worker-1", $_.upcase]'
|
|
396
|
+
|
|
397
|
+
# binary routing IDs (0x prefix)
|
|
398
|
+
omq router -b tcp://:5555 --target 0xdeadbeef -D "reply"
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Pipe
|
|
402
|
+
|
|
403
|
+
Pipe creates an in-process PULL → eval → PUSH pipeline:
|
|
404
|
+
|
|
405
|
+
```sh
|
|
406
|
+
# basic pipe (positional: first = input, second = output)
|
|
407
|
+
omq pipe -c ipc://@work -c ipc://@sink -e '$F.map(&:upcase)'
|
|
408
|
+
|
|
409
|
+
# parallel Ractor workers (default: all CPUs)
|
|
410
|
+
omq pipe -c ipc://@work -c ipc://@sink -P -r./fib.rb -e 'fib(Integer($_)).to_s'
|
|
411
|
+
|
|
412
|
+
# fixed number of workers
|
|
413
|
+
omq pipe -c ipc://@work -c ipc://@sink -P 4 -e '$F.map(&:upcase)'
|
|
414
|
+
|
|
415
|
+
# exit when producer disconnects
|
|
416
|
+
omq pipe -c ipc://@work -c ipc://@sink --transient -e '$F.map(&:upcase)'
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Multi-peer pipe with `--in`/`--out`
|
|
420
|
+
|
|
421
|
+
Use `--in` and `--out` to attach multiple endpoints per side. These are modal switches — subsequent `-b`/`-c` flags attach to the current side:
|
|
422
|
+
|
|
423
|
+
```sh
|
|
424
|
+
# fan-in: 2 producers → 1 consumer
|
|
425
|
+
omq pipe --in -c ipc://@work1 -c ipc://@work2 --out -c ipc://@sink -e '$F'
|
|
426
|
+
|
|
427
|
+
# fan-out: 1 producer → 2 consumers (round-robin)
|
|
428
|
+
omq pipe --in -b tcp://:5555 --out -c ipc://@sink1 -c ipc://@sink2 -e '$F'
|
|
429
|
+
|
|
430
|
+
# bind on input, connect on output
|
|
431
|
+
omq pipe --in -b tcp://:5555 -b tcp://:5556 --out -c tcp://sink:5557 -e '$F'
|
|
432
|
+
|
|
433
|
+
# parallel workers with fan-in (all must be -c)
|
|
434
|
+
omq pipe --in -c ipc://@a -c ipc://@b --out -c ipc://@sink -P 4 -e '$F'
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
`-P`/`--parallel` requires all endpoints to be `--connect`. In parallel mode, each Ractor worker gets its own PULL/PUSH pair connecting to all endpoints.
|
|
438
|
+
|
|
439
|
+
Note: in Ractor workers, use `__F` instead of `$F` (global variables aren't shared across Ractors).
|
|
440
|
+
|
|
441
|
+
## Transient mode
|
|
442
|
+
|
|
443
|
+
`--transient` makes the socket exit when all peers disconnect. Useful for pipeline workers and sinks:
|
|
444
|
+
|
|
445
|
+
```sh
|
|
446
|
+
# worker exits when producer is done
|
|
447
|
+
omq pipe -c ipc://@work -c ipc://@sink --transient -e '$F.map(&:upcase)'
|
|
448
|
+
|
|
449
|
+
# sink exits when all workers disconnect
|
|
450
|
+
omq pull -b tcp://:5557 --transient
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Exit codes
|
|
454
|
+
|
|
455
|
+
| Code | Meaning |
|
|
456
|
+
|------|---------|
|
|
457
|
+
| 0 | Success |
|
|
458
|
+
| 1 | Error (connection, argument, runtime) |
|
|
459
|
+
| 2 | Timeout |
|
|
460
|
+
| 3 | Eval error (`-e`/`-E` expression raised) |
|
|
461
|
+
|
|
462
|
+
## License
|
|
463
|
+
|
|
464
|
+
[ISC](LICENSE)
|