cosmonats 0.8.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -11
  3. data/lib/cosmo/api/counter.rb +1 -1
  4. data/lib/cosmo/api/cron/entry.rb +10 -11
  5. data/lib/cosmo/api/cron.rb +3 -3
  6. data/lib/cosmo/api/job.rb +1 -1
  7. data/lib/cosmo/api/kv.rb +3 -3
  8. data/lib/cosmo/api/stats/busy.rb +5 -0
  9. data/lib/cosmo/api/stats/metrics.rb +3 -3
  10. data/lib/cosmo/api/stats.rb +15 -0
  11. data/lib/cosmo/api/stream.rb +7 -2
  12. data/lib/cosmo/client.rb +10 -13
  13. data/lib/cosmo/job/data.rb +1 -1
  14. data/lib/cosmo/job/processor.rb +5 -6
  15. data/lib/cosmo/utils.rb +0 -1
  16. data/lib/cosmo/version.rb +1 -1
  17. data/lib/cosmo/web/assets/app.css +271 -629
  18. data/lib/cosmo/web/assets/app.js +158 -0
  19. data/lib/cosmo/web/assets/fonts/OFL.txt +92 -0
  20. data/lib/cosmo/web/assets/fonts/geist-mono.woff2 +0 -0
  21. data/lib/cosmo/web/assets/fonts/geist.woff2 +0 -0
  22. data/lib/cosmo/web/assets/logo.svg +10 -0
  23. data/lib/cosmo/web/cache.rb +75 -0
  24. data/lib/cosmo/web/chart.rb +62 -38
  25. data/lib/cosmo/web/circuit.rb +55 -0
  26. data/lib/cosmo/web/context.rb +0 -4
  27. data/lib/cosmo/web/controllers/application.rb +16 -0
  28. data/lib/cosmo/web/controllers/batches.rb +1 -0
  29. data/lib/cosmo/web/controllers/crons.rb +9 -2
  30. data/lib/cosmo/web/controllers/jobs.rb +50 -59
  31. data/lib/cosmo/web/controllers/metrics.rb +1 -0
  32. data/lib/cosmo/web/controllers/overview.rb +70 -0
  33. data/lib/cosmo/web/controllers/processes.rb +33 -0
  34. data/lib/cosmo/web/controllers/streams.rb +26 -28
  35. data/lib/cosmo/web/helpers/application.rb +57 -0
  36. data/lib/cosmo/web/renderer.rb +19 -6
  37. data/lib/cosmo/web/throughput.rb +104 -0
  38. data/lib/cosmo/web/views/batches/_table.erb +42 -54
  39. data/lib/cosmo/web/views/batches/index.erb +3 -10
  40. data/lib/cosmo/web/views/crons/_table.erb +32 -53
  41. data/lib/cosmo/web/views/crons/index.erb +2 -9
  42. data/lib/cosmo/web/views/jobs/_args.erb +5 -0
  43. data/lib/cosmo/web/views/jobs/_busy.erb +25 -121
  44. data/lib/cosmo/web/views/jobs/_dead.erb +42 -82
  45. data/lib/cosmo/web/views/jobs/_enqueued.erb +33 -113
  46. data/lib/cosmo/web/views/jobs/_panel.erb +29 -0
  47. data/lib/cosmo/web/views/jobs/_queue_row.erb +18 -0
  48. data/lib/cosmo/web/views/jobs/_queues.erb +20 -0
  49. data/lib/cosmo/web/views/jobs/_scheduled.erb +20 -54
  50. data/lib/cosmo/web/views/jobs/index.erb +4 -17
  51. data/lib/cosmo/web/views/layout.erb +73 -22
  52. data/lib/cosmo/web/views/metrics/_chart.erb +37 -32
  53. data/lib/cosmo/web/views/metrics/_panel.erb +3 -3
  54. data/lib/cosmo/web/views/metrics/_table.erb +24 -40
  55. data/lib/cosmo/web/views/metrics/index.erb +8 -16
  56. data/lib/cosmo/web/views/overview/_badges.erb +3 -0
  57. data/lib/cosmo/web/views/overview/_kpis.erb +19 -0
  58. data/lib/cosmo/web/views/overview/_running.erb +18 -0
  59. data/lib/cosmo/web/views/overview/_throughput.erb +50 -0
  60. data/lib/cosmo/web/views/overview/_workers.erb +25 -0
  61. data/lib/cosmo/web/views/overview/index.erb +28 -0
  62. data/lib/cosmo/web/views/processes/_table.erb +67 -0
  63. data/lib/cosmo/web/views/processes/index.erb +1 -0
  64. data/lib/cosmo/web/views/shared/_pager.erb +22 -0
  65. data/lib/cosmo/web/views/shared/unavailable.erb +7 -0
  66. data/lib/cosmo/web/views/streams/_info.erb +28 -86
  67. data/lib/cosmo/web/views/streams/_list.erb +7 -0
  68. data/lib/cosmo/web/views/streams/_pause_banner.erb +8 -16
  69. data/lib/cosmo/web/views/streams/_stream.erb +52 -0
  70. data/lib/cosmo/web/views/streams/index.erb +3 -11
  71. data/lib/cosmo/web/views/streams/info.erb +3 -11
  72. data/lib/cosmo/web.rb +66 -35
  73. data/sig/cosmo/api/cron/entry.rbs +2 -1
  74. data/sig/cosmo/api/stats/busy.rbs +2 -0
  75. data/sig/cosmo/api/stats/metrics.rbs +1 -1
  76. data/sig/cosmo/api/stats.rbs +4 -0
  77. data/sig/cosmo/api/stream.rbs +2 -0
  78. data/sig/cosmo/client.rbs +1 -0
  79. metadata +31 -18
  80. data/lib/cosmo/utils/overrides.rb +0 -206
  81. data/lib/cosmo/utils/warnings.rb +0 -17
  82. data/lib/cosmo/web/controllers/actions.rb +0 -16
  83. data/lib/cosmo/web/views/actions/index.erb +0 -7
  84. data/lib/cosmo/web/views/jobs/_processes.erb +0 -145
  85. data/lib/cosmo/web/views/jobs/_stats.erb +0 -77
  86. data/lib/cosmo/web/views/jobs/_tabs.erb +0 -14
  87. data/lib/cosmo/web/views/jobs/busy.erb +0 -18
  88. data/lib/cosmo/web/views/jobs/dead.erb +0 -18
  89. data/lib/cosmo/web/views/jobs/enqueued.erb +0 -18
  90. data/lib/cosmo/web/views/jobs/scheduled.erb +0 -18
  91. data/lib/cosmo/web/views/streams/_stream_row.erb +0 -42
  92. data/lib/cosmo/web/views/streams/_table.erb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 685ae4ff6735c88c3605b58f86168ce4acc2509d7dd66c7335cdeab50fc9ffa4
