whoosh 1.1.0 → 1.2.1

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: 03b756c310f71435839a3628bfdfa9499bb70869b9475e75eb6cce861caa9583
4
- data.tar.gz: a848d792014e80cc6bee413ad14277881895a62a40854644f3a088bfe0ea164a
3
+ metadata.gz: f647a91b22fcee8322c6af7bf0ab0ac0108aa3113e2d4db89a3f187bb6a1caa3
4
+ data.tar.gz: e46c07b2f4fbaf1233bb52a321ff161952989313adbbfebdb5b7bf1c08675f08
5
5
  SHA512:
6
- metadata.gz: b764fa79a9e8297c66e0ce0c56be1127c1d1780ccb3840a0868a55841c4cb1108efffeff90cdaf4105a7df9259764aff7a8afe1047a42809e0c039b1e74be11d
7
- data.tar.gz: 297cc80e8ca6ba22df946e07204f114bda7634480f5af72520caa0bdfde7b8604e6b66bf1b081f3527bede997a320b9ffc1b44c14fba21c2a05e5778101451c5
6
+ metadata.gz: 1ca2dbc63ab481d5d61260cfbfca37768a9498fa74fe90d68f039884f05ca2a79f0fa08da07fdca04ba175e95ab0997d5cc00acf0e8b2bbceef5ca576f8fef91
7
+ data.tar.gz: b0361d6a812b80e093125f7be7d5681977ab615b6ebccacbe87753352223e6058e0076dd2eace8e3092a60a57e3387b9ebe0baed4b2f623befeb8f1c38d9435e
data/lib/whoosh/app.rb CHANGED
@@ -29,6 +29,7 @@ module Whoosh
29
29
  auto_register_database
30
30
  auto_register_storage
31
31
  auto_register_http
32
+ auto_register_vectors
32
33
  auto_configure_jobs
33
34
  @metrics = Metrics.new
34
35
  auto_register_metrics
@@ -302,6 +303,10 @@ module Whoosh
302
303
  @di.provide(:http) { HTTP }
303
304
  end
304
305
 
306
+ def auto_register_vectors
307
+ @di.provide(:vectors) { VectorStore.build(@config.data) }
308
+ end
309
+
305
310
  def auto_configure_jobs
306
311
  backend = Jobs.build_backend(@config.data)
307
312
  Jobs.configure(backend: backend, di: @di)
@@ -224,6 +224,64 @@ module Whoosh
224
224
  end
225
225
  }
226
226
 
227
+ desc "ci", "Run full CI pipeline (lint + security + tests)"
228
+ def ci
229
+ puts "=> Whoosh CI Pipeline"
230
+ puts "=" * 50
231
+ puts ""
232
+
233
+ steps = []
234
+
235
+ # Step 1: Rubocop (lint)
236
+ if system("bundle exec rubocop --version > /dev/null 2>&1")
237
+ steps << { name: "Rubocop (lint)", cmd: "bundle exec rubocop --format simple" }
238
+ else
239
+ puts " [skip] Rubocop not installed (add rubocop to Gemfile)"
240
+ end
241
+
242
+ # Step 2: Brakeman (security)
243
+ if system("bundle exec brakeman --version > /dev/null 2>&1")
244
+ steps << { name: "Brakeman (security)", cmd: "bundle exec brakeman -q --no-pager" }
245
+ else
246
+ puts " [skip] Brakeman not installed (add brakeman to Gemfile)"
247
+ end
248
+
249
+ # Step 3: RSpec (tests)
250
+ if system("bundle exec rspec --version > /dev/null 2>&1")
251
+ steps << { name: "RSpec (tests)", cmd: "bundle exec rspec --format progress" }
252
+ else
253
+ puts " [skip] RSpec not installed"
254
+ end
255
+
256
+ # Step 4: Gem build check
257
+ gemspec = Dir.glob("*.gemspec").first
258
+ if gemspec
259
+ steps << { name: "Gem build", cmd: "gem build #{gemspec} --quiet" }
260
+ end
261
+
262
+ failed = []
263
+ steps.each_with_index do |step, i|
264
+ puts "--- [#{i + 1}/#{steps.length}] #{step[:name]} ---"
265
+ success = system(step[:cmd])
266
+ if success
267
+ puts " ✓ #{step[:name]} passed"
268
+ else
269
+ puts " ✗ #{step[:name]} FAILED"
270
+ failed << step[:name]
271
+ end
272
+ puts ""
273
+ end
274
+
275
+ puts "=" * 50
276
+ if failed.empty?
277
+ puts "=> All checks passed! ✓"
278
+ exit 0
279
+ else
280
+ puts "=> FAILED: #{failed.join(', ')}"
281
+ exit 1
282
+ end
283
+ end
284
+
227
285
  desc "new NAME", "Create a new Whoosh project"
228
286
  option :minimal, type: :boolean, default: false
229
287
  option :full, type: :boolean, default: false
@@ -31,6 +31,7 @@ module Whoosh
31
31
  write(dir, ".rspec", rspec_config)
32
32
  write(dir, "Dockerfile", dockerfile)
33
33
  write(dir, ".dockerignore", dockerignore)
34
+ write(dir, ".rubocop.yml", rubocop_config)
34
35
  write(dir, "README.md", readme(name))
35
36
 
36
37
  # Create empty SQLite DB directory
@@ -55,6 +56,8 @@ module Whoosh
55
56
  puts " http://localhost:9292/docs # Swagger UI"
56
57
  puts " http://localhost:9292/metrics # Prometheus metrics"
57
58
  puts ""
59
+ puts " whoosh ci # run lint + security + tests"
60
+ puts ""
58
61
  end
59
62
 
60
63
  class << self
@@ -145,6 +148,8 @@ module Whoosh
145
148
  group :development, :test do
146
149
  gem "rspec"
147
150
  gem "rack-test"
151
+ gem "rubocop", require: false
152
+ gem "brakeman", require: false
148
153
  end
149
154
  GEM
150
155
 
@@ -175,8 +180,10 @@ module Whoosh
175
180
  max_connections: 10
176
181
  log_level: debug
177
182
 
183
+ # Cache & Jobs auto-detect:
184
+ # No REDIS_URL → in-memory (just works)
185
+ # Set REDIS_URL → auto-switches to Redis
178
186
  cache:
179
- store: memory
180
187
  default_ttl: 300
181
188
 
182
189
  jobs:
@@ -188,6 +195,12 @@ module Whoosh
188
195
  level: info
189
196
  format: json
190
197
 
198
+ # Vector store auto-detect:
199
+ # zvec gem installed → uses zvec, otherwise → in-memory
200
+ # vector:
201
+ # adapter: auto
202
+ # path: db/vectors
203
+
191
204
  docs:
192
205
  enabled: true
193
206
 
@@ -341,6 +354,35 @@ module Whoosh
341
354
  DOCKERFILE
342
355
  end
343
356
 
357
+ def rubocop_config
358
+ <<~YAML
359
+ AllCops:
360
+ TargetRubyVersion: 3.4
361
+ NewCops: enable
362
+ SuggestExtensions: false
363
+ Exclude:
364
+ - db/migrations/**/*
365
+ - vendor/**/*
366
+
367
+ Style/FrozenStringLiteralComment:
368
+ Enabled: true
369
+
370
+ Style/StringLiterals:
371
+ EnforcedStyle: double_quotes
372
+
373
+ Layout/LineLength:
374
+ Max: 120
375
+
376
+ Metrics/MethodLength:
377
+ Max: 25
378
+
379
+ Metrics/BlockLength:
380
+ Exclude:
381
+ - spec/**/*
382
+ - test/**/*
383
+ YAML
384
+ end
385
+
344
386
  def dockerignore
345
387
  <<~IGNORE
346
388
  .git
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whoosh
4
+ module VectorStore
5
+ class MemoryStore
6
+ def initialize
7
+ @collections = {}
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ # Store a vector with metadata
12
+ def insert(collection, id:, vector:, metadata: {})
13
+ @mutex.synchronize do
14
+ @collections[collection] ||= {}
15
+ @collections[collection][id] = { vector: vector, metadata: metadata }
16
+ end
17
+ end
18
+
19
+ # Search by cosine similarity, return top-k results
20
+ def search(collection, vector:, limit: 10)
21
+ @mutex.synchronize do
22
+ items = @collections[collection]
23
+ return [] unless items && !items.empty?
24
+
25
+ scored = items.map do |id, data|
26
+ score = cosine_similarity(vector, data[:vector])
27
+ { id: id, score: score, metadata: data[:metadata] }
28
+ end
29
+
30
+ scored.sort_by { |r| -r[:score] }.first(limit)
31
+ end
32
+ end
33
+
34
+ # Delete a vector
35
+ def delete(collection, id:)
36
+ @mutex.synchronize do
37
+ @collections[collection]&.delete(id)
38
+ end
39
+ end
40
+
41
+ # Count vectors in a collection
42
+ def count(collection)
43
+ @mutex.synchronize do
44
+ @collections[collection]&.size || 0
45
+ end
46
+ end
47
+
48
+ # Drop a collection
49
+ def drop(collection)
50
+ @mutex.synchronize do
51
+ @collections.delete(collection)
52
+ end
53
+ end
54
+
55
+ def close
56
+ # No-op
57
+ end
58
+
59
+ private
60
+
61
+ def cosine_similarity(a, b)
62
+ return 0.0 if a.empty? || b.empty? || a.length != b.length
63
+
64
+ dot = 0.0
65
+ mag_a = 0.0
66
+ mag_b = 0.0
67
+
68
+ a.length.times do |i|
69
+ dot += a[i] * b[i]
70
+ mag_a += a[i] * a[i]
71
+ mag_b += b[i] * b[i]
72
+ end
73
+
74
+ denom = Math.sqrt(mag_a) * Math.sqrt(mag_b)
75
+ denom.zero? ? 0.0 : dot / denom
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whoosh
4
+ module VectorStore
5
+ autoload :MemoryStore, "whoosh/vector_store/memory_store"
6
+
7
+ # Auto-detect: zvec gem → use it, otherwise → in-memory
8
+ def self.build(config_data = {})
9
+ vector_config = config_data["vector"] || {}
10
+ adapter = vector_config["adapter"] || "auto"
11
+
12
+ case adapter
13
+ when "auto"
14
+ # Try zvec first, fall back to memory
15
+ if zvec_available?
16
+ require "whoosh/vector_store/zvec_store"
17
+ ZvecStore.new(**zvec_options(vector_config))
18
+ else
19
+ MemoryStore.new
20
+ end
21
+ when "memory"
22
+ MemoryStore.new
23
+ when "zvec"
24
+ require "whoosh/vector_store/zvec_store"
25
+ ZvecStore.new(**zvec_options(vector_config))
26
+ else
27
+ MemoryStore.new
28
+ end
29
+ end
30
+
31
+ def self.zvec_available?
32
+ require "zvec"
33
+ true
34
+ rescue LoadError
35
+ false
36
+ end
37
+
38
+ def self.zvec_options(config)
39
+ { path: config["path"] || "db/vectors" }
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Whoosh
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.1"
5
5
  end
data/lib/whoosh.rb CHANGED
@@ -28,6 +28,7 @@ module Whoosh
28
28
  autoload :Jobs, "whoosh/jobs"
29
29
  autoload :Metrics, "whoosh/metrics"
30
30
  autoload :Paginate, "whoosh/paginate"
31
+ autoload :VectorStore, "whoosh/vector_store"
31
32
 
32
33
  module Auth
33
34
  autoload :ApiKey, "whoosh/auth/api_key"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whoosh
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johannes Dwi Cahyo
@@ -241,6 +241,8 @@ files:
241
241
  - lib/whoosh/test.rb
242
242
  - lib/whoosh/types.rb
243
243
  - lib/whoosh/uploaded_file.rb
244
+ - lib/whoosh/vector_store.rb
245
+ - lib/whoosh/vector_store/memory_store.rb
244
246
  - lib/whoosh/version.rb
245
247
  homepage: https://github.com/johannesdwicahyo/whoosh
246
248
  licenses: