search-engine-for-typesense 30.1.6.13 → 30.1.6.14

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: 687864490764b552a2e0a30095711766c1ce073dcb0de9538ecc710ae142aa31
4
- data.tar.gz: 48aa2e8cdcc750685264e432e8bf3d74b6b61fb11a0e0220d42bd393d90a4e97
3
+ metadata.gz: 751fec345b2fe0e2e9290ffba4af92639a58fc7663d6cd78e09527ace07d5104
4
+ data.tar.gz: 3332605b4afa0d24b66c4f4f0ab5f7be4a6676765213bf9441b508fddc1c8f9f
5
5
  SHA512:
6
- metadata.gz: 67a000ce3cfa475b57d6059c640ba42682747e32ed3f2ca680bd64f5b4f08f0def7aa205e5d1ba44df9004fc76899734602f0f395542af211962290706dfeb3e
7
- data.tar.gz: 1aa9dab69f9daad382d32e9d291d0d3226b057363a0da981e77b6b595a92f516257f8776518813d01a6616c0ac823c6d5efe7f0d148473ef52d3043c37afb31d
6
+ metadata.gz: bdc7e4250b406a06004d532cd379a597866a1a5419c90697fd177692f6b8a31c682e7dbc1c1f3727a823190d95c6612022a64520bc88e3dd3cec9a98e555e7c9
7
+ data.tar.gz: 68f460a120e7763bb2353531b0fadbc3f1b4f642eafebcc6cecd2af6ec1423db936159f6cce586f74d47f4e10782e0e02dfaf3673caa1e94d037f0628dcd9844
@@ -131,7 +131,8 @@ module SearchEngine
131
131
  # returns a non-ok status. Aborting the block prevents the alias swap,
132
132
  # keeping the previous (fully-indexed) physical collection active.
133
133
  #
134
- # The partially-indexed new physical is left intact for inspection.
134
+ # The partially-indexed new physical is automatically cleaned up by
135
+ # the +ensure+ block in {Schema.apply!}.
135
136
  class IndexationAborted < Error
136
137
  # @return [Hash] the indexation result hash (:status, :docs_total, etc.)
137
138
  attr_reader :result
@@ -156,9 +156,10 @@ module SearchEngine
156
156
  # The reindexing step can be provided via an optional block (yielded with the new
157
157
  # physical name). If no block is given, and the klass responds to
158
158
  # `reindex_all_to(physical_name)`, that method will be called. If neither is available,
159
- # an ArgumentError is raised and no alias swap occurs. If reindexing fails, the
160
- # newly created physical is left intact for inspection; retention cleanup only runs
161
- # after a successful alias swap.
159
+ # an ArgumentError is raised and no alias swap occurs. If reindexing fails or
160
+ # is interrupted, the newly created physical collection is automatically deleted
161
+ # to prevent orphan accumulation. Retention cleanup only runs after a successful
162
+ # alias swap.
162
163
  #
163
164
  # @param klass [Class] model class inheriting from {SearchEngine::Base}
164
165
  # @param client [SearchEngine::Client] optional client wrapper (for tests)
@@ -168,8 +169,12 @@ module SearchEngine
168
169
  # @raise [SearchEngine::Errors::Api, ArgumentError]
169
170
  # @see `https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/schema#lifecycle`
170
171
  # @see `https://typesense.org/docs/latest/api/collections.html`
171
- def apply!(klass, client: nil, force_rebuild: false)
172
+ def apply!(klass, client: nil, force_rebuild: false) # rubocop:disable Metrics/MethodLength
172
173
  client ||= SearchEngine.client
174
+ new_physical = nil
175
+ physical_created = false
176
+ apply_succeeded = false
177
+
173
178
  # Optimization: Try in-place update first if not forced to rebuild.
174
179
  # If update! returns true, the schema is synced (either no changes or successfully patched).
175
180
  if !force_rebuild && update!(klass, client: client)
@@ -178,6 +183,7 @@ module SearchEngine
178
183
  # Resolve current physical to return consistent result
179
184
  physical = client.resolve_alias(logical) || logical
180
185
 
186
+ apply_succeeded = true
181
187
  return update_result_payload(logical, physical)
182
188
  end
183
189
 
@@ -203,6 +209,7 @@ module SearchEngine
203
209
  end
204
210
 
205
211
  client.create_collection(create_schema)
212
+ physical_created = true
206
213
 
207
214
  if block_given?
208
215
  yield new_physical
@@ -216,6 +223,7 @@ module SearchEngine
216
223
  current_after_reindex = client.resolve_alias(logical)
217
224
  swapped = current_after_reindex != new_physical
218
225
  client.upsert_alias(logical, new_physical) if swapped
226
+ apply_succeeded = true
219
227
 
220
228
  # Retention cleanup
221
229
  _, dropped = enforce_retention!(logical, new_physical, client: client, keep_last: effective_keep_last(klass))
@@ -245,6 +253,8 @@ module SearchEngine
245
253
  dropped_physicals: dropped,
246
254
  action: :rebuild
247
255
  }
256
+ ensure
257
+ cleanup_interrupted_physical!(new_physical, client) if physical_created && !apply_succeeded
248
258
  end
249
259
 
250
260
  # Roll back the alias for the given klass to the previous retained physical collection.
@@ -390,6 +400,32 @@ module SearchEngine
390
400
 
391
401
  private
392
402
 
403
+ # Best-effort cleanup of a physical collection left behind by an interrupted
404
+ # or failed {.apply!}. Rescues all errors so it never masks the original
405
+ # exception. If the delete itself fails, a warning directs users to
406
+ # {.prune_orphans!} as a fallback.
407
+ #
408
+ # @param physical_name [String, nil] the physical collection to remove
409
+ # @param client [SearchEngine::Client, nil]
410
+ # @return [void]
411
+ def cleanup_interrupted_physical!(physical_name, client)
412
+ return if physical_name.nil? || client.nil?
413
+
414
+ client.delete_collection(physical_name, timeout_ms: 60_000)
415
+
416
+ if defined?(ActiveSupport::Notifications)
417
+ SearchEngine::Instrumentation.instrument(
418
+ 'search_engine.schema.cleanup_interrupted',
419
+ physical: physical_name, status: :ok
420
+ ) {}
421
+ end
422
+ rescue StandardError => error
423
+ warn(
424
+ 'SearchEngine::Schema — failed to clean up interrupted physical ' \
425
+ "'#{physical_name}': #{error.message}. Run prune_orphans! to remove it."
426
+ )
427
+ end
428
+
393
429
  # Generate a new physical name using UTC timestamp + 3-digit sequence.
394
430
  # Example: "products_20250131_235959_001"
395
431
  def generate_physical_name(logical, client:)
@@ -3,5 +3,5 @@
3
3
  module SearchEngine
4
4
  # Current gem version.
5
5
  # @return [String]
6
- VERSION = '30.1.6.13'
6
+ VERSION = '30.1.6.14'
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search-engine-for-typesense
3
3
  version: !ruby/object:Gem::Version
4
- version: 30.1.6.13
4
+ version: 30.1.6.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Shkoda