nats-pure 2.1.2 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
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 KeyValue
19
+ class Error < NATS::Error; end
20
+
21
+ # When a key is not found.
22
+ class KeyNotFoundError < Error
23
+ attr_reader :entry, :op
24
+ def initialize(params={})
25
+ @entry = params[:entry]
26
+ @op = params[:op]
27
+ @message = params[:message]
28
+ end
29
+
30
+ def to_s
31
+ msg = "nats: key not found"
32
+ msg = "#{msg}: #{@message}" if @message
33
+ msg
34
+ end
35
+ end
36
+
37
+ # When a key is not found because it was deleted.
38
+ class KeyDeletedError < KeyNotFoundError
39
+ def to_s
40
+ "nats: key was deleted"
41
+ end
42
+ end
43
+
44
+ # When there was no bucket present.
45
+ class BucketNotFoundError < Error; end
46
+
47
+ # When it is an invalid bucket.
48
+ class BadBucketError < Error; end
49
+
50
+ # When the result is an unexpected sequence.
51
+ class KeyWrongLastSequenceError < Error
52
+ def initialize(msg)
53
+ @msg = msg
54
+ end
55
+ def to_s
56
+ "nats: #{@msg}"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,89 @@
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 Manager
18
+ def key_value(bucket)
19
+ stream = "KV_#{bucket}"
20
+ begin
21
+ si = stream_info(stream)
22
+ rescue NATS::JetStream::Error::NotFound
23
+ raise BucketNotFoundError.new("nats: bucket not found")
24
+ end
25
+ if si.config.max_msgs_per_subject < 1
26
+ raise BadBucketError.new("nats: bad bucket")
27
+ end
28
+
29
+ KeyValue.new(
30
+ name: bucket,
31
+ stream: stream,
32
+ pre: "$KV.#{bucket}.",
33
+ js: self,
34
+ direct: si.config.allow_direct
35
+ )
36
+ end
37
+
38
+ def create_key_value(config)
39
+ config = if not config.is_a?(KeyValue::API::KeyValueConfig)
40
+ KeyValue::API::KeyValueConfig.new(config)
41
+ else
42
+ config
43
+ end
44
+ config.history ||= 1
45
+ config.replicas ||= 1
46
+ duplicate_window = 2 * 60 # 2 minutes
47
+ if config.ttl
48
+ if config.ttl < duplicate_window
49
+ duplicate_window = config.ttl
50
+ end
51
+ config.ttl = config.ttl * ::NATS::NANOSECONDS
52
+ end
53
+
54
+ stream = JetStream::API::StreamConfig.new(
55
+ name: "KV_#{config.bucket}",
56
+ description: config.description,
57
+ subjects: ["$KV.#{config.bucket}.>"],
58
+ allow_direct: config.direct,
59
+ allow_rollup_hdrs: true,
60
+ deny_delete: true,
61
+ discard: "new",
62
+ duplicate_window: duplicate_window * ::NATS::NANOSECONDS,
63
+ max_age: config.ttl,
64
+ max_bytes: config.max_bytes,
65
+ max_consumers: -1,
66
+ max_msg_size: config.max_value_size,
67
+ max_msgs: -1,
68
+ max_msgs_per_subject: config.history,
69
+ num_replicas: config.replicas,
70
+ storage: config.storage,
71
+ republish: config.republish,
72
+ )
73
+
74
+ si = add_stream(stream)
75
+ KeyValue.new(
76
+ name: config.bucket,
77
+ stream: stream.name,
78
+ pre: "$KV.#{config.bucket}.",
79
+ js: self,
80
+ direct: si.config.allow_direct
81
+ )
82
+ end
83
+
84
+ def delete_key_value(bucket)
85
+ delete_stream("KV_#{bucket}")
86
+ end
87
+ end
88
+ end
89
+ end
data/lib/nats/io/kv.rb CHANGED
@@ -11,7 +11,11 @@
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
13
  #
