nats-pure 2.3.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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