vectra-client 1.0.8 → 1.1.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.
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vectra
4
+ module Middleware
5
+ # Response object returned through middleware chain
6
+ #
7
+ # @example Success response
8
+ # response = Response.new(result: { success: true })
9
+ # response.success? # => true
10
+ # response.result # => { success: true }
11
+ #
12
+ # @example Error response
13
+ # response = Response.new(error: StandardError.new('Failed'))
14
+ # response.failure? # => true
15
+ # response.error # => #<StandardError: Failed>
16
+ #
17
+ # @example With metadata
18
+ # response = Response.new(result: [])
19
+ # response.metadata[:duration_ms] = 45
20
+ # response.metadata[:cache_hit] = true
21
+ #
22
+ class Response
23
+ attr_accessor :result, :error, :metadata
24
+
25
+ # @param result [Object] The successful result
26
+ # @param error [Exception, nil] The error if failed
27
+ def initialize(result: nil, error: nil)
28
+ @result = result
29
+ @error = error
30
+ @metadata = {}
31
+ end
32
+
33
+ # Check if the response was successful
34
+ #
35
+ # @return [Boolean] true if no error
36
+ def success?
37
+ error.nil?
38
+ end
39
+
40
+ # Check if the response failed
41
+ #
42
+ # @return [Boolean] true if error present
43
+ def failure?
44
+ !success?
45
+ end
46
+
47
+ # Raise error if present
48
+ #
49
+ # @raise [Exception] The stored error
50
+ # @return [void]
51
+ def raise_if_error!
52
+ raise error if error
53
+ end
54
+
55
+ # Get the result or raise error
56
+ #
57
+ # @return [Object] The result
58
+ # @raise [Exception] If error present
59
+ def value!
60
+ raise_if_error!
61
+ result
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vectra
4
+ module Middleware
5
+ # Retry middleware for handling transient failures
6
+ #
7
+ # Automatically retries failed requests with configurable backoff strategy.
8
+ #
9
+ # @example With default settings (3 attempts, exponential backoff)
10
+ # Vectra::Client.use Vectra::Middleware::Retry
11
+ #
12
+ # @example With custom settings
13
+ # Vectra::Client.use Vectra::Middleware::Retry, max_attempts: 5, backoff: :linear
14
+ #
15
+ # @example Per-client retry
16
+ # client = Vectra::Client.new(
17
+ # provider: :pinecone,
18
+ # middleware: [[Vectra::Middleware::Retry, { max_attempts: 3 }]]
19
+ # )
20
+ #
21
+ class Retry < Base
22
+ # @param max_attempts [Integer] Maximum number of attempts (default: 3)
23
+ # @param backoff [Symbol, Numeric] Backoff strategy (:exponential, :linear) or fixed delay
24
+ def initialize(max_attempts: 3, backoff: :exponential)
25
+ super()
26
+ @max_attempts = max_attempts
27
+ @backoff = backoff
28
+ end
29
+
30
+ def call(request, app)
31
+ attempt = 0
32
+ last_error = nil
33
+
34
+ loop do
35
+ attempt += 1
36
+
37
+ begin
38
+ response = app.call(request)
39
+
40
+ # If successful, return immediately
41
+ if response.success?
42
+ response.metadata[:retry_count] = attempt - 1
43
+ return response
44
+ end
45
+
46
+ # If error is retryable and we haven't exceeded max attempts, retry
47
+ if response.error && retryable?(response.error) && attempt < @max_attempts
48
+ sleep(backoff_delay(attempt))
49
+ next
50
+ end
51
+
52
+ # Error is not retryable or max attempts reached, return response
53
+ response.metadata[:retry_count] = attempt - 1
54
+ return response
55
+ rescue StandardError => e
56
+ last_error = e
57
+
58
+ # If error is retryable and we haven't exceeded max attempts, retry
59
+ if retryable?(e) && attempt < @max_attempts
60
+ sleep(backoff_delay(attempt))
61
+ next
62
+ end
63
+
64
+ # Error is not retryable or max attempts reached, raise
65
+ raise
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ # Check if error is retryable
73
+ #
74
+ # @param error [Exception] The error to check
75
+ # @return [Boolean] true if error is retryable
76
+ def retryable?(error)
77
+ error.is_a?(Vectra::RateLimitError) ||
78
+ error.is_a?(Vectra::ConnectionError) ||
79
+ error.is_a?(Vectra::TimeoutError) ||
80
+ error.is_a?(Vectra::ServerError)
81
+ end
82
+
83
+ # Calculate backoff delay
84
+ #
85
+ # @param attempt [Integer] Current attempt number
86
+ # @return [Float] Delay in seconds
87
+ def backoff_delay(attempt)
88
+ case @backoff
89
+ when :exponential
90
+ # 0.2s, 0.4s, 0.8s, 1.6s, ...
91
+ (2**(attempt - 1)) * 0.2
92
+ when :linear
93
+ # 0.5s, 1.0s, 1.5s, 2.0s, ...
94
+ attempt * 0.5
95
+ when Numeric
96
+ @backoff
97
+ else
98
+ 1.0
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vectra
4
+ module Middleware
5
+ # Middleware stack executor
6
+ #
7
+ # Builds and executes a chain of middleware around provider calls.
8
+ # Similar to Rack middleware, each middleware wraps the next one
9
+ # in the chain until reaching the actual provider.
10
+ #
11
+ # @example Basic usage
12
+ # provider = Vectra::Providers::Memory.new
13
+ # middlewares = [LoggingMiddleware.new, RetryMiddleware.new]
14
+ # stack = Stack.new(provider, middlewares)
15
+ #
16
+ # result = stack.call(:upsert, index: 'test', vectors: [...])
17
+ #
18
+ class Stack
19
+ # @param provider [Vectra::Providers::Base] The actual provider
20
+ # @param middlewares [Array<Base>] Array of middleware instances
21
+ def initialize(provider, middlewares = [])
22
+ @provider = provider
23
+ @middlewares = middlewares
24
+ end
25
+
26
+ # Execute the middleware stack for an operation
27
+ #
28
+ # @param operation [Symbol] The operation to perform (:upsert, :query, etc.)
29
+ # @param params [Hash] The operation parameters
30
+ # @return [Object] The result from the provider
31
+ # @raise [Exception] Any error from middleware or provider
32
+ def call(operation, **params)
33
+ request = Request.new(operation: operation, **params)
34
+
35
+ # Build middleware chain
36
+ app = build_chain(request)
37
+
38
+ # Execute chain
39
+ response = app.call(request)
40
+
41
+ # Raise if error occurred
42
+ raise response.error if response.error
43
+
44
+ response.result
45
+ end
46
+
47
+ private
48
+
49
+ # Build the middleware chain
50
+ #
51
+ # @param request [Request] The request object (unused here, but available)
52
+ # @return [Proc] The complete middleware chain
53
+ def build_chain(_request)
54
+ # Final app: actual provider call
55
+ final_app = lambda do |req|
56
+ # Remove middleware-specific params before calling provider
57
+ provider_params = req.to_h.except(:provider)
58
+ result = @provider.public_send(req.operation, **provider_params)
59
+ Response.new(result: result)
60
+ rescue StandardError => e
61
+ Response.new(error: e)
62
+ end
63
+
64
+ # Wrap with middlewares in reverse order
65
+ # (last middleware in array is first to execute)
66
+ @middlewares.reverse.inject(final_app) do |next_app, middleware|
67
+ lambda do |req|
68
+ middleware.call(req, next_app)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vectra
4
- VERSION = "1.0.8"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/vectra.rb CHANGED
@@ -18,6 +18,15 @@ require_relative "vectra/health_check"
18
18
  require_relative "vectra/credential_rotation"
19
19
  require_relative "vectra/audit_log"
20
20
  require_relative "vectra/active_record"
21
+ require_relative "vectra/middleware/request"
22
+ require_relative "vectra/middleware/response"
23
+ require_relative "vectra/middleware/base"
24
+ require_relative "vectra/middleware/stack"
25
+ require_relative "vectra/middleware/logging"
26
+ require_relative "vectra/middleware/retry"
27
+ require_relative "vectra/middleware/instrumentation"
28
+ require_relative "vectra/middleware/pii_redaction"
29
+ require_relative "vectra/middleware/cost_tracker"
21
30
  require_relative "vectra/providers/base"
22
31
  require_relative "vectra/providers/pinecone"
23
32
  require_relative "vectra/providers/qdrant"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vectra-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.8
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mijo Kristo
@@ -268,6 +268,7 @@ files:
268
268
  - docs/guides/faq.md
269
269
  - docs/guides/getting-started.md
270
270
  - docs/guides/installation.md
271
+ - docs/guides/middleware.md
271
272
  - docs/guides/monitoring.md
272
273
  - docs/guides/performance.md
273
274
  - docs/guides/rails-integration.md
@@ -294,6 +295,7 @@ files:
294
295
  - examples/grafana-dashboard.json
295
296
  - examples/grafana-setup.md
296
297
  - examples/instrumentation_demo.rb
298
+ - examples/middleware_demo.rb
297
299
  - examples/prometheus-exporter.rb
298
300
  - lib/generators/vectra/index_generator.rb
299
301
  - lib/generators/vectra/install_generator.rb
@@ -316,6 +318,15 @@ files:
316
318
  - lib/vectra/instrumentation/new_relic.rb
317
319
  - lib/vectra/instrumentation/sentry.rb
318
320
  - lib/vectra/logging.rb
321
+ - lib/vectra/middleware/base.rb
322
+ - lib/vectra/middleware/cost_tracker.rb
323
+ - lib/vectra/middleware/instrumentation.rb
324
+ - lib/vectra/middleware/logging.rb
325
+ - lib/vectra/middleware/pii_redaction.rb
326
+ - lib/vectra/middleware/request.rb
327
+ - lib/vectra/middleware/response.rb
328
+ - lib/vectra/middleware/retry.rb
329
+ - lib/vectra/middleware/stack.rb
319
330
  - lib/vectra/pool.rb
320
331
  - lib/vectra/providers/base.rb
321
332
  - lib/vectra/providers/memory.rb