nats-pure 2.1.2 → 2.2.1

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.
@@ -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