4
- data.tar.gz: b508d08c685c6d726c1e3c56c79432d4afc952384d04ae210d0d6d673caf6c01
3
+ metadata.gz: 40e30ee2fff77d85db1a3deedf05e040c72dca39d64da01220517d3d5ae8048b
4
+ data.tar.gz: 6f0e5a50448674483f09f54938336940899ea81fe46ce66da23478eeee871bac
5
5
  SHA512:
6
- metadata.gz: 074bed30d8889561ecf77fd90d5d599e3f3cc9f487df781e08d88135b71b8ab31826b77dad8420b830e1a00632f047c46fee139d6a4e9817c0ca414795e46575
7
- data.tar.gz: 99d1169938570c778981447df1385d177ad5a432e355690498c8ddac8a1e4eef6f9eedeb593b1541dc2dd51472906827e80032dcd2b0e4484435d7dce1e67771
6
+ metadata.gz: 6d57b79ee594fd7a7ace94e1e3f5d70de87b9a413b85389cddb9db51f39ed09542bd0f0a7d3c4308e7009b7a2d9997b9be0c2965efa61e259bd07baf9bfa4734
7
+ data.tar.gz: 8122f92e84e4176a403678225ab66b753270ab5e49334568bc3e88cfa292451bc63ea6f5383bdb458ab89f8b319a6ed33b7af471f636ffe7314978c956b6acb1
data/README.md CHANGED
@@ -58,8 +58,6 @@ bundle exec cosmo -C config/cosmo.yml -c 20 jobs # Jobs only
58
58
  bundle exec cosmo -C config/cosmo.yml -c 20 streams # Streams only
