nats-pure 0.7.2 → 2.4.0

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