search-engine-for-typesense 30.1.4 → 30.1.5

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: 4fb677abb16142281c9c9171d6fba238aac168da395019672cd085818b72e726
4
- data.tar.gz: 770426cb9622b18ca1bd90acef3957ac969dc89a6e781f4603599ec005494662
3
+ metadata.gz: ae511da617970ab104c22bde9c151ed099d9eb9c55909d9bc34f328ea56ac5f4
4
+ data.tar.gz: 49d4de291b6239a7f70e6bd93de83ef175916d7c403029d7a7382189fe90b9d7
5
5
  SHA512:
6
- metadata.gz: fcb4690a5bedcfa92815e1157b142b107abc82fba535ed9566902b569210dac401ed3e496983af48d18747a0ce6f5452182a1cddc7611fb1004af43d7ab2117a
7
- data.tar.gz: 616bd994b94675ed4be2c554b55aef5842bae91ca38e9afd16106ff282602836d98aea60e19a8beec3c864e1e5381b10da521a2ceb6f4b8184f25f50845d71b2
6
+ metadata.gz: ba3c5e26e351975c5c967954b3f20e0fe14f438aab7126672c1b9ad42ec1bbc15c224f5412a5d0cd778e237451af23a8a4e237731ab2c9bc2e2dabb3650256e9
7
+ data.tar.gz: 82766d2a92e2f2a3a81f430d066254ff83b14966a3cce4b7171102979c07e0fe53925e2aa118075b6c679f8dd0881364d5c5d681c4c56e9c819b6ecb5aceaea1
@@ -13,19 +13,20 @@ module SearchEngine
13
13
  # @param client [SearchEngine::Client, nil]
14
14
  # @param pre [Symbol, nil] :ensure (ensure presence) or :index (ensure + fix drift)
15
15
  # @param force_rebuild [Boolean] when true, force schema rebuild (blue/green)
16
- # @return [void]
16
+ # @return [Hash, nil] result hash with :status, :docs_total, :success_total, :failed_total, :sample_error
17
17
  def index_collection(partition: nil, client: nil, pre: nil, force_rebuild: false)
18
18
  logical = respond_to?(:collection) ? collection.to_s : name.to_s
19
19
  puts
20
20
  puts(%(>>>>>> Indexing Collection "#{logical}"))
21
21
  client_obj = client || SearchEngine.client
22
22
 
23
- if partition.nil?
24
- __se_index_full(client: client_obj, pre: pre, force_rebuild: force_rebuild)
25
- else
26
- __se_index_partial(partition: partition, client: client_obj, pre: pre)
27
- end
28
- nil
23
+ result = if partition.nil?
24
+ __se_index_full(client: client_obj, pre: pre, force_rebuild: force_rebuild)
25
+ else
26
+ __se_index_partial(partition: partition, client: client_obj, pre: pre)
27
+ end
28
+
29
+ result.is_a?(Hash) ? result.merge(collection: logical) : { collection: logical, status: :ok }
29
30
  end
30
31
 
31
32
  def reindex_collection!(pre: nil)
@@ -66,8 +67,9 @@ module SearchEngine
66
67
  indexed_inside_apply,
67
68
  force_rebuild
68
69
  )
69
- __se_full_indexation(applied, indexed_inside_apply)
70
+ result = __se_full_indexation(applied, indexed_inside_apply)
70
71
  __se_full_retention(applied, logical, client)
72
+ result
71
73
  end
72
74
 
73
75
  def __se_full_apply_if_missing(client, missing)
@@ -116,21 +118,19 @@ module SearchEngine
116
118
  end
117
119
 
118
120
  def __se_full_indexation(applied, indexed_inside_apply)
119
- cascade_ok = false
121
+ result = nil
120
122
  if applied && indexed_inside_apply
121
123
  puts('Step 5: Indexing — skip (performed during schema apply)')
122
- begin
123
- cascade_ok = indexed_inside_apply.to_sym == :ok
124
- rescue StandardError
125
- cascade_ok = false
126
- end
124
+ result = indexed_inside_apply if indexed_inside_apply.is_a?(Hash)
127
125
  else
128
126
  puts('Step 5: Indexing — processing')
129
- idx_status = __se_index_partitions!(into: nil)
127
+ result = __se_index_partitions!(into: nil)
130
128
  puts('Step 5: Indexing — done')
131
- cascade_ok = (idx_status == :ok)
132
129
  end
130
+
131
+ cascade_ok = result.is_a?(Hash) ? result[:status] == :ok : false
133
132
  __se_cascade_after_indexation!(context: :full) if cascade_ok
133
+ result
134
134
  end
135
135
 
136
136
  def __se_full_retention(applied, logical, client)
@@ -152,32 +152,33 @@ module SearchEngine
152
152
  puts("Step 1: Presence — processing → #{missing ? 'missing' : 'present'}")
153
153
  if missing
154
154
  puts('Partial: collection is not present. Quit early.')
155
- return
155
+ return { status: :failed, docs_total: 0, success_total: 0, failed_total: 0,
156
+ sample_error: 'Collection not present' }
156
157
  end
157
158
 
158
159
  puts('Step 2: Check Schema Status — processing')
159
160
  drift = __se_schema_drift?(diff)
160
161
  if drift
161
162
  puts('Partial: schema is not up-to-date. Exit early (run full indexing).')
162
- return
163
+ return { status: :failed, docs_total: 0, success_total: 0, failed_total: 0,
164
+ sample_error: 'Schema drift detected' }
163
165
  end
164
166
  puts('Step 2: Check Schema Status — in_sync')
165
167
 
166
168
  __se_preflight_dependencies!(mode: pre, client: client) if pre
167
169
 
168
170
  puts('Step 3: Partial Indexing — processing')
169
- all_ok = true
171
+ summaries = []
170
172
  partitions.each do |p|
171
173
  summary = SearchEngine::Indexer.rebuild_partition!(self, partition: p, into: nil)
174
+ summaries << summary
172
175
  puts(SearchEngine::Logging::PartitionProgress.line(p, summary))
173
- begin
174
- all_ok &&= (summary.status == :ok)
175
- rescue StandardError
176
- all_ok &&= false
177
- end
178
176
  end
179
177
  puts('Step 3: Partial Indexing — done')
180
- __se_cascade_after_indexation!(context: :full) if all_ok
178
+
179
+ result = __se_build_index_result(summaries)
180
+ __se_cascade_after_indexation!(context: :full) if result[:status] == :ok
181
+ result
181
182
  end
182
183
 
183
184
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize
@@ -320,32 +320,51 @@ module SearchEngine
320
320
  __se_index_partitions_parallel!(parts, into, max_p)
321
321
  else
322
322
  summary = SearchEngine::Indexer.rebuild_partition!(self, partition: nil, into: into)
323
- summary.status
323
+ __se_build_index_result([summary])
324
324
  end
325
325
  end
326
+
327
+ # Aggregate an array of Indexer::Summary structs into a single result hash.
328
+ # @param summaries [Array<SearchEngine::Indexer::Summary>]
329
+ # @return [Hash] { status:, docs_total:, success_total:, failed_total:, sample_error: }
330
+ def __se_build_index_result(summaries)
331
+ docs = 0
332
+ success = 0
333
+ failed = 0
334
+ sample_error = nil
335
+
336
+ Array(summaries).each do |s|
337
+ docs += s.docs_total.to_i
338
+ success += s.success_total.to_i
339
+ failed += s.failed_total.to_i
340
+ sample_error ||= __se_extract_sample_error(s)
341
+ end
342
+
343
+ status = if failed.positive? && success.zero?
344
+ :failed
345
+ elsif failed.positive?
346
+ :partial
347
+ else
348
+ :ok
349
+ end
350
+
351
+ { status: status, docs_total: docs, success_total: success, failed_total: failed, sample_error: sample_error }
352
+ end
353
+
354
+ private :__se_build_index_result
326
355
  end
327
356
 
328
357
  class_methods do
329
358
  # Sequential processing of partition list
330
359
  def __se_index_partitions_seq!(parts, into)
331
- agg = :ok
360
+ summaries = []
332
361
  parts.each do |part|
333
362
  summary = SearchEngine::Indexer.rebuild_partition!(self, partition: part, into: into)
363
+ summaries << summary
334
364
  puts(SearchEngine::Logging::PartitionProgress.line(part, summary))
335
- # Log batches individually if there are multiple batches
336
365
  __se_log_batches_from_summary(summary.batches) if summary.batches_total.to_i > 1
337
- begin
338
- st = summary.status
339
- if st == :failed
340
- agg = :failed
341
- elsif st == :partial && agg == :ok
342
- agg = :partial
343
- end
344
- rescue StandardError
345
- agg = :failed
346
- end
347
366
  end
348
- agg
367
+ __se_build_index_result(summaries)
349
368
  end
350
369
  end
351
370
 
@@ -356,42 +375,37 @@ module SearchEngine
356
375
  pool = Concurrent::FixedThreadPool.new(max_p)
357
376
  ctx = SearchEngine::Instrumentation.context
358
377
  mtx = Mutex.new
359
- agg = :ok
378
+ summaries = []
379
+ partition_errors = []
360
380
  begin
361
381
  parts.each do |part|
362
382
  pool.post do
363
383
  SearchEngine::Instrumentation.with_context(ctx) do
364
384
  summary = SearchEngine::Indexer.rebuild_partition!(self, partition: part, into: into)
365
385
  mtx.synchronize do
386
+ summaries << summary
366
387
  puts(SearchEngine::Logging::PartitionProgress.line(part, summary))
367
- # Log batches individually if there are multiple batches
368
388
  __se_log_batches_from_summary(summary.batches) if summary.batches_total.to_i > 1
369
- begin
370
- st = summary.status
371
- if st == :failed
372
- agg = :failed
373
- elsif st == :partial && agg == :ok
374
- agg = :partial
375
- end
376
- rescue StandardError
377
- agg = :failed
378
- end
379
389
  end
380
390
  end
381
391
  rescue StandardError => error
382
392
  mtx.synchronize do
383
393
  warn(" partition=#{part.inspect} → error=#{error.class}: #{error.message.to_s[0, 200]}")
384
- agg = :failed
394
+ partition_errors << "#{error.class}: #{error.message.to_s[0, 200]}"
385
395
  end
386
396
  end
387
397
  end
388
398
  ensure
389
399
  pool.shutdown
390
- # Wait up to 1 hour, then force-kill and wait a bit more to ensure cleanup
391
400
  pool.wait_for_termination(3600) || pool.kill
392
401
  pool.wait_for_termination(60)
393
402
  end
394
- agg
403
+ result = __se_build_index_result(summaries)
404
+ if partition_errors.any?
405
+ result[:status] = :failed
406
+ result[:sample_error] ||= partition_errors.first
407
+ end
408
+ result
395
409
  end
396
410
  end
397
411
 
@@ -107,50 +107,60 @@ module SearchEngine
107
107
  cascade_count: cascade_order.size
108
108
  }
109
109
 
110
+ collection_results = []
110
111
  failed_collections_total = 0
111
112
 
112
113
  SearchEngine::Instrumentation.with_context(bulk: true, bulk_suppress_cascade: true, bulk_mode: mode.to_sym) do
113
114
  SearchEngine::Instrumentation.instrument('search_engine.bulk.run', payload.merge(stats)) do |ctx|
114
- # Stage 1 — process referenced-first inputs (that are not referrers of other inputs)
115
- stage1_list.each do |name|
116
- klass = safe_collection_class(name)
117
- unless klass
118
- failed_collections_total += 1
119
- next
120
- end
121
-
122
- case mode.to_sym
123
- when :index
124
- klass.index_collection
125
- else
126
- klass.reindex_collection!
127
- end
128
- end
129
-
130
- # Stage 2 — process collected referencers once
131
- cascade_order.each do |name|
132
- klass = safe_collection_class(name)
133
- unless klass
134
- failed_collections_total += 1
135
- next
136
- end
137
-
138
- case mode.to_sym
139
- when :index
140
- klass.index_collection(pre: :ensure, force_rebuild: true)
141
- else
142
- klass.reindex_collection!
143
- end
144
- end
115
+ run_stage!(mode, stage1_list, :input, collection_results)
116
+ run_stage!(mode, cascade_order, :cascade, collection_results)
145
117
 
118
+ failed_collections_total = collection_results.count { |r| r[:status] != :ok }
146
119
  ctx[:failed_collections_total] = failed_collections_total
147
120
  end
148
121
  end
149
122
 
150
123
  payload[:failed_collections_total] = failed_collections_total
124
+ payload[:collection_results] = collection_results
151
125
  payload.merge(stats)
152
126
  end
153
127
 
128
+ def run_stage!(mode, names, stage, collection_results)
129
+ names.each do |name|
130
+ klass = safe_collection_class(name)
131
+ unless klass
132
+ collection_results << unresolved_result(name, stage)
133
+ next
134
+ end
135
+
136
+ result = run_single_collection(mode, klass, stage)
137
+ collection_results << normalize_collection_result(result, name, stage)
138
+ end
139
+ end
140
+
141
+ def run_single_collection(mode, klass, stage)
142
+ case mode.to_sym
143
+ when :index
144
+ stage == :cascade ? klass.index_collection(pre: :ensure, force_rebuild: true) : klass.index_collection
145
+ else
146
+ klass.reindex_collection!
147
+ end
148
+ end
149
+
150
+ def unresolved_result(name, stage)
151
+ { collection: name, stage: stage, status: :failed,
152
+ docs_total: 0, success_total: 0, failed_total: 0,
153
+ sample_error: 'Unresolved collection class' }
154
+ end
155
+
156
+ def normalize_collection_result(result, name, stage)
157
+ if result.is_a?(Hash)
158
+ result.merge(stage: stage)
159
+ else
160
+ { collection: name, stage: stage, status: :ok }
161
+ end
162
+ end
163
+
154
164
  # Normalize inputs to logical collection names.
155
165
  # @param list [Array<Symbol, String, Class>]
156
166
  # @return [Array<String>]
@@ -3,5 +3,5 @@
3
3
  module SearchEngine
4
4
  # Current gem version.
5
5
  # @return [String]
6
- VERSION = '30.1.4'
6
+ VERSION = '30.1.5'
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search-engine-for-typesense
3
3
  version: !ruby/object:Gem::Version
4
- version: 30.1.4
4
+ version: 30.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Shkoda
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-03-08 00:00:00.000000000 Z
11
+ date: 2026-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby