cosmonats 0.1.3 → 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 +4 -4
- data/README.md +8 -7
- data/lib/cosmo/api/busy.rb +66 -0
- data/lib/cosmo/api/counter.rb +70 -0
- data/lib/cosmo/api/job.rb +46 -0
- data/lib/cosmo/api/kv.rb +63 -0
- data/lib/cosmo/api/stats.rb +44 -0
- data/lib/cosmo/api/stream.rb +110 -0
- data/lib/cosmo/api.rb +11 -0
- data/lib/cosmo/cli.rb +6 -4
- data/lib/cosmo/client.rb +35 -2
- data/lib/cosmo/config.rb +8 -6
- data/lib/cosmo/defaults.yml +31 -30
- data/lib/cosmo/job/processor.rb +58 -19
- data/lib/cosmo/job.rb +1 -1
- data/lib/cosmo/logger.rb +4 -0
- data/lib/cosmo/processor.rb +7 -1
- data/lib/cosmo/stream/data.rb +1 -0
- data/lib/cosmo/stream/processor.rb +18 -3
- data/lib/cosmo/stream.rb +2 -2
- data/lib/cosmo/utils/overrides.rb +15 -0
- data/lib/cosmo/utils/warnings.rb +17 -0
- data/lib/cosmo/utils.rb +14 -0
- data/lib/cosmo/version.rb +1 -1
- data/lib/cosmo/web/assets/app.css +431 -0
- data/lib/cosmo/web/assets/htmx.2.0.8.min.js.gz +0 -0
- data/lib/cosmo/web/context.rb +28 -0
- data/lib/cosmo/web/controllers/actions.rb +16 -0
- data/lib/cosmo/web/controllers/application.rb +43 -0
- data/lib/cosmo/web/controllers/jobs.rb +97 -0
- data/lib/cosmo/web/controllers/streams.rb +44 -0
- data/lib/cosmo/web/helpers/application.rb +76 -0
- data/lib/cosmo/web/renderer.rb +58 -0
- data/lib/cosmo/web/views/actions/index.erb +7 -0
- data/lib/cosmo/web/views/jobs/_busy.erb +50 -0
- data/lib/cosmo/web/views/jobs/_dead.erb +65 -0
- data/lib/cosmo/web/views/jobs/_enqueued.erb +60 -0
- data/lib/cosmo/web/views/jobs/_scheduled.erb +49 -0
- data/lib/cosmo/web/views/jobs/_stats.erb +69 -0
- data/lib/cosmo/web/views/jobs/busy.erb +16 -0
- data/lib/cosmo/web/views/jobs/dead.erb +17 -0
- data/lib/cosmo/web/views/jobs/enqueued.erb +16 -0
- data/lib/cosmo/web/views/jobs/index.erb +12 -0
- data/lib/cosmo/web/views/jobs/scheduled.erb +17 -0
- data/lib/cosmo/web/views/layout.erb +33 -0
- data/lib/cosmo/web/views/streams/_info.erb +89 -0
- data/lib/cosmo/web/views/streams/_table.erb +42 -0
- data/lib/cosmo/web/views/streams/index.erb +11 -0
- data/lib/cosmo/web/views/streams/info.erb +11 -0
- data/lib/cosmo/web.rb +66 -0
- data/lib/cosmo.rb +2 -7
- data/sig/cosmo/api/busy.rbs +35 -0
- data/sig/cosmo/api/counter.rbs +34 -0
- data/sig/cosmo/api/job.rbs +31 -0
- data/sig/cosmo/api/kv.rbs +30 -0
- data/sig/cosmo/api/stats.rbs +21 -0
- data/sig/cosmo/api/stream.rbs +44 -0
- data/sig/cosmo/client.rbs +13 -3
- data/sig/cosmo/processor.rbs +1 -1
- data/sig/cosmo/stream/data.rbs +1 -1
- data/sig/cosmo/stream/processor.rbs +2 -0
- data/sig/cosmo/stream.rbs +1 -0
- metadata +59 -3
- /data/sig/cosmo/{message.rbs → stream/message.rbs} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b63356c69b61ea32b4791b519830003e4f42330d39947a332cfbfdb20ed91c7d
|
|
4
|
+
data.tar.gz: 6cc0401b58b06038dcc4dc7e4bea9ff695662fa1b251b89fd681a66e893387c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c7ef63abf649154cb9a5e5662e29297cd4b171810f819b16401f08651a5ee3a8c3111421ed3873ea4c12477d05cd6329e18f67feda91edadf7605eadf2a4b91
|
|
7
|
+
data.tar.gz: d1bb560e31a2c0fb6c321d52599a543a372f184e136f29778a44451648eaa7beed1405d83d1fe996f87b8ee029432d8f395c3fad348a7eaacb73de958ad31573
|
data/README.md
CHANGED
|
@@ -248,18 +248,19 @@ consumers:
|
|
|
248
248
|
max_deliver: 3
|
|
249
249
|
subjects: ["events.>"]
|
|
250
250
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
251
|
+
setup:
|
|
252
|
+
streams:
|
|
253
|
+
my_stream:
|
|
254
|
+
storage: file # or memory
|
|
255
|
+
retention: workqueue # or limits
|
|
256
|
+
max_age: 86400 # 1d in seconds
|
|
257
|
+
subjects: ["events.>"]
|
|
257
258
|
```
|
|
258
259
|
|
|
259
260
|
**Programmatic:**
|
|
260
261
|
```ruby
|
|
261
262
|
Cosmo::Config.set(:concurrency, 20)
|
|
262
|
-
Cosmo::Config.set(:streams, :custom, { storage: 'file', subjects: ['custom.>'] })
|
|
263
|
+
Cosmo::Config.set(:setup, :streams, :custom, { storage: 'file', subjects: ['custom.>'] })
|
|
263
264
|
```
|
|
264
265
|
|
|
265
266
|
**Environment variables:**
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
module Cosmo
|
|
6
|
+
module API
|
|
7
|
+
class Busy
|
|
8
|
+
TTL = 70
|
|
9
|
+
HEARTBEAT = 30
|
|
10
|
+
BUCKET = "cosmostats"
|
|
11
|
+
|
|
12
|
+
def self.instance
|
|
13
|
+
@instance ||= new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
@messages = {}
|
|
18
|
+
@kv = KV.new(BUCKET, { ttl: TTL })
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def with(message)
|
|
22
|
+
add(message)
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
delete(message)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add(message)
|
|
29
|
+
@thread ||= Thread.new { heartbeat_loop }
|
|
30
|
+
seq = message.metadata.sequence.stream
|
|
31
|
+
value = Utils::Json.dump({ data: message.data, stream: message.metadata.stream, worker: worker_id, started_at: Time.now.to_i })
|
|
32
|
+
@messages[seq] = value
|
|
33
|
+
@kv.set(seq, value)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def delete(message)
|
|
37
|
+
seq = message.metadata.sequence.stream
|
|
38
|
+
@messages.delete(seq)
|
|
39
|
+
@kv.purge(seq)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def list(limit: 25)
|
|
43
|
+
@kv.keys(limit:).filter_map { Utils::Json.parse(@kv.get(_1)) }.map { _1.merge(data: Utils::Json.parse(_1[:data])) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def size
|
|
47
|
+
@kv.size
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def heartbeat_loop
|
|
53
|
+
loop do
|
|
54
|
+
sleep(HEARTBEAT)
|
|
55
|
+
@messages.dup.each { |seq, value| @kv.set(seq, value) rescue StandardError }
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
Logger.debug "Busy heartbeat error: #{e.class} #{e.message}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def worker_id
|
|
62
|
+
@worker_id ||= "#{Socket.gethostname}-#{Process.pid}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cosmo
|
|
4
|
+
module API
|
|
5
|
+
class Counter
|
|
6
|
+
STREAM_NAME = "cosmostats"
|
|
7
|
+
|
|
8
|
+
def self.instance
|
|
9
|
+
@instance ||= new("jobs")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(namespace)
|
|
13
|
+
@namespace = namespace
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def with
|
|
17
|
+
result = yield
|
|
18
|
+
increment(:processed) if result == true
|
|
19
|
+
increment(:failed) if result == false
|
|
20
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
21
|
+
increment(:failed)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def increment(key, by: 1)
|
|
25
|
+
publish(key, "+#{by}")
|
|
26
|
+
end
|
|
27
|
+
alias incr increment
|
|
28
|
+
|
|
29
|
+
def decrement(key, by: 1)
|
|
30
|
+
publish(key, "-#{by}")
|
|
31
|
+
end
|
|
32
|
+
alias decr decrement
|
|
33
|
+
|
|
34
|
+
def reset(key)
|
|
35
|
+
client.purge(STREAM_NAME, subject(key))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def get(key)
|
|
39
|
+
raw = client.get_message(STREAM_NAME, direct: true, subject: subject(key))
|
|
40
|
+
Utils::Json.parse(raw.data, default: { "val" => 0 })[:val].to_i
|
|
41
|
+
rescue NATS::JetStream::Error::NotFound, NATS::JetStream::Error::ServiceUnavailable
|
|
42
|
+
0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def publish(key, value)
|
|
48
|
+
rescued = nil
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
client.publish(subject(key), "", header: { "Nats-Incr" => value }).val.to_i
|
|
52
|
+
rescue NATS::JetStream::Error::NoStreamResponse
|
|
53
|
+
raise if rescued
|
|
54
|
+
|
|
55
|
+
rescued = true
|
|
56
|
+
client.create_stream(STREAM_NAME, subjects: ["#{STREAM_NAME}.>"], allow_msg_counter: true, allow_direct: true, description: "Cosmo statistics")
|
|
57
|
+
retry
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def subject(key)
|
|
62
|
+
"#{STREAM_NAME}.#{@namespace}.#{key}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def client
|
|
66
|
+
@client ||= Client.instance
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cosmo
|
|
4
|
+
module API
|
|
5
|
+
class Job
|
|
6
|
+
attr_reader :message, :stream
|
|
7
|
+
|
|
8
|
+
def initialize(stream, message)
|
|
9
|
+
@stream = stream
|
|
10
|
+
@message = message
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def data
|
|
14
|
+
@data ||= Utils::Json.parse(@message.data)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def seq
|
|
18
|
+
@message.seq
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def headers
|
|
22
|
+
@message.headers
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def execute_at
|
|
26
|
+
headers&.dig("X-Execute-At")&.to_i
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def x_stream
|
|
30
|
+
headers&.dig("X-Stream")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def x_subject
|
|
34
|
+
headers&.dig("X-Subject")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def subject
|
|
38
|
+
@message.subject
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def timestamp
|
|
42
|
+
headers&.dig("Nats-Time-Stamp")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/cosmo/api/kv.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cosmo
|
|
4
|
+
module API
|
|
5
|
+
class KV
|
|
6
|
+
def initialize(name, options = nil)
|
|
7
|
+
@name = name
|
|
8
|
+
@options = Hash(options)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def set(key, value)
|
|
12
|
+
kv.put(key, value.to_s)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get(key)
|
|
16
|
+
kv.get(key).value
|
|
17
|
+
rescue NATS::KeyValue::KeyNotFoundError
|
|
18
|
+
# nop
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def delete(key)
|
|
22
|
+
kv.delete(key)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def keys(subject = nil, limit: 25)
|
|
26
|
+
results = []
|
|
27
|
+
params = { ignore_deletes: true, meta_only: true }
|
|
28
|
+
watcher = kv.watch(subject || ">", params)
|
|
29
|
+
|
|
30
|
+
watcher.each do |entry|
|
|
31
|
+
break unless entry
|
|
32
|
+
|
|
33
|
+
results << entry.key
|
|
34
|
+
break if results.size >= limit
|
|
35
|
+
end
|
|
36
|
+
watcher.stop
|
|
37
|
+
|
|
38
|
+
results
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def purge(key)
|
|
42
|
+
kv.purge(key)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def clean
|
|
46
|
+
Client.instance.purge("KV_#{@name}", ">")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def count
|
|
50
|
+
keys.size
|
|
51
|
+
rescue NATS::KeyValue::NoKeysFoundError
|
|
52
|
+
0
|
|
53
|
+
end
|
|
54
|
+
alias size count
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def kv
|
|
59
|
+
@kv ||= Client.instance.kv(@name, **@options)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cosmo/api/counter"
|
|
4
|
+
require "cosmo/api/busy"
|
|
5
|
+
|
|
6
|
+
module Cosmo
|
|
7
|
+
module API
|
|
8
|
+
module Stats
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def summary
|
|
12
|
+
{ processed:, failed:, busy:, enqueued:, retries:, scheduled:, dead: }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def processed
|
|
16
|
+
Counter.instance.get(:processed)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def failed
|
|
20
|
+
Counter.instance.get(:failed)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def busy
|
|
24
|
+
Busy.instance.size
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def enqueued
|
|
28
|
+
Stream.jobs.sum(&:size)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def retries
|
|
32
|
+
Stream.jobs.sum(&:retries)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def scheduled
|
|
36
|
+
Stream.new("scheduled").size
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def dead
|
|
40
|
+
Stream.new("dead").size
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cosmo/api/job"
|
|
4
|
+
|
|
5
|
+
module Cosmo
|
|
6
|
+
module API
|
|
7
|
+
class Stream
|
|
8
|
+
LIMIT = 20
|
|
9
|
+
|
|
10
|
+
include Enumerable
|
|
11
|
+
|
|
12
|
+
def self.all
|
|
13
|
+
client.list_streams.filter_map { new(_1) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.jobs
|
|
17
|
+
names = Config[:setup][:jobs].keys - %i[scheduled dead]
|
|
18
|
+
all.select { names.include?(_1.name.to_sym) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.client
|
|
22
|
+
@client ||= Client.instance
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :name
|
|
26
|
+
|
|
27
|
+
def initialize(name)
|
|
28
|
+
@name = name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def info
|
|
32
|
+
info = client.stream_info(name)
|
|
33
|
+
{ state: info.state, config: info.config }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def total
|
|
37
|
+
info[:state].messages.to_i
|
|
38
|
+
rescue StandardError
|
|
39
|
+
0
|
|
40
|
+
end
|
|
41
|
+
alias size total
|
|
42
|
+
|
|
43
|
+
def retries
|
|
44
|
+
client.list_consumers(name).sum { it["num_redelivered"].to_i }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def each
|
|
48
|
+
return if total.zero?
|
|
49
|
+
|
|
50
|
+
state = info[:state]
|
|
51
|
+
current = @offset || state.first_seq.to_i
|
|
52
|
+
last = state.last_seq.to_i
|
|
53
|
+
|
|
54
|
+
loop do
|
|
55
|
+
break if current > last
|
|
56
|
+
|
|
57
|
+
job = message(current)
|
|
58
|
+
break unless job
|
|
59
|
+
|
|
60
|
+
yield job
|
|
61
|
+
current += 1
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def offset(value)
|
|
66
|
+
@offset = value.to_i
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def messages(page: nil, limit: nil)
|
|
71
|
+
jobs = []
|
|
72
|
+
limit = (limit || LIMIT).to_i
|
|
73
|
+
state = info[:state]
|
|
74
|
+
start = state.first_seq.to_i
|
|
75
|
+
start += (page.to_i - 1) * limit if page
|
|
76
|
+
|
|
77
|
+
offset(start).each do |message|
|
|
78
|
+
jobs << message
|
|
79
|
+
break if jobs.size >= limit
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
jobs
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def message(seq)
|
|
86
|
+
Job.new(name, client.get_message(name, seq: seq, direct: true))
|
|
87
|
+
rescue NATS::JetStream::Error::NotFound
|
|
88
|
+
# nop, acked/nacked
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def retry(seq)
|
|
92
|
+
job = message(seq)
|
|
93
|
+
return unless job
|
|
94
|
+
|
|
95
|
+
client.publish(job.x_subject, job.message.data)
|
|
96
|
+
delete(seq)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def delete(seq)
|
|
100
|
+
client.delete_message(name, seq)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def client
|
|
106
|
+
self.class.client
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/cosmo/api.rb
ADDED
data/lib/cosmo/cli.rb
CHANGED
|
@@ -102,10 +102,12 @@ module Cosmo
|
|
|
102
102
|
load_config(flags)
|
|
103
103
|
boot_application
|
|
104
104
|
|
|
105
|
-
Config[:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
Config[:setup]&.each_value do |configs|
|
|
106
|
+
configs.each do |name, config|
|
|
107
|
+
Client.instance.stream_info(name)
|
|
108
|
+
rescue NATS::JetStream::Error::NotFound
|
|
109
|
+
Client.instance.create_stream(name, config)
|
|
110
|
+
end
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
puts "Cosmo streams were created/updated"
|
data/lib/cosmo/client.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "nats/client"
|
|
4
|
+
require "cosmo/utils/overrides"
|
|
4
5
|
|
|
5
6
|
module Cosmo
|
|
6
7
|
class Client
|
|
@@ -11,7 +12,9 @@ module Cosmo
|
|
|
11
12
|
attr_reader :nc, :js
|
|
12
13
|
|
|
13
14
|
def initialize(nats_url: ENV.fetch("NATS_URL", "nats://localhost:4222"))
|
|
15
|
+
Logger.debug "Connecting to NATS server at #{nats_url}..."
|
|
14
16
|
@nc = NATS.connect(nats_url)
|
|
17
|
+
Logger.debug "Connection established"
|
|
15
18
|
@js = @nc.jetstream
|
|
16
19
|
end
|
|
17
20
|
|
|
@@ -43,8 +46,38 @@ module Cosmo
|
|
|
43
46
|
data["streams"].filter_map { _1.dig("config", "name") }
|
|
44
47
|
end
|
|
45
48
|
|
|
46
|
-
def
|
|
47
|
-
|
|
49
|
+
def list_consumers(stream_name)
|
|
50
|
+
response = nc.request("$JS.API.CONSUMER.LIST.#{stream_name}", "")
|
|
51
|
+
data = Utils::Json.parse(response.data, symbolize_names: false)
|
|
52
|
+
data["consumers"]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def consumer_info(stream_name, consumer_name)
|
|
56
|
+
js.consumer_info(stream_name, consumer_name)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_message(name, **options)
|
|
60
|
+
js.get_msg(name, **options)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def delete_message(name, seq)
|
|
64
|
+
response = nc.request("$JS.API.STREAM.MSG.DELETE.#{name}", JSON.dump({ seq: seq }))
|
|
65
|
+
Utils::Json.parse(response.data, symbolize_names: false)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def purge(stream_name, subject)
|
|
69
|
+
payload = subject ? Utils::Json.dump({ filter: subject }) : ""
|
|
70
|
+
response = @nc.request("$JS.API.STREAM.PURGE.#{stream_name}", payload)
|
|
71
|
+
result = Utils::Json.parse(response.data, default: {}, symbolize_names: false)
|
|
72
|
+
raise NATS::JetStream::Error, result.dig("error", "description") if result["error"]
|
|
73
|
+
|
|
74
|
+
result["purged"] # number of messages purged
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def kv(name, **options)
|
|
78
|
+
js.key_value(name)
|
|
79
|
+
rescue NATS::KeyValue::BucketNotFoundError
|
|
80
|
+
js.create_key_value({ bucket: name }.merge(options))
|
|
48
81
|
end
|
|
49
82
|
|
|
50
83
|
def close
|
data/lib/cosmo/config.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Cosmo
|
|
|
18
18
|
YAML.load_file(path, aliases: true).tap { normalize!(_1) }
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def self.normalize!(config) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
21
|
+
def self.normalize!(config) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
22
22
|
Utils::Hash.symbolize_keys!(config)
|
|
23
23
|
|
|
24
24
|
config[:consumers]&.each_key do |name|
|
|
@@ -30,11 +30,13 @@ module Cosmo
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
config[:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
config[:setup]&.each_key do |type|
|
|
34
|
+
config[:setup][type]&.each_key do |name|
|
|
35
|
+
c = config[:setup][type][name]
|
|
36
|
+
c[:max_age] = c[:max_age].to_i * NANO if c[:max_age]
|
|
37
|
+
c[:duplicate_window] = c[:duplicate_window].to_i * NANO if c[:duplicate_window]
|
|
38
|
+
c[:subjects] = c[:subjects].map { |s| format(s, name: name) } if c[:subjects]
|
|
39
|
+
end
|
|
38
40
|
end
|
|
39
41
|
end
|
|
40
42
|
|
data/lib/cosmo/defaults.yml
CHANGED
|
@@ -27,39 +27,40 @@ consumers:
|
|
|
27
27
|
max_ack_pending: 100
|
|
28
28
|
ack_wait: 10
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
30
|
+
setup:
|
|
31
|
+
jobs:
|
|
32
|
+
critical:
|
|
33
|
+
<<: &config
|
|
34
|
+
storage: file
|
|
35
|
+
retention: workqueue
|
|
36
|
+
duplicate_window: 120 # 2m
|
|
37
|
+
discard: old
|
|
38
|
+
allow_direct: true
|
|
39
|
+
subjects:
|
|
40
|
+
- jobs.%{name}.>
|
|
41
|
+
description: Very critical priority jobs
|
|
42
|
+
high:
|
|
43
|
+
<<: *config
|
|
44
|
+
description: Higher priority jobs
|
|
45
|
+
default:
|
|
46
|
+
<<: *config
|
|
47
|
+
description: Default priority jobs
|
|
48
|
+
low:
|
|
49
|
+
<<: *config
|
|
50
|
+
description: Lower priority jobs
|
|
51
|
+
scheduled:
|
|
52
|
+
<<: *config
|
|
53
|
+
description: Scheduled jobs
|
|
54
|
+
dead:
|
|
55
|
+
<<: *config
|
|
56
|
+
retention: limits
|
|
57
|
+
max_msgs: 10000
|
|
58
|
+
max_age: 604800 # 7d
|
|
59
|
+
description: Broken jobs (DLQ)
|
|
59
60
|
|
|
60
61
|
development:
|
|
61
62
|
verbose: false
|
|
62
|
-
concurrency:
|
|
63
|
+
concurrency: *concurrency
|
|
63
64
|
|
|
64
65
|
staging:
|
|
65
66
|
verbose: true
|