vectra-client 1.1.2 → 1.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f59705f200c8a164cc9303e6761776af49e4a81a88f101eae2e371eccca8807
4
- data.tar.gz: 217dd74d00151f3ba6e94ed80718999656f9616b84e843dd937197cee612540a
3
+ metadata.gz: c2ed5842cd4c611fd0c140abc8925a2d13577c4f2094dbc71c7b2772aceda9e4
4
+ data.tar.gz: da3005805b97d5b36c632d3e2c34ad9a3966eb65b09ce2f5f84911858c2d34d5
5
5
  SHA512:
6
- metadata.gz: 8e62f19e82dfb88a14ae50f7cee6afdd8d058ece9972de8f447ba3cc881420d401cfd0d3a093d299e8273795958085349a8d3e6416ecd429f50ea2a05103dea3
7
- data.tar.gz: ace8ecc519dc588f917e6b29721ed9cfa3ff9b3f3fd7b3e82911233ed7d6e59a9f443e81ae9c0d20dd4891f904d8497cb506064964b00b3dc7587f12137e12bf
6
+ metadata.gz: bb3324f260afb42c5e03356f8d09c902571fcaddc39969541c918523c4ebadbdb8dadf35764d9d04cce2b308ee4aa18d9f7c3266690a1a728ceeecb9908a3622
7
+ data.tar.gz: adca9a174757547d1306dbaa727069fa1954a8dc40b101c1cb378bfb3663c94ad7ec0a35082ea578ef4dd4a7ab53477bdfde6dd258623c83e161eb8c6cff2b42
data/CHANGELOG.md CHANGED
@@ -1,25 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.1.4](https://github.com/stokry/vectra/tree/v1.1.4) (2026-01-20)
4
+
5
+ [Full Changelog](https://github.com/stokry/vectra/compare/v1.1.3...v1.1.4)
6
+
7
+ - Add `Client#valid?` – non-raising validation (returns true/false)
8
+ - Add `Client#for_tenant` – multi-tenant block helper for namespace isolation
9
+
10
+ ## [v1.1.3](https://github.com/stokry/vectra/tree/v1.1.3) (2026-01-20)
11
+
12
+ [Full Changelog](https://github.com/stokry/vectra/compare/v1.1.2...v1.1.3)
13
+
14
+ ## [v1.1.2](https://github.com/stokry/vectra/tree/v1.1.2) (2026-01-19)
15
+
16
+ [Full Changelog](https://github.com/stokry/vectra/compare/v1.1.1...v1.1.2)
17
+
3
18
  ## [v1.1.1](https://github.com/stokry/vectra/tree/v1.1.1) (2026-01-15)
4
19
 
5
20
  [Full Changelog](https://github.com/stokry/vectra/compare/v1.1.0...v1.1.1)
6
21
 
7
- ### Added
8
- - **Text Search Support** - New `text_search` method for keyword-only search without requiring embeddings
9
- - Qdrant: BM25 text search
10
- - Weaviate: BM25 text search via GraphQL
11
- - pgvector: PostgreSQL full-text search (`to_tsvector`, `plainto_tsquery`, `ts_rank`)
12
- - Memory: Simple keyword matching (for testing)
13
- - Raises `UnsupportedFeatureError` for Pinecone (use sparse vectors instead)
14
- - **Documentation Search** - Added search functionality to documentation site with `simple-jekyll-search`
15
- - Client-side search with fuzzy matching
16
- - Search index auto-generated from all documentation pages
17
- - Responsive search UI in navigation
18
-
19
- ### Changed
20
- - Updated API documentation to include `text_search` method in overview and cheatsheet
21
- - Enhanced documentation with text search examples and use cases
22
-
23
22
  ## [v1.1.0](https://github.com/stokry/vectra/tree/v1.1.0) (2026-01-15)
24
23
 
25
24
  [Full Changelog](https://github.com/stokry/vectra/compare/v1.0.8...v1.1.0)
@@ -51,6 +51,36 @@ client.query(vector: query_embedding, top_k: 10)
51
51
 
52
52
  In a Rails app with `config/vectra.yml` generated by `rails generate vectra:index`, if that YAML file contains only one entry, `Vectra::Client.new` will automatically use that entry's `index` (and `namespace`, if present) as defaults.
53
53
 
54
+ ### Temporary defaults (block-scoped)
55
+
56
+ ```ruby
57
+ client.with_defaults(index: "products", namespace: "tenant-2") do |c|
58
+ c.upsert(vectors: [...])
59
+ c.query(vector: query_embedding, top_k: 10)
60
+ end
61
+ ```
62
+
63
+ ### Validate client config & capabilities
64
+
65
+ ```ruby
66
+ client.validate!
67
+ client.validate!(require_default_index: true)
68
+ client.validate!(features: [:text_search])
69
+
70
+ # Non-raising: returns true/false
71
+ client.valid?
72
+ client.valid?(require_default_index: true)
73
+ ```
74
+
75
+ ### Multi-tenant (for_tenant)
76
+
77
+ ```ruby
78
+ client.for_tenant("acme", namespace_prefix: "tenant_") do |c|
79
+ c.upsert(vectors: [...])
80
+ c.query(vector: query_embedding, top_k: 10)
81
+ end
82
+ ```
83
+
54
84
  ### Upsert
55
85
 
56
86
  ```ruby
data/docs/api/methods.md CHANGED
@@ -426,6 +426,94 @@ After the block finishes (even if it raises), the previous `config.timeout` valu
426
426
 
427
427
  ---
428
428
 
429
+ ### `client.validate!(require_default_index: false, require_default_namespace: false, features: [])`
430
+
431
+ Validate the client configuration and (optionally) your defaults and provider feature support.
432
+
433
+ This is useful in boot-time checks (Rails initializers), health endpoints, and CI.
434
+
435
+ **Parameters:**
436
+ - `require_default_index` (Boolean) - Require `client.default_index` to be set
437
+ - `require_default_namespace` (Boolean) - Require `client.default_namespace` to be set
438
+ - `features` (Array<Symbol> or Symbol) - Provider features (methods) that must be supported (e.g. `:text_search`)
439
+
440
+ **Returns:** `Vectra::Client` (self)
441
+
442
+ **Raises:** `Vectra::ConfigurationError` when validation fails
443
+
444
+ **Example:**
445
+ ```ruby
446
+ # Ensure client is configured
447
+ client.validate!
448
+
449
+ # Ensure calls can omit index:
450
+ client.validate!(require_default_index: true)
451
+
452
+ # Ensure provider supports text_search:
453
+ client.validate!(features: [:text_search])
454
+ ```
455
+
456
+ ---
457
+
458
+ ### `client.with_defaults(index: ..., namespace: ...) { ... }`
459
+
460
+ Temporarily override the client's **default index and/or namespace** inside a block.
461
+
462
+ Unlike `with_index_and_namespace`, this helper accepts keyword arguments and only overrides what you pass.
463
+
464
+ **Returns:** Block result
465
+
466
+ **Example:**
467
+ ```ruby
468
+ client.with_defaults(index: "products", namespace: "tenant-2") do |c|
469
+ c.upsert(vectors: [...]) # uses products/tenant-2
470
+ c.query(vector: embedding, top_k: 10)
471
+ end
472
+
473
+ # Previous defaults are restored after the block.
474
+ ```
475
+
476
+ ---
477
+
478
+ ### `client.valid?(require_default_index: false, require_default_namespace: false, features: [])`
479
+
480
+ Non-raising validation. Returns `true` if the client passes `validate!`, `false` otherwise. Accepts the same options as `validate!`.
481
+
482
+ **Returns:** `Boolean`
483
+
484
+ **Example:**
485
+ ```ruby
486
+ next unless client.valid?
487
+ client.upsert(vectors: [...])
488
+
489
+ # With same options as validate!
490
+ client.valid?(require_default_index: true)
491
+ client.valid?(features: [:text_search])
492
+ ```
493
+
494
+ ---
495
+
496
+ ### `client.for_tenant(tenant_id, namespace_prefix: "tenant_") { ... }`
497
+
498
+ Multi-tenant block helper. Temporarily sets the default namespace to `"#{namespace_prefix}#{tenant_id}"`, yields the client, then restores the previous namespace. `tenant_id` can be a string, symbol, or anything responding to `to_s`.
499
+
500
+ **Parameters:**
501
+ - `tenant_id` (String, Symbol, #to_s) - Tenant identifier
502
+ - `namespace_prefix` (String) - Prefix for namespace (default: `"tenant_"`)
503
+
504
+ **Returns:** Block result
505
+
506
+ **Example:**
507
+ ```ruby
508
+ client.for_tenant("acme", namespace_prefix: "tenant_") do |c|
509
+ c.upsert(vectors: [...])
510
+ c.query(vector: embedding, top_k: 10)
511
+ end
512
+ # All operations use namespace "tenant_acme"; previous default restored after block.
513
+ ```
514
+
515
+ ---
516
+
429
517
  ### `client.health_check`
430
518
 
431
519
  Detailed health check with provider-specific information.
data/lib/vectra/client.rb CHANGED
@@ -44,6 +44,9 @@ module Vectra
44
44
 
45
45
  attr_reader :config, :provider, :default_index, :default_namespace
46
46
 
47
+ DEFAULT_UNSET = Object.new.freeze
48
+ private_constant :DEFAULT_UNSET
49
+
47
50
  class << self
48
51
  # Get the global middleware stack
49
52
  #
@@ -621,6 +624,67 @@ module Vectra
621
624
  result
622
625
  end
623
626
 
627
+ # Validate the client configuration and provider capabilities.
628
+ #
629
+ # This is a convenience wrapper around `config.validate!` that can also
630
+ # validate your defaults and provider feature support.
631
+ #
632
+ # @param require_default_index [Boolean] require default index to be set
633
+ # @param require_default_namespace [Boolean] require default namespace to be set
634
+ # @param features [Array<Symbol>, Symbol] provider features (methods) required, e.g. :text_search
635
+ # @return [true]
636
+ # @raise [ConfigurationError] when client is misconfigured
637
+ #
638
+ # @example Basic validation
639
+ # client.validate! # => client
640
+ #
641
+ # @example Ensure you can call methods without passing index:
642
+ # client.validate!(require_default_index: true)
643
+ #
644
+ # @example Ensure provider supports text search:
645
+ # client.validate!(features: [:text_search])
646
+ #
647
+ def validate!(require_default_index: false, require_default_namespace: false, features: [])
648
+ errors = []
649
+
650
+ append_client_config_errors(errors)
651
+ append_client_provider_errors(errors)
652
+ append_client_default_errors(
653
+ errors,
654
+ require_default_index: require_default_index,
655
+ require_default_namespace: require_default_namespace
656
+ )
657
+ append_client_feature_errors(errors, features: features)
658
+
659
+ raise ConfigurationError, client_validation_message(errors) if errors.any?
660
+
661
+ self
662
+ end
663
+
664
+ # Non-raising validation. Returns true if client passes validate!, false otherwise.
665
+ #
666
+ # Accepts the same options as validate!.
667
+ #
668
+ # @param require_default_index [Boolean] require default index to be set
669
+ # @param require_default_namespace [Boolean] require default namespace to be set
670
+ # @param features [Array<Symbol>, Symbol] provider features required, e.g. :text_search
671
+ # @return [Boolean]
672
+ #
673
+ # @example
674
+ # next unless client.valid?
675
+ # client.upsert(vectors: [...])
676
+ #
677
+ def valid?(require_default_index: false, require_default_namespace: false, features: [])
678
+ validate!(
679
+ require_default_index: require_default_index,
680
+ require_default_namespace: require_default_namespace,
681
+ features: features
682
+ )
683
+ true
684
+ rescue ConfigurationError
685
+ false
686
+ end
687
+
624
688
  # Chainable query builder
625
689
  #
626
690
  # @api public
@@ -786,6 +850,52 @@ module Vectra
786
850
  @default_namespace = namespace if @default_namespace.nil? && namespace.is_a?(String) && !namespace.empty?
787
851
  end
788
852
 
853
+ def append_client_config_errors(errors)
854
+ config.validate!
855
+ rescue ConfigurationError, UnsupportedProviderError => e
856
+ errors << e.message
857
+ end
858
+
859
+ def append_client_provider_errors(errors)
860
+ errors << "Provider is not initialized" if provider.nil?
861
+ end
862
+
863
+ def append_client_default_errors(errors, require_default_index:, require_default_namespace:)
864
+ append_default_index_error(errors) if require_default_index
865
+ append_default_namespace_error(errors) if require_default_namespace
866
+ end
867
+
868
+ def append_default_index_error(errors)
869
+ if default_index.nil? || (default_index.respond_to?(:empty?) && default_index.empty?)
870
+ errors << "Default index is not set (pass `index:` to Vectra::Client.new, or set it via config/vectra.yml in Rails)"
871
+ elsif !default_index.is_a?(String)
872
+ errors << "Default index must be a String"
873
+ end
874
+ end
875
+
876
+ def append_default_namespace_error(errors)
877
+ if default_namespace.nil? || (default_namespace.respond_to?(:empty?) && default_namespace.empty?)
878
+ errors << "Default namespace is not set (pass `namespace:` to Vectra::Client.new, or set it via config/vectra.yml in Rails)"
879
+ elsif !default_namespace.is_a?(String)
880
+ errors << "Default namespace must be a String"
881
+ end
882
+ end
883
+
884
+ def append_client_feature_errors(errors, features:)
885
+ return if provider.nil?
886
+
887
+ Array(features).compact.each do |feature|
888
+ method = feature.to_sym
889
+ next if provider.respond_to?(method)
890
+
891
+ errors << "Provider does not support `#{method}`"
892
+ end
893
+ end
894
+
895
+ def client_validation_message(errors)
896
+ "Client validation failed:\n- #{errors.join("\n- ")}"
897
+ end
898
+
789
899
  def validate_index!(index)
790
900
  raise ValidationError, "Index name cannot be nil" if index.nil?
791
901
  raise ValidationError, "Index name must be a string" unless index.is_a?(String)
@@ -908,7 +1018,47 @@ module Vectra
908
1018
  end
909
1019
  end
910
1020
 
911
- public :with_index, :with_namespace, :with_index_and_namespace, :with_timeout
1021
+ # Temporarily override default index and/or namespace within a block.
1022
+ #
1023
+ # Unlike `with_index_and_namespace`, this method accepts keyword arguments
1024
+ # and only overrides the values you pass.
1025
+ #
1026
+ # @param index [String, nil] temporary index name (omit to keep current)
1027
+ # @param namespace [String, nil] temporary namespace (omit to keep current)
1028
+ # @yield [Client] yields self with overridden defaults
1029
+ # @return [Object] block result
1030
+ def with_defaults(index: DEFAULT_UNSET, namespace: DEFAULT_UNSET)
1031
+ previous_index = @default_index
1032
+ previous_namespace = @default_namespace
1033
+
1034
+ @default_index = index unless index.equal?(DEFAULT_UNSET)
1035
+ @default_namespace = namespace unless namespace.equal?(DEFAULT_UNSET)
1036
+
1037
+ yield self
1038
+ ensure
1039
+ @default_index = previous_index
1040
+ @default_namespace = previous_namespace
1041
+ end
1042
+
1043
+ # Multi-tenant block helper: temporarily sets default namespace to +"#{namespace_prefix}#{tenant_id}"+.
1044
+ #
1045
+ # @param tenant_id [String, Symbol, #to_s] tenant identifier
1046
+ # @param namespace_prefix [String] prefix for namespace (default: "tenant_")
1047
+ # @yield [Client] yields self with overridden namespace
1048
+ # @return [Object] block result
1049
+ #
1050
+ # @example
1051
+ # client.for_tenant("acme", namespace_prefix: "tenant_") do |c|
1052
+ # c.upsert(vectors: [...])
1053
+ # c.query(vector: emb, top_k: 10)
1054
+ # end
1055
+ #
1056
+ def for_tenant(tenant_id, namespace_prefix: "tenant_")
1057
+ ns = "#{namespace_prefix}#{tenant_id}"
1058
+ with_namespace(ns) { yield self }
1059
+ end
1060
+
1061
+ public :with_index, :with_namespace, :with_index_and_namespace, :with_defaults, :with_timeout, :for_tenant
912
1062
  end
913
1063
  # rubocop:enable Metrics/ClassLength
914
1064
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vectra
4
- VERSION = "1.1.2"
4
+ VERSION = "1.1.4"
5
5
  end
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.1.2
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mijo Kristo