statelydb 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a723737fdce6e8ad2bec8937cfd79f6d968df0fc3e9e747c64e18af9ce769601
4
- data.tar.gz: 1f0279a8b9507f4cffacc360ab82e2289471a40de39bd3891f0c7db5c63877c6
3
+ metadata.gz: f1d7563d7f40d84bdc1ef3dacff41707ed1d915b638223d33b488ca4c7de531f
4
+ data.tar.gz: dd682b92fbc100505e15820c5415acadc9defc6bc2f2d0dad5b5797deddbf24b
5
5
  SHA512:
6
- metadata.gz: 815835070a91366b18948afe145eb548ae4f6ecbc6559ed3b8193288cd1084b7ac1a416264b0769afea93f4a8c01e17b0474facb46fe90460b0b3db803466428
7
- data.tar.gz: e0c75f31911432f2544c4097fbc0c7a1615624257e80495aef9e5b3a0dddc92dd9341683d24416c4a32ab19b8bd34a2d79abb577781d75ee41b55a02f2e2e11c
6
+ metadata.gz: c0f27aa770fdf0c7b3386ac37862226cb4b5215766d265e47c82323b2157966e6ce0d87b5e4081d8d9dcd549f45cd0f6527203b0025b0e6c62ce0780c81dae74
7
+ data.tar.gz: de86db0a2a0227e6bec231d11c378bffff42fa7911ccc0874bd3ed97fae7f73d0ac29ce55b31817f75bfa82c0b63daa79dabd28276a66e17e2748770450f6243
@@ -30,16 +30,19 @@ module StatelyDB
30
30
  # @param [String] client_secret The StatelyDB client secret credential
31
31
  # @param [String] client_id The StatelyDB client ID credential
32
32
  # @param [String] access_key The StatelyDB access key credential
33
+ # @param [Float] base_retry_backoff_secs The base retry backoff in seconds
33
34
  def initialize(
34
35
  origin: "https://oauth.stately.cloud",
35
36
  audience: "api.stately.cloud",
36
37
  client_secret: ENV.fetch("STATELY_CLIENT_SECRET", nil),
37
38
  client_id: ENV.fetch("STATELY_CLIENT_ID", nil),
38
- access_key: ENV.fetch("STATELY_ACCESS_KEY", nil)
39
+ access_key: ENV.fetch("STATELY_ACCESS_KEY", nil),
40
+ base_retry_backoff_secs: 1
39
41
  )
40
42
  super()
41
43
  @actor = Async::Actor.new(Actor.new(origin: origin, audience: audience,
42
- client_secret: client_secret, client_id: client_id, access_key: access_key))
44
+ client_secret: client_secret, client_id: client_id, access_key: access_key,
45
+ base_retry_backoff_secs: base_retry_backoff_secs))
43
46
  # this initialization cannot happen in the constructor because it is async and must run on the event loop
44
47
  # which is not available in the constructor
45
48
  @actor.init
@@ -64,18 +67,23 @@ module StatelyDB
64
67
  # @param [String] audience The OAuth Audience for the token
65
68
  # @param [String] client_secret The StatelyDB client secret credential
66
69
  # @param [String] client_id The StatelyDB client ID credential
70
+ # @param [String] access_key The StatelyDB access key credential
71
+ # @param [Float] base_retry_backoff_secs The base retry backoff in seconds
67
72
  def initialize(
68
73
  origin:,
69
74
  audience:,
70
75
  client_secret:,
71
76
  client_id:,
72
- access_key:
77
+ access_key:,
78
+ base_retry_backoff_secs:
73
79
  )
74
80
  super()
75
81
 
76
82
  @token_fetcher = nil
77
83
  if !access_key.nil?
78
- @token_fetcher = StatelyDB::Common::Auth::StatelyAccessTokenFetcher.new(origin: origin, access_key: access_key)
84
+ @token_fetcher = StatelyDB::Common::Auth::StatelyAccessTokenFetcher.new(
85
+ origin: origin, access_key: access_key, base_retry_backoff_secs: base_retry_backoff_secs
86
+ )
79
87
  elsif !client_secret.nil? && !client_id.nil?
80
88
  @token_fetcher = StatelyDB::Common::Auth::Auth0TokenFetcher.new(origin: origin, audience: audience,
81
89
  client_secret: client_secret, client_id: client_id)
@@ -58,8 +58,6 @@ module StatelyDB
58
58
  body = JSON.dump({ "client_id" => @client_id, client_secret: @client_secret, audience: @audience,
59
59
  grant_type: DEFAULT_GRANT_TYPE })
60
60
  Sync do
61
- # TODO: Wrap this in a retry loop and parse errors like we
62
- # do in the Go SDK.
63
61
  response = @client.post("/oauth/token", headers, body)
64
62
  raise "Auth request failed" if response.status != 200
65
63
 
@@ -77,11 +75,22 @@ module StatelyDB
77
75
 
78
76
  # StatelyAccessTokenFetcher is a TokenFetcher that fetches tokens from the StatelyDB API
79
77
  class StatelyAccessTokenFetcher < TokenFetcher
78
+ NON_RETRYABLE_ERRORS = [
79
+ GRPC::Core::StatusCodes::UNAUTHENTICATED,
80
+ GRPC::Core::StatusCodes::PERMISSION_DENIED,
81
+ GRPC::Core::StatusCodes::NOT_FOUND,
82
+ GRPC::Core::StatusCodes::UNIMPLEMENTED,
83
+ GRPC::Core::StatusCodes::INVALID_ARGUMENT
84
+ ].freeze
85
+ RETRY_ATTEMPTS = 10
86
+
80
87
  # @param [String] origin The origin of the OAuth server
81
88
  # @param [String] access_key The StatelyDB access key credential
82
- def initialize(origin:, access_key:)
89
+ # @param [Float] base_retry_backoff_secs The base backoff time in seconds
90
+ def initialize(origin:, access_key:, base_retry_backoff_secs:)
83
91
  super()
84
92
  @access_key = access_key
93
+ @base_retry_backoff_secs = base_retry_backoff_secs
85
94
  @channel = Common::Net.new_channel(endpoint: origin)
86
95
  error_interceptor = Common::ErrorInterceptor.new
87
96
  @stub = Stately::Auth::AuthService::Stub.new(nil, nil, channel_override: @channel,
@@ -91,14 +100,45 @@ module StatelyDB
91
100
  # Fetch a new token from the StatelyDB API
92
101
  # @return [TokenResult] The fetched TokenResult
93
102
  def fetch
94
- resp = @stub.get_auth_token(Stately::Auth::GetAuthTokenRequest.new(access_key: @access_key))
95
- TokenResult.new(token: resp.auth_token, expires_in_secs: resp.expires_in_s)
103
+ RETRY_ATTEMPTS.times do |i|
104
+ resp = @stub.get_auth_token(Stately::Auth::GetAuthTokenRequest.new(access_key: @access_key))
105
+ return TokenResult.new(token: resp.auth_token, expires_in_secs: resp.expires_in_s)
106
+ rescue StatelyDB::Error => e
107
+ # raise if it's the final attempt or if the error is not retryable
108
+ raise e unless self.class.retryable_error?(e) && i < RETRY_ATTEMPTS - 1
109
+
110
+ # exponential backoff
111
+ sleep(backoff(i, @base_retry_backoff_secs))
112
+ end
96
113
  end
97
114
 
98
115
  def close
99
116
  @channel&.close
100
117
  end
118
+
119
+ # Check if an error is retryable
120
+ # @param [StatelyDB::Error] err The error to check
121
+ # @return [Boolean] True if the error is retryable
122
+ def self.retryable_error?(err)
123
+ !NON_RETRYABLE_ERRORS.include?(err.code)
124
+ end
101
125
  end
102
126
  end
103
127
  end
104
128
  end
129
+
130
+ # backoff returns a duration to wait before retrying a request. `attempt` is
131
+ # the current attempt number, starting from 0 (e.g. the first attempt is 0,
132
+ # then 1, then 2...).
133
+ #
134
+ # @param [Integer] attempt The current attempt number
135
+ # @param [Float] base_backoff The base backoff time in seconds
136
+ # @return [Float] The duration in seconds to wait before retrying
137
+ def backoff(attempt, base_backoff)
138
+ # Double the base backoff time per attempt, starting with 1
139
+ exp = 2**attempt
140
+ # Add a full jitter to the backoff time, from no wait to 100% of the exponential backoff.
141
+ # See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
142
+ jitter = rand
143
+ (exp * jitter * base_backoff)
144
+ end
data/sig/statelydb.rbi CHANGED
@@ -636,11 +636,22 @@ module StatelyDB
636
636
 
637
637
  # StatelyAccessTokenFetcher is a TokenFetcher that fetches tokens from the StatelyDB API
638
638
  class StatelyAccessTokenFetcher < StatelyDB::Common::Auth::TokenFetcher
639
+ NON_RETRYABLE_ERRORS = T.let([
640
+ GRPC::Core::StatusCodes::UNAUTHENTICATED,
641
+ GRPC::Core::StatusCodes::PERMISSION_DENIED,
642
+ GRPC::Core::StatusCodes::NOT_FOUND,
643
+ GRPC::Core::StatusCodes::UNIMPLEMENTED,
644
+ GRPC::Core::StatusCodes::INVALID_ARGUMENT
645
+ ].freeze, T.untyped)
646
+ RETRY_ATTEMPTS = T.let(10, T.untyped)
647
+
639
648
  # _@param_ `origin` — The origin of the OAuth server
640
649
  #
641
650
  # _@param_ `access_key` — The StatelyDB access key credential
642
- sig { params(origin: String, access_key: String).void }
643
- def initialize(origin:, access_key:); end
651
+ #
652
+ # _@param_ `base_retry_backoff_secs` — The base backoff time in seconds
653
+ sig { params(origin: String, access_key: String, base_retry_backoff_secs: Float).void }
654
+ def initialize(origin:, access_key:, base_retry_backoff_secs:); end
644
655
 
645
656
  # Fetch a new token from the StatelyDB API
646
657
  #
@@ -650,6 +661,14 @@ module StatelyDB
650
661
 
651
662
  sig { returns(T.untyped) }
652
663
  def close; end
664
+
665
+ # Check if an error is retryable
666
+ #
667
+ # _@param_ `err` — The error to check
668
+ #
669
+ # _@return_ — True if the error is retryable
670
+ sig { params(err: StatelyDB::Error).returns(T::Boolean) }
671
+ def self.retryable_error?(err); end
653
672
  end
654
673
 
655
674
  # TokenProvider is an abstract base class that should be extended
@@ -682,16 +701,19 @@ module StatelyDB
682
701
  # _@param_ `client_id` — The StatelyDB client ID credential
683
702
  #
684
703
  # _@param_ `access_key` — The StatelyDB access key credential
704
+ #
705
+ # _@param_ `base_retry_backoff_secs` — The base retry backoff in seconds
685
706
  sig do
686
707
  params(
687
708
  origin: String,
688
709
  audience: String,
689
710
  client_secret: String,
690
711
  client_id: String,
691
- access_key: String
712
+ access_key: String,
713
+ base_retry_backoff_secs: Float
692
714
  ).void
693
715
  end
694
- def initialize(origin: "https://oauth.stately.cloud", audience: "api.stately.cloud", client_secret: ENV.fetch("STATELY_CLIENT_SECRET", nil), client_id: ENV.fetch("STATELY_CLIENT_ID", nil), access_key: ENV.fetch("STATELY_ACCESS_KEY", nil)); end
716
+ def initialize(origin: "https://oauth.stately.cloud", audience: "api.stately.cloud", client_secret: ENV.fetch("STATELY_CLIENT_SECRET", nil), client_id: ENV.fetch("STATELY_CLIENT_ID", nil), access_key: ENV.fetch("STATELY_ACCESS_KEY", nil), base_retry_backoff_secs: 1); end
695
717
 
696
718
  # Close the token provider and kill any background operations
697
719
  # This just invokes the close method on the actor which should do the cleanup
@@ -714,16 +736,21 @@ module StatelyDB
714
736
  # _@param_ `client_secret` — The StatelyDB client secret credential
715
737
  #
716
738
  # _@param_ `client_id` — The StatelyDB client ID credential
739
+ #
740
+ # _@param_ `access_key` — The StatelyDB access key credential
741
+ #
742
+ # _@param_ `base_retry_backoff_secs` — The base retry backoff in seconds
717
743
  sig do
718
744
  params(
719
745
  origin: String,
720
746
  audience: String,
721
747
  client_secret: String,
722
748
  client_id: String,
723
- access_key: T.untyped
749
+ access_key: String,
750
+ base_retry_backoff_secs: Float
724
751
  ).void
725
752
  end
726
- def initialize(origin:, audience:, client_secret:, client_id:, access_key:); end
753
+ def initialize(origin:, audience:, client_secret:, client_id:, access_key:, base_retry_backoff_secs:); end
727
754
 
728
755
  # Initialize the actor. This runs on the actor thread which means
729
756
  # we can dispatch async operations here.
data/sig/statelydb.rbs CHANGED
@@ -550,10 +550,15 @@ module StatelyDB
550
550
 
551
551
  # StatelyAccessTokenFetcher is a TokenFetcher that fetches tokens from the StatelyDB API
552
552
  class StatelyAccessTokenFetcher < StatelyDB::Common::Auth::TokenFetcher
553
+ NON_RETRYABLE_ERRORS: untyped
554
+ RETRY_ATTEMPTS: untyped
555
+
553
556
  # _@param_ `origin` — The origin of the OAuth server
554
557
  #
555
558
  # _@param_ `access_key` — The StatelyDB access key credential
556
- def initialize: (origin: String, access_key: String) -> void
559
+ #
560
+ # _@param_ `base_retry_backoff_secs` — The base backoff time in seconds
561
+ def initialize: (origin: String, access_key: String, base_retry_backoff_secs: Float) -> void
557
562
 
558
563
  # Fetch a new token from the StatelyDB API
559
564
  #
@@ -561,6 +566,13 @@ module StatelyDB
561
566
  def fetch: () -> TokenResult
562
567
 
563
568
  def close: () -> untyped
569
+
570
+ # Check if an error is retryable
571
+ #
572
+ # _@param_ `err` — The error to check
573
+ #
574
+ # _@return_ — True if the error is retryable
575
+ def self.retryable_error?: (StatelyDB::Error err) -> bool
564
576
  end
565
577
 
566
578
  # TokenProvider is an abstract base class that should be extended
@@ -591,12 +603,15 @@ module StatelyDB
591
603
  # _@param_ `client_id` — The StatelyDB client ID credential
592
604
  #
593
605
  # _@param_ `access_key` — The StatelyDB access key credential
606
+ #
607
+ # _@param_ `base_retry_backoff_secs` — The base retry backoff in seconds
594
608
  def initialize: (
595
609
  ?origin: String,
596
610
  ?audience: String,
597
611
  ?client_secret: String,
598
612
  ?client_id: String,
599
- ?access_key: String
613
+ ?access_key: String,
614
+ ?base_retry_backoff_secs: Float
600
615
  ) -> void
601
616
 
602
617
  # Close the token provider and kill any background operations
@@ -618,12 +633,17 @@ module StatelyDB
618
633
  # _@param_ `client_secret` — The StatelyDB client secret credential
619
634
  #
620
635
  # _@param_ `client_id` — The StatelyDB client ID credential
636
+ #
637
+ # _@param_ `access_key` — The StatelyDB access key credential
638
+ #
639
+ # _@param_ `base_retry_backoff_secs` — The base retry backoff in seconds
621
640
  def initialize: (
622
641
  origin: String,
623
642
  audience: String,
624
643
  client_secret: String,
625
644
  client_id: String,
626
- access_key: untyped
645
+ access_key: String,
646
+ base_retry_backoff_secs: Float
627
647
  ) -> void
628
648
 
629
649
  # Initialize the actor. This runs on the actor thread which means
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.14.0
4
+ version: 0.15.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-12-12 00:00:00.000000000 Z
11
+ date: 2024-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async