nats-pure 2.3.0 → 2.5.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +10 -3
  4. data/lib/nats/client.rb +7 -2
  5. data/lib/nats/io/client.rb +304 -282
  6. data/lib/nats/io/errors.rb +2 -0
  7. data/lib/nats/io/jetstream/api.rb +54 -47
  8. data/lib/nats/io/jetstream/errors.rb +30 -14
  9. data/lib/nats/io/jetstream/js/config.rb +9 -3
  10. data/lib/nats/io/jetstream/js/header.rb +15 -9
  11. data/lib/nats/io/jetstream/js/status.rb +11 -5
  12. data/lib/nats/io/jetstream/js/sub.rb +4 -2
  13. data/lib/nats/io/jetstream/js.rb +10 -8
  14. data/lib/nats/io/jetstream/manager.rb +104 -83
  15. data/lib/nats/io/jetstream/msg/ack.rb +15 -9
  16. data/lib/nats/io/jetstream/msg/ack_methods.rb +24 -22
  17. data/lib/nats/io/jetstream/msg/metadata.rb +9 -7
  18. data/lib/nats/io/jetstream/msg.rb +11 -4
  19. data/lib/nats/io/jetstream/pull_subscription.rb +21 -10
  20. data/lib/nats/io/jetstream/push_subscription.rb +3 -1
  21. data/lib/nats/io/jetstream.rb +125 -54
  22. data/lib/nats/io/kv/api.rb +7 -3
  23. data/lib/nats/io/kv/bucket_status.rb +7 -5
  24. data/lib/nats/io/kv/errors.rb +25 -2
  25. data/lib/nats/io/kv/manager.rb +19 -10
  26. data/lib/nats/io/kv.rb +359 -22
  27. data/lib/nats/io/msg.rb +19 -19
  28. data/lib/nats/io/parser.rb +23 -23
  29. data/lib/nats/io/rails.rb +2 -0
  30. data/lib/nats/io/subscription.rb +25 -22
  31. data/lib/nats/io/version.rb +4 -2
  32. data/lib/nats/io/websocket.rb +10 -8
  33. data/lib/nats/nuid.rb +33 -22
  34. data/lib/nats/service/callbacks.rb +22 -0
  35. data/lib/nats/service/endpoint.rb +155 -0
  36. data/lib/nats/service/errors.rb +44 -0
  37. data/lib/nats/service/group.rb +37 -0
  38. data/lib/nats/service/monitoring.rb +108 -0
  39. data/lib/nats/service/stats.rb +52 -0
  40. data/lib/nats/service/status.rb +66 -0
  41. data/lib/nats/service/validator.rb +31 -0
  42. data/lib/nats/service.rb +121 -0
  43. data/lib/nats/utils/list.rb +26 -0
  44. data/lib/nats-pure.rb +5 -0
  45. data/lib/nats.rb +10 -6
  46. metadata +176 -5
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright 2021 The NATS Authors
2
4
  # Licensed under the Apache License, Version 2.0 (the "License");
3
5
  # you may not use this file except in compliance with the License.
@@ -11,17 +13,14 @@
11
13
  # See the License for the specific language governing permissions and
12
14
  # limitations under the License.
13
15
  #
14
- require_relative 'msg'
15
- require_relative 'client'
16
- require_relative 'errors'
17
- require_relative 'kv'
18
- require_relative 'jetstream/api'
19
- require_relative 'jetstream/errors'
20
- require_relative 'jetstream/js'
21
- require_relative 'jetstream/manager'
22
- require_relative 'jetstream/msg'
23
- require_relative 'jetstream/pull_subscription'
24
- require_relative 'jetstream/push_subscription'
16
+ require_relative "kv"
17
+ require_relative "jetstream/api"
18
+ require_relative "jetstream/errors"
19
+ require_relative "jetstream/js"
20
+ require_relative "jetstream/manager"
21
+ require_relative "jetstream/msg"
22
+ require_relative "jetstream/pull_subscription"
23
+ require_relative "jetstream/push_subscription"
25
24
 
26
25
  module NATS
27
26
  # JetStream returns a context with a similar API as the NATS::Client
@@ -33,6 +32,8 @@ module NATS
33
32
  # js = nc.jetstream()
34
33
  #
35
34
  class JetStream
35
+ attr_reader :opts, :prefix, :nc
36
+
36
37
  # Create a new JetStream context for a NATS connection.
37
38
  #
38
39
  # @param conn [NATS::Client]
@@ -40,15 +41,15 @@ module NATS
40
41
  # @option params [String] :prefix JetStream API prefix to use for the requests.
41
42
  # @option params [String] :domain JetStream Domain to use for the requests.
42
43
  # @option params [Float] :timeout Default timeout to use for JS requests.
43
- def initialize(conn, params={})
44
+ def initialize(conn, params = {})
44
45
  @nc = conn
45
46
  @prefix = if params[:prefix]
46
- params[:prefix]
47
- elsif params[:domain]
48
- "$JS.#{params[:domain]}.API"
49
- else
50
- JS::DefaultAPIPrefix
51
- end
47
+ params[:prefix]
48
+ elsif params[:domain]
49
+ "$JS.#{params[:domain]}.API"
50
+ else
51
+ JS::DefaultAPIPrefix
52
+ end
52
53
  @opts = params
53
54
  @opts[:timeout] ||= 5 # seconds
54
55
  params[:prefix] = @prefix
@@ -80,7 +81,7 @@ module NATS
80
81
  # @option params [String] :stream Expected Stream to which the message is being published.
81
82
  # @raise [NATS::Timeout] When it takes too long to receive an ack response.
82
83
  # @return [PubAck] The pub ack response.
83
- def publish(subject, payload="", **params)
84
+ def publish(subject, payload = "", **params)
84
85
  params[:timeout] ||= @opts[:timeout]
85
86
  if params[:stream]
86
87
  params[:header] ||= {}
@@ -89,8 +90,8 @@ module NATS
89
90
 
90
91
  # Send message with headers.
91
92
  msg = NATS::Msg.new(subject: subject,
92
- data: payload,
93
- header: params[:header])
93
+ data: payload,
94
+ header: params[:header])
94
95
 
95
96
  begin
96
97
  resp = @nc.request_msg(msg, **params)
@@ -105,27 +106,57 @@ module NATS
105
106
 
106
107
  # subscribe binds or creates a push subscription to a JetStream pull consumer.
107
108
  #
108
- # @param subject [String] Subject from which the messages will be fetched.
109
+ # @param subject [String, Array] Subject(s) from which the messages will be fetched.
109
110
  # @param params [Hash] Options to customize the PushSubscription.
110
111
  # @option params [String] :stream Name of the Stream to which the consumer belongs.
111
112
  # @option params [String] :consumer Name of the Consumer to which the PushSubscription will be bound.
113
+ # @option params [String] :name Name of the Consumer to which the PushSubscription will be bound.
112
114
  # @option params [String] :durable Consumer durable name from where the messages will be fetched.
113
115
  # @option params [Hash] :config Configuration for the consumer.
114
116
  # @return [NATS::JetStream::PushSubscription]
115
- def subscribe(subject, params={}, &cb)
117
+ def subscribe(subject, params = {}, &cb)
116
118
  params[:consumer] ||= params[:durable]
117
- stream = params[:stream].nil? ? find_stream_name_by_subject(subject) : params[:stream]
119
+ params[:consumer] ||= params[:name]
120
+ multi_filter = if subject.is_a?(Array) && (subject.size == 1)
121
+ subject = subject.first
122
+ false
123
+ elsif subject.is_a?(Array) && (subject.size > 1)
124
+ true
125
+ end
126
+
127
+ stream = if params[:stream].nil?
128
+ if multi_filter
129
+ # Use the first subject to try to find the stream.
130
+ streams = subject.map do |s|
131
+ find_stream_name_by_subject(s)
132
+ rescue NATS::JetStream::Error::NotFound
133
+ raise NATS::JetStream::Error.new("nats: could not find stream matching filter subject '#{s}'")
134
+ end
135
+
136
+ # Ensure that the filter subjects are not ambiguous.
137
+ streams.uniq!
138
+ if streams.count > 1
139
+ raise NATS::JetStream::Error.new("nats: multiple streams matched filter subjects: #{streams}")
140
+ end
141
+
142
+ streams.first
143
+ else
144
+ find_stream_name_by_subject(subject)
145
+ end
146
+ else
147
+ params[:stream]
148
+ end
118
149
 
119
150
  queue = params[:queue]
120
151
  durable = params[:durable]
121
- flow_control = params[:flow_control]
152
+ params[:flow_control]
122
153
  manual_ack = params[:manual_ack]
123
154
  idle_heartbeat = params[:idle_heartbeat]
124
155
  flow_control = params[:flow_control]
125
156
  config = params[:config]
126
157
 
127
158
  if queue
128
- if durable and durable != queue
159
+ if durable && (durable != queue)
129
160
  raise NATS::JetStream::Error.new("nats: cannot create queue subscription '#{queue}' to consumer '#{durable}'")
130
161
  else
131
162
  durable = queue
@@ -136,7 +167,7 @@ module NATS
136
167
  consumer_found = false
137
168
  should_create = false
138
169
 
139
- if not durable
170
+ if !durable
140
171
  should_create = true
141
172
  else
142
173
  begin
@@ -151,18 +182,16 @@ module NATS
151
182
  end
152
183
 
153
184
  if consumer_found
154
- if not config.deliver_group
185
+ if !config.deliver_group
155
186
  if queue
156
187
  raise NATS::JetStream::Error.new("nats: cannot create a queue subscription for a consumer without a deliver group")
157
188
  elsif cinfo.push_bound
158
189
  raise NATS::JetStream::Error.new("nats: consumer is already bound to a subscription")
159
190
  end
160
- else
161
- if not queue
162
- raise NATS::JetStream::Error.new("nats: cannot create a subscription for a consumer with a deliver group #{config.deliver_group}")
163
- elsif queue != config.deliver_group
164
- raise NATS::JetStream::Error.new("nats: cannot create a queue subscription #{queue} for a consumer with a deliver group #{config.deliver_group}")
165
- end
191
+ elsif !queue
192
+ raise NATS::JetStream::Error.new("nats: cannot create a subscription for a consumer with a deliver group #{config.deliver_group}")
193
+ elsif queue != config.deliver_group
194
+ raise NATS::JetStream::Error.new("nats: cannot create a queue subscription #{queue} for a consumer with a deliver group #{config.deliver_group}")
166
195
  end
167
196
  elsif should_create
168
197
  # Auto-create consumer if none found.
@@ -175,21 +204,24 @@ module NATS
175
204
  raise NATS::JetStream::Error.new("nats: invalid ConsumerConfig")
176
205
  end
177
206
 
178
- config.durable_name = durable if not config.durable_name
179
- config.deliver_group = queue if not config.deliver_group
207
+ config.durable_name = durable if !config.durable_name
208
+ config.deliver_group = queue if !config.deliver_group
180
209
 
181
210
  # Create inbox for push consumer.
182
211
  deliver = @nc.new_inbox
183
212
  config.deliver_subject = deliver
184
213
 
185
214
  # Auto created consumers use the filter subject.
186
- config.filter_subject = subject
215
+ if multi_filter
216
+ config[:filter_subjects] ||= subject
217
+ else
218
+ config[:filter_subject] ||= subject
219
+ end
187
220
 
188
221
  # Heartbeats / FlowControl
189
222
  config.flow_control = flow_control
190
- if idle_heartbeat or config.idle_heartbeat
223
+ if idle_heartbeat || config.idle_heartbeat
191
224
  idle_heartbeat = config.idle_heartbeat if config.idle_heartbeat
192
- idle_heartbeat = idle_heartbeat * ::NATS::NANOSECONDS
193
225
  config.idle_heartbeat = idle_heartbeat
194
226
  end
195
227
 
@@ -199,11 +231,16 @@ module NATS
199
231
  end
200
232
 
201
233
  # Enable auto acking for async callbacks unless disabled.
202
- if cb and not manual_ack
234
+ # In case ack policy is none then we also do not require to ack.
235
+ if cb && !manual_ack && (config.ack_policy != "none")
203
236
  ocb = cb
204
237
  new_cb = proc do |msg|
205
238
  ocb.call(msg)
206
- msg.ack rescue JetStream::Error::MsgAlreadyAckd
239
+ begin
240
+ msg.ack
241
+ rescue
242
+ JetStream::Error::MsgAlreadyAckd
243
+ end
207
244
  end
208
245
  cb = new_cb
209
246
  end
@@ -212,42 +249,76 @@ module NATS
212
249
  sub.jsi = JS::Sub.new(
213
250
  js: self,
214
251
  stream: stream,
215
- consumer: consumer,
252
+ consumer: consumer
216
253
  )
217
254
  sub
218
255
  end
219
256
 
220
257
  # pull_subscribe binds or creates a subscription to a JetStream pull consumer.
221
258
  #
222
- # @param subject [String] Subject from which the messages will be fetched.
259
+ # @param subject [String, Array] Subject or subjects from which the messages will be fetched.
223
260
  # @param durable [String] Consumer durable name from where the messages will be fetched.
224
261
  # @param params [Hash] Options to customize the PullSubscription.
225
262
  # @option params [String] :stream Name of the Stream to which the consumer belongs.
226
263
  # @option params [String] :consumer Name of the Consumer to which the PullSubscription will be bound.
264
+ # @option params [String] :name Name of the Consumer to which the PullSubscription will be bound.
227
265
  # @option params [Hash] :config Configuration for the consumer.
228
266
  # @return [NATS::JetStream::PullSubscription]
229
- def pull_subscribe(subject, durable, params={})
230
- if durable.empty? && !params[:consumer]
267
+ def pull_subscribe(subject, durable, params = {})
268
+ if (!durable || durable.empty?) && !(params[:consumer] || params[:name])
231
269
  raise JetStream::Error::InvalidDurableName.new("nats: invalid durable name")
232
270
  end
271
+ multi_filter = if subject.is_a?(Array) && (subject.size == 1)
272
+ subject = subject.first
273
+ false
274
+ elsif subject.is_a?(Array) && (subject.size > 1)
275
+ true
276
+ end
277
+
233
278
  params[:consumer] ||= durable
234
- stream = params[:stream].nil? ? find_stream_name_by_subject(subject) : params[:stream]
279
+ params[:consumer] ||= params[:name]
280
+ stream = if params[:stream].nil?
281
+ if multi_filter
282
+ # Use the first subject to try to find the stream.
283
+ streams = subject.map do |s|
284
+ find_stream_name_by_subject(s)
285
+ rescue NATS::JetStream::Error::NotFound
286
+ raise NATS::JetStream::Error.new("nats: could not find stream matching filter subject '#{s}'")
287
+ end
288
+
289
+ # Ensure that the filter subjects are not ambiguous.
290
+ streams.uniq!
291
+ if streams.count > 1
292
+ raise NATS::JetStream::Error.new("nats: multiple streams matched filter subjects: #{streams}")
293
+ end
235
294
 
295
+ streams.first
296
+ else
297
+ find_stream_name_by_subject(subject)
298
+ end
299
+ else
300
+ params[:stream]
301
+ end
236
302
  begin
237
303
  consumer_info(stream, params[:consumer])
238
304
  rescue NATS::JetStream::Error::NotFound => e
239
305
  # If attempting to bind, then this is a hard error.
240
- raise e if params[:stream]
306
+ raise e if params[:stream] && !multi_filter
241
307
 
242
- config = if not params[:config]
243
- JetStream::API::ConsumerConfig.new
244
- elsif params[:config].is_a?(JetStream::API::ConsumerConfig)
245
- params[:config]
246
- else
247
- JetStream::API::ConsumerConfig.new(params[:config])
248
- end
308
+ config = if !(params[:config])
309
+ JetStream::API::ConsumerConfig.new
310
+ elsif params[:config].is_a?(JetStream::API::ConsumerConfig)
311
+ params[:config]
312
+ else
313
+ JetStream::API::ConsumerConfig.new(params[:config])
314
+ end
249
315
  config[:durable_name] = durable
250
316
  config[:ack_policy] ||= JS::Config::AckExplicit
317
+ if multi_filter
318
+ config[:filter_subjects] ||= subject
319
+ else
320
+ config[:filter_subject] ||= subject
321
+ end
251
322
  add_consumer(stream, config)
252
323
  end
253
324
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright 2021 The NATS Authors
2
4
  # Licensed under the Apache License, Version 2.0 (the "License");
3
5
  # you may not use this file except in compliance with the License.
@@ -27,11 +29,13 @@ module NATS
27
29
  :placement,
28
30
  :republish,
29
31
  :direct,
30
- keyword_init: true) do
31
- def initialize(opts={})
32
+ :validate_keys,
33
+ keyword_init: true
34
+ ) do
35
+ def initialize(opts = {})
32
36
  rem = opts.keys - members
33
37
  opts.delete_if { |k| rem.include?(k) }
34
- super(opts)
38
+ super
35
39
  end
36
40
  end
37
41
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright 2021 The NATS Authors
2
4
  # Licensed under the Apache License, Version 2.0 (the "License");
3
5
  # you may not use this file except in compliance with the License.
@@ -15,23 +17,23 @@
15
17
  module NATS
16
18
  class KeyValue
17
19
  class BucketStatus
18
- attr_reader :bucket
20
+ attr_reader :bucket, :stream_info
19
21
 
20
22
  def initialize(info, bucket)
21
- @nfo = info
23
+ @stream_info = info
22
24
  @bucket = bucket
23
25
  end
24
26
 
25
27
  def values
26
- @nfo.state.messages
28
+ @stream_info.state.messages
27
29
  end
28
30
 
