search-engine-for-typesense 30.1.0 → 30.1.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 +4 -4
- data/README.md +5 -5
- data/app/search_engine/search_engine/index_partition_job.rb +7 -26
- data/lib/generators/search_engine/install/install_generator.rb +1 -1
- data/lib/generators/search_engine/install/templates/initializer.rb.tt +11 -11
- data/lib/generators/search_engine/model/model_generator.rb +2 -2
- data/lib/generators/search_engine/model/templates/model.rb.tt +2 -2
- data/lib/search_engine/admin/stopwords.rb +1 -1
- data/lib/search_engine/admin/synonyms.rb +1 -1
- data/lib/search_engine/ast/node.rb +1 -1
- data/lib/search_engine/ast.rb +1 -1
- data/lib/search_engine/base/creation.rb +4 -4
- data/lib/search_engine/cli/doctor.rb +6 -6
- data/lib/search_engine/client/request_builder.rb +2 -2
- data/lib/search_engine/client.rb +19 -19
- data/lib/search_engine/collection_resolver.rb +7 -2
- data/lib/search_engine/config/presets.rb +7 -7
- data/lib/search_engine/config.rb +5 -5
- data/lib/search_engine/console_helpers.rb +4 -4
- data/lib/search_engine/dispatcher.rb +2 -2
- data/lib/search_engine/dsl/parser.rb +9 -9
- data/lib/search_engine/errors.rb +6 -6
- data/lib/search_engine/filters/sanitizer.rb +24 -44
- data/lib/search_engine/hydration/materializers.rb +13 -7
- data/lib/search_engine/indexer/batch_planner.rb +3 -8
- data/lib/search_engine/indexer/import_dispatcher.rb +1 -1
- data/lib/search_engine/indexer/retry_policy.rb +9 -6
- data/lib/search_engine/indexer.rb +3 -176
- data/lib/search_engine/instrumentation.rb +1 -1
- data/lib/search_engine/joins/guard.rb +4 -4
- data/lib/search_engine/joins/resolver.rb +2 -2
- data/lib/search_engine/logging_subscriber.rb +4 -4
- data/lib/search_engine/mapper.rb +13 -21
- data/lib/search_engine/multi.rb +2 -2
- data/lib/search_engine/multi_result.rb +1 -1
- data/lib/search_engine/notifications/compact_logger.rb +3 -3
- data/lib/search_engine/otel.rb +5 -5
- data/lib/search_engine/partitioner.rb +5 -5
- data/lib/search_engine/ranking_plan.rb +4 -4
- data/lib/search_engine/relation/compiler.rb +11 -6
- data/lib/search_engine/relation/dsl/filters.rb +9 -9
- data/lib/search_engine/relation/dsl/selection.rb +3 -3
- data/lib/search_engine/relation/dsl.rb +17 -17
- data/lib/search_engine/relation/dx.rb +4 -4
- data/lib/search_engine/relation/materializers.rb +1 -1
- data/lib/search_engine/relation/options.rb +1 -1
- data/lib/search_engine/relation.rb +1 -1
- data/lib/search_engine/result.rb +1 -1
- data/lib/search_engine/schema.rb +4 -4
- data/lib/search_engine/sources/active_record_source.rb +0 -1
- data/lib/search_engine/sources/lambda_source.rb +1 -1
- data/lib/search_engine/sources/sql_source.rb +1 -1
- data/lib/search_engine/sources.rb +3 -3
- data/lib/search_engine/test/stub_client.rb +8 -8
- data/lib/search_engine/test.rb +2 -2
- data/lib/search_engine/version.rb +1 -1
- metadata +2 -2
data/lib/search_engine/config.rb
CHANGED
|
@@ -305,13 +305,13 @@ module SearchEngine
|
|
|
305
305
|
# Controls validation rules and list limits.
|
|
306
306
|
class CurationConfig
|
|
307
307
|
# @return [Integer] maximum number of pinned IDs allowed (default: 50)
|
|
308
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/curation
|
|
308
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/curation
|
|
309
309
|
attr_accessor :max_pins
|
|
310
310
|
# @return [Integer] maximum number of hidden IDs allowed (default: 200)
|
|
311
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/curation
|
|
311
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/curation
|
|
312
312
|
attr_accessor :max_hidden
|
|
313
313
|
# @return [Regexp] allowed curated ID pattern (used for IDs and override tags)
|
|
314
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/curation
|
|
314
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/curation
|
|
315
315
|
attr_accessor :id_regex
|
|
316
316
|
|
|
317
317
|
def initialize
|
|
@@ -393,7 +393,7 @@ module SearchEngine
|
|
|
393
393
|
|
|
394
394
|
# Expose presets configuration.
|
|
395
395
|
# @return [SearchEngine::Config::PresetsConfig]
|
|
396
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/presets
|
|
396
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/presets
|
|
397
397
|
def presets
|
|
398
398
|
@presets ||= PresetsConfig.new
|
|
399
399
|
end
|
|
@@ -403,7 +403,7 @@ module SearchEngine
|
|
|
403
403
|
# Normalizes values on assignment.
|
|
404
404
|
# @param value [Object]
|
|
405
405
|
# @return [void]
|
|
406
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/presets#config-default-preset
|
|
406
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/presets#config-default-preset
|
|
407
407
|
def presets=(value)
|
|
408
408
|
cfg = presets
|
|
409
409
|
if value.is_a?(PresetsConfig)
|
|
@@ -38,7 +38,7 @@ module SearchEngine
|
|
|
38
38
|
# @example
|
|
39
39
|
# SE.q('milk').per(5)
|
|
40
40
|
# SE.q.where(category: 'dairy')
|
|
41
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/dx
|
|
41
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/dx
|
|
42
42
|
def q(query = nil, **opts)
|
|
43
43
|
model = default_model!
|
|
44
44
|
rel = model.all
|
|
@@ -91,7 +91,7 @@ module SearchEngine
|
|
|
91
91
|
raise ArgumentError,
|
|
92
92
|
'No default model configured. Set SearchEngine.config.default_console_model ' \
|
|
93
93
|
'or define a single SearchEngine::Base model. See ' \
|
|
94
|
-
'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/dx#generators--console-helpers.'
|
|
94
|
+
'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/dx#generators--console-helpers.'
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
uniq_klasses = mapping.values.uniq
|
|
@@ -100,7 +100,7 @@ module SearchEngine
|
|
|
100
100
|
names = uniq_klasses.map { |k| k.respond_to?(:name) && k.name ? k.name : k.to_s }.sort
|
|
101
101
|
raise ArgumentError,
|
|
102
102
|
"Ambiguous default model: #{names.join(', ')}. Set SearchEngine.config.default_console_model. " \
|
|
103
|
-
'See https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/dx#generators--console-helpers.'
|
|
103
|
+
'See https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/dx#generators--console-helpers.'
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
def resolve_model_class(value)
|
|
@@ -121,7 +121,7 @@ module SearchEngine
|
|
|
121
121
|
rescue NameError
|
|
122
122
|
raise ArgumentError,
|
|
123
123
|
"Unknown model constant #{name.inspect} for default_console_model. Ensure it's loaded. " \
|
|
124
|
-
'See https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/dx#generators--console-helpers.'
|
|
124
|
+
'See https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/dx#generators--console-helpers.'
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
private_class_method :resolve_model_class
|
|
@@ -20,14 +20,14 @@ module SearchEngine
|
|
|
20
20
|
unless klass.is_a?(Class)
|
|
21
21
|
raise SearchEngine::Errors::InvalidParams.new(
|
|
22
22
|
'klass must be a Class',
|
|
23
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/indexer#troubleshooting',
|
|
23
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/indexer#troubleshooting',
|
|
24
24
|
details: { arg: :klass }
|
|
25
25
|
)
|
|
26
26
|
end
|
|
27
27
|
unless klass.ancestors.include?(SearchEngine::Base)
|
|
28
28
|
raise SearchEngine::Errors::InvalidParams.new(
|
|
29
29
|
'klass must inherit from SearchEngine::Base',
|
|
30
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/indexer#troubleshooting',
|
|
30
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/indexer#troubleshooting',
|
|
31
31
|
details: { klass: klass.to_s }
|
|
32
32
|
)
|
|
33
33
|
end
|
|
@@ -189,7 +189,7 @@ module SearchEngine
|
|
|
189
189
|
unless m
|
|
190
190
|
raise SearchEngine::Errors::InvalidOperator.new(
|
|
191
191
|
"invalid template '#{template}'. Supported: =, !=, >, >=, <, <=, IN, NOT IN, MATCHES, PREFIX",
|
|
192
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#troubleshooting',
|
|
192
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#troubleshooting',
|
|
193
193
|
details: { template: template }
|
|
194
194
|
)
|
|
195
195
|
end
|
|
@@ -272,7 +272,7 @@ module SearchEngine
|
|
|
272
272
|
|
|
273
273
|
raise SearchEngine::Errors::InvalidOperator.new(
|
|
274
274
|
"expected #{needed} args for #{needed} placeholders in template '#{template}', got #{provided}.",
|
|
275
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#troubleshooting',
|
|
275
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#troubleshooting',
|
|
276
276
|
details: { needed: needed, provided: provided, template: template }
|
|
277
277
|
)
|
|
278
278
|
end
|
|
@@ -337,7 +337,7 @@ module SearchEngine
|
|
|
337
337
|
|
|
338
338
|
raise SearchEngine::Errors::InvalidType.new(
|
|
339
339
|
invalid_type_message(field: field, klass: klass, expectation: 'a non-empty Array', got: values),
|
|
340
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#troubleshooting',
|
|
340
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#troubleshooting',
|
|
341
341
|
details: { field: field, got_class: values.class.name }
|
|
342
342
|
)
|
|
343
343
|
end
|
|
@@ -394,7 +394,7 @@ module SearchEngine
|
|
|
394
394
|
|
|
395
395
|
raise SearchEngine::Errors::InvalidType.new(
|
|
396
396
|
invalid_type_message(field: nil, klass: nil, expectation: 'boolean', got: value),
|
|
397
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#troubleshooting',
|
|
397
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#troubleshooting',
|
|
398
398
|
details: { got: value }
|
|
399
399
|
)
|
|
400
400
|
end
|
|
@@ -426,7 +426,7 @@ module SearchEngine
|
|
|
426
426
|
rescue StandardError
|
|
427
427
|
raise SearchEngine::Errors::InvalidType.new(
|
|
428
428
|
invalid_type_message(field: field, klass: klass, expectation: 'time', got: value),
|
|
429
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#troubleshooting',
|
|
429
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#troubleshooting',
|
|
430
430
|
details: { field: field, got: value }
|
|
431
431
|
)
|
|
432
432
|
end
|
|
@@ -443,7 +443,7 @@ module SearchEngine
|
|
|
443
443
|
rescue StandardError
|
|
444
444
|
raise SearchEngine::Errors::InvalidType.new(
|
|
445
445
|
invalid_type_message(field: field, klass: klass, expectation: 'integer', got: value),
|
|
446
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#troubleshooting',
|
|
446
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#troubleshooting',
|
|
447
447
|
details: { field: field, got: value }
|
|
448
448
|
)
|
|
449
449
|
end
|
|
@@ -452,7 +452,7 @@ module SearchEngine
|
|
|
452
452
|
if value.is_a?(Numeric)
|
|
453
453
|
raise SearchEngine::Errors::InvalidType.new(
|
|
454
454
|
invalid_type_message(field: field, klass: klass, expectation: 'integer', got: value),
|
|
455
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#troubleshooting',
|
|
455
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#troubleshooting',
|
|
456
456
|
details: { field: field, got: value }
|
|
457
457
|
)
|
|
458
458
|
end
|
|
@@ -470,7 +470,7 @@ module SearchEngine
|
|
|
470
470
|
rescue StandardError
|
|
471
471
|
raise SearchEngine::Errors::InvalidType.new(
|
|
472
472
|
invalid_type_message(field: field, klass: klass, expectation: 'numeric', got: value),
|
|
473
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#troubleshooting',
|
|
473
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#troubleshooting',
|
|
474
474
|
details: { field: field, got: value }
|
|
475
475
|
)
|
|
476
476
|
end
|
|
@@ -573,7 +573,7 @@ module SearchEngine
|
|
|
573
573
|
|
|
574
574
|
raise SearchEngine::Errors::JoinNotApplied.new(
|
|
575
575
|
"Call .joins(:#{assoc_name}) before filtering/sorting on #{assoc_name} fields",
|
|
576
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/joins#troubleshooting',
|
|
576
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/joins#troubleshooting',
|
|
577
577
|
details: { assoc: assoc_name, used_for: 'filtering' }
|
|
578
578
|
)
|
|
579
579
|
end
|
data/lib/search_engine/errors.rb
CHANGED
|
@@ -15,7 +15,7 @@ module SearchEngine
|
|
|
15
15
|
# @!attribute [r] hint
|
|
16
16
|
# @return [String, nil] short actionable suggestion (no secrets)
|
|
17
17
|
# @!attribute [r] doc
|
|
18
|
-
# @return [String, nil] docs URL with optional anchor (e.g., "https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/query-dsl#operators")
|
|
18
|
+
# @return [String, nil] docs URL with optional anchor (e.g., "https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/query-dsl#operators")
|
|
19
19
|
# @!attribute [r] details
|
|
20
20
|
# @return [Object, nil] machine-readable context (JSON-serializable)
|
|
21
21
|
# @!attribute [r] code
|
|
@@ -240,21 +240,21 @@ module SearchEngine
|
|
|
240
240
|
|
|
241
241
|
# Raised when a curated ID does not match the configured pattern.
|
|
242
242
|
#
|
|
243
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/curation
|
|
243
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/curation
|
|
244
244
|
# @example
|
|
245
245
|
# raise SearchEngine::Errors::InvalidCuratedId, 'InvalidCuratedId: "foo bar" is not a valid curated ID. Expected pattern: /\A[\w\-:\.]+\z/. Try removing illegal characters.'
|
|
246
246
|
class InvalidCuratedId < Error; end
|
|
247
247
|
|
|
248
248
|
# Raised when pinned/hidden lists exceed configured limits after normalization.
|
|
249
249
|
#
|
|
250
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/curation
|
|
250
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/curation
|
|
251
251
|
# @example
|
|
252
252
|
# raise SearchEngine::Errors::CurationLimitExceeded, 'CurationLimitExceeded: pinned list exceeds max_pins=50 (attempted 51). Reduce inputs or raise the limit in SearchEngine.config.curation.'
|
|
253
253
|
class CurationLimitExceeded < Error; end
|
|
254
254
|
|
|
255
255
|
# Raised when an override tag is blank or invalid per allowed pattern.
|
|
256
256
|
#
|
|
257
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/curation
|
|
257
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/curation
|
|
258
258
|
# @example
|
|
259
259
|
# raise SearchEngine::Errors::InvalidOverrideTag, 'InvalidOverrideTag: "" is invalid. Use non-blank strings that match the allowed pattern.'
|
|
260
260
|
class InvalidOverrideTag < Error; end
|
|
@@ -269,7 +269,7 @@ module SearchEngine
|
|
|
269
269
|
# raise SearchEngine::Errors::InvalidOption.new(
|
|
270
270
|
# 'InvalidOption: tag must be a simple HTML-like token',
|
|
271
271
|
# hint: 'Use a simple tag like <em> or <mark>',
|
|
272
|
-
# doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/highlighting#options'
|
|
272
|
+
# doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/highlighting#options'
|
|
273
273
|
# )
|
|
274
274
|
class InvalidOption < Error; end
|
|
275
275
|
|
|
@@ -282,7 +282,7 @@ module SearchEngine
|
|
|
282
282
|
# raise SearchEngine::Errors::HitLimitExceeded.new(
|
|
283
283
|
# 'HitLimitExceeded: 12000 results exceed max=10000',
|
|
284
284
|
# hint: 'Increase `validate_hits!(max:)` or narrow your filters.',
|
|
285
|
-
# doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/hit-limits#validation',
|
|
285
|
+
# doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/hit-limits#validation',
|
|
286
286
|
# details: { total_hits: 12_000, max: 10_000, collection: 'products' }
|
|
287
287
|
# )
|
|
288
288
|
class HitLimitExceeded < Error; end
|
|
@@ -22,31 +22,13 @@ module SearchEngine
|
|
|
22
22
|
# @return [String]
|
|
23
23
|
def quote(value)
|
|
24
24
|
case value
|
|
25
|
-
when NilClass
|
|
26
|
-
'null'
|
|
27
|
-
when TrueClass
|
|
28
|
-
'true'
|
|
29
|
-
when FalseClass
|
|
30
|
-
'false'
|
|
31
|
-
when Numeric
|
|
32
|
-
value.to_s
|
|
33
25
|
when String
|
|
34
26
|
%("#{escape_string(value)}")
|
|
35
|
-
when Time
|
|
36
|
-
%("#{value.iso8601}")
|
|
37
|
-
when DateTime
|
|
38
|
-
%("#{value.iso8601}")
|
|
39
|
-
when Date
|
|
40
|
-
%("#{value.iso8601}")
|
|
41
27
|
when Array
|
|
42
28
|
elements = value.flatten(1).map { |el| quote(el) }
|
|
43
29
|
"[#{elements.join(', ')}]"
|
|
44
30
|
else
|
|
45
|
-
|
|
46
|
-
%("#{value.to_time.iso8601}")
|
|
47
|
-
else
|
|
48
|
-
%("#{escape_string(value.to_s)}")
|
|
49
|
-
end
|
|
31
|
+
quote_non_string(value)
|
|
50
32
|
end
|
|
51
33
|
end
|
|
52
34
|
|
|
@@ -64,37 +46,14 @@ module SearchEngine
|
|
|
64
46
|
return quote(value) if value.is_a?(Array)
|
|
65
47
|
|
|
66
48
|
case value
|
|
67
|
-
when NilClass
|
|
68
|
-
'null'
|
|
69
|
-
when TrueClass
|
|
70
|
-
'true'
|
|
71
|
-
when FalseClass
|
|
72
|
-
'false'
|
|
73
|
-
when Numeric
|
|
74
|
-
value.to_s
|
|
75
49
|
when String, Symbol
|
|
76
50
|
str = value.to_s
|
|
77
51
|
lc = str.strip.downcase
|
|
78
|
-
# Avoid ambiguity with special literals when user passes them as strings
|
|
79
52
|
return %("#{escape_string(str)}") if %w[true false null].include?(lc)
|
|
80
53
|
|
|
81
|
-
|
|
82
|
-
str
|
|
83
|
-
else
|
|
84
|
-
%("#{escape_string(str)}")
|
|
85
|
-
end
|
|
86
|
-
when Time
|
|
87
|
-
%("#{value.iso8601}")
|
|
88
|
-
when DateTime
|
|
89
|
-
%("#{value.iso8601}")
|
|
90
|
-
when Date
|
|
91
|
-
%("#{value.iso8601}")
|
|
54
|
+
safe_bare_string?(str) ? str : %("#{escape_string(str)}")
|
|
92
55
|
else
|
|
93
|
-
|
|
94
|
-
%("#{value.to_time.iso8601}")
|
|
95
|
-
else
|
|
96
|
-
%("#{escape_string(value.to_s)}")
|
|
97
|
-
end
|
|
56
|
+
quote_non_string(value)
|
|
98
57
|
end
|
|
99
58
|
end
|
|
100
59
|
|
|
@@ -160,6 +119,27 @@ module SearchEngine
|
|
|
160
119
|
count
|
|
161
120
|
end
|
|
162
121
|
|
|
122
|
+
# Shared quoting logic for non-String, non-Array values.
|
|
123
|
+
# @param value [Object]
|
|
124
|
+
# @return [String]
|
|
125
|
+
# @api private
|
|
126
|
+
def quote_non_string(value)
|
|
127
|
+
case value
|
|
128
|
+
when NilClass then 'null'
|
|
129
|
+
when TrueClass then 'true'
|
|
130
|
+
when FalseClass then 'false'
|
|
131
|
+
when Numeric then value.to_s
|
|
132
|
+
when Time, DateTime then %("#{value.iso8601}")
|
|
133
|
+
when Date then %("#{value.iso8601}")
|
|
134
|
+
else
|
|
135
|
+
if value.respond_to?(:to_time)
|
|
136
|
+
%("#{value.to_time.iso8601}")
|
|
137
|
+
else
|
|
138
|
+
%("#{escape_string(value.to_s)}")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
163
143
|
# Escape a raw string for inclusion inside double quotes.
|
|
164
144
|
# @param str [String]
|
|
165
145
|
# @return [String]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
3
5
|
module SearchEngine
|
|
4
6
|
module Hydration
|
|
5
7
|
# Centralized executors for materialization: to_a/each/count/ids/pluck/pick.
|
|
@@ -505,7 +507,7 @@ module SearchEngine
|
|
|
505
507
|
if orders.any? { |o| o.start_with?('$') }
|
|
506
508
|
raise SearchEngine::Errors::InvalidOption.new(
|
|
507
509
|
'Sorting by joined fields is not supported by client-side join fallback',
|
|
508
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/joins#client-side-fallback'
|
|
510
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/joins#client-side-fallback'
|
|
509
511
|
)
|
|
510
512
|
end
|
|
511
513
|
include_str = begin
|
|
@@ -516,7 +518,7 @@ module SearchEngine
|
|
|
516
518
|
if include_str&.split(',')&.any? { |seg| seg.strip.start_with?('$') }
|
|
517
519
|
raise SearchEngine::Errors::InvalidOption.new(
|
|
518
520
|
'Selecting joined fields is not supported by client-side join fallback',
|
|
519
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/joins#client-side-fallback'
|
|
521
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/joins#client-side-fallback'
|
|
520
522
|
)
|
|
521
523
|
end
|
|
522
524
|
|
|
@@ -569,7 +571,7 @@ module SearchEngine
|
|
|
569
571
|
# Unsupported node type for fallback (e.g., ranges, not_eq, etc.)
|
|
570
572
|
raise SearchEngine::Errors::InvalidOption.new(
|
|
571
573
|
'Only equality and IN predicates on joined fields are supported by client-side join fallback',
|
|
572
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/joins#client-side-fallback'
|
|
574
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/joins#client-side-fallback'
|
|
573
575
|
)
|
|
574
576
|
end
|
|
575
577
|
end
|
|
@@ -627,7 +629,7 @@ module SearchEngine
|
|
|
627
629
|
# Fallback does not support OR with joined nodes; reject early
|
|
628
630
|
raise SearchEngine::Errors::InvalidOption.new(
|
|
629
631
|
'OR with joined predicates is not supported by client-side join fallback',
|
|
630
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/joins#client-side-fallback'
|
|
632
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/joins#client-side-fallback'
|
|
631
633
|
)
|
|
632
634
|
else
|
|
633
635
|
if node.respond_to?(:field)
|
|
@@ -715,7 +717,10 @@ module SearchEngine
|
|
|
715
717
|
end
|
|
716
718
|
|
|
717
719
|
def coerce_pluck_field_names(fields)
|
|
718
|
-
Array(fields).flatten.
|
|
720
|
+
Array(fields).flatten.filter_map do |f|
|
|
721
|
+
s = f.to_s.strip
|
|
722
|
+
s unless s.empty?
|
|
723
|
+
end
|
|
719
724
|
end
|
|
720
725
|
module_function :coerce_pluck_field_names
|
|
721
726
|
|
|
@@ -744,7 +749,7 @@ module SearchEngine
|
|
|
744
749
|
raise SearchEngine::Errors::InvalidSelection.new(
|
|
745
750
|
msg,
|
|
746
751
|
hint: hint,
|
|
747
|
-
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/field-selection#guardrails--errors',
|
|
752
|
+
doc: 'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/field-selection#guardrails--errors',
|
|
748
753
|
details: { requested: names, include_base: include_base, exclude_base: exclude_base }
|
|
749
754
|
)
|
|
750
755
|
end
|
|
@@ -755,8 +760,9 @@ module SearchEngine
|
|
|
755
760
|
if exclude_base.include?(field)
|
|
756
761
|
"InvalidSelection: field :#{field} not in effective selection. Remove exclude(:#{field})."
|
|
757
762
|
else
|
|
763
|
+
seen = Set.new(include_base)
|
|
758
764
|
suggestion_fields = include_base.dup
|
|
759
|
-
requested.each { |f| suggestion_fields << f
|
|
765
|
+
requested.each { |f| suggestion_fields << f if seen.add?(f) }
|
|
760
766
|
symbols = suggestion_fields.map { |t| ":#{t}" }.join(',')
|
|
761
767
|
"InvalidSelection: field :#{field} not in effective selection. Use `reselect(#{symbols})`."
|
|
762
768
|
end
|
|
@@ -19,20 +19,15 @@ module SearchEngine
|
|
|
19
19
|
# @param buffer [String] a reusable String buffer to encode into
|
|
20
20
|
# @return [Array(Integer, Integer)] [docs_count, bytes_sent]
|
|
21
21
|
# @raise [SearchEngine::Errors::InvalidParams] when a document is not a Hash or missing :id
|
|
22
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/indexer
|
|
22
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/indexer
|
|
23
23
|
def self.encode_jsonl!(docs, buffer)
|
|
24
24
|
count = 0
|
|
25
25
|
buffer.clear
|
|
26
26
|
size = docs.size
|
|
27
|
+
now_i = defined?(Time.zone) && Time.zone ? Time.zone.now.to_i : Time.now.to_i
|
|
27
28
|
docs.each_with_index do |raw, idx|
|
|
28
29
|
doc = ensure_hash_document(raw)
|
|
29
30
|
ensure_id!(doc)
|
|
30
|
-
# Force system timestamp field prior to serialization to Typesense
|
|
31
|
-
now_i = if defined?(Time) && defined?(Time.zone) && Time.zone
|
|
32
|
-
Time.zone.now.to_i
|
|
33
|
-
else
|
|
34
|
-
Time.now.to_i
|
|
35
|
-
end
|
|
36
31
|
doc[:doc_updated_at] = now_i if doc.is_a?(Hash)
|
|
37
32
|
buffer << JSON.generate(doc)
|
|
38
33
|
buffer << "\n" if idx < (size - 1)
|
|
@@ -62,7 +57,7 @@ module SearchEngine
|
|
|
62
57
|
raise SearchEngine::Errors::InvalidParams,
|
|
63
58
|
'Indexer requires batches of Hash-like documents with at least an :id key. ' \
|
|
64
59
|
'Mapping DSL is not available yet. See ' \
|
|
65
|
-
'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/indexer.'
|
|
60
|
+
'https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/indexer.'
|
|
66
61
|
end
|
|
67
62
|
end
|
|
68
63
|
|
|
@@ -28,7 +28,7 @@ module SearchEngine
|
|
|
28
28
|
# @param dry_run [Boolean] when true, do not perform network call
|
|
29
29
|
# @return [Hash] stats payload: { index:, docs_count:, success_count:, failure_count:, attempts:, http_status:, duration_ms:, bytes_sent:, errors_sample: [] }
|
|
30
30
|
# @raise [SearchEngine::Errors::Api] when the underlying client raises an API error (propagated)
|
|
31
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/indexer
|
|
31
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/indexer
|
|
32
32
|
if dry_run
|
|
33
33
|
# Emit instrumentation parity without network
|
|
34
34
|
http_status = 200
|
|
@@ -39,7 +39,7 @@ module SearchEngine
|
|
|
39
39
|
# Build a policy from a config-like Hash.
|
|
40
40
|
# @param cfg [Hash]
|
|
41
41
|
# @return [RetryPolicy]
|
|
42
|
-
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/indexer
|
|
42
|
+
# @see https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/v30.1/indexer
|
|
43
43
|
def self.from_config(cfg)
|
|
44
44
|
c = cfg || {}
|
|
45
45
|
new(
|
|
@@ -72,7 +72,6 @@ module SearchEngine
|
|
|
72
72
|
# @param error [Exception]
|
|
73
73
|
# @return [Float]
|
|
74
74
|
def next_delay(attempt, _error)
|
|
75
|
-
# Exponential backoff with bounded jitter
|
|
76
75
|
exp = [@base * (2 ** (attempt - 1)), @max].min
|
|
77
76
|
jitter = exp * @jitter_fraction
|
|
78
77
|
delta = random_in_range(-jitter..jitter)
|
|
@@ -80,13 +79,17 @@ module SearchEngine
|
|
|
80
79
|
sleep_time.positive? ? sleep_time : 0.0
|
|
81
80
|
end
|
|
82
81
|
|
|
82
|
+
# Whether an HTTP status code represents a transient/retryable error.
|
|
83
|
+
# @param code [Integer]
|
|
84
|
+
# @return [Boolean]
|
|
85
|
+
def self.transient_status?(code)
|
|
86
|
+
code == 429 || (code >= 500 && code <= 599)
|
|
87
|
+
end
|
|
88
|
+
|
|
83
89
|
private
|
|
84
90
|
|
|
85
91
|
def transient_status?(code)
|
|
86
|
-
|
|
87
|
-
return true if code >= 500 && code <= 599
|
|
88
|
-
|
|
89
|
-
false
|
|
92
|
+
self.class.transient_status?(code)
|
|
90
93
|
end
|
|
91
94
|
|
|
92
95
|
def random_in_range(range)
|