nats-pure 0.7.2 → 2.4.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +201 -0
  3. data/README.md +251 -0
  4. data/lib/nats/client.rb +16 -0
  5. data/lib/nats/io/client.rb +1559 -1277
  6. data/lib/nats/io/errors.rb +74 -0
  7. data/lib/nats/io/jetstream/api.rb +309 -0
  8. data/lib/nats/io/jetstream/errors.rb +104 -0
  9. data/lib/nats/io/jetstream/js/config.rb +26 -0
  10. data/lib/nats/io/jetstream/js/header.rb +31 -0
  11. data/lib/nats/io/jetstream/js/status.rb +27 -0
  12. data/lib/nats/io/jetstream/js/sub.rb +30 -0
  13. data/lib/nats/io/jetstream/js.rb +93 -0
  14. data/lib/nats/io/jetstream/manager.rb +303 -0
  15. data/lib/nats/io/jetstream/msg/ack.rb +57 -0
  16. data/lib/nats/io/jetstream/msg/ack_methods.rb +111 -0
  17. data/lib/nats/io/jetstream/msg/metadata.rb +37 -0
  18. data/lib/nats/io/jetstream/msg.rb +26 -0
  19. data/lib/nats/io/jetstream/pull_subscription.rb +260 -0
  20. data/lib/nats/io/jetstream/push_subscription.rb +42 -0
  21. data/lib/nats/io/jetstream.rb +344 -0
  22. data/lib/nats/io/kv/api.rb +39 -0
  23. data/lib/nats/io/kv/bucket_status.rb +38 -0
  24. data/lib/nats/io/kv/errors.rb +60 -0
  25. data/lib/nats/io/kv/manager.rb +89 -0
  26. data/lib/nats/io/kv.rb +178 -0
  27. data/lib/nats/io/msg.rb +58 -0
  28. data/lib/nats/io/parser.rb +7 -7
  29. data/lib/nats/io/rails.rb +29 -0
  30. data/lib/nats/io/subscription.rb +157 -0
  31. data/lib/nats/io/version.rb +8 -4
  32. data/lib/nats/io/websocket.rb +75 -0
  33. data/lib/nats/nuid.rb +3 -1
  34. data/lib/nats.rb +39 -0
  35. data/sig/nats/io/client.rbs +304 -0
  36. data/sig/nats/io/errors.rbs +54 -0
  37. data/sig/nats/io/jetstream/api.rbs +35 -0
  38. data/sig/nats/io/jetstream/errors.rbs +54 -0
  39. data/sig/nats/io/jetstream/js/config.rbs +11 -0
  40. data/sig/nats/io/jetstream/js/header.rbs +17 -0
  41. data/sig/nats/io/jetstream/js/status.rbs +13 -0
  42. data/sig/nats/io/jetstream/js/sub.rbs +14 -0
  43. data/sig/nats/io/jetstream/js.rbs +27 -0
  44. data/sig/nats/io/jetstream/manager.rbs +33 -0
  45. data/sig/nats/io/jetstream/msg/ack.rbs +35 -0
  46. data/sig/nats/io/jetstream/msg/ack_methods.rbs +25 -0
  47. data/sig/nats/io/jetstream/msg/metadata.rbs +15 -0
  48. data/sig/nats/io/jetstream/msg.rbs +6 -0
  49. data/sig/nats/io/jetstream/pull_subscription.rbs +14 -0
  50. data/sig/nats/io/jetstream/push_subscription.rbs +7 -0
  51. data/sig/nats/io/jetstream.rbs +15 -0
  52. data/sig/nats/io/kv/api.rbs +8 -0
  53. data/sig/nats/io/kv/bucket_status.rbs +17 -0
  54. data/sig/nats/io/kv/errors.rbs +30 -0
  55. data/sig/nats/io/kv/manager.rbs +11 -0
  56. data/sig/nats/io/kv.rbs +39 -0
  57. data/sig/nats/io/msg.rbs +14 -0
  58. data/sig/nats/io/parser.rbs +32 -0
  59. data/sig/nats/io/subscription.rbs +33 -0
  60. data/sig/nats/io/version.rbs +9 -0
  61. data/sig/nats/nuid.rbs +32 -0
  62. metadata +74 -4
@@ -0,0 +1,260 @@
1
+ # Copyright 2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+
15
+ require_relative 'errors'
16
+
17
+ module NATS
18
+ class JetStream
19
+ # PullSubscription is included into NATS::Subscription so that it
20
+ # can be used to fetch messages from a pull based consumer from
21
+ # JetStream.
22
+ #
23
+ # @example Create a pull subscription using JetStream context.
24
+ #
25
+ # require 'nats/client'
26
+ #
27
+ # nc = NATS.connect
28
+ # js = nc.jetstream
29
+ # psub = js.pull_subscribe("foo", "bar")
30
+ #
31
+ # loop do
32
+ # msgs = psub.fetch(5)
33
+ # msgs.each do |msg|
34
+ # msg.ack
35
+ # end
36
+ # end
37
+ #
38
+ # @!visibility public
39
+ module PullSubscription
40
+ # next_msg is not available for pull based subscriptions.
41
+ # @raise [NATS::JetStream::Error]
42
+ def next_msg(params={})
43
+ raise ::NATS::JetStream::Error.new("nats: pull subscription cannot use next_msg")
44
+ end
45
+
46
+ # fetch makes a request to be delivered more messages from a pull consumer.
47
+ #
48
+ # @param batch [Fixnum] Number of messages to pull from the stream.
49
+ # @param params [Hash] Options to customize the fetch request.
50
+ # @option params [Float] :timeout Duration of the fetch request before it expires.
51
+ # @return [Array<NATS::Msg>]
52
+ def fetch(batch=1, params={})
53
+ if batch < 1
54
+ raise ::NATS::JetStream::Error.new("nats: invalid batch size")
55
+ end
56
+
57
+ t = MonotonicTime.now
58
+ timeout = params[:timeout] ||= 5
59
+ expires = (timeout * 1_000_000_000) - 100_000
60
+ next_req = {
61
+ batch: batch
62
+ }
63
+
64
+ msgs = []
65
+ case
66
+ when batch < 1
67
+ raise ::NATS::JetStream::Error.new("nats: invalid batch size")
68
+ when batch == 1
69
+ ####################################################
70
+ # Fetch (1) #
71
+ ####################################################
72
+
73
+ # Check if there is any pending message in the queue that is
74
+ # ready to be consumed.
75
+ synchronize do
76
+ unless @pending_queue.empty?
77
+ msg = @pending_queue.pop
78
+ @pending_size -= msg.data.size
79
+ # Check for a no msgs response status.
80
+ if JS.is_status_msg(msg)
81
+ case msg.header["Status"]
82
+ when JS::Status::NoMsgs
83
+ msg = nil
84
+ when JS::Status::RequestTimeout
85
+ # Skip
86
+ else
87
+ raise JS.from_msg(msg)
88
+ end
89
+ else
90
+ msgs << msg
91
+ end
92
+ end
93
+ end
94
+
95
+ # Make lingering request with expiration.
96
+ next_req[:expires] = expires
97
+ if msgs.empty?
98
+ # Make publish request and wait for response.
99
+ @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
100
+
101
+ # Wait for result of fetch or timeout.
102
+ synchronize { wait_for_msgs_cond.wait(timeout) }
103
+
104
+ unless @pending_queue.empty?
105
+ msg = @pending_queue.pop
106
+ @pending_size -= msg.data.size
107
+
108
+ msgs << msg
109
+ end
110
+
111
+ duration = MonotonicTime.since(t)
112
+ if duration > timeout
113
+ raise ::NATS::Timeout.new("nats: fetch timeout")
114
+ end
115
+
116
+ # Should have received at least a message at this point,
117
+ # if that is not the case then error already.
118
+ if JS.is_status_msg(msgs.first)
119
+ msg = msgs.first
120
+ case msg.header[JS::Header::Status]
121
+ when JS::Status::RequestTimeout
122
+ raise NATS::Timeout.new("nats: fetch request timeout")
123
+ else
124
+ raise JS.from_msg(msgs.first)
125
+ end
126
+ end
127
+ end
128
+ when batch > 1
129
+ ####################################################
130
+ # Fetch (n) #
131
+ ####################################################
132
+
133
+ # Check if there already enough in the pending buffer.
134
+ synchronize do
135
+ if batch <= @pending_queue.size
136
+ batch.times do
137
+ msg = @pending_queue.pop
138
+ @pending_size -= msg.data.size
139
+
140
+ # Check for a no msgs response status.
141
+ if JS.is_status_msg(msg)
142
+ case msg.header[JS::Header::Status]
143
+ when JS::Status::NoMsgs, JS::Status::RequestTimeout
144
+ # Skip these
145
+ next
146
+ else
147
+ raise JS.from_msg(msg)
148
+ end
149
+ else
150
+ msgs << msg
151
+ end
152
+ end
153
+
154
+ return msgs
155
+ end
156
+ end
157
+
158
+ # Make publish request and wait any response.
159
+ next_req[:no_wait] = true
160
+ @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
161
+
162
+ # Not receiving even one is a timeout.
163
+ start_time = MonotonicTime.now
164
+ msg = nil
165
+
166
+ synchronize do
167
+ wait_for_msgs_cond.wait(timeout)
168
+
169
+ unless @pending_queue.empty?
170
+ msg = @pending_queue.pop
171
+ @pending_size -= msg.data.size
172
+ end
173
+ end
174
+
175
+ # Check if the first message was a response saying that
176
+ # there are no messages.
177
+ if !msg.nil? && JS.is_status_msg(msg)
178
+ case msg.header[JS::Header::Status]
179
+ when JS::Status::NoMsgs
180
+ # Make another request that does wait.
181
+ next_req[:expires] = expires
182
+ next_req.delete(:no_wait)
183
+
184
+ @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
185
+ when JS::Status::RequestTimeout
186
+ raise NATS::Timeout.new("nats: fetch request timeout")
187
+ else
188
+ raise JS.from_msg(msg)
189
+ end
190
+ else
191
+ msgs << msg unless msg.nil?
192
+ end
193
+
194
+ # Check if have not received yet a single message.
195
+ duration = MonotonicTime.since(start_time)
196
+ if msgs.empty? and duration > timeout
197
+ raise NATS::Timeout.new("nats: fetch timeout")
198
+ end
199
+
200
+ needed = batch - msgs.count
201
+ while needed > 0 and MonotonicTime.since(start_time) < timeout
202
+ duration = MonotonicTime.since(start_time)
203
+
204
+ # Wait for the rest of the messages.
205
+ synchronize do
206
+
207
+ # Wait until there is a message delivered.
208
+ if @pending_queue.empty?
209
+ deadline = timeout - duration
210
+ wait_for_msgs_cond.wait(deadline) if deadline > 0
211
+
212
+ duration = MonotonicTime.since(start_time)
213
+ if msgs.empty? && @pending_queue.empty? and duration > timeout
214
+ raise NATS::Timeout.new("nats: fetch timeout")
215
+ end
216
+ else
217
+ msg = @pending_queue.pop
218
+ @pending_size -= msg.data.size
219
+
220
+ if JS.is_status_msg(msg)
221
+ case msg.header[JS::Header::Status]
222
+ when JS::Status::NoMsgs, JS::Status::RequestTimeout
223
+ duration = MonotonicTime.since(start_time)
224
+
225
+ if duration > timeout
226
+ # Only received a subset of the messages.
227
+ if !msgs.empty?
228
+ return msgs
229
+ else
230
+ raise NATS::Timeout.new("nats: fetch timeout")
231
+ end
232
+ end
233
+ else
234
+ raise JS.from_msg(msg)
235
+ end
236
+
237
+ else
238
+ # Add to the set of messages that will be returned.
239
+ msgs << msg
240
+ needed -= 1
241
+ end
242
+ end
243
+ end # :end: synchronize
244
+ end
245
+ end
246
+
247
+ msgs
248
+ end
249
+
250
+ # consumer_info retrieves the current status of the pull subscription consumer.
251
+ # @param params [Hash] Options to customize API request.
252
+ # @option params [Float] :timeout Time to wait for response.
253
+ # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
254
+ def consumer_info(params={})
255
+ @jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
256
+ end
257
+ end
258
+ private_constant :PullSubscription
259
+ end
260
+ end
@@ -0,0 +1,42 @@
1
+ # Copyright 2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+
15
+ module NATS
16
+ class JetStream
17
+ # PushSubscription is included into NATS::Subscription so that it
18
+ #
19
+ # @example Create a push subscription using JetStream context.
20
+ #
21
+ # require 'nats/client'
22
+ #
23
+ # nc = NATS.connect
24
+ # js = nc.jetstream
25
+ # sub = js.subscribe("foo", "bar")
26
+ # msg = sub.next_msg
27
+ # msg.ack
28
+ # sub.unsubscribe
29
+ #
30
+ # @!visibility public
31
+ module PushSubscription
32
+ # consumer_info retrieves the current status of the pull subscription consumer.
33
+ # @param params [Hash] Options to customize API request.
34
+ # @option params [Float] :timeout Time to wait for response.
35
+ # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
36
+ def consumer_info(params={})
37
+ @jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
38
+ end
39
+ end
40
+ private_constant :PushSubscription
41
+ end
42
+ end
@@ -0,0 +1,344 @@
1
+ # Copyright 2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
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'
25
+
26
+ module NATS
27
+ # JetStream returns a context with a similar API as the NATS::Client
28
+ # but with enhanced functions to persist and consume messages from
29
+ # the NATS JetStream engine.
30
+ #
31
+ # @example
32
+ # nc = NATS.connect("demo.nats.io")
33
+ # js = nc.jetstream()
34
+ #
35
+ class JetStream
36
+ # Create a new JetStream context for a NATS connection.
37
+ #
38
+ # @param conn [NATS::Client]
39
+ # @param params [Hash] Options to customize JetStream context.
40
+ # @option params [String] :prefix JetStream API prefix to use for the requests.
41
+ # @option params [String] :domain JetStream Domain to use for the requests.
42
+ # @option params [Float] :timeout Default timeout to use for JS requests.
43
+ def initialize(conn, params={})
44
+ @nc = conn
45
+ @prefix = if params[:prefix]
46
+ params[:prefix]
47
+ elsif params[:domain]
48
+ "$JS.#{params[:domain]}.API"
49
+ else
50
+ JS::DefaultAPIPrefix
51
+ end
52
+ @opts = params
53
+ @opts[:timeout] ||= 5 # seconds
54
+ params[:prefix] = @prefix
55
+
56
+ # Include JetStream::Manager
57
+ extend Manager
58
+ extend KeyValue::Manager
59
+ end
60
+
61
+ # PubAck is the API response from a successfully published message.
62
+ #
63
+ # @!attribute [stream] stream
64
+ # @return [String] Name of the stream that processed the published message.
65
+ # @!attribute [seq] seq
66
+ # @return [Fixnum] Sequence of the message in the stream.
67
+ # @!attribute [duplicate] duplicate
68
+ # @return [Boolean] Indicates whether the published message is a duplicate.
69
+ # @!attribute [domain] domain
70
+ # @return [String] JetStream Domain that processed the ack response.
71
+ PubAck = Struct.new(:stream, :seq, :duplicate, :domain, keyword_init: true)
72
+
73
+ # publish produces a message for JetStream.
74
+ #
75
+ # @param subject [String] The subject from a stream where the message will be sent.
76
+ # @param payload [String] The payload of the message.
77
+ # @param params [Hash] Options to customize the publish message request.
78
+ # @option params [Float] :timeout Time to wait for an PubAck response or an error.
79
+ # @option params [Hash] :header NATS Headers to use for the message.
80
+ # @option params [String] :stream Expected Stream to which the message is being published.
81
+ # @raise [NATS::Timeout] When it takes too long to receive an ack response.
82
+ # @return [PubAck] The pub ack response.
83
+ def publish(subject, payload="", **params)
84
+ params[:timeout] ||= @opts[:timeout]
85
+ if params[:stream]
86
+ params[:header] ||= {}
87
+ params[:header][JS::Header::ExpectedStream] = params[:stream]
88
+ end
89
+
90
+ # Send message with headers.
91
+ msg = NATS::Msg.new(subject: subject,
92
+ data: payload,
93
+ header: params[:header])
94
+
95
+ begin
96
+ resp = @nc.request_msg(msg, **params)
97
+ result = JSON.parse(resp.data, symbolize_names: true)
98
+ rescue ::NATS::IO::NoRespondersError
99
+ raise JetStream::Error::NoStreamResponse.new("nats: no response from stream")
100
+ end
101
+ raise JS.from_error(result[:error]) if result[:error]
102
+
103
+ PubAck.new(result)
104
+ end
105
+
106
+ # subscribe binds or creates a push subscription to a JetStream pull consumer.
107
+ #
108
+ # @param subject [String, Array] Subject(s) from which the messages will be fetched.
109
+ # @param params [Hash] Options to customize the PushSubscription.
110
+ # @option params [String] :stream Name of the Stream to which the consumer belongs.
111
+ # @option params [String] :consumer Name of the Consumer to which the PushSubscription will be bound.
112
+ # @option params [String] :name Name of the Consumer to which the PushSubscription will be bound.
113
+ # @option params [String] :durable Consumer durable name from where the messages will be fetched.
114
+ # @option params [Hash] :config Configuration for the consumer.
115
+ # @return [NATS::JetStream::PushSubscription]
116
+ def subscribe(subject, params={}, &cb)
117
+ params[:consumer] ||= params[:durable]
118
+ params[:consumer] ||= params[:name]
119
+ multi_filter = case
120
+ when (subject.is_a?(Array) and subject.size == 1)
121
+ subject = subject.first
122
+ false
123
+ when (subject.is_a?(Array) and subject.size > 1)
124
+ true
125
+ end
126
+
127
+ #
128
+ stream = if params[:stream].nil?
129
+ if multi_filter
130
+ # Use the first subject to try to find the stream.
131
+ streams = subject.map do |s|
132
+ begin
133
+ find_stream_name_by_subject(s)
134
+ rescue NATS::JetStream::Error::NotFound
135
+ raise NATS::JetStream::Error.new("nats: could not find stream matching filter subject '#{s}'")
136
+ end
137
+ end
138
+
139
+ # Ensure that the filter subjects are not ambiguous.
140
+ streams.uniq!
141
+ if streams.count > 1
142
+ raise NATS::JetStream::Error.new("nats: multiple streams matched filter subjects: #{streams}")
143
+ end
144
+
145
+ streams.first
146
+ else
147
+ find_stream_name_by_subject(subject)
148
+ end
149
+ else
150
+ params[:stream]
151
+ end
152
+
153
+ queue = params[:queue]
154
+ durable = params[:durable]
155
+ flow_control = params[:flow_control]
156
+ manual_ack = params[:manual_ack]
157
+ idle_heartbeat = params[:idle_heartbeat]
158
+ flow_control = params[:flow_control]
159
+ config = params[:config]
160
+
161
+ if queue
162
+ if durable and durable != queue
163
+ raise NATS::JetStream::Error.new("nats: cannot create queue subscription '#{queue}' to consumer '#{durable}'")
164
+ else
165
+ durable = queue
166
+ end
167
+ end
168
+
169
+ cinfo = nil
170
+ consumer_found = false
171
+ should_create = false
172
+
173
+ if not durable
174
+ should_create = true
175
+ else
176
+ begin
177
+ cinfo = consumer_info(stream, durable)
178
+ config = cinfo.config
179
+ consumer_found = true
180
+ consumer = durable
181
+ rescue NATS::JetStream::Error::NotFound
182
+ should_create = true
183
+ consumer_found = false
184
+ end
185
+ end
186
+
187
+ if consumer_found
188
+ if not config.deliver_group
189
+ if queue
190
+ raise NATS::JetStream::Error.new("nats: cannot create a queue subscription for a consumer without a deliver group")
191
+ elsif cinfo.push_bound
192
+ raise NATS::JetStream::Error.new("nats: consumer is already bound to a subscription")
193
+ end
194
+ else
195
+ if not queue
196
+ raise NATS::JetStream::Error.new("nats: cannot create a subscription for a consumer with a deliver group #{config.deliver_group}")
197
+ elsif queue != config.deliver_group
198
+ raise NATS::JetStream::Error.new("nats: cannot create a queue subscription #{queue} for a consumer with a deliver group #{config.deliver_group}")
199
+ end
200
+ end
201
+ elsif should_create
202
+ # Auto-create consumer if none found.
203
+ if config.nil?
204
+ # Defaults
205
+ config = JetStream::API::ConsumerConfig.new({ack_policy: "explicit"})
206
+ elsif config.is_a?(Hash)
207
+ config = JetStream::API::ConsumerConfig.new(config)
208
+ elsif !config.is_a?(JetStream::API::ConsumerConfig)
209
+ raise NATS::JetStream::Error.new("nats: invalid ConsumerConfig")
210
+ end
211
+
212
+ config.durable_name = durable if not config.durable_name
213
+ config.deliver_group = queue if not config.deliver_group
214
+
215
+ # Create inbox for push consumer.
216
+ deliver = @nc.new_inbox
217
+ config.deliver_subject = deliver
218
+
219
+ # Auto created consumers use the filter subject.
220
+ if multi_filter
221
+ config[:filter_subjects] ||= subject
222
+ else
223
+ config[:filter_subject] ||= subject
224
+ end
225
+
226
+ # Heartbeats / FlowControl
227
+ config.flow_control = flow_control
228
+ if idle_heartbeat or config.idle_heartbeat
229
+ idle_heartbeat = config.idle_heartbeat if config.idle_heartbeat
230
+ idle_heartbeat = idle_heartbeat * ::NATS::NANOSECONDS
231
+ config.idle_heartbeat = idle_heartbeat
232
+ end
233
+
234
+ # Auto create the consumer.
235
+ cinfo = add_consumer(stream, config)
236
+ consumer = cinfo.name
237
+ end
238
+
239
+ # Enable auto acking for async callbacks unless disabled.
240
+ if cb and not manual_ack
241
+ ocb = cb
242
+ new_cb = proc do |msg|
243
+ ocb.call(msg)
244
+ msg.ack rescue JetStream::Error::MsgAlreadyAckd
245
+ end
246
+ cb = new_cb
247
+ end
248
+ sub = @nc.subscribe(config.deliver_subject, queue: config.deliver_group, &cb)
249
+ sub.extend(PushSubscription)
250
+ sub.jsi = JS::Sub.new(
251
+ js: self,
252
+ stream: stream,
253
+ consumer: consumer,
254
+ )
255
+ sub
256
+ end
257
+
258
+ # pull_subscribe binds or creates a subscription to a JetStream pull consumer.
259
+ #
260
+ # @param subject [String, Array] Subject or subjects from which the messages will be fetched.
261
+ # @param durable [String] Consumer durable name from where the messages will be fetched.
262
+ # @param params [Hash] Options to customize the PullSubscription.
263
+ # @option params [String] :stream Name of the Stream to which the consumer belongs.
264
+ # @option params [String] :consumer Name of the Consumer to which the PullSubscription will be bound.
265
+ # @option params [String] :name Name of the Consumer to which the PullSubscription will be bound.
266
+ # @option params [Hash] :config Configuration for the consumer.
267
+ # @return [NATS::JetStream::PullSubscription]
268
+ def pull_subscribe(subject, durable, params={})
269
+ if (!durable or durable.empty?) && !(params[:consumer] or params[:name])
270
+ raise JetStream::Error::InvalidDurableName.new("nats: invalid durable name")
271
+ end
272
+ multi_filter = case
273
+ when (subject.is_a?(Array) and subject.size == 1)
274
+ subject = subject.first
275
+ false
276
+ when (subject.is_a?(Array) and subject.size > 1)
277
+ true
278
+ end
279
+
280
+ params[:consumer] ||= durable
281
+ params[:consumer] ||= params[:name]
282
+ stream = if params[:stream].nil?
283
+ if multi_filter
284
+ # Use the first subject to try to find the stream.
285
+ streams = subject.map do |s|
286
+ begin
287
+ find_stream_name_by_subject(s)
288
+ rescue NATS::JetStream::Error::NotFound
289
+ raise NATS::JetStream::Error.new("nats: could not find stream matching filter subject '#{s}'")
290
+ end
291
+ end
292
+
293
+ # Ensure that the filter subjects are not ambiguous.
294
+ streams.uniq!
295
+ if streams.count > 1
296
+ raise NATS::JetStream::Error.new("nats: multiple streams matched filter subjects: #{streams}")
297
+ end
298
+
299
+ streams.first
300
+ else
301
+ find_stream_name_by_subject(subject)
302
+ end
303
+ else
304
+ params[:stream]
305
+ end
306
+ begin
307
+ consumer_info(stream, params[:consumer])
308
+ rescue NATS::JetStream::Error::NotFound => e
309
+ # If attempting to bind, then this is a hard error.
310
+ raise e if params[:stream] and !multi_filter
311
+
312
+ config = if not params[:config]
313
+ JetStream::API::ConsumerConfig.new
314
+ elsif params[:config].is_a?(JetStream::API::ConsumerConfig)
315
+ params[:config]
316
+ else
317
+ JetStream::API::ConsumerConfig.new(params[:config])
318
+ end
319
+ config[:durable_name] = durable
320
+ config[:ack_policy] ||= JS::Config::AckExplicit
321
+ if multi_filter
322
+ config[:filter_subjects] ||= subject
323
+ else
324
+ config[:filter_subject] ||= subject
325
+ end
326
+ add_consumer(stream, config)
327
+ end
328
+
329
+ deliver = @nc.new_inbox
330
+ sub = @nc.subscribe(deliver)
331
+ sub.extend(PullSubscription)
332
+
333
+ consumer = params[:consumer]
334
+ subject = "#{@prefix}.CONSUMER.MSG.NEXT.#{stream}.#{consumer}"
335
+ sub.jsi = JS::Sub.new(
336
+ js: self,
337
+ stream: stream,
338
+ consumer: params[:consumer],
339
+ nms: subject
340
+ )
341
+ sub
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,39 @@
1
+ # Copyright 2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+
15
+ module NATS
16
+ class KeyValue
17
+ module API
18
+ KeyValueConfig = Struct.new(
19
+ :bucket,
20
+ :description,
21
+ :max_value_size,
22
+ :history,
23
+ :ttl,
24
+ :max_bytes,
25
+ :storage,
26
+ :replicas,
27
+ :placement,
28
+ :republish,
29
+ :direct,
30
+ keyword_init: true) do
31
+ def initialize(opts={})
32
+ rem = opts.keys - members
33
+ opts.delete_if { |k| rem.include?(k) }
34
+ super(opts)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end