29
31
  def history
30
- @nfo.config.max_msgs_per_subject
32
+ @stream_info.config.max_msgs_per_subject
31
33
  end
32
34
 
33
35
  def ttl
34
- @nfo.config.max_age / ::NATS::NANOSECONDS
36
+ @stream_info.config.max_age / ::NATS::NANOSECONDS
35
37
  end
36
38
  end
37
39
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright 2021 The NATS Authors
2
4
  # Licensed under the Apache License, Version 2.0 (the "License");
3
5
  # you may not use this file except in compliance with the License.
@@ -12,7 +14,7 @@
12
14
  # limitations under the License.
13
15
  #
14
16
 
15
- require_relative '../errors'
17
+ require_relative "../errors"
16
18
 
17
19
  module NATS
18
20
  class KeyValue
@@ -21,7 +23,7 @@ module NATS
21
23
  # When a key is not found.
22
24
  class KeyNotFoundError < Error
23
25
  attr_reader :entry, :op
24
- def initialize(params={})
26
+ def initialize(params = {})
25
27
  @entry = params[:entry]
26
28
  @op = params[:op]
27
29
  @message = params[:message]
@@ -52,9 +54,30 @@ module NATS
52
54
  def initialize(msg)
53
55
  @msg = msg
54
56
  end
57
+
55
58
  def to_s
56
59
  "nats: #{@msg}"
57
60
  end
58
61
  end
62
+
63
+ # When there are no keys.
64
+ class NoKeysFoundError < Error
65
+ def to_s
66
+ "nats: no keys found"
67
+ end
68
+ end
69
+
70
+ # When history is too large.
71
+ class KeyHistoryTooLargeError < Error
72
+ def to_s
73
+ "nats: history limited to a max of 64"
74
+ end
75
+ end
76
+
77
+ class InvalidKeyError < Error
78
+ def to_s
79
+ "nats: invalid key"
80
+ end
81
+ end
59
82
  end
60
83
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright 2021 The NATS Authors
2
4
  # Licensed under the Apache License, Version 2.0 (the "License");
3
5
  # you may not use this file except in compliance with the License.
@@ -15,7 +17,7 @@
15
17
  module NATS
16
18
  class KeyValue
17
19
  module Manager
18
- def key_value(bucket)
20
+ def key_value(bucket, params = {})
19
21
  stream = "KV_#{bucket}"
20
22
  begin
21
23
  si = stream_info(stream)
@@ -31,16 +33,18 @@ module NATS
31
33
  stream: stream,
32
34
  pre: "$KV.#{bucket}.",
33
35
  js: self,
34
- direct: si.config.allow_direct
36
+ direct: si.config.allow_direct,
37
+ validate_keys: params[:validate_keys]
35
38
  )
36
39
  end
37
40
 
38
41
  def create_key_value(config)
39
- config = if not config.is_a?(KeyValue::API::KeyValueConfig)
40
- KeyValue::API::KeyValueConfig.new(config)
41
- else
42
- config
43
- end
42
+ config = if !config.is_a?(KeyValue::API::KeyValueConfig)
43
+ config = {bucket: config} if config.is_a?(String)
44
+ KeyValue::API::KeyValueConfig.new(config)
45
+ else
46
+ config
47
+ end
44
48
  config.history ||= 1
45
49
  config.replicas ||= 1
46
50
  duplicate_window = 2 * 60 # 2 minutes
@@ -51,6 +55,10 @@ module NATS
51
55
  config.ttl = config.ttl * ::NATS::NANOSECONDS
52
56
  end
53
57
 
58
+ if config.history > 64
59
+ raise NATS::KeyValue::KeyHistoryTooLargeError
60
+ end
61
+
54
62
  stream = JetStream::API::StreamConfig.new(
55
63
  name: "KV_#{config.bucket}",
56
64
  description: config.description,
@@ -68,8 +76,8 @@ module NATS
68
76
  max_msgs_per_subject: config.history,
69
77
  num_replicas: config.replicas,
70
78
  storage: config.storage,
71
- republish: config.republish,
72
- )
79
+ republish: config.republish
80
+ )
73
81
 
74
82
  si = add_stream(stream)
75
83
  KeyValue.new(
@@ -77,7 +85,8 @@ module NATS
77
85
  stream: stream.name,
78
86
  pre: "$KV.#{config.bucket}.",
79
87
  js: self,
80
- direct: si.config.allow_direct
88
+ direct: si.config.allow_direct,
89
+ validate_keys: config.validate_keys
81
90
  )
82
91
  end
83
92