nats-pure 2.2.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/nats/io/js.rb DELETED
@@ -1,1434 +0,0 @@
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 'time'
19
- require 'base64'
20
-
21
- module NATS
22
- # JetStream returns a context with a similar API as the NATS::Client
23
- # but with enhanced functions to persist and consume messages from
24
- # the NATS JetStream engine.
25
- #
26
- # @example
27
- # nc = NATS.connect("demo.nats.io")
28
- # js = nc.jetstream()
29
- #
30
- class JetStream
31
- # Create a new JetStream context for a NATS connection.
32
- #
33
- # @param conn [NATS::Client]
34
- # @param params [Hash] Options to customize JetStream context.
35
- # @option params [String] :prefix JetStream API prefix to use for the requests.
36
- # @option params [String] :domain JetStream Domain to use for the requests.
37
- # @option params [Float] :timeout Default timeout to use for JS requests.
38
- def initialize(conn, params={})
39
- @nc = conn
40
- @prefix = if params[:prefix]
41
- params[:prefix]
42
- elsif params[:domain]
43
- "$JS.#{params[:domain]}.API"
44
- else
45
- JS::DefaultAPIPrefix
46
- end
47
- @opts = params
48
- @opts[:timeout] ||= 5 # seconds
49
- params[:prefix] = @prefix
50
-
51
- # Include JetStream::Manager
52
- extend Manager
53
- extend KeyValue::Manager
54
- end
55
-
56
- # PubAck is the API response from a successfully published message.
57
- #
58
- # @!attribute [stream] stream
59
- # @return [String] Name of the stream that processed the published message.
60
- # @!attribute [seq] seq
61
- # @return [Fixnum] Sequence of the message in the stream.
62
- # @!attribute [duplicate] duplicate
63
- # @return [Boolean] Indicates whether the published message is a duplicate.
64
- # @!attribute [domain] domain
65
- # @return [String] JetStream Domain that processed the ack response.
66
- PubAck = Struct.new(:stream, :seq, :duplicate, :domain, keyword_init: true)
67
-
68
- # publish produces a message for JetStream.
69
- #
70
- # @param subject [String] The subject from a stream where the message will be sent.
71
- # @param payload [String] The payload of the message.
72
- # @param params [Hash] Options to customize the publish message request.
73
- # @option params [Float] :timeout Time to wait for an PubAck response or an error.
74
- # @option params [Hash] :header NATS Headers to use for the message.
75
- # @option params [String] :stream Expected Stream to which the message is being published.
76
- # @raise [NATS::Timeout] When it takes too long to receive an ack response.
77
- # @return [PubAck] The pub ack response.
78
- def publish(subject, payload="", **params)
79
- params[:timeout] ||= @opts[:timeout]
80
- if params[:stream]
81
- params[:header] ||= {}
82
- params[:header][JS::Header::ExpectedStream] = params[:stream]
83
- end
84
-
85
- # Send message with headers.
86
- msg = NATS::Msg.new(subject: subject,
87
- data: payload,
88
- header: params[:header])
89
-
90
- begin
91
- resp = @nc.request_msg(msg, **params)
92
- result = JSON.parse(resp.data, symbolize_names: true)
93
- rescue ::NATS::IO::NoRespondersError
94
- raise JetStream::Error::NoStreamResponse.new("nats: no response from stream")
95
- end
96
- raise JS.from_error(result[:error]) if result[:error]
97
-
98
- PubAck.new(result)
99
- end
100
-
101
- # subscribe binds or creates a push subscription to a JetStream pull consumer.
102
- #
103
- # @param subject [String] Subject from which the messages will be fetched.
104
- # @param params [Hash] Options to customize the PushSubscription.
105
- # @option params [String] :stream Name of the Stream to which the consumer belongs.
106
- # @option params [String] :consumer Name of the Consumer to which the PushSubscription will be bound.
107
- # @option params [String] :durable Consumer durable name from where the messages will be fetched.
108
- # @option params [Hash] :config Configuration for the consumer.
109
- # @return [NATS::JetStream::PushSubscription]
110
- def subscribe(subject, params={}, &cb)
111
- params[:consumer] ||= params[:durable]
112
- stream = params[:stream].nil? ? find_stream_name_by_subject(subject) : params[:stream]
113
-
114
- queue = params[:queue]
115
- durable = params[:durable]
116
- flow_control = params[:flow_control]
117
- manual_ack = params[:manual_ack]
118
- idle_heartbeat = params[:idle_heartbeat]
119
- flow_control = params[:flow_control]
120
- config = params[:config]
121
-
122
- if queue
123
- if durable and durable != queue
124
- raise NATS::JetStream::Error.new("nats: cannot create queue subscription '#{queue}' to consumer '#{durable}'")
125
- else
126
- durable = queue
127
- end
128
- end
129
-
130
- cinfo = nil
131
- consumer_found = false
132
- should_create = false
133
-
134
- if not durable
135
- should_create = true
136
- else
137
- begin
138
- cinfo = consumer_info(stream, durable)
139
- config = cinfo.config
140
- consumer_found = true
141
- consumer = durable
142
- rescue NATS::JetStream::Error::NotFound
143
- should_create = true
144
- consumer_found = false
145
- end
146
- end
147
-
148
- if consumer_found
149
- if not config.deliver_group
150
- if queue
151
- raise NATS::JetStream::Error.new("nats: cannot create a queue subscription for a consumer without a deliver group")
152
- elsif cinfo.push_bound
153
- raise NATS::JetStream::Error.new("nats: consumer is already bound to a subscription")
154
- end
155
- else
156
- if not queue
157
- raise NATS::JetStream::Error.new("nats: cannot create a subscription for a consumer with a deliver group #{config.deliver_group}")
158
- elsif queue != config.deliver_group
159
- raise NATS::JetStream::Error.new("nats: cannot create a queue subscription #{queue} for a consumer with a deliver group #{config.deliver_group}")
160
- end
161
- end
162
- elsif should_create
163
- # Auto-create consumer if none found.
164
- if config.nil?
165
- # Defaults
166
- config = JetStream::API::ConsumerConfig.new({ack_policy: "explicit"})
167
- elsif config.is_a?(Hash)
168
- config = JetStream::API::ConsumerConfig.new(config)
169
- elsif !config.is_a?(JetStream::API::ConsumerConfig)
170
- raise NATS::JetStream::Error.new("nats: invalid ConsumerConfig")
171
- end
172
-
173
- config.durable_name = durable if not config.durable_name
174
- config.deliver_group = queue if not config.deliver_group
175
-
176
- # Create inbox for push consumer.
177
- deliver = @nc.new_inbox
178
- config.deliver_subject = deliver
179
-
180
- # Auto created consumers use the filter subject.
181
- config.filter_subject = subject
182
-
183
- # Heartbeats / FlowControl
184
- config.flow_control = flow_control
185
- if idle_heartbeat or config.idle_heartbeat
186
- idle_heartbeat = config.idle_heartbeat if config.idle_heartbeat
187
- idle_heartbeat = idle_heartbeat * ::NATS::NANOSECONDS
188
- config.idle_heartbeat = idle_heartbeat
189
- end
190
-
191
- # Auto create the consumer.
192
- cinfo = add_consumer(stream, config)
193
- consumer = cinfo.name
194
- end
195
-
196
- # Enable auto acking for async callbacks unless disabled.
197
- if cb and not manual_ack
198
- ocb = cb
199
- new_cb = proc do |msg|
200
- ocb.call(msg)
201
- msg.ack rescue JetStream::Error::MsgAlreadyAckd
202
- end
203
- cb = new_cb
204
- end
205
- sub = @nc.subscribe(config.deliver_subject, queue: config.deliver_group, &cb)
206
- sub.extend(PushSubscription)
207
- sub.jsi = JS::Sub.new(
208
- js: self,
209
- stream: stream,
210
- consumer: consumer,
211
- )
212
- sub
213
- end
214
-
215
- # pull_subscribe binds or creates a subscription to a JetStream pull consumer.
216
- #
217
- # @param subject [String] Subject from which the messages will be fetched.
218
- # @param durable [String] Consumer durable name from where the messages will be fetched.
219
- # @param params [Hash] Options to customize the PullSubscription.
220
- # @option params [String] :stream Name of the Stream to which the consumer belongs.
221
- # @option params [String] :consumer Name of the Consumer to which the PullSubscription will be bound.
222
- # @option params [Hash] :config Configuration for the consumer.
223
- # @return [NATS::JetStream::PullSubscription]
224
- def pull_subscribe(subject, durable, params={})
225
- if durable.empty? && !params[:consumer]
226
- raise JetStream::Error::InvalidDurableName.new("nats: invalid durable name")
227
- end
228
- params[:consumer] ||= durable
229
- stream = params[:stream].nil? ? find_stream_name_by_subject(subject) : params[:stream]
230
-
231
- begin
232
- consumer_info(stream, params[:consumer])
233
- rescue NATS::JetStream::Error::NotFound => e
234
- # If attempting to bind, then this is a hard error.
235
- raise e if params[:stream]
236
-
237
- config = if not params[:config]
238
- JetStream::API::ConsumerConfig.new
239
- elsif params[:config].is_a?(JetStream::API::ConsumerConfig)
240
- params[:config]
241
- else
242
- JetStream::API::ConsumerConfig.new(params[:config])
243
- end
244
- config[:durable_name] = durable
245
- config[:ack_policy] ||= JS::Config::AckExplicit
246
- add_consumer(stream, config)
247
- end
248
-
249
- deliver = @nc.new_inbox
250
- sub = @nc.subscribe(deliver)
251
- sub.extend(PullSubscription)
252
-
253
- consumer = params[:consumer]
254
- subject = "#{@prefix}.CONSUMER.MSG.NEXT.#{stream}.#{consumer}"
255
- sub.jsi = JS::Sub.new(
256
- js: self,
257
- stream: stream,
258
- consumer: params[:consumer],
259
- nms: subject
260
- )
261
- sub
262
- end
263
-
264
- # A JetStream::Manager can be used to make requests to the JetStream API.
265
- #
266
- # @example
267
- # require 'nats/client'
268
- #
269
- # nc = NATS.connect("demo.nats.io")
270
- #
271
- # config = JetStream::API::StreamConfig.new()
272
- # nc.jsm.add_stream(config)
273
- #
274
- #
275
- module Manager
276
- # add_stream creates a stream with a given config.
277
- # @param config [JetStream::API::StreamConfig] Configuration of the stream to create.
278
- # @param params [Hash] Options to customize API request.
279
- # @option params [Float] :timeout Time to wait for response.
280
- # @return [JetStream::API::StreamCreateResponse] The result of creating a Stream.
281
- def add_stream(config, params={})
282
- config = if not config.is_a?(JetStream::API::StreamConfig)
283
- JetStream::API::StreamConfig.new(config)
284
- else
285
- config
286
- end
287
- stream = config[:name]
288
- raise ArgumentError.new(":name is required to create streams") unless stream
289
- raise ArgumentError.new("Spaces, tabs, period (.), greater than (>) or asterisk (*) are prohibited in stream names") if stream =~ /(\s|\.|\>|\*)/
290
- req_subject = "#{@prefix}.STREAM.CREATE.#{stream}"
291
- result = api_request(req_subject, config.to_json, params)
292
- JetStream::API::StreamCreateResponse.new(result)
293
- end
294
-
295
- # stream_info retrieves the current status of a stream.
296
- # @param stream [String] Name of the stream.
297
- # @param params [Hash] Options to customize API request.
298
- # @option params [Float] :timeout Time to wait for response.
299
- # @return [JetStream::API::StreamInfo] The latest StreamInfo of the stream.
300
- def stream_info(stream, params={})
301
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
302
-
303
- req_subject = "#{@prefix}.STREAM.INFO.#{stream}"
304
- result = api_request(req_subject, '', params)
305
- JetStream::API::StreamInfo.new(result)
306
- end
307
-
308
- # delete_stream deletes a stream.
309
- # @param stream [String] Name of the stream.
310
- # @param params [Hash] Options to customize API request.
311
- # @option params [Float] :timeout Time to wait for response.
312
- # @return [Boolean]
313
- def delete_stream(stream, params={})
314
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
315
-
316
- req_subject = "#{@prefix}.STREAM.DELETE.#{stream}"
317
- result = api_request(req_subject, '', params)
318
- result[:success]
319
- end
320
-
321
- # add_consumer creates a consumer with a given config.
322
- # @param stream [String] Name of the stream.
323
- # @param config [JetStream::API::ConsumerConfig] Configuration of the consumer to create.
324
- # @param params [Hash] Options to customize API request.
325
- # @option params [Float] :timeout Time to wait for response.
326
- # @return [JetStream::API::ConsumerInfo] The result of creating a Consumer.
327
- def add_consumer(stream, config, params={})
328
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
329
- config = if not config.is_a?(JetStream::API::ConsumerConfig)
330
- JetStream::API::ConsumerConfig.new(config)
331
- else
332
- config
333
- end
334
-
335
- req_subject = case
336
- when config[:name]
337
- # NOTE: Only supported after nats-server v2.9.0
338
- if config[:filter_subject] && config[:filter_subject] != ">"
339
- "#{@prefix}.CONSUMER.CREATE.#{stream}.#{config[:name]}.#{config[:filter_subject]}"
340
- else
341
- "#{@prefix}.CONSUMER.CREATE.#{stream}.#{config[:name]}"
342
- end
343
- when config[:durable_name]
344
- "#{@prefix}.CONSUMER.DURABLE.CREATE.#{stream}.#{config[:durable_name]}"
345
- else
346
- "#{@prefix}.CONSUMER.CREATE.#{stream}"
347
- end
348
-
349
- config[:ack_policy] ||= JS::Config::AckExplicit
350
- # Check if have to normalize ack wait so that it is in nanoseconds for Go compat.
351
- if config[:ack_wait]
352
- raise ArgumentError.new("nats: invalid ack wait") unless config[:ack_wait].is_a?(Integer)
353
- config[:ack_wait] = config[:ack_wait] * ::NATS::NANOSECONDS
354
- end
355
- if config[:inactive_threshold]
356
- raise ArgumentError.new("nats: invalid inactive threshold") unless config[:inactive_threshold].is_a?(Integer)
357
- config[:inactive_threshold] = config[:inactive_threshold] * ::NATS::NANOSECONDS
358
- end
359
-
360
- req = {
361
- stream_name: stream,
362
- config: config
363
- }
364
-
365
- result = api_request(req_subject, req.to_json, params)
366
- JetStream::API::ConsumerInfo.new(result).freeze
367
- end
368
-
369
- # consumer_info retrieves the current status of a consumer.
370
- # @param stream [String] Name of the stream.
371
- # @param consumer [String] Name of the consumer.
372
- # @param params [Hash] Options to customize API request.
373
- # @option params [Float] :timeout Time to wait for response.
374
- # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
375
- def consumer_info(stream, consumer, params={})
376
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
377
- raise JetStream::Error::InvalidConsumerName.new("nats: invalid consumer name") if consumer.nil? or consumer.empty?
378
-
379
- req_subject = "#{@prefix}.CONSUMER.INFO.#{stream}.#{consumer}"
380
- result = api_request(req_subject, '', params)
381
- JetStream::API::ConsumerInfo.new(result)
382
- end
383
-
384
- # delete_consumer deletes a consumer.
385
- # @param stream [String] Name of the stream.
386
- # @param consumer [String] Name of the consumer.
387
- # @param params [Hash] Options to customize API request.
388
- # @option params [Float] :timeout Time to wait for response.
389
- # @return [Boolean]
390
- def delete_consumer(stream, consumer, params={})
391
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
392
- raise JetStream::Error::InvalidConsumerName.new("nats: invalid consumer name") if consumer.nil? or consumer.empty?
393
-
394
- req_subject = "#{@prefix}.CONSUMER.DELETE.#{stream}.#{consumer}"
395
- result = api_request(req_subject, '', params)
396
- result[:success]
397
- end
398
-
399
- # find_stream_name_by_subject does a lookup for the stream to which
400
- # the subject belongs.
401
- # @param subject [String] The subject that belongs to a stream.
402
- # @param params [Hash] Options to customize API request.
403
- # @option params [Float] :timeout Time to wait for response.
404
- # @return [String] The name of the JetStream stream for the subject.
405
- def find_stream_name_by_subject(subject, params={})
406
- req_subject = "#{@prefix}.STREAM.NAMES"
407
- req = { subject: subject }
408
- result = api_request(req_subject, req.to_json, params)
409
- raise JetStream::Error::NotFound unless result[:streams]
410
-
411
- result[:streams].first
412
- end
413
-
414
- # get_msg retrieves a message from the stream.
415
- # @param next [Boolean] Fetch the next message for a subject.
416
- # @param seq [Integer] Sequence number of a message.
417
- # @param subject [String] Subject of the message.
418
- # @param direct [Boolean] Use direct mode to for faster access (requires NATS v2.9.0)
419
- def get_msg(stream_name, params={})
420
- req = {}
421
- case
422
- when params[:next]
423
- req[:seq] = params[:seq]
424
- req[:next_by_subj] = params[:subject]
425
- when params[:seq]
426
- req[:seq] = params[:seq]
427
- when params[:subject]
428
- req[:last_by_subj] = params[:subject]
429
- end
430
-
431
- data = req.to_json
432
- if params[:direct]
433
- if params[:subject] and not params[:seq]
434
- # last_by_subject type request requires no payload.
435
- data = ''
436
- req_subject = "#{@prefix}.DIRECT.GET.#{stream_name}.#{params[:subject]}"
437
- else
438
- req_subject = "#{@prefix}.DIRECT.GET.#{stream_name}"
439
- end
440
- else
441
- req_subject = "#{@prefix}.STREAM.MSG.GET.#{stream_name}"
442
- end
443
- resp = api_request(req_subject, data, direct: params[:direct])
444
- msg = if params[:direct]
445
- _lift_msg_to_raw_msg(resp)
446
- else
447
- JetStream::API::RawStreamMsg.new(resp[:message])
448
- end
449
-
450
- msg
451
- end
452
-
453
- def get_last_msg(stream_name, subject, params={})
454
- params[:subject] = subject
455
- get_msg(stream_name, params)
456
- end
457
-
458
- def account_info
459
- api_request("#{@prefix}.INFO")
460
- end
461
-
462
- private
463
-
464
- def api_request(req_subject, req="", params={})
465
- params[:timeout] ||= @opts[:timeout]
466
- msg = begin
467
- @nc.request(req_subject, req, **params)
468
- rescue NATS::IO::NoRespondersError
469
- raise JetStream::Error::ServiceUnavailable
470
- end
471
-
472
- result = if params[:direct]
473
- msg
474
- else
475
- JSON.parse(msg.data, symbolize_names: true)
476
- end
477
- if result.is_a?(Hash) and result[:error]
478
- raise JS.from_error(result[:error])
479
- end
480
-
481
- result
482
- end
483
-
484
- def _lift_msg_to_raw_msg(msg)
485
- if msg.header and msg.header['Status']
486
- status = msg.header['Status']
487
- if status == '404'
488
- raise ::NATS::JetStream::Error::NotFound.new
489
- else
490
- raise JS.from_msg(msg)
491
- end
492
- end
493
- subject = msg.header['Nats-Subject']
494
- seq = msg.header['Nats-Sequence']
495
- raw_msg = JetStream::API::RawStreamMsg.new(
496
- subject: subject,
497
- seq: seq,
498
- headers: msg.header,
499
- )
500
- raw_msg.data = msg.data
501
-
502
- raw_msg
503
- end
504
- end
505
-
506
- # PushSubscription is included into NATS::Subscription so that it
507
- #
508
- # @example Create a push subscription using JetStream context.
509
- #
510
- # require 'nats/client'
511
- #
512
- # nc = NATS.connect
513
- # js = nc.jetstream
514
- # sub = js.subscribe("foo", "bar")
515
- # msg = sub.next_msg
516
- # msg.ack
517
- # sub.unsubscribe
518
- #
519
- # @!visibility public
520
- module PushSubscription
521
- # consumer_info retrieves the current status of the pull subscription consumer.
522
- # @param params [Hash] Options to customize API request.
523
- # @option params [Float] :timeout Time to wait for response.
524
- # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
525
- def consumer_info(params={})
526
- @jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
527
- end
528
- end
529
- private_constant :PushSubscription
530
-
531
- # PullSubscription is included into NATS::Subscription so that it
532
- # can be used to fetch messages from a pull based consumer from
533
- # JetStream.
534
- #
535
- # @example Create a pull subscription using JetStream context.
536
- #
537
- # require 'nats/client'
538
- #
539
- # nc = NATS.connect
540
- # js = nc.jetstream
541
- # psub = js.pull_subscribe("foo", "bar")
542
- #
543
- # loop do
544
- # msgs = psub.fetch(5)
545
- # msgs.each do |msg|
546
- # msg.ack
547
- # end
548
- # end
549
- #
550
- # @!visibility public
551
- module PullSubscription
552
- # next_msg is not available for pull based subscriptions.
553
- # @raise [NATS::JetStream::Error]
554
- def next_msg(params={})
555
- raise ::NATS::JetStream::Error.new("nats: pull subscription cannot use next_msg")
556
- end
557
-
558
- # fetch makes a request to be delivered more messages from a pull consumer.
559
- #
560
- # @param batch [Fixnum] Number of messages to pull from the stream.
561
- # @param params [Hash] Options to customize the fetch request.
562
- # @option params [Float] :timeout Duration of the fetch request before it expires.
563
- # @return [Array<NATS::Msg>]
564
- def fetch(batch=1, params={})
565
- if batch < 1
566
- raise ::NATS::JetStream::Error.new("nats: invalid batch size")
567
- end
568
-
569
- t = MonotonicTime.now
570
- timeout = params[:timeout] ||= 5
571
- expires = (timeout * 1_000_000_000) - 100_000
572
- next_req = {
573
- batch: batch
574
- }
575
-
576
- msgs = []
577
- case
578
- when batch < 1
579
- raise ::NATS::JetStream::Error.new("nats: invalid batch size")
580
- when batch == 1
581
- ####################################################
582
- # Fetch (1) #
583
- ####################################################
584
-
585
- # Check if there is any pending message in the queue that is
586
- # ready to be consumed.
587
- synchronize do
588
- unless @pending_queue.empty?
589
- msg = @pending_queue.pop
590
- @pending_size -= msg.data.size
591
- # Check for a no msgs response status.
592
- if JS.is_status_msg(msg)
593
- case msg.header["Status"]
594
- when JS::Status::NoMsgs
595
- msg = nil
596
- when JS::Status::RequestTimeout
597
- # Skip
598
- else
599
- raise JS.from_msg(msg)
600
- end
601
- else
602
- msgs << msg
603
- end
604
- end
605
- end
606
-
607
- # Make lingering request with expiration.
608
- next_req[:expires] = expires
609
- if msgs.empty?
610
- # Make publish request and wait for response.
611
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
612
-
613
- # Wait for result of fetch or timeout.
614
- synchronize { wait_for_msgs_cond.wait(timeout) }
615
-
616
- unless @pending_queue.empty?
617
- msg = @pending_queue.pop
618
- @pending_size -= msg.data.size
619
-
620
- msgs << msg
621
- end
622
-
623
- duration = MonotonicTime.since(t)
624
- if duration > timeout
625
- raise ::NATS::Timeout.new("nats: fetch timeout")
626
- end
627
-
628
- # Should have received at least a message at this point,
629
- # if that is not the case then error already.
630
- if JS.is_status_msg(msgs.first)
631
- msg = msgs.first
632
- case msg.header[JS::Header::Status]
633
- when JS::Status::RequestTimeout
634
- raise NATS::Timeout.new("nats: fetch request timeout")
635
- else
636
- raise JS.from_msg(msgs.first)
637
- end
638
- end
639
- end
640
- when batch > 1
641
- ####################################################
642
- # Fetch (n) #
643
- ####################################################
644
-
645
- # Check if there already enough in the pending buffer.
646
- synchronize do
647
- if batch <= @pending_queue.size
648
- batch.times do
649
- msg = @pending_queue.pop
650
- @pending_size -= msg.data.size
651
-
652
- # Check for a no msgs response status.
653
- if JS.is_status_msg(msg)
654
- case msg.header[JS::Header::Status]
655
- when JS::Status::NoMsgs, JS::Status::RequestTimeout
656
- # Skip these
657
- next
658
- else
659
- raise JS.from_msg(msg)
660
- end
661
- else
662
- msgs << msg
663
- end
664
- end
665
-
666
- return msgs
667
- end
668
- end
669
-
670
- # Make publish request and wait any response.
671
- next_req[:no_wait] = true
672
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
673
-
674
- # Not receiving even one is a timeout.
675
- start_time = MonotonicTime.now
676
- msg = nil
677
-
678
- synchronize do
679
- wait_for_msgs_cond.wait(timeout)
680
-
681
- unless @pending_queue.empty?
682
- msg = @pending_queue.pop
683
- @pending_size -= msg.data.size
684
- end
685
- end
686
-
687
- # Check if the first message was a response saying that
688
- # there are no messages.
689
- if !msg.nil? && JS.is_status_msg(msg)
690
- case msg.header[JS::Header::Status]
691
- when JS::Status::NoMsgs
692
- # Make another request that does wait.
693
- next_req[:expires] = expires
694
- next_req.delete(:no_wait)
695
-
696
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
697
- when JS::Status::RequestTimeout
698
- raise NATS::Timeout.new("nats: fetch request timeout")
699
- else
700
- raise JS.from_msg(msg)
701
- end
702
- else
703
- msgs << msg unless msg.nil?
704
- end
705
-
706
- # Check if have not received yet a single message.
707
- duration = MonotonicTime.since(start_time)
708
- if msgs.empty? and duration > timeout
709
- raise NATS::Timeout.new("nats: fetch timeout")
710
- end
711
-
712
- needed = batch - msgs.count
713
- while needed > 0 and MonotonicTime.since(start_time) < timeout
714
- duration = MonotonicTime.since(start_time)
715
-
716
- # Wait for the rest of the messages.
717
- synchronize do
718
-
719
- # Wait until there is a message delivered.
720
- if @pending_queue.empty?
721
- deadline = timeout - duration
722
- wait_for_msgs_cond.wait(deadline) if deadline > 0
723
-
724
- duration = MonotonicTime.since(start_time)
725
- if msgs.empty? && @pending_queue.empty? and duration > timeout
726
- raise NATS::Timeout.new("nats: fetch timeout")
727
- end
728
- else
729
- msg = @pending_queue.pop
730
- @pending_size -= msg.data.size
731
-
732
- if JS.is_status_msg(msg)
733
- case msg.header[JS::Header::Status]
734
- when JS::Status::NoMsgs, JS::Status::RequestTimeout
735
- duration = MonotonicTime.since(start_time)
736
-
737
- if duration > timeout
738
- # Only received a subset of the messages.
739
- if !msgs.empty?
740
- return msgs
741
- else
742
- raise NATS::Timeout.new("nats: fetch timeout")
743
- end
744
- end
745
- else
746
- raise JS.from_msg(msg)
747
- end
748
-
749
- else
750
- # Add to the set of messages that will be returned.
751
- msgs << msg
752
- needed -= 1
753
- end
754
- end
755
- end # :end: synchronize
756
- end
757
- end
758
-
759
- msgs
760
- end
761
-
762
- # consumer_info retrieves the current status of the pull subscription consumer.
763
- # @param params [Hash] Options to customize API request.
764
- # @option params [Float] :timeout Time to wait for response.
765
- # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
766
- def consumer_info(params={})
767
- @jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
768
- end
769
- end
770
- private_constant :PullSubscription
771
-
772
- #######################################
773
- # #
774
- # JetStream Message and Ack Methods #
775
- # #
776
- #######################################
777
-
778
- # JetStream::Msg module includes the methods so that a regular NATS::Msg
779
- # can be enhanced with JetStream features like acking and metadata.
780
- module Msg
781
- module Ack
782
- # Ack types
783
- Ack = ("+ACK".freeze)
784
- Nak = ("-NAK".freeze)
785
- Progress = ("+WPI".freeze)
786
- Term = ("+TERM".freeze)
787
-
788
- Empty = (''.freeze)
789
- DotSep = ('.'.freeze)
790
- NoDomainName = ('_'.freeze)
791
-
792
- # Position
793
- Prefix0 = ('$JS'.freeze)
794
- Prefix1 = ('ACK'.freeze)
795
- Domain = 2
796
- AccHash = 3
797
- Stream = 4
798
- Consumer = 5
799
- NumDelivered = 6
800
- StreamSeq = 7
801
- ConsumerSeq = 8
802
- Timestamp = 9
803
- NumPending = 10
804
-
805
- # Subject without domain:
806
- # $JS.ACK.<stream>.<consumer>.<delivered>.<sseq>.<cseq>.<tm>.<pending>
807
- #
808
- V1TokenCounts = 9
809
-
810
- # Subject with domain:
811
- # $JS.ACK.<domain>.<account hash>.<stream>.<consumer>.<delivered>.<sseq>.<cseq>.<tm>.<pending>.<a token with a random value>
812
- #
813
- V2TokenCounts = 12
814
-
815
- SequencePair = Struct.new(:stream, :consumer)
816
- end
817
- private_constant :Ack
818
-
819
- class Metadata
820
- attr_reader :sequence, :num_delivered, :num_pending, :timestamp, :stream, :consumer, :domain
821
-
822
- def initialize(opts)
823
- @sequence = Ack::SequencePair.new(opts[Ack::StreamSeq].to_i, opts[Ack::ConsumerSeq].to_i)
824
- @domain = opts[Ack::Domain]
825
- @num_delivered = opts[Ack::NumDelivered].to_i
826
- @num_pending = opts[Ack::NumPending].to_i
827
- @timestamp = Time.at((opts[Ack::Timestamp].to_i / 1_000_000_000.0))
828
- @stream = opts[Ack::Stream]
829
- @consumer = opts[Ack::Consumer]
830
- # TODO: Not exposed in Go client either right now.
831
- # account = opts[Ack::AccHash]
832
- end
833
- end
834
-
835
- module AckMethods
836
- def ack(**params)
837
- ensure_is_acked_once!
838
-
839
- resp = if params[:timeout]
840
- @nc.request(@reply, Ack::Ack, **params)
841
- else
842
- @nc.publish(@reply, Ack::Ack)
843
- end
844
- @sub.synchronize { @ackd = true }
845
-
846
- resp
847
- end
848
-
849
- def ack_sync(**params)
850
- ensure_is_acked_once!
851
-
852
- params[:timeout] ||= 0.5
853
- resp = @nc.request(@reply, Ack::Ack, **params)
854
- @sub.synchronize { @ackd = true }
855
-
856
- resp
857
- end
858
-
859
- def nak(**params)
860
- ensure_is_acked_once!
861
-
862
- resp = if params[:timeout]
863
- @nc.request(@reply, Ack::Nak, **params)
864
- else
865
- @nc.publish(@reply, Ack::Nak)
866
- end
867
- @sub.synchronize { @ackd = true }
868
-
869
- resp
870
- end
871
-
872
- def term(**params)
873
- ensure_is_acked_once!
874
-
875
- resp = if params[:timeout]
876
- @nc.request(@reply, Ack::Term, **params)
877
- else
878
- @nc.publish(@reply, Ack::Term)
879
- end
880
- @sub.synchronize { @ackd = true }
881
-
882
- resp
883
- end
884
-
885
- def in_progress(**params)
886
- params[:timeout] ? @nc.request(@reply, Ack::Progress, **params) : @nc.publish(@reply, Ack::Progress)
887
- end
888
-
889
- def metadata
890
- @meta ||= parse_metadata(reply)
891
- end
892
-
893
- private
894
-
895
- def ensure_is_acked_once!
896
- @sub.synchronize do
897
- if @ackd
898
- raise JetStream::Error::MsgAlreadyAckd.new("nats: message was already acknowledged: #{self}")
899
- end
900
- end
901
- end
902
-
903
- def parse_metadata(reply)
904
- tokens = reply.split(Ack::DotSep)
905
- n = tokens.count
906
-
907
- case
908
- when n < Ack::V1TokenCounts || (n > Ack::V1TokenCounts and n < Ack::V2TokenCounts)
909
- raise NotJSMessage.new("nats: not a jetstream message")
910
- when tokens[0] != Ack::Prefix0 || tokens[1] != Ack::Prefix1
911
- raise NotJSMessage.new("nats: not a jetstream message")
912
- when n == Ack::V1TokenCounts
913
- tokens.insert(Ack::Domain, Ack::Empty)
914
- tokens.insert(Ack::AccHash, Ack::Empty)
915
- when tokens[Ack::Domain] == Ack::NoDomainName
916
- tokens[Ack::Domain] = Ack::Empty
917
- end
918
-
919
- Metadata.new(tokens)
920
- end
921
- end
922
- end
923
-
924
- ####################################
925
- # #
926
- # JetStream Configuration Options #
927
- # #
928
- ####################################
929
-
930
- # Misc internal functions to support JS API.
931
- # @private
932
- module JS
933
- DefaultAPIPrefix = ("$JS.API".freeze)
934
-
935
- module Status
936
- CtrlMsg = ("100".freeze)
937
- NoMsgs = ("404".freeze)
938
- NotFound = ("404".freeze)
939
- RequestTimeout = ("408".freeze)
940
- ServiceUnavailable = ("503".freeze)
941
- end
942
-
943
- module Header
944
- Status = ("Status".freeze)
945
- Desc = ("Description".freeze)
946
- MsgID = ("Nats-Msg-Id".freeze)
947
- ExpectedStream = ("Nats-Expected-Stream".freeze)
948
- ExpectedLastSeq = ("Nats-Expected-Last-Sequence".freeze)
949
- ExpectedLastSubjSeq = ("Nats-Expected-Last-Subject-Sequence".freeze)
950
- ExpectedLastMsgID = ("Nats-Expected-Last-Msg-Id".freeze)
951
- LastConsumerSeq = ("Nats-Last-Consumer".freeze)
952
- LastStreamSeq = ("Nats-Last-Stream".freeze)
953
- end
954
-
955
- module Config
956
- # AckPolicy
957
- AckExplicit = ("explicit".freeze)
958
- AckAll = ("all".freeze)
959
- AckNone = ("none".freeze)
960
- end
961
-
962
- class Sub
963
- attr_reader :js, :stream, :consumer, :nms
964
-
965
- def initialize(opts={})
966
- @js = opts[:js]
967
- @stream = opts[:stream]
968
- @consumer = opts[:consumer]
969
- @nms = opts[:nms]
970
- end
971
- end
972
-
973
- class << self
974
- def next_req_to_json(next_req)
975
- req = {}
976
- req[:batch] = next_req[:batch]
977
- req[:expires] = next_req[:expires].to_i if next_req[:expires]
978
- req[:no_wait] = next_req[:no_wait] if next_req[:no_wait]
979
- req.to_json
980
- end
981
-
982
- def is_status_msg(msg)
983
- return (!msg.nil? and (!msg.header.nil? and msg.header[Header::Status]))
984
- end
985
-
986
- # check_503_error raises exception when a NATS::Msg has a 503 status header.
987
- # @param msg [NATS::Msg] The message with status headers.
988
- # @raise [NATS::JetStream::Error::ServiceUnavailable]
989
- def check_503_error(msg)
990
- return if msg.nil? or msg.header.nil?
991
- if msg.header[Header::Status] == Status::ServiceUnavailable
992
- raise ::NATS::JetStream::Error::ServiceUnavailable
993
- end
994
- end
995
-
996
- # from_msg takes a plain NATS::Msg and checks its headers to confirm
997
- # if it was an error:
998
- #
999
- # msg.header={"Status"=>"503"})
1000
- # msg.header={"Status"=>"408", "Description"=>"Request Timeout"})
1001
- #
1002
- # @param msg [NATS::Msg] The message with status headers.
1003
- # @return [NATS::JetStream::API::Error]
1004
- def from_msg(msg)
1005
- check_503_error(msg)
1006
- code = msg.header[JS::Header::Status]
1007
- desc = msg.header[JS::Header::Desc]
1008
- return ::NATS::JetStream::API::Error.new({code: code, description: desc})
1009
- end
1010
-
1011
- # from_error takes an API response that errored and maps the error
1012
- # into a JetStream error type based on the status and error code.
1013
- def from_error(err)
1014
- return unless err
1015
- case err[:code]
1016
- when 503
1017
- ::NATS::JetStream::Error::ServiceUnavailable.new(err)
1018
- when 500
1019
- ::NATS::JetStream::Error::ServerError.new(err)
1020
- when 404
1021
- case err[:err_code]
1022
- when 10059
1023
- ::NATS::JetStream::Error::StreamNotFound.new(err)
1024
- when 10014
1025
- ::NATS::JetStream::Error::ConsumerNotFound.new(err)
1026
- else
1027
- ::NATS::JetStream::Error::NotFound.new(err)
1028
- end
1029
- when 400
1030
- ::NATS::JetStream::Error::BadRequest.new(err)
1031
- else
1032
- ::NATS::JetStream::API::Error.new(err)
1033
- end
1034
- end
1035
- end
1036
- end
1037
- private_constant :JS
1038
-
1039
- #####################
1040
- # #
1041
- # JetStream Errors #
1042
- # #
1043
- #####################
1044
-
1045
- # Error is any error that may arise when interacting with JetStream.
1046
- class Error < Error
1047
-
1048
- # When there is a NATS::IO::NoResponders error after making a publish request.
1049
- class NoStreamResponse < Error; end
1050
-
1051
- # When an invalid durable or consumer name was attempted to be used.
1052
- class InvalidDurableName < Error; end
1053
-
1054
- # When an ack not longer valid.
1055
- class InvalidJSAck < Error; end
1056
-
1057
- # When an ack has already been acked.
1058
- class MsgAlreadyAckd < Error; end
1059
-
1060
- # When the delivered message does not behave as a message delivered by JetStream,
1061
- # for example when the ack reply has unrecognizable fields.
1062
- class NotJSMessage < Error; end
1063
-
1064
- # When the stream name is invalid.
1065
- class InvalidStreamName < Error; end
1066
-
1067
- # When the consumer name is invalid.
1068
- class InvalidConsumerName < Error; end
1069
-
1070
- # When the server responds with an error from the JetStream API.
1071
- class APIError < Error
1072
- attr_reader :code, :err_code, :description, :stream, :seq
1073
-
1074
- def initialize(params={})
1075
- @code = params[:code]
1076
- @err_code = params[:err_code]
1077
- @description = params[:description]
1078
- @stream = params[:stream]
1079
- @seq = params[:seq]
1080
- end
1081
-
1082
- def to_s
1083
- "#{@description} (status_code=#{@code}, err_code=#{@err_code})"
1084
- end
1085
- end
1086
-
1087
- # When JetStream is not currently available, this could be due to JetStream
1088
- # not being enabled or temporarily unavailable due to a leader election when
1089
- # running in cluster mode.
1090
- # This condition is represented with a message that has 503 status code header.
1091
- class ServiceUnavailable < APIError
1092
- def initialize(params={})
1093
- super(params)
1094
- @code ||= 503
1095
- end
1096
- end
1097
-
1098
- # When there is a hard failure in the JetStream.
1099
- # This condition is represented with a message that has 500 status code header.
1100
- class ServerError < APIError
1101
- def initialize(params={})
1102
- super(params)
1103
- @code ||= 500
1104
- end
1105
- end
1106
-
1107
- # When a JetStream object was not found.
1108
- # This condition is represented with a message that has 404 status code header.
1109
- class NotFound < APIError
1110
- def initialize(params={})
1111
- super(params)
1112
- @code ||= 404
1113
- end
1114
- end
1115
-
1116
- # When the stream is not found.
1117
- class StreamNotFound < NotFound; end
1118
-
1119
- # When the consumer or durable is not found by name.
1120
- class ConsumerNotFound < NotFound; end
1121
-
1122
- # When the JetStream client makes an invalid request.
1123
- # This condition is represented with a message that has 400 status code header.
1124
- class BadRequest < APIError
1125
- def initialize(params={})
1126
- super(params)
1127
- @code ||= 400
1128
- end
1129
- end
1130
- end
1131
-
1132
- #######################
1133
- # #
1134
- # JetStream API Types #
1135
- # #
1136
- #######################
1137
-
1138
- # JetStream::API are the types used to interact with the JetStream API.
1139
- module API
1140
- # When the server responds with an error from the JetStream API.
1141
- Error = ::NATS::JetStream::Error::APIError
1142
-
1143
- # SequenceInfo is a pair of consumer and stream sequence and last activity.
1144
- # @!attribute consumer_seq
1145
- # @return [Integer] The consumer sequence.
1146
- # @!attribute stream_seq
1147
- # @return [Integer] The stream sequence.
1148
- SequenceInfo = Struct.new(:consumer_seq, :stream_seq, :last_active,
1149
- keyword_init: true) do
1150
- def initialize(opts={})
1151
- # Filter unrecognized fields and freeze.
1152
- rem = opts.keys - members
1153
- opts.delete_if { |k| rem.include?(k) }
1154
- super(opts)
1155
- freeze
1156
- end
1157
- end
1158
-
1159
- # ConsumerInfo is the current status of a JetStream consumer.
1160
- #
1161
- # @!attribute stream_name
1162
- # @return [String] name of the stream to which the consumer belongs.
1163
- # @!attribute name
1164
- # @return [String] name of the consumer.
1165
- # @!attribute created
1166
- # @return [String] time when the consumer was created.
1167
- # @!attribute config
1168
- # @return [ConsumerConfig] consumer configuration.
1169
- # @!attribute delivered
1170
- # @return [SequenceInfo]
1171
- # @!attribute ack_floor
1172
- # @return [SequenceInfo]
1173
- # @!attribute num_ack_pending
1174
- # @return [Integer]
1175
- # @!attribute num_redelivered
1176
- # @return [Integer]
1177
- # @!attribute num_waiting
1178
- # @return [Integer]
1179
- # @!attribute num_pending
1180
- # @return [Integer]
1181
- # @!attribute cluster
1182
- # @return [Hash]
1183
- ConsumerInfo = Struct.new(:type, :stream_name, :name, :created,
1184
- :config, :delivered, :ack_floor,
1185
- :num_ack_pending, :num_redelivered, :num_waiting,
1186
- :num_pending, :cluster, :push_bound,
1187
- keyword_init: true) do
1188
- def initialize(opts={})
1189
- opts[:created] = Time.parse(opts[:created])
1190
- opts[:ack_floor] = SequenceInfo.new(opts[:ack_floor])
1191
- opts[:delivered] = SequenceInfo.new(opts[:delivered])
1192
- opts[:config][:ack_wait] = opts[:config][:ack_wait] / ::NATS::NANOSECONDS
1193
- opts[:config] = ConsumerConfig.new(opts[:config])
1194
- opts.delete(:cluster)
1195
- # Filter unrecognized fields just in case.
1196
- rem = opts.keys - members
1197
- opts.delete_if { |k| rem.include?(k) }
1198
- super(opts)
1199
- freeze
1200
- end
1201
- end
1202
-
1203
- # ConsumerConfig is the consumer configuration.
1204
- #
1205
- # @!attribute durable_name
1206
- # @return [String]
1207
- # @!attribute deliver_policy
1208
- # @return [String]
1209
- # @!attribute ack_policy
1210
- # @return [String]
1211
- # @!attribute ack_wait
1212
- # @return [Integer]
1213
- # @!attribute max_deliver
1214
- # @return [Integer]
1215
- # @!attribute replay_policy
1216
- # @return [String]
1217
- # @!attribute max_waiting
1218
- # @return [Integer]
1219
- # @!attribute max_ack_pending
1220
- # @return [Integer]
1221
- ConsumerConfig = Struct.new(:name, :durable_name, :description,
1222
- :deliver_policy, :opt_start_seq, :opt_start_time,
1223
- :ack_policy, :ack_wait, :max_deliver, :backoff,
1224
- :filter_subject, :replay_policy, :rate_limit_bps,
1225
- :sample_freq, :max_waiting, :max_ack_pending,
1226
- :flow_control, :idle_heartbeat, :headers_only,
1227
-
1228
- # Pull based options
1229
- :max_batch, :max_expires,
1230
- # Push based consumers
1231
- :deliver_subject, :deliver_group,
1232
- # Ephemeral inactivity threshold
1233
- :inactive_threshold,
1234
- # Generally inherited by parent stream and other markers,
1235
- # now can be configured directly.
1236
- :num_replicas,
1237
- # Force memory storage
1238
- :mem_storage,
1239
- keyword_init: true) do
1240
- def initialize(opts={})
1241
- # Filter unrecognized fields just in case.
1242
- rem = opts.keys - members
1243
- opts.delete_if { |k| rem.include?(k) }
1244
- super(opts)
1245
- end
1246
-
1247
- def to_json(*args)
1248
- config = self.to_h
1249
- config.delete_if { |_k, v| v.nil? }
1250
- config.to_json(*args)
1251
- end
1252
- end
1253
-
1254
- # StreamConfig represents the configuration of a stream from JetStream.
1255
- #
1256
- # @!attribute type
1257
- # @return [String]
1258
- # @!attribute config
1259
- # @return [Hash]
1260
- # @!attribute created
1261
- # @return [String]
1262
- # @!attribute state
1263
- # @return [StreamState]
1264
- # @!attribute did_create
1265
- # @return [Boolean]
1266
- # @!attribute name
1267
- # @return [String]
1268
- # @!attribute subjects
1269
- # @return [Array]
1270
- # @!attribute retention
1271
- # @return [String]
1272
- # @!attribute max_consumers
1273
- # @return [Integer]
1274
- # @!attribute max_msgs
1275
- # @return [Integer]
1276
- # @!attribute max_bytes
1277
- # @return [Integer]
1278
- # @!attribute max_age
1279
- # @return [Integer]
1280
- # @!attribute max_msgs_per_subject
1281
- # @return [Integer]
1282
- # @!attribute max_msg_size
1283
- # @return [Integer]
1284
- # @!attribute discard
1285
- # @return [String]
1286
- # @!attribute storage
1287
- # @return [String]
1288
- # @!attribute num_replicas
1289
- # @return [Integer]
1290
- # @!attribute duplicate_window
1291
- # @return [Integer]
1292
- StreamConfig = Struct.new(
1293
- :name,
1294
- :description,
1295
- :subjects,
1296
- :retention,
1297
- :max_consumers,
1298
- :max_msgs,
1299
- :max_bytes,
1300
- :discard,
1301
- :max_age,
1302
- :max_msgs_per_subject,
1303
- :max_msg_size,
1304
- :storage,
1305
- :num_replicas,
1306
- :no_ack,
1307
- :duplicate_window,
1308
- :placement,
1309
- :mirror,
1310
- :sources,
1311
- :sealed,
1312
- :deny_delete,
1313
- :deny_purge,
1314
- :allow_rollup_hdrs,
1315
- :republish,
1316
- :allow_direct,
1317
- :mirror_direct,
1318
- keyword_init: true) do
1319
- def initialize(opts={})
1320
- # Filter unrecognized fields just in case.
1321
- rem = opts.keys - members
1322
- opts.delete_if { |k| rem.include?(k) }
1323
- super(opts)
1324
- end
1325
-
1326
- def to_json(*args)
1327
- config = self.to_h
1328
- config.delete_if { |_k, v| v.nil? }
1329
- config.to_json(*args)
1330
- end
1331
- end
1332
-
1333
- # StreamInfo is the info about a stream from JetStream.
1334
- #
1335
- # @!attribute type
1336
- # @return [String]
1337
- # @!attribute config
1338
- # @return [Hash]
1339
- # @!attribute created
1340
- # @return [String]
1341
- # @!attribute state
1342
- # @return [Hash]
1343
- # @!attribute domain
1344
- # @return [String]
1345
- StreamInfo = Struct.new(:type, :config, :created, :state, :domain,
1346
- keyword_init: true) do
1347
- def initialize(opts={})
1348
- opts[:config] = StreamConfig.new(opts[:config])
1349
- opts[:state] = StreamState.new(opts[:state])
1350
- opts[:created] = ::Time.parse(opts[:created])
1351
-
1352
- # Filter fields and freeze.
1353
- rem = opts.keys - members
1354
- opts.delete_if { |k| rem.include?(k) }
1355
- super(opts)
1356
- freeze
1357
- end
1358
- end
1359
-
1360
- # StreamState is the state of a stream.
1361
- #
1362
- # @!attribute messages
1363
- # @return [Integer]
1364
- # @!attribute bytes
1365
- # @return [Integer]
1366
- # @!attribute first_seq
1367
- # @return [Integer]
1368
- # @!attribute last_seq
1369
- # @return [Integer]
1370
- # @!attribute consumer_count
1371
- # @return [Integer]
1372
- StreamState = Struct.new(:messages, :bytes, :first_seq, :first_ts,
1373
- :last_seq, :last_ts, :consumer_count,
1374
- keyword_init: true) do
1375
- def initialize(opts={})
1376
- rem = opts.keys - members
1377
- opts.delete_if { |k| rem.include?(k) }
1378
- super(opts)
1379
- end
1380
- end
1381
-
1382
- # StreamCreateResponse is the response from the JetStream $JS.API.STREAM.CREATE API.
1383
- #
1384
- # @!attribute type
1385
- # @return [String]
1386
- # @!attribute config
1387
- # @return [StreamConfig]
1388
- # @!attribute created
1389
- # @return [String]
1390
- # @!attribute state
1391
- # @return [StreamState]
1392
- # @!attribute did_create
1393
- # @return [Boolean]
1394
- StreamCreateResponse = Struct.new(:type, :config, :created, :state, :did_create,
1395
- keyword_init: true) do
1396
- def initialize(opts={})
1397
- rem = opts.keys - members
1398
- opts.delete_if { |k| rem.include?(k) }
1399
- opts[:config] = StreamConfig.new(opts[:config])
1400
- opts[:state] = StreamState.new(opts[:state])
1401
- super(opts)
1402
- freeze
1403
- end
1404
- end
1405
-
1406
- RawStreamMsg = Struct.new(:subject, :seq, :data, :headers, keyword_init: true) do
1407
- def initialize(opts)
1408
- opts[:data] = Base64.decode64(opts[:data]) if opts[:data]
1409
- if opts[:hdrs]
1410
- header = Base64.decode64(opts[:hdrs])
1411
- hdr = {}
1412
- lines = header.lines
1413
- lines.slice(1, header.size).each do |line|
1414
- line.rstrip!
1415
- next if line.empty?
1416
- key, value = line.strip.split(/\s*:\s*/, 2)
1417
- hdr[key] = value
1418
- end
1419
- opts[:headers] = hdr
1420
- end
1421
-
1422
- # Filter out members not present.
1423
- rem = opts.keys - members
1424
- opts.delete_if { |k| rem.include?(k) }
1425
- super(opts)
1426
- end
1427
-
1428
- def sequence
1429
- self.seq
1430
- end
1431
- end
1432
- end
1433
- end
1434
- end