59
59
  ```
60
60
 
61
- ![webui.gif](webui.gif)
62
-
63
61
  ## 📖 Index
64
62
 
65
63
  - [Why?](#-why)
@@ -433,9 +431,11 @@ Cosmo leaves a custom logger's formatter alone, so its lines lack Cosmo's `jid`/
433
431
  | `COSMO_CLIENT_NAME` | `cosmo-<program>-<host>-<pid>` | NATS connection name, shown in `nats server report connections` and `/connz` |
434
432
  | `COSMO_JS_TIMEOUT` | `5` | JetStream API timeout in seconds; raise it for the web UI on a busy server |
435
433
  | `COSMO_CONNECT_TIMEOUT` | `2` | NATS TCP connect timeout in seconds |
434
+ | `NATS_MAX_RECONNECT_ATTEMPTS` | `-1` | Reconnect attempts before giving up on NATS; `-1` keeps trying through any outage |
436
435
  | `COSMO_JOBS_STREAMS` | all configured | Comma-separated job streams to subscribe to, when `--streams`/`--stream` isn't given |
437
436
  | `COSMO_JOBS_FETCH_TIMEOUT` | `0.1` | Seconds a jobs worker waits on each fetch |
438
437
  | `COSMO_WEB_POLL_INTERVAL` | `5` | Web UI auto-refresh interval in seconds |
438
+ | `COSMO_WEB_CACHE_TTL` | `2` | Seconds the web UI shares a refresh between viewers; `0` turns sharing off |
439
439
 
440
440
 
441
441
  ## 🔧 Advanced Usage
@@ -543,7 +543,7 @@ end
543
543
  ```
544
544
  - Batch tracking data (pending counts, callbacks, results) expires automatically after
545
545
  `config.batches.expiry` (default: 3 days), set in `Cosmo.configure`.
546
- - Open and finished batches — with pending/succeeded/failed counts — are listed live in the web UI's **Batches** tab.
546
+ - Open and finished batches — with pending/succeeded/failed counts — are listed live on the web UI's **Batches** page.
547
547
  - Only `Cosmo::Job`-based jobs are tracked; the ActiveJob adapter doesn't currently participate in
548
548
  batches (see [`docs/active_job.md`](docs/active_job.md)).
549
549
 
@@ -896,19 +896,29 @@ sudo systemctl enable cosmo && sudo systemctl start cosmo
896
896
 
897
897
  ## 📊 Monitoring
898
898
 
