prosody 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.config/rail.toml +26 -0
- data/.release-please-manifest.json +1 -1
- data/.ruby-version +1 -1
- data/.taplo.toml +1 -1
- data/AGENTS.md +29 -15
- data/CHANGELOG.md +7 -0
- data/CONFIGURATION.md +19 -16
- data/Cargo.lock +254 -235
- data/Cargo.toml +11 -8
- data/README.md +100 -23
- data/examples/keyed_state.rb +10 -2
- data/examples/keyed_state.rbs +1 -0
- data/ext/prosody/Cargo.toml +1 -1
- data/ext/prosody/src/admin.rs +36 -36
- data/ext/prosody/src/bridge/mod.rs +5 -10
- data/ext/prosody/src/client/config/connections.rs +170 -0
- data/ext/prosody/src/client/config/middleware.rs +184 -0
- data/ext/prosody/src/client/config/mod.rs +396 -0
- data/ext/prosody/src/client/config/state.rs +323 -0
- data/ext/prosody/src/client/mod.rs +31 -117
- data/ext/prosody/src/client/readers.rs +106 -0
- data/ext/prosody/src/client/request.rs +2 -2
- data/ext/prosody/src/client/support.rs +13 -32
- data/ext/prosody/src/gvl.rs +8 -6
- data/ext/prosody/src/handler/{context.rs → context/mod.rs} +36 -150
- data/ext/prosody/src/handler/context/vending.rs +138 -0
- data/ext/prosody/src/handler/message.rs +36 -0
- data/ext/prosody/src/handler/mod.rs +18 -8
- data/ext/prosody/src/handler/state/deque.rs +144 -0
- data/ext/prosody/src/handler/state/mod.rs +163 -268
- data/ext/prosody/src/handler/state/query.rs +264 -0
- data/ext/prosody/src/handler/state/registration.rs +15 -86
- data/ext/prosody/src/handler/state/scan.rs +33 -141
- data/ext/prosody/src/handler/state/set.rs +98 -0
- data/ext/prosody/src/lib.rs +54 -36
- data/ext/prosody/src/logging.rs +6 -6
- data/ext/prosody/src/published.rs +171 -152
- data/ext/prosody/src/scheduler/result.rs +2 -1
- data/ext/prosody/src/util.rs +65 -3
- data/lib/prosody/client.rb +32 -0
- data/lib/prosody/configuration.rb +20 -12
- data/lib/prosody/demand.rb +27 -0
- data/lib/prosody/native_stubs/client.rb +157 -0
- data/lib/prosody/native_stubs/context.rb +178 -0
- data/lib/prosody/native_stubs/message.rb +133 -0
- data/lib/prosody/native_stubs.rb +11 -956
- data/lib/prosody/state/deque.rb +253 -0
- data/lib/prosody/state/map.rb +313 -0
- data/lib/prosody/state/set.rb +121 -0
- data/lib/prosody/state/value.rb +58 -0
- data/lib/prosody/state.rb +157 -677
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +2 -1
- data/sig/configuration.rbs +19 -10
- data/sig/prosody.rbs +37 -4
- data/sig/published.rbs +102 -0
- data/sig/state.rbs +109 -122
- data/typecheck/payload_types.rb +12 -0
- data/typecheck/payload_types.rbs +1 -0
- metadata +30 -11
- data/ext/prosody/src/client/config.rs +0 -1300
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The deque collection: the owned {Prosody::DequeState} handle and the
|
|
4
|
+
# {Prosody::PublishedDeque} reader.
|
|
5
|
+
|
|
6
|
+
module Prosody
|
|
7
|
+
# A deque keyed-state handle.
|
|
8
|
+
#
|
|
9
|
+
# Traversal is explicit: {#each}/{#reverse_each} yield single elements over a
|
|
10
|
+
# native scan, closing the scan via `ensure`. No aggregate-mixin methods are
|
|
11
|
+
# provided.
|
|
12
|
+
class DequeState < State::Handle
|
|
13
|
+
include State::Scanning
|
|
14
|
+
|
|
15
|
+
# Appends an element at the back.
|
|
16
|
+
#
|
|
17
|
+
# @param value [Object] the element (JSON, or a message)
|
|
18
|
+
# @return [void]
|
|
19
|
+
# @raise [PermanentStateError] if `value` is `nil` (use {#clear} to delete)
|
|
20
|
+
def push(value) = @native.push_back(value)
|
|
21
|
+
|
|
22
|
+
# Prepends an element at the front.
|
|
23
|
+
#
|
|
24
|
+
# @param value [Object] the element (JSON, or a message)
|
|
25
|
+
# @return [void]
|
|
26
|
+
# @raise [PermanentStateError] if `value` is `nil` (use {#clear} to delete)
|
|
27
|
+
def unshift(value) = @native.push_front(value)
|
|
28
|
+
|
|
29
|
+
# Removes and returns the back element.
|
|
30
|
+
#
|
|
31
|
+
# @return [Object, nil] the removed element, or `nil` when empty
|
|
32
|
+
def pop = @native.pop_back
|
|
33
|
+
|
|
34
|
+
# Removes and returns the front element.
|
|
35
|
+
#
|
|
36
|
+
# @return [Object, nil] the removed element, or `nil` when empty
|
|
37
|
+
def shift = @native.pop_front
|
|
38
|
+
|
|
39
|
+
# The number of live elements.
|
|
40
|
+
#
|
|
41
|
+
# @return [Integer]
|
|
42
|
+
def length = @native.len
|
|
43
|
+
|
|
44
|
+
alias_method :size, :length
|
|
45
|
+
|
|
46
|
+
# Whether the deque holds no live elements.
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def empty? = @native.is_empty
|
|
50
|
+
|
|
51
|
+
# Removes every element.
|
|
52
|
+
#
|
|
53
|
+
# @return [void]
|
|
54
|
+
def clear = @native.clear
|
|
55
|
+
|
|
56
|
+
# Reads the element at `index`, resolving negatives Array-style (mirrors
|
|
57
|
+
# +Array#[]+'s read domain, without the indexer). A non-negative index
|
|
58
|
+
# reads from the front; `-1` is the back element, `-n` the nth from the end.
|
|
59
|
+
# `-1` fast-paths through {#last} (no length read); other negatives resolve
|
|
60
|
+
# against the current length (one length read + one element read),
|
|
61
|
+
# consistent because the deque has a single writer per attempt.
|
|
62
|
+
#
|
|
63
|
+
# @param index [Integer] the position (negative counts from the back)
|
|
64
|
+
# @return [Object, nil] the element, or `nil` outside the bounds
|
|
65
|
+
# @raise [TransientStateError] if `index` is not an Integer
|
|
66
|
+
def get(index)
|
|
67
|
+
unless index.is_a?(Integer)
|
|
68
|
+
raise TransientStateError, "get: index must be an Integer, got #{index.inspect}"
|
|
69
|
+
end
|
|
70
|
+
index.negative? ? at_negative(index) : @native.get(index)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Traverses the live elements in index order.
|
|
74
|
+
#
|
|
75
|
+
# Without a block, returns an {Enumerator} over the native scan. Each step
|
|
76
|
+
# fiber-yields; the scan is closed via `ensure` on stop or exception.
|
|
77
|
+
#
|
|
78
|
+
# Accepts the position keywords documented on {State::Scanning}. Positions
|
|
79
|
+
# count from the front and must be non-negative. To read from the back,
|
|
80
|
+
# use {#reverse_each} with `limit:`.
|
|
81
|
+
#
|
|
82
|
+
# @param query [Hash] optional `from:`, `after:`, `to:`, `before:`,
|
|
83
|
+
# `range:`, and `limit:` keywords
|
|
84
|
+
# @yieldparam element [Object]
|
|
85
|
+
# @return [Enumerator, void]
|
|
86
|
+
# @raise [ArgumentError, TypeError] if a query keyword is invalid
|
|
87
|
+
def each(**query, &block) = traverse(:scan, :forward, query, &block)
|
|
88
|
+
|
|
89
|
+
# Traverses the live elements in reverse index order.
|
|
90
|
+
#
|
|
91
|
+
# @param query [Hash] optional position keywords, as on {#each}
|
|
92
|
+
# @yieldparam element [Object]
|
|
93
|
+
# @return [Enumerator, void]
|
|
94
|
+
def reverse_each(**query, &block) = traverse(:scan, :backward, query, &block)
|
|
95
|
+
|
|
96
|
+
# --- idiomatic Array-style conveniences -----------------------------
|
|
97
|
+
# Composed from the canonical ops above; bounded reads only (no +to_a+,
|
|
98
|
+
# +map+, +sort+, or +Enumerable+ that would materialize the whole deque).
|
|
99
|
+
#
|
|
100
|
+
# Deliberately NOT provided: +[]+ and +at+. This is a remote deque; +get+
|
|
101
|
+
# and +fetch+ accept a single +Integer+ index (negatives resolve from the
|
|
102
|
+
# back, Array-style), but wearing +Array+'s +[]+/+at+ would invite a range
|
|
103
|
+
# read (+deque[0..2]+) that cannot be honored. Use the explicit {#get}, or
|
|
104
|
+
# {#first}/{#last} for the ends.
|
|
105
|
+
|
|
106
|
+
# Prepends +value+, returning +self+ for chaining (mirrors +Array#prepend+).
|
|
107
|
+
# A wrapper, not an alias: the native write returns +nil+.
|
|
108
|
+
#
|
|
109
|
+
# @param value [Object]
|
|
110
|
+
# @return [self]
|
|
111
|
+
def prepend(value)
|
|
112
|
+
unshift(value)
|
|
113
|
+
self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Appends +value+, returning +self+ for chaining (mirrors +Array#append+).
|
|
117
|
+
# A wrapper, not an alias: the native write returns +nil+.
|
|
118
|
+
#
|
|
119
|
+
# @param value [Object]
|
|
120
|
+
# @return [self]
|
|
121
|
+
def append(value)
|
|
122
|
+
push(value)
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Appends +value+ and returns +self+ (mirrors +Array#<<+). Alias of
|
|
127
|
+
# {#append}.
|
|
128
|
+
alias_method :<<, :append
|
|
129
|
+
|
|
130
|
+
# The front element, or +nil+ when empty (mirrors +Array#first+). An
|
|
131
|
+
# endpoint-slot read in one round trip (no length read). Under a TTL an
|
|
132
|
+
# expired front slot yields +nil+ even when live interior elements remain —
|
|
133
|
+
# a peek never searches inward.
|
|
134
|
+
#
|
|
135
|
+
# @return [Object, nil]
|
|
136
|
+
def first = @native.peek_front
|
|
137
|
+
|
|
138
|
+
# The back element, or +nil+ when empty (mirrors +Array#last+). An
|
|
139
|
+
# endpoint-slot read in one round trip (no length read); same TTL-hole
|
|
140
|
+
# semantics as {#first}.
|
|
141
|
+
#
|
|
142
|
+
# @return [Object, nil]
|
|
143
|
+
def last = @native.peek_back
|
|
144
|
+
|
|
145
|
+
# Reads the element at +index+, raising or defaulting when out of range
|
|
146
|
+
# (mirrors +Array#fetch+). A +nil+ result is unambiguously "out of range"
|
|
147
|
+
# under the null ban. A negative index counts from the back, as in {#get}.
|
|
148
|
+
# A non-Integer index raises {TransientStateError}.
|
|
149
|
+
#
|
|
150
|
+
# @param index [Integer] the position (negative counts from the back)
|
|
151
|
+
# @param default [Object] returned when +index+ is out of range
|
|
152
|
+
# @yieldparam index [Integer] called (instead of +default+) when out of range
|
|
153
|
+
# @return [Object]
|
|
154
|
+
# @raise [IndexError] when out of range and no default or block is given
|
|
155
|
+
# @raise [TransientStateError] if +index+ is not an Integer
|
|
156
|
+
def fetch(index, *default, &block)
|
|
157
|
+
if default.length > 1
|
|
158
|
+
raise ArgumentError, "wrong number of arguments (given #{default.length + 1}, expected 1..2)"
|
|
159
|
+
end
|
|
160
|
+
unless index.is_a?(Integer)
|
|
161
|
+
raise TransientStateError, "fetch: index must be an Integer, got #{index.inspect}"
|
|
162
|
+
end
|
|
163
|
+
warn "warning: block supersedes default value argument" if block && !default.empty?
|
|
164
|
+
|
|
165
|
+
value = get(index)
|
|
166
|
+
case value
|
|
167
|
+
when nil
|
|
168
|
+
return block.call(index) if block
|
|
169
|
+
raise IndexError, "index #{index} outside deque bounds" if default.empty?
|
|
170
|
+
|
|
171
|
+
# Steep merges the overloads, so it cannot type the default as D.
|
|
172
|
+
default.fetch(0) #: untyped
|
|
173
|
+
else
|
|
174
|
+
value
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
# Resolves a negative Array-style index against the current length: +-1+
|
|
181
|
+
# fast-paths through {#last} (no length read), other negatives read the
|
|
182
|
+
# length and index from the front. Returns +nil+ when the index resolves
|
|
183
|
+
# before the front (past the far end of the deque).
|
|
184
|
+
def at_negative(index)
|
|
185
|
+
return @native.peek_back if index == -1
|
|
186
|
+
|
|
187
|
+
resolved = @native.len + index
|
|
188
|
+
resolved.negative? ? nil : @native.get(resolved)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# A read-only view of a published deque, opened by
|
|
193
|
+
# +client.state(subsystem, definition)+. Each read takes the user key and
|
|
194
|
+
# sees only committed state. A failed read raises the same state errors as
|
|
195
|
+
# an owned handle. A read in a forked child process raises +RuntimeError+.
|
|
196
|
+
class PublishedDeque
|
|
197
|
+
include State::Scanning
|
|
198
|
+
|
|
199
|
+
# @param native [Prosody::NativePublishedDeque] the native reader
|
|
200
|
+
def initialize(native) = @native = native
|
|
201
|
+
|
|
202
|
+
# Reads the committed element at +index+ in the deque for +key+. A
|
|
203
|
+
# negative index counts from the back, as in {DequeState#get}.
|
|
204
|
+
#
|
|
205
|
+
# @param index [Integer] the position (negative counts from the back)
|
|
206
|
+
# @return [Object, nil] the element, or +nil+ outside the bounds
|
|
207
|
+
# @raise [TransientStateError] if +index+ is not an Integer
|
|
208
|
+
def get(key, index)
|
|
209
|
+
unless index.is_a?(Integer)
|
|
210
|
+
raise TransientStateError, "get: index must be an Integer, got #{index.inspect}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
return @native.get(key, index) unless index.negative?
|
|
214
|
+
# This repeats DequeState#at_negative, because a reader needs the key.
|
|
215
|
+
return last(key) if index == -1
|
|
216
|
+
|
|
217
|
+
resolved = length(key) + index
|
|
218
|
+
resolved.negative? ? nil : @native.get(key, resolved)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# The number of committed elements in the deque for +key+.
|
|
222
|
+
#
|
|
223
|
+
# @return [Integer]
|
|
224
|
+
def length(key) = @native.length(key)
|
|
225
|
+
alias_method :size, :length
|
|
226
|
+
|
|
227
|
+
# Whether the deque for +key+ has no committed elements.
|
|
228
|
+
#
|
|
229
|
+
# @return [Boolean]
|
|
230
|
+
def empty?(key) = @native.is_empty(key)
|
|
231
|
+
|
|
232
|
+
# The front element of the deque for +key+.
|
|
233
|
+
#
|
|
234
|
+
# @return [Object, nil] the element, or +nil+ when the deque is empty
|
|
235
|
+
def first(key) = @native.peek_front(key)
|
|
236
|
+
|
|
237
|
+
# The back element of the deque for +key+.
|
|
238
|
+
#
|
|
239
|
+
# @return [Object, nil] the element, or +nil+ when the deque is empty
|
|
240
|
+
def last(key) = @native.peek_back(key)
|
|
241
|
+
|
|
242
|
+
# Traverses the committed elements for +key+ from front to back. Each
|
|
243
|
+
# traversal accepts the position keywords documented on {State::Scanning}.
|
|
244
|
+
#
|
|
245
|
+
# @return [Enumerator, void]
|
|
246
|
+
def each(key, **query, &block) = traverse(:scan, key, :forward, query, &block)
|
|
247
|
+
|
|
248
|
+
# Traverses the committed elements for +key+ from back to front.
|
|
249
|
+
#
|
|
250
|
+
# @return [Enumerator, void]
|
|
251
|
+
def reverse_each(key, **query, &block) = traverse(:scan, key, :backward, query, &block)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The map collection: the owned {Prosody::MapState} handle and the
|
|
4
|
+
# {Prosody::PublishedMap} reader.
|
|
5
|
+
|
|
6
|
+
module Prosody
|
|
7
|
+
# A `String`-keyed ordered-map keyed-state handle.
|
|
8
|
+
#
|
|
9
|
+
# Traversal is explicit: {#each_pair}/{#reverse_each_pair} yield `key, value`
|
|
10
|
+
# pairs over a native scan, closing the scan via `ensure`. The map has no
|
|
11
|
+
# aggregate mixin methods, because they would load the whole remote
|
|
12
|
+
# collection.
|
|
13
|
+
class MapState < State::Handle
|
|
14
|
+
include State::Scanning
|
|
15
|
+
|
|
16
|
+
# Reads the value for `key`.
|
|
17
|
+
#
|
|
18
|
+
# @param key [String] the map key
|
|
19
|
+
# @return [Object, nil] the value, or `nil` when the key is absent
|
|
20
|
+
def get(key) = @native.get(key)
|
|
21
|
+
|
|
22
|
+
# Reads several keys in a single isolated batch.
|
|
23
|
+
#
|
|
24
|
+
# @param keys [Array<String>] the keys to read, in order
|
|
25
|
+
# @return [Array<Object, nil>] one result per input key; `nil` for absent keys
|
|
26
|
+
def get_many(keys) = @native.get_many(keys)
|
|
27
|
+
|
|
28
|
+
# Tests several keys for presence in a single batch. Like {#key?}, it
|
|
29
|
+
# decodes no values.
|
|
30
|
+
#
|
|
31
|
+
# @param keys [Array<String>] the keys to test, in order
|
|
32
|
+
# @return [Array<Boolean>] one result per input key
|
|
33
|
+
def contains_many(keys) = @native.contains_many(keys)
|
|
34
|
+
|
|
35
|
+
# Whether the map holds no live entries (mirrors +Hash#empty?+).
|
|
36
|
+
#
|
|
37
|
+
# @return [Boolean]
|
|
38
|
+
def empty? = @native.is_empty
|
|
39
|
+
|
|
40
|
+
# Inserts or overwrites `key`.
|
|
41
|
+
#
|
|
42
|
+
# @param key [String] the map key
|
|
43
|
+
# @param value [Object] the value to store (JSON, or a message)
|
|
44
|
+
# @return [void]
|
|
45
|
+
# @raise [PermanentStateError] if `value` is `nil` (use {#delete} to remove)
|
|
46
|
+
def set(key, value) = @native.set(key, value)
|
|
47
|
+
|
|
48
|
+
# Removes `key`.
|
|
49
|
+
#
|
|
50
|
+
# Documented divergence from `Hash#delete`: this returns `nil`, never the
|
|
51
|
+
# removed value (the erased FFI seam does not surface it).
|
|
52
|
+
#
|
|
53
|
+
# @param key [String] the map key
|
|
54
|
+
# @return [nil]
|
|
55
|
+
def delete(key)
|
|
56
|
+
@native.remove(key)
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Removes every entry.
|
|
61
|
+
#
|
|
62
|
+
# @return [void]
|
|
63
|
+
def clear = @native.clear
|
|
64
|
+
|
|
65
|
+
# Traverses the live entries in key order, yielding `key, value`.
|
|
66
|
+
#
|
|
67
|
+
# Without a block, returns an {Enumerator} over the native scan. Each step
|
|
68
|
+
# fiber-yields; the scan is closed via `ensure` on stop or exception. The
|
|
69
|
+
# enumerator is valid only within the current handler invocation.
|
|
70
|
+
#
|
|
71
|
+
# Every map traversal accepts the query keywords documented on
|
|
72
|
+
# {State::Scanning}. For keyset paging, pass the last key of the previous
|
|
73
|
+
# page as `after:` and the page size as `limit:`.
|
|
74
|
+
#
|
|
75
|
+
# @param query [Hash] optional `from:`, `after:`, `to:`, `before:`,
|
|
76
|
+
# `range:`, `prefix:`, and `limit:` keywords
|
|
77
|
+
# @yieldparam key [String]
|
|
78
|
+
# @yieldparam value [Object]
|
|
79
|
+
# @return [Enumerator, void]
|
|
80
|
+
# @raise [ArgumentError, TypeError] if a query keyword is invalid
|
|
81
|
+
def each_pair(**query, &block) = traverse(:scan, :forward, query, &block)
|
|
82
|
+
|
|
83
|
+
# Traverses the live entries in reverse key order, yielding `key, value`.
|
|
84
|
+
#
|
|
85
|
+
# @param query [Hash] optional query keywords, as on {#each_pair}
|
|
86
|
+
# @yieldparam key [String]
|
|
87
|
+
# @yieldparam value [Object]
|
|
88
|
+
# @return [Enumerator, void]
|
|
89
|
+
def reverse_each_pair(**query, &block) = traverse(:scan, :backward, query, &block)
|
|
90
|
+
|
|
91
|
+
# Traverses the live keys in key order, yielding each key (mirrors
|
|
92
|
+
# +Hash#each_key+). The key scan decodes no values, so a message-backed map
|
|
93
|
+
# fetches no Kafka messages. It can still read the store. Without a block,
|
|
94
|
+
# it returns an {Enumerator} that reads on demand. The map has no +keys+
|
|
95
|
+
# array, because it would load the whole remote keyset. The block form
|
|
96
|
+
# returns +nil+, as {#each_pair} does.
|
|
97
|
+
#
|
|
98
|
+
# @param query [Hash] optional query keywords, as on {#each_pair}
|
|
99
|
+
# @yieldparam key [String]
|
|
100
|
+
# @return [Enumerator, void]
|
|
101
|
+
def each_key(**query, &block) = traverse(:keys, :forward, query, &block)
|
|
102
|
+
|
|
103
|
+
# Traverses the live keys in reverse key order, yielding each key.
|
|
104
|
+
#
|
|
105
|
+
# @param query [Hash] optional query keywords, as on {#each_pair}
|
|
106
|
+
# @yieldparam key [String]
|
|
107
|
+
# @return [Enumerator, void]
|
|
108
|
+
def reverse_each_key(**query, &block) = traverse(:keys, :backward, query, &block)
|
|
109
|
+
|
|
110
|
+
# Traverses the live values in key order (mirrors +Hash#each_value+).
|
|
111
|
+
#
|
|
112
|
+
# @param query [Hash] optional query keywords, as on {#each_pair}
|
|
113
|
+
# @yieldparam value [Object]
|
|
114
|
+
# @return [Enumerator, void]
|
|
115
|
+
def each_value(**query, &block) = traverse_values(:forward, query, &block)
|
|
116
|
+
|
|
117
|
+
# Traverses the live values in reverse key order.
|
|
118
|
+
#
|
|
119
|
+
# @param query [Hash] optional query keywords, as on {#each_pair}
|
|
120
|
+
# @yieldparam value [Object]
|
|
121
|
+
# @return [Enumerator, void]
|
|
122
|
+
def reverse_each_value(**query, &block) = traverse_values(:backward, query, &block)
|
|
123
|
+
|
|
124
|
+
# --- idiomatic Hash-style aliases and conveniences ------------------
|
|
125
|
+
# Each is composed from the canonical ops above and adds no capability
|
|
126
|
+
# the naming matrix lacks. Bounded reads only: there is deliberately no
|
|
127
|
+
# +keys+/+values+/+to_h+/+count+ or +Enumerable+, which would materialize
|
|
128
|
+
# the whole (potentially unbounded) remote collection.
|
|
129
|
+
|
|
130
|
+
# Reads +key+. Idiomatic alias of {#get} (mirrors +Hash#[]+).
|
|
131
|
+
alias_method :[], :get
|
|
132
|
+
|
|
133
|
+
# Writes +key+. Idiomatic alias of {#set} (mirrors +Hash#[]=+). As with any
|
|
134
|
+
# Ruby +[]=+, `map[key] = value` evaluates to +value+ regardless of return.
|
|
135
|
+
alias_method :[]=, :set
|
|
136
|
+
|
|
137
|
+
# Writes +key+, returning the stored +value+ (mirrors +Hash#store+). A
|
|
138
|
+
# wrapper, not an alias: a caller sees the return value of +store+, and
|
|
139
|
+
# the native write returns +nil+.
|
|
140
|
+
#
|
|
141
|
+
# @param key [String]
|
|
142
|
+
# @param value [Object]
|
|
143
|
+
# @return [Object] the stored +value+
|
|
144
|
+
def store(key, value)
|
|
145
|
+
set(key, value)
|
|
146
|
+
value
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Traverses live entries in key order. Idiomatic alias of {#each_pair}
|
|
150
|
+
# (mirrors +Hash#each+).
|
|
151
|
+
alias_method :each, :each_pair
|
|
152
|
+
|
|
153
|
+
# Reads several keys positionally (mirrors +Hash#values_at+).
|
|
154
|
+
#
|
|
155
|
+
# @param keys [Array<String>] the keys to read
|
|
156
|
+
# @return [Array<Object, nil>] one result per key; +nil+ for absent keys
|
|
157
|
+
def values_at(*keys) = get_many(keys)
|
|
158
|
+
|
|
159
|
+
# Reads +key+, raising or defaulting when absent (mirrors +Hash#fetch+).
|
|
160
|
+
# Performs a single read; a +nil+ result is unambiguously "absent" under
|
|
161
|
+
# the null ban.
|
|
162
|
+
#
|
|
163
|
+
# @param key [String]
|
|
164
|
+
# @param default [Object] returned when +key+ is absent
|
|
165
|
+
# @yieldparam key [String] called (instead of +default+) when +key+ is absent
|
|
166
|
+
# @return [Object]
|
|
167
|
+
# @raise [KeyError] when +key+ is absent and no default or block is given
|
|
168
|
+
def fetch(key, *default, &block)
|
|
169
|
+
if default.length > 1
|
|
170
|
+
raise ArgumentError, "wrong number of arguments (given #{default.length + 1}, expected 1..2)"
|
|
171
|
+
end
|
|
172
|
+
warn "warning: block supersedes default value argument" if block && !default.empty?
|
|
173
|
+
|
|
174
|
+
value = @native.get(key)
|
|
175
|
+
return value unless value.nil?
|
|
176
|
+
return block.call(key) if block
|
|
177
|
+
raise KeyError.new("key not found: #{key.inspect}", key: key, receiver: self) if default.empty?
|
|
178
|
+
|
|
179
|
+
# Steep merges the overloads, so it cannot type the default as D.
|
|
180
|
+
default.fetch(0) #: untyped
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Whether +key+ has a live value (mirrors +Hash#key?+). The check decodes
|
|
184
|
+
# no value, so a message-backed map fetches no Kafka message. It returns
|
|
185
|
+
# +true+ for an entry whose message cannot be fetched. A cache miss can
|
|
186
|
+
# still read the store.
|
|
187
|
+
#
|
|
188
|
+
# @param key [String]
|
|
189
|
+
# @return [Boolean]
|
|
190
|
+
def key?(key) = @native.contains_key(key)
|
|
191
|
+
alias_method :has_key?, :key?
|
|
192
|
+
alias_method :include?, :key?
|
|
193
|
+
alias_method :member?, :key?
|
|
194
|
+
|
|
195
|
+
# Reads +key+ and digs into the nested value (mirrors +Hash#dig+). A single
|
|
196
|
+
# bounded read; digging continues in the returned local value.
|
|
197
|
+
#
|
|
198
|
+
# @param key [String]
|
|
199
|
+
# @return [Object, nil]
|
|
200
|
+
# @raise [TypeError] if a nested value does not respond to +dig+
|
|
201
|
+
def dig(key, *rest)
|
|
202
|
+
value = @native.get(key)
|
|
203
|
+
return value if rest.empty? || value.nil?
|
|
204
|
+
|
|
205
|
+
unless value.respond_to?(:dig)
|
|
206
|
+
raise TypeError, "#{value.class} does not have #dig method"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
value.dig(*rest)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Reads +keys+ as a single bounded batch, returning a +Hash+ of only the
|
|
213
|
+
# keys that are present (mirrors +Hash#slice+). Absent keys are omitted.
|
|
214
|
+
#
|
|
215
|
+
# @param keys [Array<String>] the keys to read
|
|
216
|
+
# @return [Hash{String => Object}] present keys mapped to their values
|
|
217
|
+
def slice(*keys) = keys.zip(get_many(keys)).to_h.compact
|
|
218
|
+
|
|
219
|
+
# Reads +keys+ as a single bounded batch, requiring every key to be present
|
|
220
|
+
# (mirrors +Hash#fetch_values+). Without a block, a missing key raises
|
|
221
|
+
# {KeyError}; with a block, the block is called with each missing key and
|
|
222
|
+
# its result substituted.
|
|
223
|
+
#
|
|
224
|
+
# @param keys [Array<String>] the keys to read, in order
|
|
225
|
+
# @yieldparam key [String] called for each absent key
|
|
226
|
+
# @return [Array<Object>] one value per key, in order
|
|
227
|
+
# @raise [KeyError] when a key is absent and no block is given
|
|
228
|
+
def fetch_values(*keys, &block)
|
|
229
|
+
keys.zip(get_many(keys)).map do |key, value|
|
|
230
|
+
case value
|
|
231
|
+
when nil
|
|
232
|
+
next block.call(key) if block
|
|
233
|
+
|
|
234
|
+
raise KeyError.new("key not found: #{key.inspect}", key: key, receiver: self)
|
|
235
|
+
else
|
|
236
|
+
value
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# A read-only view of a published map, opened by
|
|
243
|
+
# +client.state(subsystem, definition)+. Each read takes the user key and
|
|
244
|
+
# sees only committed state. A failed read raises the same state errors as
|
|
245
|
+
# an owned handle. A read in a forked child process raises +RuntimeError+.
|
|
246
|
+
class PublishedMap
|
|
247
|
+
include State::Scanning
|
|
248
|
+
|
|
249
|
+
# @param native [Prosody::NativePublishedMap] the native reader
|
|
250
|
+
def initialize(native) = @native = native
|
|
251
|
+
|
|
252
|
+
# Reads the committed entry for +map_key+ in the map for +key+.
|
|
253
|
+
#
|
|
254
|
+
# @return [Object, nil] the value, or +nil+ when the entry is absent
|
|
255
|
+
def get(key, map_key) = @native.get(key, map_key)
|
|
256
|
+
|
|
257
|
+
# Reads several entries of the map for +key+ in one batch.
|
|
258
|
+
#
|
|
259
|
+
# @return [Array<Object, nil>] one result per map key; +nil+ for an absent entry
|
|
260
|
+
def get_many(key, map_keys) = @native.get_many(key, map_keys)
|
|
261
|
+
|
|
262
|
+
# Whether the map for +key+ has a committed entry for +map_key+.
|
|
263
|
+
#
|
|
264
|
+
# @return [Boolean]
|
|
265
|
+
def key?(key, map_key) = @native.contains_key(key, map_key)
|
|
266
|
+
alias_method :has_key?, :key?
|
|
267
|
+
alias_method :include?, :key?
|
|
268
|
+
alias_method :member?, :key?
|
|
269
|
+
|
|
270
|
+
# Tests several entries of the map for +key+ in one batch.
|
|
271
|
+
#
|
|
272
|
+
# @return [Array<Boolean>] one result per map key
|
|
273
|
+
def contains_many(key, map_keys) = @native.contains_many(key, map_keys)
|
|
274
|
+
|
|
275
|
+
# Whether the map for +key+ has no committed entries.
|
|
276
|
+
#
|
|
277
|
+
# @return [Boolean]
|
|
278
|
+
def empty?(key) = @native.is_empty(key)
|
|
279
|
+
|
|
280
|
+
# Traverses the committed entries for +key+ in key order, yielding one
|
|
281
|
+
# +[map_key, value]+ pair for each entry. Each traversal accepts the query
|
|
282
|
+
# keywords documented on {State::Scanning}.
|
|
283
|
+
#
|
|
284
|
+
# @return [Enumerator, void]
|
|
285
|
+
def each_pair(key, **query, &block) = traverse(:scan, key, :forward, query, &block)
|
|
286
|
+
|
|
287
|
+
# Traverses the committed entries for +key+ in reverse key order.
|
|
288
|
+
#
|
|
289
|
+
# @return [Enumerator, void]
|
|
290
|
+
def reverse_each_pair(key, **query, &block) = traverse(:scan, key, :backward, query, &block)
|
|
291
|
+
|
|
292
|
+
# Traverses the committed map keys for +key+ in key order.
|
|
293
|
+
#
|
|
294
|
+
# @return [Enumerator, void]
|
|
295
|
+
def each_key(key, **query, &block) = traverse(:keys, key, :forward, query, &block)
|
|
296
|
+
|
|
297
|
+
# Traverses the committed map keys for +key+ in reverse key order.
|
|
298
|
+
#
|
|
299
|
+
# @return [Enumerator, void]
|
|
300
|
+
def reverse_each_key(key, **query, &block) = traverse(:keys, key, :backward, query, &block)
|
|
301
|
+
|
|
302
|
+
# Traverses the committed values for +key+ in key order.
|
|
303
|
+
#
|
|
304
|
+
# @return [Enumerator, void]
|
|
305
|
+
def each_value(key, **query, &block) = traverse_values(key, :forward, query, &block)
|
|
306
|
+
|
|
307
|
+
# Traverses the committed values for +key+ in reverse key order.
|
|
308
|
+
#
|
|
309
|
+
# @return [Enumerator, void]
|
|
310
|
+
def reverse_each_value(key, **query, &block) = traverse_values(key, :backward, query, &block)
|
|
311
|
+
alias_method :each, :each_pair
|
|
312
|
+
end
|
|
313
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prosody
|
|
4
|
+
# A presence-only ordered set of String members, vended by
|
|
5
|
+
# +context.state(Prosody.set(...))+. It mirrors Ruby's +Set+.
|
|
6
|
+
#
|
|
7
|
+
# Writes are buffered and made durable when the handler succeeds or by
|
|
8
|
+
# {#commit}. Every operation yields the fiber, never the thread. There is
|
|
9
|
+
# deliberately no +Enumerable+ or +to_a+: they would read the whole remote
|
|
10
|
+
# set. Traverse members with {#each} or {#reverse_each}.
|
|
11
|
+
class SetState < State::Handle
|
|
12
|
+
include State::Scanning
|
|
13
|
+
|
|
14
|
+
# Adds +member+ (mirrors +Set#add+).
|
|
15
|
+
#
|
|
16
|
+
# @param member [String]
|
|
17
|
+
# @return [self]
|
|
18
|
+
def add(member)
|
|
19
|
+
@native.insert(member)
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
alias_method :<<, :add
|
|
24
|
+
|
|
25
|
+
# Removes +member+ when present (mirrors +Set#delete+).
|
|
26
|
+
#
|
|
27
|
+
# @param member [String]
|
|
28
|
+
# @return [self]
|
|
29
|
+
def delete(member)
|
|
30
|
+
@native.remove(member)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Whether +member+ belongs to the set (mirrors +Set#include?+).
|
|
35
|
+
#
|
|
36
|
+
# @param member [String]
|
|
37
|
+
# @return [Boolean]
|
|
38
|
+
def include?(member) = @native.contains(member)
|
|
39
|
+
|
|
40
|
+
alias_method :member?, :include?
|
|
41
|
+
|
|
42
|
+
# Tests several members in a single batch.
|
|
43
|
+
#
|
|
44
|
+
# @param members [Array<String>] the members to test, in order
|
|
45
|
+
# @return [Array<Boolean>] one result per input member
|
|
46
|
+
def contains_many(members) = @native.contains_many(members)
|
|
47
|
+
|
|
48
|
+
# Whether the set has no live members (mirrors +Set#empty?+).
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
def empty? = @native.is_empty
|
|
52
|
+
|
|
53
|
+
# Removes every member (mirrors +Set#clear+).
|
|
54
|
+
#
|
|
55
|
+
# @return [self]
|
|
56
|
+
def clear
|
|
57
|
+
@native.clear
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Traverses the live members in ascending order.
|
|
62
|
+
#
|
|
63
|
+
# Without a block, returns an {Enumerator} over the native scan. Accepts
|
|
64
|
+
# the query keywords documented on {State::Scanning}, including
|
|
65
|
+
# +prefix:+.
|
|
66
|
+
#
|
|
67
|
+
# @param query [Hash] optional query keywords
|
|
68
|
+
# @yieldparam member [String]
|
|
69
|
+
# @return [Enumerator, void]
|
|
70
|
+
# @raise [ArgumentError, TypeError] if a query keyword is invalid
|
|
71
|
+
def each(**query, &block) = traverse(:keys, :forward, query, &block)
|
|
72
|
+
|
|
73
|
+
# Traverses the live members in descending order.
|
|
74
|
+
#
|
|
75
|
+
# @param query [Hash] optional query keywords, as on {#each}
|
|
76
|
+
# @yieldparam member [String]
|
|
77
|
+
# @return [Enumerator, void]
|
|
78
|
+
def reverse_each(**query, &block) = traverse(:keys, :backward, query, &block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# A read-only view of a published set, opened by
|
|
82
|
+
# +client.state(subsystem, definition)+. Each read takes the user key and
|
|
83
|
+
# sees only committed state. A failed read raises the same state errors as
|
|
84
|
+
# an owned handle. A read in a forked child process raises +RuntimeError+.
|
|
85
|
+
class PublishedSet
|
|
86
|
+
include State::Scanning
|
|
87
|
+
|
|
88
|
+
# @param native [Prosody::NativePublishedSet] the native reader
|
|
89
|
+
def initialize(native)
|
|
90
|
+
@native = native
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Whether +member+ belongs to the committed set for +key+.
|
|
94
|
+
#
|
|
95
|
+
# @return [Boolean]
|
|
96
|
+
def include?(key, member) = @native.contains(key, member)
|
|
97
|
+
|
|
98
|
+
alias_method :member?, :include?
|
|
99
|
+
|
|
100
|
+
# Tests several members of the committed set for +key+ in one batch.
|
|
101
|
+
#
|
|
102
|
+
# @return [Array<Boolean>] one result per input member
|
|
103
|
+
def contains_many(key, members) = @native.contains_many(key, members)
|
|
104
|
+
|
|
105
|
+
# Whether the committed set for +key+ has no members.
|
|
106
|
+
#
|
|
107
|
+
# @return [Boolean]
|
|
108
|
+
def empty?(key) = @native.is_empty(key)
|
|
109
|
+
|
|
110
|
+
# Traverses the committed members for +key+ in ascending order. Accepts
|
|
111
|
+
# the query keywords documented on {State::Scanning}.
|
|
112
|
+
#
|
|
113
|
+
# @return [Enumerator, void]
|
|
114
|
+
def each(key, **query, &block) = traverse(:keys, key, :forward, query, &block)
|
|
115
|
+
|
|
116
|
+
# Traverses the committed members for +key+ in descending order.
|
|
117
|
+
#
|
|
118
|
+
# @return [Enumerator, void]
|
|
119
|
+
def reverse_each(key, **query, &block) = traverse(:keys, key, :backward, query, &block)
|
|
120
|
+
end
|
|
121
|
+
end
|