nnq-cli 0.2.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/CHANGELOG.md +78 -0
- data/LICENSE +15 -0
- data/README.md +391 -0
- data/exe/nnq +10 -0
- data/lib/nnq/cli/base_runner.rb +448 -0
- data/lib/nnq/cli/bus.rb +33 -0
- data/lib/nnq/cli/cli_parser.rb +485 -0
- data/lib/nnq/cli/config.rb +59 -0
- data/lib/nnq/cli/expression_evaluator.rb +142 -0
- data/lib/nnq/cli/formatter.rb +140 -0
- data/lib/nnq/cli/pair.rb +33 -0
- data/lib/nnq/cli/pipe.rb +206 -0
- data/lib/nnq/cli/pipe_worker.rb +138 -0
- data/lib/nnq/cli/pub_sub.rb +16 -0
- data/lib/nnq/cli/push_pull.rb +19 -0
- data/lib/nnq/cli/ractor_helpers.rb +81 -0
- data/lib/nnq/cli/req_rep.rb +105 -0
- data/lib/nnq/cli/socket_setup.rb +93 -0
- data/lib/nnq/cli/surveyor_respondent.rb +112 -0
- data/lib/nnq/cli/term.rb +86 -0
- data/lib/nnq/cli/transient_monitor.rb +41 -0
- data/lib/nnq/cli/version.rb +7 -0
- data/lib/nnq/cli.rb +190 -0
- metadata +110 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2aac6e193d793225be560b51a32d4f3e713afbab8b25652be6cd76750f789e3e
|
|
4
|
+
data.tar.gz: a31568088dbc3ba5ad3df07f3e1f0f98543c1ad0c631718ae71f76d02b602c0a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fe5db04b1ebb441274c4b182d3eabed59229c040593fe7f0388c123972a6059572c6a2dff3633e1f72abd0a6771b3b6e2812e6b00d07b9c4d75ad9bd831b8059
|
|
7
|
+
data.tar.gz: bba50ee147da4897cefce7297834584127183a04a6bb3a8c15525dabff70c04f865eaa8204dc69eebc991f72fc1cc1c01928ad5490be1ee164f656f39c70cc45
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 — 2026-04-15
|
|
4
|
+
|
|
5
|
+
- **Peer wait for bind-mode bounded senders** — `nnq push -b ... -d
|
|
6
|
+
hello` now waits for the first peer before sending, so one-shot
|
|
7
|
+
`-d`/`-f`/`-E` payloads don't just get queued into HWM and dropped on
|
|
8
|
+
exit. Interactive stdin still sends without waiting.
|
|
9
|
+
- **`--count N` honored on one-shot sends** — `-d`/`-f` and pure-
|
|
10
|
+
generator `-E` loop N times instead of firing once.
|
|
11
|
+
- **Interactive TTY fallback** — bare `nnq push -c tcp://...` on a
|
|
12
|
+
terminal reads lines from the TTY until ^D (matching omq-cli).
|
|
13
|
+
Pure-generator `-E` is checked before the TTY fallback.
|
|
14
|
+
- **`nnq:` log prefix** — `BaseRunner#log` routes through
|
|
15
|
+
`Term.log_prefix` with an `nnq: ` prefix, so every stderr line from a
|
|
16
|
+
CLI run looks consistent with attach/event lines.
|
|
17
|
+
- **No more "frames"/"parts"** — NNG has no multipart concept; all
|
|
18
|
+
`parts` variables renamed to `msg`, and comments/docs updated from
|
|
19
|
+
"frame" to "message"/"body".
|
|
20
|
+
- **Eval `#to_s` coercion** — non-string eval results (e.g.
|
|
21
|
+
`-E 'Time.now'`) are coerced via `#to_s` instead of raising
|
|
22
|
+
`NoMethodError` on `#to_str`. Array elements are coerced
|
|
23
|
+
individually.
|
|
24
|
+
- **`@name` IPC shorthand** — `@foo` expands to `ipc://@foo`
|
|
25
|
+
(Linux abstract namespace) in `-b`/`-c` arguments.
|
|
26
|
+
- **Pipe: bare endpoint promotion** — `pipe -c SRC --out -c DST`
|
|
27
|
+
automatically promotes the bare `-c SRC` to `--in`.
|
|
28
|
+
- **Pipe: fan-out fairness yield** — multi-output pipes yield after
|
|
29
|
+
each send so send-pump fibers distribute messages fairly across
|
|
30
|
+
output peers.
|
|
31
|
+
- **Formatter: empty-frame preview** — empty bodies render as `''`
|
|
32
|
+
instead of `[0B]` in verbose output.
|
|
33
|
+
- **Formatter: nil-safe compression** — `compress`/`decompress` skip
|
|
34
|
+
nil and empty frames instead of crashing.
|
|
35
|
+
- **`NNQ::CLI::Term` module** — consolidates verbose log formatting
|
|
36
|
+
(timestamps at `-vvvv`, monitor events, endpoint attach lines) into a
|
|
37
|
+
stateless module. Replaces four duplicated inline formatting blocks
|
|
38
|
+
across BaseRunner, PipeRunner, PipeWorker, and SocketSetup.
|
|
39
|
+
- **Default HWM → 64** — down from 100. 64 matches the send pump's
|
|
40
|
+
per-fairness-batch limit (one batch exactly fills a full queue).
|
|
41
|
+
Pipe sockets no longer use a separate `PIPE_HWM = 16`; they go
|
|
42
|
+
through `SocketSetup.build` like every other socket type.
|
|
43
|
+
- **Pipe: drop peer wait unless `--timeout`** — without `--timeout`,
|
|
44
|
+
`PULL#receive` blocks naturally and `PUSH` buffers up to `send_hwm`.
|
|
45
|
+
Only wait for peers in fail-fast mode.
|
|
46
|
+
- **Endpoint normalization** — binds: `tcp://:PORT` normalizes to
|
|
47
|
+
loopback (`[::1]` on IPv6-capable hosts, `127.0.0.1` otherwise);
|
|
48
|
+
`tcp://*:PORT` normalizes to `0.0.0.0`. Connects: both `tcp://:PORT`
|
|
49
|
+
and `tcp://*:PORT` normalize to `tcp://localhost:PORT` (preserving
|
|
50
|
+
Happy Eyeballs).
|
|
51
|
+
- **YJIT by default** — `exe/nnq` calls `RubyVM::YJIT.enable` before
|
|
52
|
+
loading the CLI (skipped if `RUBYOPT` is set, interpreter lacks YJIT,
|
|
53
|
+
or YJIT is already on).
|
|
54
|
+
- **Consistent `nnq:` prefix** on all attach and event log lines.
|
|
55
|
+
- **`-vvvv` timestamps** — ISO8601 UTC with µs precision.
|
|
56
|
+
- **3 new socket runners** — `nnq bus`, `nnq surveyor`, `nnq respondent`.
|
|
57
|
+
- **Versioned socket symbols** — RUNNER_MAP uses `:PUSH0`, `:PULL0`, etc.
|
|
58
|
+
|
|
59
|
+
## 0.1.0 — 2026-04-09
|
|
60
|
+
|
|
61
|
+
Initial release — NNQ command-line tool, sister of omq-cli for the SP
|
|
62
|
+
(nanomsg) wire protocol.
|
|
63
|
+
|
|
64
|
+
- 7 socket-type runners: push, pull, pub, sub, req, rep, pair.
|
|
65
|
+
- `pipe` virtual socket (PULL → eval → PUSH) with Ractor-based
|
|
66
|
+
parallelism (`-P N`). Each worker owns its own Async reactor and
|
|
67
|
+
PULL/PUSH pair; messages fan out via round-robin PUSH to workers and
|
|
68
|
+
merge back through a shared PULL sink.
|
|
69
|
+
- Ruby eval (`-e` / `-E` with BEGIN/END blocks), `-r` script loading
|
|
70
|
+
with `NNQ.outgoing` / `NNQ.incoming` handlers.
|
|
71
|
+
- 6 formats: ASCII, quoted, raw, JSON Lines, msgpack, Marshal.
|
|
72
|
+
- LZ4 compression (`--compress` / `--compress-in` / `--compress-out`).
|
|
73
|
+
- `--transient` mode — cleanly exits once all peers have disconnected.
|
|
74
|
+
- `-v` / `-vv` / `-vvv` monitor events piped to stderr.
|
|
75
|
+
- No CURVE, no heartbeat, no multipart — nnq is single-frame SP.
|
|
76
|
+
|
|
77
|
+
Requires `nnq ~> 0.4` (for `freeze_for_ractors!`, `Socket#monitor`,
|
|
78
|
+
`#all_peers_gone`, and `PULL#receive` `read_timeout` support).
|
data/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 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,391 @@
|
|
|
1
|
+
# nnq — nanomsg SP CLI
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/nnq-cli)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.ruby-lang.org)
|
|
6
|
+
|
|
7
|
+
Command-line tool for sending and receiving nanomsg SP protocol messages on
|
|
8
|
+
any nnq socket type. Like `nngcat` from libnng, but with Ruby eval, Ractor
|
|
9
|
+
parallelism, and message handlers.
|
|
10
|
+
|
|
11
|
+
Built on [nnq](https://github.com/paddor/nnq) — pure Ruby SP wire protocol, no
|
|
12
|
+
C dependencies. Wire-compatible with libnng peers.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
gem install nnq-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
# Echo server
|
|
24
|
+
nnq rep -b tcp://:5555 --echo
|
|
25
|
+
|
|
26
|
+
# Client
|
|
27
|
+
echo "hello" | nnq req -c tcp://localhost:5555
|
|
28
|
+
|
|
29
|
+
# Upcase server — -e evals Ruby on each incoming message
|
|
30
|
+
nnq rep -b tcp://:5555 -e '$_.upcase'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Usage: nnq TYPE [options]
|
|
35
|
+
|
|
36
|
+
Types: req, rep, pub, sub, push, pull, pair
|
|
37
|
+
Virtual: pipe (PULL → eval → PUSH)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## SP messages are single-frame
|
|
41
|
+
|
|
42
|
+
Unlike ZeroMQ, nanomsg SP messages have **one frame**, not many. The CLI
|
|
43
|
+
exposes `$_` (the message body, a `String`) and `$F` (a 1-element array
|
|
44
|
+
`[$_]`) for compatibility with omq-cli expressions, but multipart shenanigans
|
|
45
|
+
don't apply.
|
|
46
|
+
|
|
47
|
+
## Connection
|
|
48
|
+
|
|
49
|
+
Every socket needs at least one `--bind` or `--connect`:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
nnq pull --bind tcp://:5557 # listen on port 5557
|
|
53
|
+
nnq push --connect tcp://host:5557 # connect to host
|
|
54
|
+
nnq pull -b ipc:///tmp/feed.sock # IPC (unix socket)
|
|
55
|
+
nnq push -c ipc://@abstract # IPC (abstract namespace, Linux)
|
|
56
|
+
nnq push -c inproc://name # in-process queue
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Bind/connect order doesn't matter — `connect` is non-blocking and the engine
|
|
60
|
+
retries with exponential back-off until the peer is reachable. Multiple
|
|
61
|
+
endpoints are allowed: `nnq pull -b tcp://:5557 -b tcp://:5558` binds both.
|
|
62
|
+
|
|
63
|
+
Pipe takes two positional endpoints (input, output) or uses `--in`/`--out` for
|
|
64
|
+
multiple per side.
|
|
65
|
+
|
|
66
|
+
## Socket types
|
|
67
|
+
|
|
68
|
+
### Unidirectional (send-only / recv-only)
|
|
69
|
+
|
|
70
|
+
| Send | Recv | Pattern |
|
|
71
|
+
|------|------|---------|
|
|
72
|
+
| `push` | `pull` | Pipeline — round-robin to workers |
|
|
73
|
+
| `pub` | `sub` | Publish/subscribe — fan-out with topic prefix filtering |
|
|
74
|
+
|
|
75
|
+
Send-only sockets read from stdin (or `--data`/`--file`) and send. Recv-only
|
|
76
|
+
sockets receive and write to stdout.
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
echo "task" | nnq push -c tcp://worker:5557
|
|
80
|
+
nnq pull -b tcp://:5557
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Bidirectional (request-reply)
|
|
84
|
+
|
|
85
|
+
| Type | Behavior |
|
|
86
|
+
|------|----------|
|
|
87
|
+
| `req` | Sends a request, waits for reply, prints reply |
|
|
88
|
+
| `rep` | Receives request, sends reply (from `--echo`, `-e`, `--data`, `--file`, or stdin) |
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
# echo server
|
|
92
|
+
nnq rep -b tcp://:5555 --echo
|
|
93
|
+
|
|
94
|
+
# upcase server
|
|
95
|
+
nnq rep -b tcp://:5555 -e '$_.upcase'
|
|
96
|
+
|
|
97
|
+
# client
|
|
98
|
+
echo "hello" | nnq req -c tcp://localhost:5555
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Bidirectional (concurrent send + recv)
|
|
102
|
+
|
|
103
|
+
| Type | Behavior |
|
|
104
|
+
|------|----------|
|
|
105
|
+
| `pair` | Exclusive 1-to-1 — concurrent send and recv tasks |
|
|
106
|
+
|
|
107
|
+
These spawn two concurrent tasks: a receiver (prints incoming) and a sender
|
|
108
|
+
(reads stdin). `-e` transforms incoming, `-E` transforms outgoing.
|
|
109
|
+
|
|
110
|
+
### Pipe (virtual)
|
|
111
|
+
|
|
112
|
+
Pipe creates an internal PULL → eval → PUSH pipeline:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
nnq pipe -c ipc://@work -c ipc://@sink -e '$_.upcase'
|
|
116
|
+
|
|
117
|
+
# with Ractor workers for CPU parallelism
|
|
118
|
+
nnq pipe -c ipc://@work -c ipc://@sink -P 4 -r./fib.rb -e 'fib(Integer($_)).to_s'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The first endpoint is the pull-side (input), the second is the push-side
|
|
122
|
+
(output). For parallel mode (`-P`) all endpoints must be `--connect`.
|
|
123
|
+
|
|
124
|
+
## Eval: -e and -E
|
|
125
|
+
|
|
126
|
+
`-e` (alias `--recv-eval`) runs a Ruby expression for each **incoming** message.
|
|
127
|
+
`-E` (alias `--send-eval`) runs a Ruby expression for each **outgoing** message.
|
|
128
|
+
|
|
129
|
+
### Globals
|
|
130
|
+
|
|
131
|
+
| Variable | Value |
|
|
132
|
+
|----------|-------|
|
|
133
|
+
| `$_` | Message body (`String`) |
|
|
134
|
+
| `$F` | `[$_]` — 1-element array, kept for omq-cli compatibility |
|
|
135
|
+
|
|
136
|
+
### Return value
|
|
137
|
+
|
|
138
|
+
| Return | Effect |
|
|
139
|
+
|--------|--------|
|
|
140
|
+
| `String` | Used as the message body |
|
|
141
|
+
| `Array` | First element used as the body |
|
|
142
|
+
| `nil` | Message is skipped (filtered) |
|
|
143
|
+
| `self` (the socket) | Signals "I already sent" (REP only) |
|
|
144
|
+
|
|
145
|
+
### Control flow
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
# skip messages matching a pattern
|
|
149
|
+
nnq pull -b tcp://:5557 -e 'next if /^#/.match?($_); $_'
|
|
150
|
+
|
|
151
|
+
# stop on "quit"
|
|
152
|
+
nnq pull -b tcp://:5557 -e 'break if /quit/.match?($_); $_'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### BEGIN/END blocks
|
|
156
|
+
|
|
157
|
+
Like awk — `BEGIN{}` runs once before the message loop, `END{}` runs after:
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
nnq pull -b tcp://:5557 -e 'BEGIN{ @sum = 0 } @sum += Integer($_); next END{ puts @sum }'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Local variables won't share state between blocks. Use `@ivars` instead.
|
|
164
|
+
|
|
165
|
+
### Which sockets accept which flag
|
|
166
|
+
|
|
167
|
+
| Socket | `-E` (send) | `-e` (recv) |
|
|
168
|
+
|--------|-------------|-------------|
|
|
169
|
+
| push, pub | transforms outgoing | error |
|
|
170
|
+
| pull, sub | error | transforms incoming |
|
|
171
|
+
| req | transforms request | transforms reply |
|
|
172
|
+
| rep | error | transforms request → return = reply |
|
|
173
|
+
| pair | transforms outgoing | transforms incoming |
|
|
174
|
+
| pipe | error | transforms in pipeline |
|
|
175
|
+
|
|
176
|
+
### Examples
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
# upcase echo server
|
|
180
|
+
nnq rep -b tcp://:5555 -e '$_.upcase'
|
|
181
|
+
|
|
182
|
+
# transform before sending
|
|
183
|
+
echo hello | nnq push -c tcp://localhost:5557 -E '$_.upcase'
|
|
184
|
+
|
|
185
|
+
# filter incoming
|
|
186
|
+
nnq pull -b tcp://:5557 -e '$_.include?("error") ? $_ : nil'
|
|
187
|
+
|
|
188
|
+
# REQ: different transforms per direction
|
|
189
|
+
echo hello | nnq req -c tcp://localhost:5555 \
|
|
190
|
+
-E '$_.upcase' -e '$_.reverse'
|
|
191
|
+
|
|
192
|
+
# generate messages without stdin
|
|
193
|
+
nnq pub -c tcp://localhost:5556 -E 'Time.now.to_s' -i 1
|
|
194
|
+
|
|
195
|
+
# use gems
|
|
196
|
+
nnq sub -c tcp://localhost:5556 -s "" -rjson -e 'JSON.parse($_)["temperature"]'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Script handlers (-r)
|
|
200
|
+
|
|
201
|
+
For non-trivial transforms, put the logic in a Ruby file and load it with `-r`:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
# handler.rb
|
|
205
|
+
db = PG.connect("dbname=app")
|
|
206
|
+
|
|
207
|
+
NNQ.outgoing { |msg| msg.upcase }
|
|
208
|
+
NNQ.incoming { |msg| db.exec(msg).values.flatten.first }
|
|
209
|
+
|
|
210
|
+
at_exit { db.close }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
nnq req -c tcp://localhost:5555 -r./handler.rb
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Registration API
|
|
218
|
+
|
|
219
|
+
| Method | Effect |
|
|
220
|
+
|--------|--------|
|
|
221
|
+
| `NNQ.outgoing { |msg| ... }` | Register outgoing message transform |
|
|
222
|
+
| `NNQ.incoming { |msg| ... }` | Register incoming message transform |
|
|
223
|
+
|
|
224
|
+
- `msg` is a `String` (the message body)
|
|
225
|
+
- Setup: use local variables and closures at the top of the script
|
|
226
|
+
- Teardown: use Ruby's `at_exit { ... }`
|
|
227
|
+
- CLI flags (`-e`/`-E`) override script-registered handlers for the same direction
|
|
228
|
+
- A script can register one direction while the CLI handles the other
|
|
229
|
+
|
|
230
|
+
## Data sources
|
|
231
|
+
|
|
232
|
+
| Flag | Behavior |
|
|
233
|
+
|------|----------|
|
|
234
|
+
| (stdin) | Read lines from stdin, one message per line |
|
|
235
|
+
| `-D "text"` | Send literal string (one-shot or repeated with `-i`) |
|
|
236
|
+
| `-F file` | Read message from file (`-F -` reads stdin as blob) |
|
|
237
|
+
| `--echo` | Echo received messages back (REP only) |
|
|
238
|
+
|
|
239
|
+
`-D` and `-F` are mutually exclusive.
|
|
240
|
+
|
|
241
|
+
## Formats
|
|
242
|
+
|
|
243
|
+
| Flag | Format |
|
|
244
|
+
|------|--------|
|
|
245
|
+
| `-A` / `--ascii` | Tab-separated frames, non-printable → dots (default) |
|
|
246
|
+
| `-Q` / `--quoted` | C-style escapes, lossless round-trip |
|
|
247
|
+
| `--raw` | Raw body, newline-delimited |
|
|
248
|
+
| `-J` / `--jsonl` | JSON Lines — `["body"]` per line |
|
|
249
|
+
| `--msgpack` | MessagePack arrays (binary stream) |
|
|
250
|
+
| `-M` / `--marshal` | Ruby Marshal (binary stream of `Array<String>` objects) |
|
|
251
|
+
|
|
252
|
+
Since SP messages are single-frame, ASCII/quoted modes don't insert tabs.
|
|
253
|
+
|
|
254
|
+
```sh
|
|
255
|
+
nnq push -c tcp://localhost:5557 < data.txt
|
|
256
|
+
nnq pull -b tcp://:5557 -J
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Timing
|
|
260
|
+
|
|
261
|
+
| Flag | Effect |
|
|
262
|
+
|------|--------|
|
|
263
|
+
| `-i SECS` | Repeat send every N seconds (wall-clock aligned) |
|
|
264
|
+
| `-n COUNT` | Max messages to send/receive (0 = unlimited) |
|
|
265
|
+
| `-d SECS` | Delay before first send |
|
|
266
|
+
| `-t SECS` | Send/receive timeout |
|
|
267
|
+
| `-l SECS` | Linger time on close (default 5s) |
|
|
268
|
+
| `--reconnect-ivl` | Reconnect interval: `SECS` or `MIN..MAX` (default 0.1) |
|
|
269
|
+
|
|
270
|
+
```sh
|
|
271
|
+
# publish a tick every second, 10 times
|
|
272
|
+
nnq pub -c tcp://localhost:5556 -D "tick" -i 1 -n 10 -d 1
|
|
273
|
+
|
|
274
|
+
# receive with 5s timeout
|
|
275
|
+
nnq pull -b tcp://:5557 -t 5
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Compression
|
|
279
|
+
|
|
280
|
+
Both sides must use `--compress` (`-z`). Uses LZ4 frame format, provided by
|
|
281
|
+
the `rlz4` gem (Ractor-safe, Rust extension via `lz4_flex`).
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
nnq push -c tcp://remote:5557 -z < data.txt
|
|
285
|
+
nnq pull -b tcp://:5557 -z
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Subscriptions
|
|
289
|
+
|
|
290
|
+
```sh
|
|
291
|
+
# subscribe to topic prefix
|
|
292
|
+
nnq sub -b tcp://:5556 -s "weather."
|
|
293
|
+
|
|
294
|
+
# subscribe to all (default)
|
|
295
|
+
nnq sub -b tcp://:5556
|
|
296
|
+
|
|
297
|
+
# multiple subscriptions
|
|
298
|
+
nnq sub -b tcp://:5556 -s "weather." -s "sports."
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Pipe
|
|
302
|
+
|
|
303
|
+
Pipe creates an in-process PULL → eval → PUSH pipeline:
|
|
304
|
+
|
|
305
|
+
```sh
|
|
306
|
+
# basic pipe (positional: first = input, second = output)
|
|
307
|
+
nnq pipe -c ipc://@work -c ipc://@sink -e '$_.upcase'
|
|
308
|
+
|
|
309
|
+
# parallel Ractor workers (default: all CPUs)
|
|
310
|
+
nnq pipe -c ipc://@work -c ipc://@sink -P -r./fib.rb -e 'fib(Integer($_)).to_s'
|
|
311
|
+
|
|
312
|
+
# fixed number of workers
|
|
313
|
+
nnq pipe -c ipc://@work -c ipc://@sink -P 4 -e '$_.upcase'
|
|
314
|
+
|
|
315
|
+
# exit when producer disconnects
|
|
316
|
+
nnq pipe -c ipc://@work -c ipc://@sink --transient -e '$_.upcase'
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Multi-peer pipe with `--in`/`--out`
|
|
320
|
+
|
|
321
|
+
Use `--in` and `--out` to attach multiple endpoints per side. These are modal
|
|
322
|
+
switches — subsequent `-b`/`-c` flags attach to the current side:
|
|
323
|
+
|
|
324
|
+
```sh
|
|
325
|
+
# fan-in: 2 producers → 1 consumer
|
|
326
|
+
nnq pipe --in -c ipc://@work1 -c ipc://@work2 --out -c ipc://@sink -e '$_'
|
|
327
|
+
|
|
328
|
+
# fan-out: 1 producer → 2 consumers (round-robin)
|
|
329
|
+
nnq pipe --in -b tcp://:5555 --out -c ipc://@sink1 -c ipc://@sink2 -e '$_'
|
|
330
|
+
|
|
331
|
+
# parallel workers with fan-in (all must be -c)
|
|
332
|
+
nnq pipe --in -c ipc://@a -c ipc://@b --out -c ipc://@sink -P 4 -e '$_'
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`-P`/`--parallel` requires all endpoints to be `--connect`. In parallel mode,
|
|
336
|
+
each Ractor worker gets its own PULL/PUSH pair connecting to all endpoints.
|
|
337
|
+
|
|
338
|
+
## Transient mode
|
|
339
|
+
|
|
340
|
+
`--transient` makes the socket exit when all peers disconnect. Useful for
|
|
341
|
+
pipeline workers and sinks:
|
|
342
|
+
|
|
343
|
+
```sh
|
|
344
|
+
# worker exits when producer is done
|
|
345
|
+
nnq pipe -c ipc://@work -c ipc://@sink --transient -e '$_.upcase'
|
|
346
|
+
|
|
347
|
+
# sink exits when all workers disconnect
|
|
348
|
+
nnq pull -b tcp://:5557 --transient
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Verbose / monitor mode
|
|
352
|
+
|
|
353
|
+
Pass `-v` (repeatable) for increasingly chatty output:
|
|
354
|
+
|
|
355
|
+
| Level | Output |
|
|
356
|
+
|-------|--------|
|
|
357
|
+
| `-v` | Bind/connect endpoints |
|
|
358
|
+
| `-vv` | Lifecycle events (`:listening`, `:connected`, `:disconnected`, ...) |
|
|
359
|
+
| `-vvv` | Per-message trace (`:message_sent`, `:message_received`) |
|
|
360
|
+
|
|
361
|
+
```sh
|
|
362
|
+
nnq pub -b tcp://:5556 -vv
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Exit codes
|
|
366
|
+
|
|
367
|
+
| Code | Meaning |
|
|
368
|
+
|------|---------|
|
|
369
|
+
| 0 | Success |
|
|
370
|
+
| 1 | Error (connection, argument, runtime) |
|
|
371
|
+
| 2 | Timeout |
|
|
372
|
+
| 3 | Eval error (`-e`/`-E` expression raised) |
|
|
373
|
+
|
|
374
|
+
## Interop with libnng / nngcat
|
|
375
|
+
|
|
376
|
+
nnq-cli speaks the SP wire protocol, so it interoperates with `nngcat` and any
|
|
377
|
+
libnng-based peer over `tcp://` and `ipc://`:
|
|
378
|
+
|
|
379
|
+
```sh
|
|
380
|
+
# nnq → nngcat
|
|
381
|
+
nngcat --pull0 --listen tcp://127.0.0.1:5555 --quoted &
|
|
382
|
+
echo hello | nnq push -c tcp://127.0.0.1:5555
|
|
383
|
+
|
|
384
|
+
# nngcat → nnq
|
|
385
|
+
nnq pull -b tcp://127.0.0.1:5555 &
|
|
386
|
+
nngcat --push0 --dial tcp://127.0.0.1:5555 --data "hello-from-nngcat"
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## License
|
|
390
|
+
|
|
391
|
+
[ISC](LICENSE)
|