899
- **Web UI** — mount `Cosmo::Web` (see [Installation](#-installation)) for a live, htmx-powered dashboard:
900
- - **Jobs** — enqueued, scheduled, busy, and dead views, with per-job retry and delete
901
- - **Processes** — the Jobs dashboard lists every live worker (idle ones included): host, pid, IP, NATS connection
902
- name, command line, subscribed streams, busy/concurrency, running/quiet/stopping state, memory, NATS RTT and
903
- reconnects. Each worker beats into the `_cosmoprocesses` KV bucket every 10s, and a crashed worker drops out within 60s
904
- - **Streams** — per-stream state (messages, bytes, consumers) with pause/resume
899
+ **Web UI** — mount `Cosmo::Web` (see [Installation](#-installation)) for a live, htmx-powered dashboard with a
900
+ light, dark, or automatic (system) theme. Fonts and assets are bundled, so it loads nothing from a CDN:
901
+ - **Overview** — processed, failed, busy, enqueued, retries, scheduled and dead totals; a 7- or 30-day throughput
902
+ chart of successful and failed runs; a grid of every worker thread, busy ones colored by stream; the queues; and the
903
+ jobs running right now, longest first
904
+ - **Jobs** — each job queue with its size, busy jobs and redeliveries, paused and resumed in place; then busy,
905
+ enqueued (per queue), scheduled and dead jobs, with retry and delete for dead ones and their error and backtrace
906
+ - **Processes** — every live worker (idle ones included): host, pid, IP, NATS connection name, command line,
907
+ subscribed streams, busy/concurrency, running/quiet/stopping state, memory, NATS RTT and reconnects. Each worker
908
+ beats into the `_cosmoprocesses` KV bucket every 10s, and a crashed worker drops out within 60s
909
+ - **Streams** — every stream with its subjects, messages, size, retention and replicas, its consumers' pending,
910
+ ack-pending and redelivered counts, and pause/resume
905
911
  - **Crons** — every schedule deployed in NATS, with run-now and delete
906
- - **Batches** — open and finished batches with pending/succeeded/failed counts
912
+ - **Batches** — open and finished batches with their progress, succeeded/failed/pending counts and callbacks
907
913
  - **Metrics** — per job class over today, 7, or 30 days: runs, failures, average execution time (successful runs), and
908
914
  average wait from enqueue to first delivery, plus a per-day chart of runs and average execution time. Recorded by
909
915
  `Cosmo::Middleware::Metrics`, flushed by every worker's heartbeat into the internal `_cosmometrics` counters stream;
910
916
  turn it off with `config.metrics.enabled = false`
911
- - Summary counters (processed / failed / busy / enqueued / retries / scheduled / dead) backed by a NATS KV counter, no separate metrics store needed
917
+
918
+ Each panel refreshes `COSMO_WEB_POLL_INTERVAL` seconds after its previous response, never on top of one still
919
+ running, backs off while refreshes fail, and pauses in a hidden tab. Identical refreshes from several viewers share a
920
+ single NATS read for `COSMO_WEB_CACHE_TTL` seconds, and while NATS is unreachable the UI answers at once instead of
921
+ holding your app server's threads.
912
922
 
913
923
  **Structured logs:**
914
924
  ```
@@ -58,7 +58,7 @@ module Cosmo
58
58
  def publish(key, value, msg_id: nil)
59
59
  rescued = nil
60
60
  headers = { "Nats-Incr" => value }
61
- headers["Nats-Msg-Id"] = msg_id if msg_id
61
+ headers[NATS::JetStream::Header::MSG_ID] = msg_id if msg_id
62
62
 
63
63
  begin
64
64
  ack = client.publish(subject(key), "", header: headers)
@@ -66,17 +66,16 @@ module Cosmo
66
66
  "jobs.#{@stream}.#{job_name}"
67
67
  end
68
68
 
69
- # Headers stored on the schedule template. NATS copies the +X-+ ones onto every message
70
- # it fires, which is how the scheduler learns where the job belongs.
71
- def schedule_headers
72
- headers = {
73
- "Nats-Schedule" => @expression,
74
- "Nats-Schedule-Target" => target_subject,
75
- "X-Stream" => @stream,
76
- "X-Subject" => dispatch_subject
77
- }
78
- headers["Nats-Schedule-Time-Zone"] = @timezone if @timezone
79
- headers
69
+ # The +schedule:+ option of nats-pure's JetStream#publish that makes the template a schedule.
70
+ # @-shortcuts (+@daily+, +@every 5m+) travel as the cron expression, which the server understands.
71
+ def schedule
72
+ { cron: @expression, target: target_subject, time_zone: @timezone }.compact
73
+ end
74
+
75
+ # Headers stored on the schedule template. NATS copies them onto every message it fires,
76
+ # which is how the scheduler learns where the job belongs.
77
+ def headers
78
+ { "X-Stream" => @stream, "X-Subject" => dispatch_subject }
80
79
  end
81
80
 
82
81
  def job_name
@@ -38,7 +38,7 @@ module Cosmo
38
38
 
39
39
  e = Entry.new(class_name: class_name, stream: stream, expression: schedule,
40
40
  args: args, timezone: timezone, name: name)
41
- client.publish(e.schedule_subject, e.job_payload, stream: STREAM, header: e.schedule_headers)
41
+ client.publish(e.schedule_subject, e.job_payload, stream: STREAM, header: e.headers, schedule: e.schedule)
42
42
  build_from_nats(e.schedule_subject)
43
43
  end
44
44
 
@@ -94,8 +94,8 @@ module Cosmo
94
94
  {
95
95
  class: body[:class],
96
96
  stream: headers["X-Stream"],
97
- schedule: headers["Nats-Schedule"],
98
- timezone: headers["Nats-Schedule-Time-Zone"],
97
+ schedule: headers[NATS::JetStream::Header::SCHEDULE],
98
+ timezone: headers[NATS::JetStream::Header::SCHEDULE_TIME_ZONE],
99
99
  args: body[:args] || [],
100
100
  name: name_from_subject(subject),
101
101
  schedule_subject: subject,
data/lib/cosmo/api/job.rb CHANGED
@@ -62,7 +62,7 @@ module Cosmo
62
62
  end
63
63
 
64
64
  def timestamp
65
- headers&.dig("Nats-Time-Stamp")
65
+ headers&.dig(NATS::JetStream::Header::TIME_STAMP)
66
66
  end
67
67
 
68
68
  # @return [Time, nil] when the message was stored in its stream, parsed from +Nats-Time-Stamp+
data/lib/cosmo/api/kv.rb CHANGED
@@ -98,14 +98,14 @@ module Cosmo
98
98
  results
99
99
  end
100
100
 
101
- # CAS = Compare-And-Swap: publish +value+ with a per-message Nats-TTL,
101
+ # CAS = Compare-And-Swap: publish +value+ with a per-message TTL of +ttl+ whole seconds (at least 1),
102
102
  # but only if the subject's current last sequence matches +last_seq+
103
103
  # (sent as the Nats-Expected-Last-Subject-Sequence header). Raises
104
104
  # NATS::KeyValue::KeyWrongLastSequenceError if it doesn't match --
105
105
  # e.g. last_seq: 0 means "only publish if nothing exists here yet".
106
106
  def publish_cas(key, value, ttl, last_seq:)
107
- headers = { "Nats-Expected-Last-Subject-Sequence" => last_seq.to_s, "Nats-TTL" => "#{ttl.to_i}s" }
108
- client.js.publish("$KV.#{@name}.#{key}", value, header: headers)
107
+ headers = { NATS::JetStream::Header::EXPECTED_LAST_SUBJECT_SEQUENCE => last_seq.to_s }
108
+ client.js.publish("$KV.#{@name}.#{key}", value, header: headers, ttl: [ttl.to_i, 1].max)
109
109
  rescue NATS::JetStream::Error::APIError => e
110
110
  raise NATS::KeyValue::KeyWrongLastSequenceError, e.description if e.err_code == 10_071
111
111
 
@@ -43,6 +43,11 @@ module Cosmo
43
43
  super.map { _1.merge(data: Utils::Json.parse(_1[:data])) }
44
44
  end
45
45
 
46
+ # @return [Hash{String => Integer}] the number of jobs running per stream, for the streams running any
47
+ def by_stream
48
+ all.group_by { _1[:stream] }.transform_values(&:size)
49
+ end
50
+
46
51
  private
47
52
 
48
53
  def heartbeat_loop
@@ -67,12 +67,12 @@ module Cosmo
67
67
  totals.map { |job, sums| row(sums).merge(job: job) }.sort_by { -_1[:count] }
68
68
  end
69
69
 
70
- # @param job_class [String]
70
+ # @param job_class [String, nil] nil sums every job class
71
71
  # @param days [Integer]
72
72
  # @return [Array<Hash>] one +{ date:, count:, failed:, exec_ms:, wait_ms: }+ per day, oldest first
73
- def daily(job_class, days: 30)
73
+ def daily(job_class = nil, days: 30) # rubocop:disable Metrics/AbcSize
74
74
  sums = Hash.new { |hash, day| hash[day] = Hash.new(0) }
75
- read(days).each { |(day, job, field), value| sums[day][field] += value if job == job_class }
75
+ read(days).each { |(day, job, field), value| sums[day][field] += value if job_class.nil? || job == job_class }
76
76
  dates(days).map { |day| row(sums[day]).merge(date: day) }
77
77
  end
78
78
 
@@ -41,6 +41,21 @@ module Cosmo
41
41
  def dead
42
42
  Config.dead.enabled ? Stream.new("dead").size : 0
43
43
  end
44
+
45
+ # @return [Array<Hash>] a {.queue} row per job stream
46
+ def queues
47
+ busy = Busy.instance.by_stream
48
+ Stream.jobs.map { queue(_1, busy) }
49
+ end
50
+
51
+ # @param stream [Stream] a job stream
52
+ # @param busy [Hash{String => Integer}] running jobs per stream, as {Busy#by_stream} counts them
53
+ # @return [Hash] the stream's +:name+, waiting messages as +:size+, running jobs as +:busy+, redeliveries as
54
+ # +:retries+, and whether it is +:paused+
55
+ def queue(stream, busy = Busy.instance.by_stream)
56
+ { name: stream.name, size: stream.size, busy: busy.fetch(stream.name, 0), retries: stream.retries,
57
+ paused: stream.paused? }
58
+ end
44
59
  end
45
60
  end
46
61
  end
@@ -44,9 +44,14 @@ module Cosmo
44
44
  alias size total
45
45
 
46
46
  def retries
47
- client.list_consumers(name).sum { _1["num_redelivered"].to_i }
47
+ consumers.sum { _1["num_redelivered"].to_i }
48
+ end
49
+
50
+ # @return [Array<Hash>] the stream's consumers as JetStream reports them, with their config and delivery state
51
+ def consumers
52
+ client.list_consumers(name)
48
53
  rescue NATS::Error
49
- 0
54
+ []
50
55
  end
51
56
 
52
57
  def each
data/lib/cosmo/client.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "socket"
4
4
  require "nats/client"
5
- require "cosmo/utils/overrides"
6
5
 
7
6
  module Cosmo
8
7
  class Client
@@ -10,6 +9,9 @@ module Cosmo
10
9
  JS_TIMEOUT = 5
11
10
  # Seconds to wait for the TCP connect+handshake. Override with COSMO_CONNECT_TIMEOUT.
12
11
  CONNECT_TIMEOUT = 2
12
+ # Reconnect attempts before the connection closes for good; -1 never gives up, so a process outlives a NATS
13
+ # outage. nats-pure lets NATS_MAX_RECONNECT_ATTEMPTS override it.
14
+ MAX_RECONNECT_ATTEMPTS = -1
13
15
 
14
16
  def self.instance
15
17
  @instance ||= Client.new
@@ -41,7 +43,8 @@ module Cosmo
41
43
  def initialize(nats_url: ENV.fetch("NATS_URL", "nats://localhost:4222"), name: self.class.default_name)
42
44
  @name = name
43
45
  Logger.debug "Connecting to NATS server at #{nats_url} as #{@name}..."
44
- @nc = NATS.connect(nats_url, name: @name, connect_timeout: self.class.connect_timeout)
46
+ @nc = NATS.connect(nats_url, name: @name, connect_timeout: self.class.connect_timeout,
47
+ max_reconnect_attempts: MAX_RECONNECT_ATTEMPTS)
45
48
  Logger.debug "Connection established"
46
49
  @js = @nc.jetstream(timeout: self.class.js_timeout)
47
50
  end
@@ -157,7 +160,7 @@ module Cosmo
157
160
  messages = {}
158
161
  loop do
159
162
  msg = sub.next_msg(timeout: self.class.js_timeout)
160
- subject = msg.header&.dig("Nats-Subject")
163
+ subject = msg.header&.dig(NATS::JetStream::Header::SUBJECT)
161
164
  break unless subject
162
165
 
163
166
  messages[subject] = msg.data
@@ -195,8 +198,9 @@ module Cosmo
195
198
 
196
199
  private
197
200
 
198
- # NOTE: nats-pure's #pull_subscribe has no path to a true ephemeral pull consumer.
199
- # Its rescue branch unconditionally sets `config[:durable_name] = durable` (jetstream.rb).
201
+ # NOTE: nats-pure's #pull_subscribe can't create a true ephemeral pull consumer: its rescue branch
202
+ # unconditionally sets `config[:durable_name] = durable` (jetstream.rb). So the consumer is created
203
+ # here, and #pull_subscribe only binds to it by name.
200
204
  def ephemeral_subscribe(subject, config) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
201
205
  config = config.dup
202
206
  stream = config.delete(:stream) or raise ArgumentError, "stream required for ephemeral consumers"
@@ -211,14 +215,7 @@ module Cosmo
211
215
  end
212
216
 
213
217
  info = js.add_consumer(stream, consumer_config)
214
-
215
- sub = nc.subscribe(nc.new_inbox)
216
- sub.extend(NATS::JetStream.const_get(:PullSubscription))
217
- sub.jsi = NATS::JetStream.const_get(:JS)::Sub.new(
218
- js: js, stream: stream, consumer: info.name,
219
- nms: "#{js.prefix}.CONSUMER.MSG.NEXT.#{stream}.#{info.name}"
220
- )
221
- sub
218
+ js.pull_subscribe(subject, nil, stream: stream, name: info.name)
222
219
  end
223
220
 
224
221
  # NOTE: KV manager in nats-pure hardcodes the fields it copies into StreamConfig,
@@ -44,7 +44,7 @@ module Cosmo
44
44
 
45
45
  # @return [Hash{String => String, Integer}] dedup id, plus where to dispatch a delayed job once it is due
46
46
  def headers
47
- headers = { "Nats-Msg-Id" => jid }
47
+ headers = { NATS::JetStream::Header::MSG_ID => jid }
48
48
  return headers unless @at
49
49
 
50
50
  target = stream(target: true)
@@ -8,8 +8,8 @@ module Cosmo
8
8
  # Headers the scheduler consumes rather than forwards. The +X-+ ones address the dispatch
9
9
  # itself, and NATS sets +Nats-Scheduler+/+Nats-Schedule-Next+ on a message it fires and
10
10
  # rejects a publish that carries them back.
11
- DISPATCH_HEADERS = %w[X-Stream X-Subject X-Execute-At Nats-Expected-Stream
12
- Nats-Scheduler Nats-Schedule-Next].freeze
11
+ DISPATCH_HEADERS = ["X-Stream", "X-Subject", "X-Execute-At", NATS::JetStream::Header::EXPECTED_STREAM,
12
+ NATS::JetStream::Header::SCHEDULER, NATS::JetStream::Header::SCHEDULE_NEXT].freeze
13
13
 
14
14
  # @raise [UnknownJobStreamError] when --streams/--stream or COSMO_JOBS_STREAMS names an unconfigured stream
15
15
  def self.validate_options!(options)
@@ -71,8 +71,7 @@ module Cosmo
71
71
  messages&.each do |message|
72
72
  headers = message.header.except(*DISPATCH_HEADERS)
73
73
  stream, subject, execute_at = message.header.values_at("X-Stream", "X-Subject", "X-Execute-At")
74
- headers["Nats-Expected-Stream"] = stream
75
- scheduler = message.header["Nats-Scheduler"]
74
+ scheduler = message.header[NATS::JetStream::Header::SCHEDULER]
76
75
  if scheduler
77
76
  headers["X-Scheduled-By"] = scheduler
78
77
  headers["X-Enqueued-At"] = message.metadata.timestamp.to_f.to_s
@@ -80,7 +79,7 @@ module Cosmo
80
79
  execute_at = execute_at.to_i
81
80
 
82
81
  if now >= execute_at
83
- client.publish(subject, message.data, header: headers)
82
+ client.publish(subject, message.data, header: headers, stream:)
84
83
  message.ack
85
84
  else
86
85
  message.nak(delay: Config.to_ns(execute_at - now))
@@ -262,7 +261,7 @@ module Cosmo
262
261
  def scheduled_by(message)
263
262
  header = message.header or return
264
263
 
265
- header["X-Scheduled-By"] || header["Nats-Scheduler"]
264
+ header["X-Scheduled-By"] || header[NATS::JetStream::Header::SCHEDULER]
266
265
  end
267
266
 
268
267
  # A cron message is enqueued when NATS fires it, not when the scheduler dispatches it on, so
data/lib/cosmo/utils.rb CHANGED
@@ -4,7 +4,6 @@ require "cosmo/utils/hash"
4
4
  require "cosmo/utils/json"
5
5
  require "cosmo/utils/string"
6
6
  require "cosmo/utils/signal"
7
- require "cosmo/utils/warnings"
8
7
  require "cosmo/utils/duration"
9
8
  require "cosmo/utils/stopwatch"
10
9
  require "cosmo/utils/thread_pool"
data/lib/cosmo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cosmo
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.0"
5
5
  end