statelydb 0.10.0 → 0.12.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/lib/api/db/put_pb.rb +1 -1
- data/lib/api/db/service_services_pb.rb +1 -1
- data/lib/common/auth/auth0_token_provider.rb +158 -75
- data/lib/common/auth/interceptor.rb +1 -1
- data/lib/common/auth/token_provider.rb +7 -1
- data/lib/error.rb +5 -0
- data/lib/statelydb.rb +51 -17
- data/lib/transaction/transaction.rb +29 -12
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14c7769c562abe9ba0294a0d3ec4a1c7a620f9233f35209d39766331f3dd73e5
|
4
|
+
data.tar.gz: db2562de5584684ef9baf9de405b38112a43eb123aa3826ae4e563c6cfa0c1d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ba09f8776771bb4b6fc078e32255a272d986e6983737e1df9fe71bf95e685484ce6d20a91f16c96973bc89f7c9c79d1c0b9411f4a683430581bbc1e27a17468
|
7
|
+
data.tar.gz: cf612cf68f6c714ba7e79d328ab2b18236c711dbe806a6fb9714dd93eb163ac9899a415bb6c5d4861d24f103bc3844dff40cde27c4eca37eecf1ac3304298f93
|
data/lib/api/db/put_pb.rb
CHANGED
@@ -7,7 +7,7 @@ require 'google/protobuf'
|
|
7
7
|
require 'db/item_pb'
|
8
8
|
|
9
9
|
|
10
|
-
descriptor_data = "\n\x0c\x64\x62/put.proto\x12\nstately.db\x1a\rdb/item.proto\"|\n\nPutRequest\x12\x19\n\x08store_id\x18\x01 \x01(\x04R\x07storeId\x12\'\n\x04puts\x18\x02 \x03(\x0b\x32\x13.stately.db.PutItemR\x04puts\x12*\n\x11schema_version_id\x18\x03 \x01(\rR\x0fschemaVersionId\"
|
10
|
+
descriptor_data = "\n\x0c\x64\x62/put.proto\x12\nstately.db\x1a\rdb/item.proto\"|\n\nPutRequest\x12\x19\n\x08store_id\x18\x01 \x01(\x04R\x07storeId\x12\'\n\x04puts\x18\x02 \x03(\x0b\x32\x13.stately.db.PutItemR\x04puts\x12*\n\x11schema_version_id\x18\x03 \x01(\rR\x0fschemaVersionId\"U\n\x07PutItem\x12$\n\x04item\x18\x01 \x01(\x0b\x32\x10.stately.db.ItemR\x04item\x12$\n\x0emust_not_exist\x18\x03 \x01(\x08R\x0cmustNotExist\"5\n\x0bPutResponse\x12&\n\x05items\x18\x01 \x03(\x0b\x32\x10.stately.db.ItemR\x05itemsBc\n\x0e\x63om.stately.dbB\x08PutProtoP\x01\xa2\x02\x03SDX\xaa\x02\nStately.Db\xca\x02\nStately\\Db\xe2\x02\x16Stately\\Db\\GPBMetadata\xea\x02\x0bStately::Dbb\x06proto3"
|
11
11
|
|
12
12
|
pool = Google::Protobuf::DescriptorPool.generated_pool
|
13
13
|
pool.add_serialized_file(descriptor_data)
|
@@ -35,7 +35,7 @@ module Stately
|
|
35
35
|
rpc :Get, ::Stately::Db::GetRequest, ::Stately::Db::GetResponse
|
36
36
|
# Delete removes one or more Items from the Store by their key paths. This
|
37
37
|
# will fail if the caller does not have permission to delete Items.
|
38
|
-
# Tombstones will be saved for deleted items for
|
38
|
+
# Tombstones will be saved for deleted items for some time, so
|
39
39
|
# that SyncList can return information about deleted items. Deletes are
|
40
40
|
# always applied atomically; all will fail or all will succeed.
|
41
41
|
rpc :Delete, ::Stately::Db::DeleteRequest, ::Stately::Db::DeleteResponse
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "async"
|
4
|
+
require "async/actor"
|
4
5
|
require "async/http/internet"
|
5
6
|
require "async/semaphore"
|
6
7
|
require "json"
|
@@ -12,6 +13,7 @@ LOGGER = Logger.new($stdout)
|
|
12
13
|
LOGGER.level = Logger::WARN
|
13
14
|
DEFAULT_GRANT_TYPE = "client_credentials"
|
14
15
|
|
16
|
+
# A module for Stately Cloud auth code
|
15
17
|
module StatelyDB
|
16
18
|
module Common
|
17
19
|
# A module for Stately Cloud auth code
|
@@ -21,102 +23,183 @@ module StatelyDB
|
|
21
23
|
# It will default to using the values of `STATELY_CLIENT_ID` and `STATELY_CLIENT_SECRET` if
|
22
24
|
# no credentials are explicitly passed and will throw an error if none are found.
|
23
25
|
class Auth0TokenProvider < TokenProvider
|
24
|
-
# @param [
|
26
|
+
# @param [Endpoint] domain The domain of the OAuth server
|
25
27
|
# @param [String] audience The OAuth Audience for the token
|
26
28
|
# @param [String] client_secret The StatelyDB client secret credential
|
27
29
|
# @param [String] client_id The StatelyDB client ID credential
|
28
30
|
def initialize(
|
29
|
-
|
31
|
+
domain: "https://oauth.stately.cloud",
|
30
32
|
audience: "api.stately.cloud",
|
31
33
|
client_secret: ENV.fetch("STATELY_CLIENT_SECRET"),
|
32
34
|
client_id: ENV.fetch("STATELY_CLIENT_ID")
|
33
35
|
)
|
34
36
|
super()
|
35
|
-
@
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@
|
40
|
-
@pending_refresh = nil
|
41
|
-
@timer = nil
|
42
|
-
|
43
|
-
Async do |_task|
|
44
|
-
refresh_token
|
45
|
-
end
|
46
|
-
|
47
|
-
# need a weak ref to ourself or the GC will never run the finalizer
|
48
|
-
ObjectSpace.define_finalizer(WeakRef.new(self), finalize)
|
37
|
+
@actor = Async::Actor.new(Actor.new(domain: domain, audience: audience, client_secret: client_secret,
|
38
|
+
client_id: client_id))
|
39
|
+
# this initialization cannot happen in the constructor because it is async and must run on the event loop
|
40
|
+
# which is not available in the constructor
|
41
|
+
@actor.init
|
49
42
|
end
|
50
43
|
|
51
|
-
#
|
52
|
-
#
|
53
|
-
def
|
54
|
-
|
55
|
-
Thread.kill(@timer) unless @timer.nil?
|
56
|
-
}
|
44
|
+
# Close the token provider and kill any background operations
|
45
|
+
# This just invokes the close method on the actor which should do the cleanup
|
46
|
+
def close
|
47
|
+
@actor.close
|
57
48
|
end
|
58
49
|
|
59
50
|
# Get the current access token
|
60
51
|
# @return [String] The current access token
|
61
|
-
def
|
62
|
-
|
63
|
-
@access_token || refresh_token
|
52
|
+
def get_token(force: false)
|
53
|
+
@actor.get_token(force: force)
|
64
54
|
end
|
65
55
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
#
|
72
|
-
@
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
56
|
+
# Actor for managing the token refresh
|
57
|
+
# This is designed to be used with Async::Actor and run on a dedicated thread.
|
58
|
+
class Actor
|
59
|
+
# @param [Endpoint] domain The domain of the OAuth server
|
60
|
+
# @param [String] audience The OAuth Audience for the token
|
61
|
+
# @param [String] client_secret The StatelyDB client secret credential
|
62
|
+
# @param [String] client_id The StatelyDB client ID credential
|
63
|
+
def initialize(
|
64
|
+
domain: "https://oauth.stately.cloud",
|
65
|
+
audience: "api.stately.cloud",
|
66
|
+
client_secret: ENV.fetch("STATELY_CLIENT_SECRET"),
|
67
|
+
client_id: ENV.fetch("STATELY_CLIENT_ID")
|
68
|
+
)
|
69
|
+
super()
|
70
|
+
@client = Async::HTTP::Client.new(Async::HTTP::Endpoint.parse(domain))
|
71
|
+
@client_id = client_id
|
72
|
+
@client_secret = client_secret
|
73
|
+
@audience = audience
|
81
74
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
@
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
75
|
+
@access_token = nil
|
76
|
+
@expires_at_secs = nil
|
77
|
+
@pending_refresh = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# Initialize the actor. This runs on the actor thread which means
|
81
|
+
# we can dispatch async operations here.
|
82
|
+
def init
|
83
|
+
refresh_token
|
84
|
+
end
|
85
|
+
|
86
|
+
# Close the token provider and kill any background operations
|
87
|
+
def close
|
88
|
+
@scheduled&.stop
|
89
|
+
@client&.close
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get the current access token
|
93
|
+
# @param [Boolean] force Whether to force a refresh of the token
|
94
|
+
# @return [String] The current access token
|
95
|
+
def get_token(force: false)
|
96
|
+
if force
|
97
|
+
@access_token = nil
|
98
|
+
@expires_at_secs = nil
|
99
|
+
else
|
100
|
+
token, ok = valid_access_token
|
101
|
+
return token if ok
|
102
|
+
end
|
103
|
+
|
104
|
+
refresh_token.wait
|
105
|
+
end
|
106
|
+
|
107
|
+
# Get the current access token and whether it is valid
|
108
|
+
# @return [Array] The current access token and whether it is valid
|
109
|
+
def valid_access_token
|
110
|
+
return "", false if @access_token.nil?
|
111
|
+
return "", false if @expires_at_secs.nil?
|
112
|
+
return "", false if @expires_at_secs < Time.now.to_i
|
113
|
+
|
114
|
+
[@access_token, true]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Refresh the access token
|
118
|
+
# @return [string] The new access token
|
119
|
+
def refresh_token
|
120
|
+
Async do
|
121
|
+
# we use an Async::Condition to dedupe multiple requests here
|
122
|
+
# if the condition exists, we wait on it to complete
|
123
|
+
# otherwise we create a condition, make the request, then signal the condition with the result
|
124
|
+
# If there is an error then we signal that instead so we can raise it for the waiters.
|
125
|
+
if @pending_refresh.nil?
|
126
|
+
begin
|
127
|
+
@pending_refresh = Async::Condition.new
|
128
|
+
new_access_token = refresh_token_impl.wait
|
129
|
+
# now broadcast the new token to any waiters
|
130
|
+
@pending_refresh.signal(new_access_token)
|
131
|
+
new_access_token
|
132
|
+
rescue StandardError => e
|
133
|
+
@pending_refresh.signal(e)
|
134
|
+
raise e
|
135
|
+
ensure
|
136
|
+
# delete the condition to restart the process
|
137
|
+
@pending_refresh = nil
|
138
|
+
end
|
139
|
+
else
|
140
|
+
res = @pending_refresh.wait
|
141
|
+
# if the refresh result is an error, re-raise it.
|
142
|
+
# otherwise return the token
|
143
|
+
raise res if res.is_a?(StandardError)
|
144
|
+
|
145
|
+
res
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Refresh the access token implementation
|
151
|
+
# @return [String] The new access token
|
152
|
+
def refresh_token_impl
|
153
|
+
Async do
|
154
|
+
resp_data = make_auth0_request
|
155
|
+
|
156
|
+
new_access_token = resp_data["access_token"]
|
157
|
+
new_expires_in_secs = resp_data["expires_in"]
|
158
|
+
new_expires_at_secs = Time.now.to_i + new_expires_in_secs
|
159
|
+
if @expires_at_secs.nil? || new_expires_at_secs > @expires_at_secs
|
160
|
+
|
161
|
+
@access_token = new_access_token
|
162
|
+
@expires_at_secs = new_expires_at_secs
|
163
|
+
else
|
164
|
+
|
165
|
+
new_access_token = @access_token
|
166
|
+
new_expires_in_secs = @expires_at_secs - Time.now.to_i
|
167
|
+
end
|
168
|
+
|
169
|
+
# Schedule a refresh of the token ahead of the expiry time
|
170
|
+
# Calculate a random multiplier between 0.9 and 0.95 to to apply to the expiry
|
104
171
|
# so that we refresh in the background ahead of expiration, but avoid
|
105
172
|
# multiple processes hammering the service at the same time.
|
106
|
-
jitter = (Random.rand * 0.
|
107
|
-
|
108
|
-
|
109
|
-
|
173
|
+
jitter = (Random.rand * 0.05) + 0.9
|
174
|
+
delay_secs = new_expires_in_secs * jitter
|
175
|
+
|
176
|
+
# do this on the fiber scheduler (the root scheduler) to avoid infinite recursion
|
177
|
+
@scheduled ||= Fiber.scheduler.async do
|
178
|
+
# Kernel.sleep is non-blocking if Ruby 3.1+ and Async 2+
|
179
|
+
# https://github.com/socketry/async/issues/305#issuecomment-1945188193
|
180
|
+
sleep(delay_secs)
|
181
|
+
refresh_token
|
182
|
+
@scheduled = nil
|
183
|
+
end
|
184
|
+
|
185
|
+
new_access_token
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def make_auth0_request
|
190
|
+
headers = [["content-type", "application/json"]]
|
191
|
+
body = JSON.dump({ "client_id" => @client_id, client_secret: @client_secret, audience: @audience,
|
192
|
+
grant_type: DEFAULT_GRANT_TYPE })
|
193
|
+
Sync do
|
194
|
+
# TODO: Wrap this in a retry loop and parse errors like we
|
195
|
+
# do in the Go SDK.
|
196
|
+
response = @client.post("/oauth/token", headers, body)
|
197
|
+
raise "Auth request failed" if response.status != 200
|
198
|
+
|
199
|
+
JSON.parse(response.read)
|
200
|
+
ensure
|
201
|
+
response&.close
|
110
202
|
end
|
111
|
-
@pending_refresh = nil
|
112
|
-
resp_data["access_token"]
|
113
|
-
rescue StandardError => e
|
114
|
-
# set the token to nil so that it will
|
115
|
-
# be refreshed on the next get
|
116
|
-
@access_token = nil
|
117
|
-
LOGGER.warn(e)
|
118
|
-
ensure
|
119
|
-
client.close
|
120
203
|
end
|
121
204
|
end
|
122
205
|
end
|
@@ -8,8 +8,14 @@ module StatelyDB
|
|
8
8
|
# for individual token provider implementations
|
9
9
|
class TokenProvider
|
10
10
|
# Get the current access token
|
11
|
+
# @param [Boolean] force Whether to force a refresh of the token
|
11
12
|
# @return [String] The current access token
|
12
|
-
def
|
13
|
+
def get_token(force: false) # rubocop:disable Lint/UnusedMethodArgument
|
14
|
+
raise "Not Implemented"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Close the token provider and kill any background operations
|
18
|
+
def close
|
13
19
|
raise "Not Implemented"
|
14
20
|
end
|
15
21
|
end
|
data/lib/error.rb
CHANGED
@@ -11,10 +11,13 @@ module StatelyDB
|
|
11
11
|
# The Error class contains common StatelyDB error types.
|
12
12
|
class Error < StandardError
|
13
13
|
# The gRPC/Connect Code for this error.
|
14
|
+
# @return [Integer]
|
14
15
|
attr_reader :code
|
15
16
|
# The more fine-grained Stately error code, which is a human-readable string.
|
17
|
+
# @return [String]
|
16
18
|
attr_reader :stately_code
|
17
19
|
# The upstream cause of the error, if available.
|
20
|
+
# @return [Exception]
|
18
21
|
attr_reader :cause
|
19
22
|
|
20
23
|
# @param [String] message
|
@@ -53,6 +56,8 @@ module StatelyDB
|
|
53
56
|
new(error.message, code: GRPC::Core::StatusCodes::UNKNOWN, stately_code: "Unknown", cause: error)
|
54
57
|
end
|
55
58
|
|
59
|
+
# Turn this error's gRPC status code into a human-readable string. e.g. 3 -> "InvalidArgument"
|
60
|
+
# @return [String]
|
56
61
|
def code_string
|
57
62
|
self.class.grpc_code_to_string(@code)
|
58
63
|
end
|
data/lib/statelydb.rb
CHANGED
@@ -38,20 +38,36 @@ module StatelyDB
|
|
38
38
|
token_provider: Common::Auth::Auth0TokenProvider.new,
|
39
39
|
endpoint: nil,
|
40
40
|
region: nil)
|
41
|
+
if store_id.nil?
|
42
|
+
raise StatelyDB::Error.new("store_id is required",
|
43
|
+
code: GRPC::Core::StatusCodes::INVALID_ARGUMENT,
|
44
|
+
stately_code: "InvalidArgument")
|
45
|
+
end
|
46
|
+
if schema.nil?
|
47
|
+
raise StatelyDB::Error.new("schema is required",
|
48
|
+
code: GRPC::Core::StatusCodes::INVALID_ARGUMENT,
|
49
|
+
stately_code: "InvalidArgument")
|
50
|
+
end
|
41
51
|
|
42
52
|
endpoint = self.class.make_endpoint(endpoint:, region:)
|
43
|
-
channel = Common::Net.new_channel(endpoint:)
|
53
|
+
@channel = Common::Net.new_channel(endpoint:)
|
54
|
+
@token_provider = token_provider
|
44
55
|
|
45
56
|
auth_interceptor = Common::Auth::Interceptor.new(token_provider:)
|
46
57
|
error_interceptor = Common::ErrorInterceptor.new
|
47
58
|
|
48
|
-
@stub = Stately::Db::DatabaseService::Stub.new(nil, nil, channel_override: channel,
|
59
|
+
@stub = Stately::Db::DatabaseService::Stub.new(nil, nil, channel_override: @channel,
|
49
60
|
interceptors: [error_interceptor, auth_interceptor])
|
50
61
|
@store_id = store_id.to_i
|
51
62
|
@schema = schema
|
52
63
|
@allow_stale = false
|
53
64
|
end
|
54
65
|
|
66
|
+
def close
|
67
|
+
@channel&.close
|
68
|
+
@token_provider&.close
|
69
|
+
end
|
70
|
+
|
55
71
|
# Set whether to allow stale results for all operations with this client. This produces a new client
|
56
72
|
# with the allow_stale flag set.
|
57
73
|
# @param allow_stale [Boolean] whether to allow stale results
|
@@ -169,12 +185,19 @@ module StatelyDB
|
|
169
185
|
# Put an Item into a StatelyDB Store at the given key_path.
|
170
186
|
#
|
171
187
|
# @param item [StatelyDB::Item] a StatelyDB Item
|
188
|
+
# @param must_not_exist [Boolean] A condition that indicates this item must
|
189
|
+
# not already exist at any of its key paths. If there is already an item
|
190
|
+
# at one of those paths, the Put operation will fail with a
|
191
|
+
# "ConditionalCheckFailed" error. Note that if the item has an
|
192
|
+
# `initialValue` field in its key, that initial value will automatically
|
193
|
+
# be chosen not to conflict with existing items, so this condition only
|
194
|
+
# applies to key paths that do not contain the `initialValue` field.
|
172
195
|
# @return [StatelyDB::Item] the item that was stored
|
173
196
|
#
|
174
|
-
# @example
|
175
|
-
#
|
176
|
-
def put(item)
|
177
|
-
resp = put_batch(item)
|
197
|
+
# @example client.data.put(my_item)
|
198
|
+
# @example client.data.put(my_item, must_not_exist: true)
|
199
|
+
def put(item, must_not_exist: false)
|
200
|
+
resp = put_batch({ item:, must_not_exist: })
|
178
201
|
|
179
202
|
# Always return a single Item.
|
180
203
|
resp.first
|
@@ -182,21 +205,32 @@ module StatelyDB
|
|
182
205
|
|
183
206
|
# Put a batch of up to 50 Items into a StatelyDB Store.
|
184
207
|
#
|
185
|
-
# @param items [StatelyDB::Item, Array<StatelyDB::Item>] the items to store.
|
208
|
+
# @param items [StatelyDB::Item, Array<StatelyDB::Item>] the items to store.
|
209
|
+
# Max 50 items.
|
186
210
|
# @return [Array<StatelyDB::Item>] the items that were stored
|
187
211
|
#
|
188
212
|
# @example
|
189
213
|
# client.data.put_batch(item1, item2)
|
214
|
+
# @example
|
215
|
+
# client.data.put_batch({ item: item1, must_not_exist: true }, item2)
|
190
216
|
def put_batch(*items)
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
217
|
+
puts = Array(items).flatten.map do |input|
|
218
|
+
if input.is_a?(Hash)
|
219
|
+
item = input[:item]
|
220
|
+
Stately::Db::PutItem.new(
|
221
|
+
item: item.send("marshal_stately"),
|
222
|
+
must_not_exist: input[:must_not_exist]
|
223
|
+
)
|
224
|
+
else
|
196
225
|
Stately::Db::PutItem.new(
|
197
|
-
item:
|
226
|
+
item: input.send("marshal_stately")
|
198
227
|
)
|
199
228
|
end
|
229
|
+
end
|
230
|
+
req = Stately::Db::PutRequest.new(
|
231
|
+
store_id: @store_id,
|
232
|
+
schema_version_id: @schema::SCHEMA_VERSION_ID,
|
233
|
+
puts:
|
200
234
|
)
|
201
235
|
resp = @stub.put(req)
|
202
236
|
|
@@ -208,8 +242,8 @@ module StatelyDB
|
|
208
242
|
# Delete up to 50 Items from a StatelyDB Store at the given key_paths.
|
209
243
|
#
|
210
244
|
# @param key_paths [String, Array<String>] the paths to the items. Max 50 key paths.
|
211
|
-
# @raise [StatelyDB::Error
|
212
|
-
# @raise [StatelyDB::Error
|
245
|
+
# @raise [StatelyDB::Error] if the parameters are invalid
|
246
|
+
# @raise [StatelyDB::Error] if the item is not found
|
213
247
|
# @return [void] nil
|
214
248
|
#
|
215
249
|
# @example
|
@@ -230,8 +264,8 @@ module StatelyDB
|
|
230
264
|
# If the block completes successfully, the transaction is committed.
|
231
265
|
#
|
232
266
|
# @return [StatelyDB::Transaction::Transaction::Result] the result of the transaction
|
233
|
-
# @raise [StatelyDB::Error
|
234
|
-
# @raise [StatelyDB::Error
|
267
|
+
# @raise [StatelyDB::Error] if the parameters are invalid
|
268
|
+
# @raise [StatelyDB::Error] if the item is not found
|
235
269
|
# @raise [Exception] if any other exception is raised
|
236
270
|
#
|
237
271
|
# @example
|
@@ -226,6 +226,13 @@ module StatelyDB
|
|
226
226
|
# the item will be returned while inside the transaction block.
|
227
227
|
#
|
228
228
|
# @param item [StatelyDB::Item] the item to store
|
229
|
+
# @param must_not_exist [Boolean] A condition that indicates this item must
|
230
|
+
# not already exist at any of its key paths. If there is already an item
|
231
|
+
# at one of those paths, the Put operation will fail with a
|
232
|
+
# "ConditionalCheckFailed" error. Note that if the item has an
|
233
|
+
# `initialValue` field in its key, that initial value will automatically
|
234
|
+
# be chosen not to conflict with existing items, so this condition only
|
235
|
+
# applies to key paths that do not contain the `initialValue` field.
|
229
236
|
# @return [String, Integer] the id of the item
|
230
237
|
#
|
231
238
|
# @example
|
@@ -235,16 +242,18 @@ module StatelyDB
|
|
235
242
|
# results.puts.each do |result|
|
236
243
|
# puts result.key_path
|
237
244
|
# end
|
238
|
-
def put(item)
|
239
|
-
resp = put_batch(item)
|
245
|
+
def put(item, must_not_exist: false)
|
246
|
+
resp = put_batch({ item:, must_not_exist: })
|
240
247
|
resp.first
|
241
248
|
end
|
242
249
|
|
243
|
-
# Put a batch of up to 50 Items into a StatelyDB Store. Results are not
|
244
|
-
# committed and will be available in the
|
245
|
-
#
|
250
|
+
# Put a batch of up to 50 Items into a StatelyDB Store. Results are not
|
251
|
+
# returned until the transaction is committed and will be available in the
|
252
|
+
# Result object returned by commit. A list of identifiers for the items
|
253
|
+
# will be returned while inside the transaction block.
|
246
254
|
#
|
247
|
-
# @param items [StatelyDB::Item, Array<StatelyDB::Item>] the items to store. Max
|
255
|
+
# @param items [StatelyDB::Item, Array<StatelyDB::Item>] the items to store. Max
|
256
|
+
# 50 items.
|
248
257
|
# @return [Array<StatelyDB::UUID, String, Integer, nil>] the ids of the items
|
249
258
|
#
|
250
259
|
# @example
|
@@ -255,14 +264,22 @@ module StatelyDB
|
|
255
264
|
# puts result.key_path
|
256
265
|
# end
|
257
266
|
def put_batch(*items)
|
258
|
-
|
267
|
+
puts = Array(items).flatten.map do |input|
|
268
|
+
if input.is_a?(Hash)
|
269
|
+
item = input[:item]
|
270
|
+
Stately::Db::PutItem.new(
|
271
|
+
item: item.send("marshal_stately"),
|
272
|
+
must_not_exist: input[:must_not_exist]
|
273
|
+
)
|
274
|
+
else
|
275
|
+
Stately::Db::PutItem.new(
|
276
|
+
item: input.send("marshal_stately")
|
277
|
+
)
|
278
|
+
end
|
279
|
+
end
|
259
280
|
req = Stately::Db::TransactionRequest.new(
|
260
281
|
put_items: Stately::Db::TransactionPut.new(
|
261
|
-
puts:
|
262
|
-
Stately::Db::PutItem.new(
|
263
|
-
item: item.send("marshal_stately")
|
264
|
-
)
|
265
|
-
end
|
282
|
+
puts:
|
266
283
|
)
|
267
284
|
)
|
268
285
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: statelydb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stately Cloud, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -16,28 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
19
|
+
version: 2.21.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
26
|
+
version: 2.21.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: async-actor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.1
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: async-http
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - '='
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
47
|
+
version: 0.85.0
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - '='
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
54
|
+
version: 0.85.0
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: grpc
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|