14
- require_relative 'errors'
14
+
15
+ require_relative 'kv/api'
16
+ require_relative 'kv/bucket_status'
17
+ require_relative 'kv/errors'
18
+ require_relative 'kv/manager'
15
19
 
16
20
  module NATS
17
21
  class KeyValue
@@ -20,46 +24,140 @@ module NATS
20
24
  KV_PURGE = "PURGE"
21
25
  MSG_ROLLUP_SUBJECT = "sub"
22
26
  MSG_ROLLUP_ALL = "all"
27
+ ROLLUP = "Nats-Rollup"
23
28
 
24
29
  def initialize(opts={})
25
30
  @name = opts[:name]
26
31
  @stream = opts[:stream]
27
32
  @pre = opts[:pre]
28
33
  @js = opts[:js]
34
+ @direct = opts[:direct]
29
35
  end
30
36
 
31
- # When a key is not found because it was deleted.
32
- class KeyDeletedError < NATS::Error; end
37
+ # get returns the latest value for the key.
38
+ def get(key, params={})
39
+ entry = nil
40
+ begin
41
+ entry = _get(key, params)
42
+ rescue KeyDeletedError
43
+ raise KeyNotFoundError
44
+ end
33
45
 
34
- # When there was no bucket present.
35
- class BucketNotFoundError < NATS::Error; end
46
+ entry
47
+ end
36
48
 
37
- # When it is an invalid bucket.
38
- class BadBucketError < NATS::Error; end
49
+ def _get(key, params={})
50
+ msg = nil
51
+ subject = "#{@pre}#{key}"
52
+
53
+ if params[:revision]
54
+ msg = @js.get_msg(@stream,
55
+ seq: params[:revision],
56
+ direct: @direct)
57
+ else
58
+ msg = @js.get_msg(@stream,
59
+ subject: subject,
60
+ seq: params[:revision],
61
+ direct: @direct)
62
+ end
39
63
 
40
- # get returns the latest value for the key.
41
- def get(key)
42
- msg = @js.get_last_msg(@stream, "#{@pre}#{key}")
43
64
  entry = Entry.new(bucket: @name, key: key, value: msg.data, revision: msg.seq)
44
65
 
66
+ if subject != msg.subject
67
+ raise KeyNotFoundError.new(
68
+ entry: entry,
69
+ message: "expected '#{subject}', but got '#{msg.subject}'"
70
+ )
71
+ end
72
+
45
73
  if not msg.headers.nil?
46
74
  op = msg.headers[KV_OP]
47
- raise KeyDeletedError.new("nats: key was deleted") if op == KV_DEL or op == KV_PURGE
75
+ if op == KV_DEL or op == KV_PURGE
76
+ raise KeyDeletedError.new(entry: entry, op: op)
77
+ end
48
78
  end
49
79
 
50
80
  entry
81
+ rescue NATS::JetStream::Error::NotFound
82
+ raise KeyNotFoundError
51
83
  end
84
+ private :_get
52
85
 
53
86
  # put will place the new value for the key into the store
54
87
  # and return the revision number.
55
88
  def put(key, value)
56
- @js.publish("#{@pre}#{key}", value)
89
+ ack = @js.publish("#{@pre}#{key}", value)
90
+ ack.seq
91
+ end
92
+
93
+ # create will add the key/value pair iff it does not exist.
94
+ def create(key, value)
95
+ pa = nil
96
+ begin
97
+ pa = update(key, value, last: 0)
98
+ rescue KeyWrongLastSequenceError => err
99
+ # In case of attempting to recreate an already deleted key,
100
+ # the client would get a KeyWrongLastSequenceError. When this happens,
101
+ # it is needed to fetch latest revision number and attempt to update.
102
+ begin
103
+ # NOTE: This reimplements the following behavior from Go client.
104
+ #
105
+ # Since we have tombstones for DEL ops for watchers, this could be from that
106
+ # so we need to double check.
107
+ #
108
+ _get(key)
109
+
110
+ # No exception so not a deleted key, so reraise the original KeyWrongLastSequenceError.
111
+ # If it was deleted then the error exception will contain metadata
112
+ # to recreate using the last revision.
113
+ raise err
114
+ rescue KeyDeletedError => err
115
+ pa = update(key, value, last: err.entry.revision)
116
+ end
117
+ end
118
+
119
+ pa
120
+ end
121
+
122
+ EXPECTED_LAST_SUBJECT_SEQUENCE = "Nats-Expected-Last-Subject-Sequence"
123
+
124
+ # update will update the value iff the latest revision matches.
125
+ def update(key, value, params={})
126
+ hdrs = {}
127
+ last = (params[:last] ||= 0)
128
+ hdrs[EXPECTED_LAST_SUBJECT_SEQUENCE] = last.to_s
129
+ ack = nil
130
+ begin
131
+ ack = @js.publish("#{@pre}#{key}", value, header: hdrs)
132
+ rescue NATS::JetStream::Error::APIError => err
133
+ if err.err_code == 10071
134
+ raise KeyWrongLastSequenceError.new(err.description)
135
+ else
136
+ raise err
137
+ end
138
+ end
139
+
140
+ ack.seq
57
141
  end
58
142
 
59
143
  # delete will place a delete marker and remove all previous revisions.
60
- def delete(key)
144
+ def delete(key, params={})
61
145
  hdrs = {}
62
146
  hdrs[KV_OP] = KV_DEL
147
+ last = (params[:last] ||= 0)
148
+ if last > 0
149
+ hdrs[EXPECTED_LAST_SUBJECT_SEQUENCE] = last.to_s
150
+ end
151
+ ack = @js.publish("#{@pre}#{key}", header: hdrs)
152
+
153
+ ack.seq
154
+ end
155
+
156
+ # purge will remove the key and all revisions.
157
+ def purge(key)
158
+ hdrs = {}
159
+ hdrs[KV_OP] = KV_PURGE
160
+ hdrs[ROLLUP] = MSG_ROLLUP_SUBJECT
63
161
  @js.publish("#{@pre}#{key}", header: hdrs)
64
162
  end
65
163
 
@@ -69,104 +167,12 @@ module NATS
69
167
  BucketStatus.new(info, @name)
70
168
  end
71
169
 
72
- Entry = Struct.new(:bucket, :key, :value, :revision, keyword_init: true) do
170
+ Entry = Struct.new(:bucket, :key, :value, :revision, :delta, :created, :operation, keyword_init: true) do
73
171
  def initialize(opts={})
74
172
  rem = opts.keys - members
75
173
  opts.delete_if { |k| rem.include?(k) }
76
174
  super(opts)
77
175
  end
78
176
  end
79
-
80
- class BucketStatus
81
- attr_reader :bucket
82
-
83
- def initialize(info, bucket)
84
- @nfo = info
85
- @bucket = bucket
86
- end
87
-
88
- def values
89
- @nfo.state.messages
90
- end
91
-
92
- def history
93
- @nfo.config.max_msgs_per_subject
94
- end
95
-
96
- def ttl
97
- @nfo.config.max_age / 1_000_000_000
98
- end
99
- end
100
-
101
- module API
102
- KeyValueConfig = Struct.new(:bucket, :description, :max_value_size,
103
- :history, :ttl, :max_bytes, :storage, :replicas,
104
- keyword_init: true) do
105
- def initialize(opts={})
106
- rem = opts.keys - members
107
- opts.delete_if { |k| rem.include?(k) }
108
- super(opts)
109
- end
110
- end
111
- end
112
-
113
- module Manager
114
- def key_value(bucket)
115
- stream = "KV_#{bucket}"
116
- begin
117
- si = stream_info(stream)
118
- rescue NATS::JetStream::Error::NotFound
119
- raise BucketNotFoundError.new("nats: bucket not found")
120
- end
121
- if si.config.max_msgs_per_subject < 1
122
- raise BadBucketError.new("nats: bad bucket")
123
- end
124
-
125
- KeyValue.new(
126
- name: bucket,
127
- stream: stream,
128
- pre: "$KV.#{bucket}.",
129
- js: self,
130
- )
131
- end
132
-
133
- def create_key_value(config)
134
- config = if not config.is_a?(JetStream::API::StreamConfig)
135
- KeyValue::API::KeyValueConfig.new(config)
136
- else
137
- config
138
- end
139
- config.history ||= 1
140
- config.replicas ||= 1
141
- if config.ttl
142
- config.ttl = config.ttl * 1_000_000_000
143
- end
144
-
145
- stream = JetStream::API::StreamConfig.new(
146
- name: "KV_#{config.bucket}",
147
- subjects: ["$KV.#{config.bucket}.>"],
148
- max_msgs_per_subject: config.history,
149
- max_bytes: config.max_bytes,
150
- max_age: config.ttl,
151
- max_msg_size: config.max_value_size,
152
- storage: config.storage,
153
- num_replicas: config.replicas,
154
- allow_rollup_hdrs: true,
155
- deny_delete: true,
156
- )
157
- resp = add_stream(stream)
158
-
159
- KeyValue.new(
160
- name: config.bucket,
161
- stream: stream.name,
162
- pre: "$KV.#{config.bucket}.",
163
- js: self,
164
- )
165
- end
166
-
167
- def delete_key_value(bucket)
168
- delete_stream("KV_#{bucket}")
169
- end
170
- end
171
177
  end
172
178
  end
data/lib/nats/io/msg.rb CHANGED
@@ -11,7 +11,7 @@
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
13
  #
14
- require_relative 'js'
14
+ require_relative 'jetstream'
15
15
 
16
16
  module NATS
17
17
  class Msg
@@ -15,7 +15,7 @@
15
15
  module NATS
16
16
  module IO
17
17
  # VERSION is the version of the client announced on CONNECT to the server.
18
- VERSION = "2.1.2".freeze
18
+ VERSION = "2.2.1".freeze
19
19
 
20
20
  # LANG is the lang runtime of the client announced on CONNECT to the server.
21
21
  LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nats-pure
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Waldemar Quevedo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-29 00:00:00.000000000 Z
11
+ date: 2022-11-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: NATS is an open-source, high-performance, lightweight cloud messaging
14
14
  system.
@@ -22,8 +22,26 @@ files:
22
22
  - lib/nats/client.rb
23
23
  - lib/nats/io/client.rb
24
24
  - lib/nats/io/errors.rb
25
- - lib/nats/io/js.rb
25
+ - lib/nats/io/jetstream.rb
26
+ - lib/nats/io/jetstream/api.rb
27
+ - lib/nats/io/jetstream/errors.rb
28
+ - lib/nats/io/jetstream/js.rb
29
+ - lib/nats/io/jetstream/js/config.rb
30
+ - lib/nats/io/jetstream/js/header.rb
31
+ - lib/nats/io/jetstream/js/status.rb
32
+ - lib/nats/io/jetstream/js/sub.rb
33
+ - lib/nats/io/jetstream/manager.rb
34
+ - lib/nats/io/jetstream/msg.rb
35
+ - lib/nats/io/jetstream/msg/ack.rb
36
+ - lib/nats/io/jetstream/msg/ack_methods.rb
37
+ - lib/nats/io/jetstream/msg/metadata.rb
38
+ - lib/nats/io/jetstream/pull_subscription.rb
39
+ - lib/nats/io/jetstream/push_subscription.rb
26
40
  - lib/nats/io/kv.rb
41
+ - lib/nats/io/kv/api.rb
42
+ - lib/nats/io/kv/bucket_status.rb
43
+ - lib/nats/io/kv/errors.rb
44
+ - lib/nats/io/kv/manager.rb
27
45
  - lib/nats/io/msg.rb
28
46
  - lib/nats/io/parser.rb
29
47
  - lib/nats/io/subscription.rb