cosmonats 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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +169 -0
  3. data/README.md +515 -0
  4. data/bin/cosmo +7 -0
  5. data/lib/cosmo/cli.rb +201 -0
  6. data/lib/cosmo/client.rb +54 -0
  7. data/lib/cosmo/config.rb +101 -0
  8. data/lib/cosmo/defaults.yml +69 -0
  9. data/lib/cosmo/engine.rb +46 -0
  10. data/lib/cosmo/job/data.rb +74 -0
  11. data/lib/cosmo/job/processor.rb +132 -0
  12. data/lib/cosmo/job.rb +67 -0
  13. data/lib/cosmo/logger.rb +66 -0
  14. data/lib/cosmo/processor.rb +56 -0
  15. data/lib/cosmo/publisher.rb +38 -0
  16. data/lib/cosmo/stream/data.rb +21 -0
  17. data/lib/cosmo/stream/message.rb +31 -0
  18. data/lib/cosmo/stream/processor.rb +94 -0
  19. data/lib/cosmo/stream/serializer.rb +19 -0
  20. data/lib/cosmo/stream.rb +76 -0
  21. data/lib/cosmo/utils/hash.rb +66 -0
  22. data/lib/cosmo/utils/json.rb +23 -0
  23. data/lib/cosmo/utils/signal.rb +24 -0
  24. data/lib/cosmo/utils/stopwatch.rb +32 -0
  25. data/lib/cosmo/utils/string.rb +24 -0
  26. data/lib/cosmo/utils/thread_pool.rb +41 -0
  27. data/lib/cosmo/version.rb +5 -0
  28. data/lib/cosmo.rb +39 -0
  29. data/lib/cosmonats.rb +3 -0
  30. data/sig/cosmo/cli.rbs +25 -0
  31. data/sig/cosmo/client.rbs +30 -0
  32. data/sig/cosmo/config.rbs +48 -0
  33. data/sig/cosmo/engine.rbs +21 -0
  34. data/sig/cosmo/job/data.rbs +35 -0
  35. data/sig/cosmo/job/processor.rbs +23 -0
  36. data/sig/cosmo/job.rbs +35 -0
  37. data/sig/cosmo/logger.rbs +39 -0
  38. data/sig/cosmo/message.rbs +38 -0
  39. data/sig/cosmo/processor.rbs +29 -0
  40. data/sig/cosmo/publisher.rbs +21 -0
  41. data/sig/cosmo/stream/data.rbs +7 -0
  42. data/sig/cosmo/stream/processor.rbs +26 -0
  43. data/sig/cosmo/stream/serializer.rbs +13 -0
  44. data/sig/cosmo/stream.rbs +38 -0
  45. data/sig/cosmo/utils/hash.rbs +25 -0
  46. data/sig/cosmo/utils/json.rbs +13 -0
  47. data/sig/cosmo/utils/signal.rbs +15 -0
  48. data/sig/cosmo/utils/stopwatch.rbs +19 -0
  49. data/sig/cosmo/utils/string.rbs +13 -0
  50. data/sig/cosmo/utils/thread_pool.rbs +18 -0
  51. data/sig/cosmo.rbs +20 -0
  52. metadata +125 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: faaf7e9f0c3b86375099ee43d1125ec1545251d121fedbf8d7596bb0ca8f2461
4
+ data.tar.gz: 97fd56077b64673e02af110dfab8edbeea424114b1a54f89b240a54d308bbcdc
5
+ SHA512:
6
+ metadata.gz: db91195e38a240f5091ca616e1cb916707b6943e12fe61b173282a438381b6d258946a59e99445c9f5c26519515c7ea8ba57b95cdfd004ff396ec36c2067002b
7
+ data.tar.gz: 76eb8aa63116623cdfe1bf1d0ce07a57148f473b9818cc2ab6097501740668d3f3b1f5202f6f83eb91340d13cb713a0836d42db01e70f0f921a9f617fed8b19d
data/LICENSE.txt ADDED
@@ -0,0 +1,169 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
166
+
167
+ Copyright (c) BitsBeam LLC
168
+
169
+ https://www.gnu.org/licenses/lgpl-3.0.txt
data/README.md ADDED
@@ -0,0 +1,515 @@
1
+ # 🚀 Cosmonats - lightweight background and stream processing
2
+
3
+ It is a Ruby background job and stream processing framework powered by NATS JetStream.
4
+ It provides a familiar API for job queues while adding powerful stream processing capabilities,
5
+ solving the scalability limitations of Redis and database-backed queues through true horizontal scaling and
6
+ disk-backed persistence.
7
+
8
+ ![logo.svg](logo.svg)
9
+
10
+ ## 📖 Index
11
+
12
+ - [Why?](#-why)
13
+ - [Features](#-features)
14
+ - [Installation](#-installation)
15
+ - [Quick Start](#-quick-start)
16
+ - [Core Concepts](#-core-concepts)
17
+ - [Jobs](#jobs)
18
+ - [Streams](#streams)
19
+ - [Configuration](#configuration)
20
+ - [Advanced Usage](#-advanced-usage)
21
+ - [CLI Reference](#-cli-reference)
22
+ - [Deployment](#-deployment)
23
+ - [Monitoring](#-monitoring)
24
+ - [Examples](#-examples)
25
+
26
+
27
+ ## 🎯 Why?
28
+ Among many others, why creating another? Cosmonats is a background processing framework for Ruby, powered by **[NATS](https://nats.io/)**.
29
+ It's designed to solve the fundamental scaling problems that plague Redis/DB-based job queues and at the same time to provide both job and stream
30
+ processing capabilities.
31
+
32
+ ### The Problem with Redis at Scale
33
+
34
+ - **Single-threaded command processing** - All operations serialized, creating contention with many workers
35
+ - **Memory-only persistence** - Everything must fit in RAM, expensive to scale
36
+ - **Vertical scaling only** - Can't truly distribute a single queue across nodes
37
+ - **Polling overhead** - Thousands of blocked connections
38
+ - **No native backpressure** - Queues can grow unbounded
39
+ - **Weak durability** - Async replication can lose jobs during failures
40
+
41
+ **Note:** Alternatives like Dragonfly solve the threading bottleneck but still face memory/scaling limitations.
42
+
43
+ ### The Problem with RDBMS at Scale
44
+
45
+ - **Database contention** - Polling queries compete with application queries for resources
46
+ - **Connection pool pressure** - Workers consume database connections, starving the application
47
+ - **Row-level locking overhead** - `SELECT FOR UPDATE SKIP LOCKED` still scans rows under high concurrency
48
+ - **Vacuum/autovacuum impact** - High-churn job tables degrade database performance
49
+ - **Vertical scaling only** - Limited by single database instance capabilities
50
+ - **Index bloat** - High UPDATE/DELETE volume causes index degradation over time
51
+ - **Table bloat** - Constant row updates fragment tables, requiring maintenance
52
+ - **`LISTEN/NOTIFY` limitations** - 8KB payload limit, no persistence, breaks down at high volumes (10K+ notifications/sec)
53
+ - **No native horizontal scaling** - Cannot distribute a single job queue across multiple database nodes
54
+
55
+ **Note:** Solutions using DB might be ok for moderate workloads but face these fundamental limitations at higher scales.
56
+
57
+ ### The Solution
58
+
59
+ Built on **NATS**, `cosmonats` provides:
60
+
61
+ ✅ **True horizontal scaling** - Distribute streams across cluster nodes
62
+ ✅ **Disk-backed persistence** - TB-scale queues with memory cache
63
+ ✅ **Replicated acknowledgments** - Survive multi-node failures
64
+ ✅ **Built-in flow control** - Automatic backpressure
65
+ ✅ **Multi-DC support** - Native geo-distribution, and super clusters
66
+ ✅ **High throughput & low latency** - Millions of messages per second
67
+ ✅ **Stream processing** - Beyond simple job queues
68
+
69
+
70
+ ## ✨ Features
71
+
72
+ ### 🎪 Job Processing
73
+ - **Familiar compatible API** - Easy migration from existing codebases
74
+ - **Priority queues** - Multiple priority levels (critical, high, default, low)
75
+ - **Scheduled jobs** - Execute jobs at specific times or after delays
76
+ - **Automatic retries** - Configurable retry strategies with exponential backoff
77
+ - **Dead letter queue** - Capture permanently failed jobs
78
+ - **Job uniqueness** - Prevent duplicate job execution
79
+
80
+ ### 🌊 Stream Processing
81
+ - **Real-time data streams** - Process continuous event streams
82
+ - **Batch processing** - Handle multiple messages efficiently
83
+ - **Message replay** - Reprocess messages from any point in time
84
+ - **Consumer groups** - Multiple consumers with load balancing
85
+ - **Exactly-once semantics** - With proper configuration
86
+ - **Custom serialization** - JSON, MessagePack, Protobuf support
87
+
88
+
89
+ ## 📦 Installation
90
+
91
+ ```ruby
92
+ # Gemfile
93
+ gem "cosmonats"
94
+ ```
95
+
96
+ **Requirements:** Ruby 3.1.0+, NATS Server ([installation guide](https://docs.nats.io/running-a-nats-service/introduction/installation))
97
+
98
+
99
+ ## 🚀 Quick Start
100
+
101
+ ### 1. Create a Job
102
+
103
+ ```ruby
104
+ class SendEmailJob
105
+ include Cosmo::Job
106
+
107
+ # configure job options (optional)
108
+ options stream: :default, retry: 3, dead: true
109
+
110
+ def perform(user_id, email_type)
111
+ user = User.find(user_id)
112
+ UserMailer.send(email_type, user).deliver_now
113
+ end
114
+ end
115
+ ```
116
+
117
+ ### 2. Enqueue Jobs
118
+
119
+ ```ruby
120
+ SendEmailJob.perform_async(123, 'welcome') # Immediately
121
+ SendEmailJob.perform_in(1.hour, 123, 'reminder') # Delayed
122
+ SendEmailJob.perform_at(1.day.from_now, 123, 'test') # Scheduled
123
+ ```
124
+
125
+ ### 3. Configure (config/cosmo.yml)
126
+
127
+ ```yaml
128
+ concurrency: 10
129
+ max_retries: 3
130
+
131
+ consumers:
132
+ jobs:
133
+ default:
134
+ ack_policy: explicit
135
+ max_deliver: 3
136
+ max_ack_pending: 3
137
+ ack_wait: 60
138
+
139
+ streams:
140
+ default:
141
+ storage: file
142
+ retention: workqueue
143
+ subjects: ["jobs.default.>"]
144
+ ```
145
+
146
+ ### 4. Setup & Run
147
+
148
+ ```bash
149
+ # Setup streams
150
+ cosmo -C config/cosmo.yml --setup
151
+
152
+ # Start processing
153
+ cosmo -C config/cosmo.yml -c 10 -r ./app/jobs jobs
154
+ ```
155
+
156
+
157
+ ## 💡 Core Concepts
158
+
159
+ ### Jobs
160
+
161
+ Simple background tasks with a familiar API:
162
+
163
+ ```ruby
164
+ class ReportJob
165
+ include Cosmo::Job
166
+
167
+ options(
168
+ stream: :critical, # Stream name
169
+ retry: 5, # Retry attempts
170
+ dead: true # Use dead letter queue
171
+ )
172
+
173
+ def perform(report_id)
174
+ logger.info "Processing report #{report_id}"
175
+ Report.find(report_id).generate!
176
+ rescue StandardError => e
177
+ logger.error "Failed: #{e.message}"
178
+ raise # Triggers retry
179
+ end
180
+ end
181
+
182
+ # Usage
183
+ ReportJob.perform_async(42) # Enqueue now
184
+ ReportJob.perform_in(30.minutes, 42) # Delayed
185
+ ReportJob.perform_at(Time.parse('2026-01-25 10:00'), 42) # Scheduled
186
+ ```
187
+
188
+ ### Streams
189
+
190
+ Real-time event processing with powerful features:
191
+
192
+ ```ruby
193
+ class ClicksProcessor
194
+ include Cosmo::Stream
195
+
196
+ options(
197
+ stream: :clickstream,
198
+ batch_size: 100,
199
+ start_position: :last, # :first, :last, :new, or timestamp
200
+ consumer: {
201
+ ack_policy: "explicit",
202
+ max_deliver: 3,
203
+ max_ack_pending: 100,
204
+ subjects: ["events.clicks.>"]
205
+ }
206
+ )
207
+
208
+ # Process one message
209
+ def process_one
210
+ data = message.data
211
+ Analytics.track_click(data)
212
+ message.ack # Success
213
+ end
214
+
215
+ # OR process batch
216
+ def process(messages)
217
+ Analytics.track_click(messages.map(&:data))
218
+ messages.each(&:ack)
219
+ end
220
+ end
221
+
222
+ # Publishing
223
+ ClicksProcessor.publish(
224
+ { user_id: 123, page: '/home' },
225
+ subject: 'events.clicks.homepage'
226
+ )
227
+
228
+ # Message acknowledgment strategies
229
+ message.ack # Success
230
+ message.nack(delay: 5_000_000_000) # Retry (5 seconds in nanoseconds)
231
+ message.term # Permanent failure, no retry
232
+ ```
233
+
234
+ ### Configuration
235
+
236
+ **File-based (config/cosmo.yml):**
237
+ ```yaml
238
+ timeout: 25 # Shutdown timeout in seconds
239
+ concurrency: 10 # Number of worker threads
240
+ max_retries: 3 # Default max retries
241
+
242
+ consumers:
243
+ streams:
244
+ - class: MyStream
245
+ batch_size: 50
246
+ consumer:
247
+ ack_policy: explicit
248
+ max_deliver: 3
249
+ subjects: ["events.>"]
250
+
251
+ streams:
252
+ my_stream:
253
+ storage: file # or memory
254
+ retention: workqueue # or limits
255
+ max_age: 86400 # 1d in seconds
256
+ subjects: ["events.>"]
257
+ ```
258
+
259
+ **Programmatic:**
260
+ ```ruby
261
+ Cosmo::Config.set(:concurrency, 20)
262
+ Cosmo::Config.set(:streams, :custom, { storage: 'file', subjects: ['custom.>'] })
263
+ ```
264
+
265
+ **Environment variables:**
266
+ ```bash
267
+ export NATS_URL=nats://localhost:4222
268
+ export COSMO_JOBS_FETCH_TIMEOUT=0.1
269
+ export COSMO_STREAMS_FETCH_TIMEOUT=0.1
270
+ ```
271
+
272
+
273
+ ## 🔧 Advanced Usage
274
+
275
+ **Priority Queues:**
276
+ ```ruby
277
+ class UrgentJob
278
+ include Cosmo::Job
279
+ options stream: :critical # priority: 50 in config
280
+ end
281
+
282
+ # config/cosmo.yml
283
+ consumers:
284
+ jobs:
285
+ critical:
286
+ priority: 50 # Polled more frequently
287
+ default:
288
+ priority: 15
289
+ ```
290
+
291
+ **Custom Serializers:**
292
+ ```ruby
293
+ module MessagePackSerializer
294
+ def self.serialize(data)
295
+ MessagePack.pack(data)
296
+ end
297
+
298
+ def self.deserialize(payload)
299
+ MessagePack.unpack(payload)
300
+ end
301
+ end
302
+
303
+ class FastStream
304
+ include Cosmo::Stream
305
+ options publisher: { serializer: MessagePackSerializer }
306
+ end
307
+ ```
308
+
309
+ **Error Handling:**
310
+ ```ruby
311
+ class ResilientJob
312
+ include Cosmo::Job
313
+ options retry: 5, dead: true
314
+
315
+ def perform(data)
316
+ process_data(data)
317
+ rescue RetryableError => e
318
+ logger.warn "Retryable: #{e.message}"
319
+ raise # Will retry
320
+ rescue FatalError => e
321
+ logger.error "Fatal: #{e.message}"
322
+ # Don't raise - won't retry
323
+ end
324
+ end
325
+ ```
326
+
327
+ **Testing:**
328
+ ```ruby
329
+ # Synchronous execution
330
+ SendEmailJob.perform_sync(123, 'test')
331
+
332
+ # Test job creation
333
+ jid = SendEmailJob.perform_async(123, 'welcome')
334
+ assert_kind_of String, jid
335
+ ```
336
+
337
+
338
+ ## 🖥️ CLI Reference
339
+
340
+ ```bash
341
+ # Setup streams
342
+ cosmo -C config/cosmo.yml --setup
343
+
344
+ # Run processors
345
+ cosmo -C config/cosmo.yml -c 20 -r ./app/jobs jobs # Jobs only
346
+ cosmo -C config/cosmo.yml -c 20 streams # Streams only
347
+ cosmo -C config/cosmo.yml -c 20 # Both
348
+ ```
349
+
350
+ **Common Flags:**
351
+
352
+ | Flag | Description | Example |
353
+ |------|-------------|---------|
354
+ | `-C, --config PATH` | Config file path | `-C config/cosmo.yml` |
355
+ | `-c, --concurrency INT` | Worker threads | `-c 20` |
356
+ | `-r, --require PATH` | Auto-require directory | `-r ./app/jobs` |
357
+ | `-t, --timeout NUM` | Shutdown timeout (sec) | `-t 60` |
358
+ | `-S, --setup` | Setup streams & exit | `--setup` |
359
+
360
+
361
+ ## 🚢 Deployment
362
+
363
+ **NATS Cluster:**
364
+ ```bash
365
+ # nats-server.conf
366
+ port: 4222
367
+ jetstream {
368
+ store_dir: /var/lib/nats
369
+ max_file: 10G
370
+ }
371
+ cluster {
372
+ name: cosmo-cluster
373
+ listen: 0.0.0.0:6222
374
+ routes: [nats://nats-2:6222, nats://nats-3:6222]
375
+ }
376
+ ```
377
+
378
+ **Docker Compose:**
379
+ ```yaml
380
+ services:
381
+ nats:
382
+ image: nats:latest
383
+ command: -js -c /etc/nats/nats-server.conf
384
+ volumes:
385
+ - ./nats.conf:/etc/nats/nats-server.conf
386
+ - nats-data:/var/lib/nats
387
+
388
+ worker:
389
+ build: .
390
+ environment:
391
+ NATS_URL: nats://nats:4222
392
+ command: bundle exec cosmo -C config/cosmo.yml -c 20 jobs
393
+ deploy:
394
+ replicas: 3
395
+ ```
396
+
397
+ **Systemd Service:**
398
+ ```ini
399
+ # /etc/systemd/system/cosmo.service
400
+ [Unit]
401
+ Description=Cosmo Background Processor
402
+ After=network.target
403
+
404
+ [Service]
405
+ Type=simple
406
+ User=deploy
407
+ WorkingDirectory=/var/www/myapp
408
+ Environment=RAILS_ENV=production
409
+ Environment=NATS_URL=nats://localhost:4222
410
+ ExecStart=/usr/local/bin/bundle exec cosmo -C config/cosmo.yml -c 20 jobs
411
+ Restart=always
412
+ RestartSec=10
413
+ StandardOutput=syslog
414
+ StandardError=syslog
415
+ SyslogIdentifier=cosmo
416
+
417
+ [Install]
418
+ WantedBy=multi-user.target
419
+ ```
420
+
421
+ Enable and start:
422
+ ```bash
423
+ sudo systemctl enable cosmo
424
+ sudo systemctl start cosmo
425
+ sudo systemctl status cosmo
426
+ ```
427
+
428
+
429
+ ## 📊 Monitoring
430
+
431
+ **Structured Logging:**
432
+ ```
433
+ 2026-01-23T10:15:30.123Z INFO pid=12345 tid=abc jid=def: start
434
+ 2026-01-23T10:15:32.456Z INFO pid=12345 tid=abc jid=def elapsed=2.333: done
435
+ ```
436
+
437
+ **Stream Metrics:**
438
+ ```ruby
439
+ client = Cosmo::Client.instance
440
+ info = client.stream_info('default')
441
+
442
+ info.state.messages # Total messages
443
+ info.state.bytes # Total bytes
444
+ info.state.consumer_count # Number of consumers
445
+ ```
446
+
447
+ **Prometheus:** NATS exposes metrics at `:8222/metrics`
448
+ - `jetstream_server_store_msgs` - Messages in stream
449
+ - `jetstream_consumer_delivered_msgs` - Delivered messages
450
+ - `jetstream_consumer_ack_pending` - Pending acknowledgments
451
+
452
+
453
+ ## 💼 Examples
454
+
455
+ **Email Queue:**
456
+ ```ruby
457
+ class EmailJob
458
+ include Cosmo::Job
459
+ options stream: :default, retry: 3
460
+
461
+ def perform(user_id, template)
462
+ user = User.find(user_id)
463
+ EmailService.send(user.email, template)
464
+ end
465
+ end
466
+
467
+ EmailJob.perform_async(123, 'welcome')
468
+ EmailJob.perform_in(1.day, 123, 'followup')
469
+ ```
470
+
471
+ **Image Processing Pipeline:**
472
+ ```ruby
473
+ class ImageProcessor
474
+ include Cosmo::Stream
475
+ options(
476
+ stream: :images,
477
+ consumer: { subjects: ['images.uploaded.>'] }
478
+ )
479
+
480
+ def process_one
481
+ processed = ImageService.process(message.data['url'])
482
+ publish(processed, subject: 'images.processed.optimized')
483
+ message.ack
484
+ rescue => e
485
+ logger.error "Processing failed: #{e.message}"
486
+ message.nack(delay: 30_000_000_000)
487
+ end
488
+ end
489
+
490
+ ImageProcessor.publish({ url: 'https://example.com/image.jpg' }, subject: 'images.uploaded.user')
491
+ ```
492
+
493
+ **Real-Time Analytics:**
494
+ ```ruby
495
+ class AnalyticsAggregator
496
+ include Cosmo::Stream
497
+ options batch_size: 1000, consumer: { subjects: ['events.*.>'] }
498
+
499
+ def process(messages)
500
+ events = messages.map(&:data)
501
+ aggregates = events.group_by { |e| e['type'] }.transform_values(&:count)
502
+ Analytics.bulk_insert(aggregates)
503
+ messages.each(&:ack)
504
+ end
505
+ end
506
+ ```
507
+
508
+
509
+ <div align="center">
510
+
511
+ **Made with ❤️ for Ruby**
512
+
513
+ *Blast off Cosmonats! 🚀*
514
+
515
+ </div>
data/bin/cosmo ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "cosmonats"
6
+
7
+ Cosmo::CLI.run