search-engine-for-typesense 1.0.1 → 30.1.0
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 +7 -1
- data/lib/search_engine/client/http_adapter.rb +1 -1
- data/lib/search_engine/client/services/base.rb +2 -2
- data/lib/search_engine/client/services/collections.rb +7 -7
- data/lib/search_engine/client/services/documents.rb +11 -7
- data/lib/search_engine/client/services/operations.rb +5 -5
- data/lib/search_engine/client/services/search.rb +3 -3
- data/lib/search_engine/client.rb +182 -27
- data/lib/search_engine/hydration/materializers.rb +1 -1
- data/lib/search_engine/indexer/import_dispatcher.rb +2 -56
- data/lib/search_engine/indexer/import_response_parser.rb +92 -0
- data/lib/search_engine/indexer.rb +2 -60
- data/lib/search_engine/relation/compiler.rb +2 -2
- data/lib/search_engine/relation/dsl.rb +14 -3
- data/lib/search_engine/version.rb +1 -1
- data/lib/search_engine.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edc1b2db8a930d829b052ce9e414921cbd78c333111596c754fabe9ce443e26c
|
|
4
|
+
data.tar.gz: bcd25036e9eb8202bc813decea0c7c8904f9b7565e5cfb254a2d7fb5760dd473
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 275bdb5de35a2c8fb31f12a187231713756f5bf381b32d1bf255d6830b0feadc521b8f1aacdb2f2069b519e3ce5f6e5b793bb1a6ee9b79ceaded017afce4259e
|
|
7
|
+
data.tar.gz: 3401b22b7440aaa90040004128cc5dea5ff76355505471f1752d9b91909acdbff120842904295fd014a349d3f785eb18c774f8e94583cd4887b651078fac6143
|
data/README.md
CHANGED
|
@@ -9,6 +9,12 @@ Mountless Rails::Engine for [Typesense](https://typesense.org). Expressive Relat
|
|
|
9
9
|
> [!NOTE]
|
|
10
10
|
> This project is not affiliated with [Typesense](https://typesense.org) and is a wrapper for the [`typesense` gem](https://github.com/typesense/typesense-ruby).
|
|
11
11
|
|
|
12
|
+
## Versioning
|
|
13
|
+
|
|
14
|
+
The gem version mirrors the Typesense server major/minor it targets. Patch releases are reserved for gem-only fixes and enhancements.
|
|
15
|
+
|
|
16
|
+
Example: `30.1.x` targets Typesense `30.1`.
|
|
17
|
+
|
|
12
18
|
## Quickstart
|
|
13
19
|
|
|
14
20
|
```ruby
|
|
@@ -142,7 +148,7 @@ See [Docs Style Guide](https://nikita-shkoda.mintlify.app/projects/search-engine
|
|
|
142
148
|
<!-- Badge references (placeholders) -->
|
|
143
149
|
[ci-badge]: https://img.shields.io/github/actions/workflow/status/lstpsche/search-engine-for-typesense/ci.yml?branch=main
|
|
144
150
|
[ci-url]: #
|
|
145
|
-
[gem-badge]: https://
|
|
151
|
+
[gem-badge]: https://badge.fury.io/rb/search-engine-for-typesense.svg?icon=si%3Arubygems
|
|
146
152
|
[gem-url]: https://rubygems.org/gems/search-engine-for-typesense
|
|
147
153
|
[docs-badge]: https://img.shields.io/badge/docs-index-blue
|
|
148
154
|
[docs-url]: https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/index
|
|
@@ -56,7 +56,7 @@ module SearchEngine
|
|
|
56
56
|
|
|
57
57
|
# Fallback: attempt a generic call via low-level typesense endpoint access if available.
|
|
58
58
|
# This keeps adapter permissive for future endpoints without adding Faraday here.
|
|
59
|
-
raise ArgumentError, "Unsupported request path for adapter: #{request.
|
|
59
|
+
raise ArgumentError, "Unsupported request path for adapter: #{request.http_method} #{request.path}"
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
end
|
|
@@ -41,8 +41,8 @@ module SearchEngine
|
|
|
41
41
|
client.__send__(:with_exception_mapping, *args, &block)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def instrument(*args)
|
|
45
|
-
client.__send__(:instrument, *args)
|
|
44
|
+
def instrument(*args, **kwargs)
|
|
45
|
+
client.__send__(:instrument, *args, **kwargs)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def log_success(*args)
|
|
@@ -28,7 +28,7 @@ module SearchEngine
|
|
|
28
28
|
|
|
29
29
|
raise
|
|
30
30
|
ensure
|
|
31
|
-
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
31
|
+
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# @param collection_name [String]
|
|
@@ -54,7 +54,7 @@ module SearchEngine
|
|
|
54
54
|
|
|
55
55
|
raise
|
|
56
56
|
ensure
|
|
57
|
-
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
57
|
+
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# @param alias_name [String]
|
|
@@ -72,7 +72,7 @@ module SearchEngine
|
|
|
72
72
|
|
|
73
73
|
symbolize_keys_deep(result)
|
|
74
74
|
ensure
|
|
75
|
-
instrument(:put, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
75
|
+
instrument(:put, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# @param schema [Hash]
|
|
@@ -88,7 +88,7 @@ module SearchEngine
|
|
|
88
88
|
|
|
89
89
|
symbolize_keys_deep(result)
|
|
90
90
|
ensure
|
|
91
|
-
instrument(:post, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
91
|
+
instrument(:post, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# @param name [String]
|
|
@@ -106,7 +106,7 @@ module SearchEngine
|
|
|
106
106
|
|
|
107
107
|
symbolize_keys_deep(result)
|
|
108
108
|
ensure
|
|
109
|
-
instrument(:patch, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
109
|
+
instrument(:patch, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
# @param name [String]
|
|
@@ -133,7 +133,7 @@ module SearchEngine
|
|
|
133
133
|
|
|
134
134
|
raise
|
|
135
135
|
ensure
|
|
136
|
-
instrument(:delete, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
136
|
+
instrument(:delete, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
# @return [Array<Hash>]
|
|
@@ -153,7 +153,7 @@ module SearchEngine
|
|
|
153
153
|
|
|
154
154
|
symbolize_keys_deep(result)
|
|
155
155
|
ensure
|
|
156
|
-
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
156
|
+
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
157
157
|
end
|
|
158
158
|
end
|
|
159
159
|
end
|
|
@@ -23,7 +23,7 @@ module SearchEngine
|
|
|
23
23
|
ts.collections[collection].documents.import(jsonl, action: action.to_s)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
instrument(:post, path, current_monotonic_ms - start, {})
|
|
26
|
+
instrument(:post, path, current_monotonic_ms - start, {}, request_token: start)
|
|
27
27
|
result
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -51,7 +51,7 @@ module SearchEngine
|
|
|
51
51
|
ts.collections[collection].documents.delete(filter_by: filter_by)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
instrument(:delete, path, current_monotonic_ms - start, {})
|
|
54
|
+
instrument(:delete, path, current_monotonic_ms - start, {}, request_token: start)
|
|
55
55
|
symbolize_keys_deep(result)
|
|
56
56
|
end
|
|
57
57
|
|
|
@@ -84,7 +84,7 @@ module SearchEngine
|
|
|
84
84
|
|
|
85
85
|
raise
|
|
86
86
|
ensure
|
|
87
|
-
instrument(:delete, path, current_monotonic_ms - start, {}) if defined?(start)
|
|
87
|
+
instrument(:delete, path, current_monotonic_ms - start, {}, request_token: start) if defined?(start)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
# @param collection [String]
|
|
@@ -112,7 +112,7 @@ module SearchEngine
|
|
|
112
112
|
result = with_exception_mapping(:patch, path, {}, start) do
|
|
113
113
|
ts.collections[collection].documents[s].update(fields)
|
|
114
114
|
end
|
|
115
|
-
instrument(:patch, path, current_monotonic_ms - start, {})
|
|
115
|
+
instrument(:patch, path, current_monotonic_ms - start, {}, request_token: start)
|
|
116
116
|
symbolize_keys_deep(result)
|
|
117
117
|
end
|
|
118
118
|
|
|
@@ -142,7 +142,7 @@ module SearchEngine
|
|
|
142
142
|
ts.collections[collection].documents.update(fields, filter_by: filter_by)
|
|
143
143
|
end
|
|
144
144
|
|
|
145
|
-
instrument(:patch, path, current_monotonic_ms - start, {})
|
|
145
|
+
instrument(:patch, path, current_monotonic_ms - start, {}, request_token: start)
|
|
146
146
|
symbolize_keys_deep(result)
|
|
147
147
|
end
|
|
148
148
|
|
|
@@ -162,7 +162,7 @@ module SearchEngine
|
|
|
162
162
|
typesense.collections[collection].documents.create(document)
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
-
instrument(:post, path, current_monotonic_ms - start, {})
|
|
165
|
+
instrument(:post, path, current_monotonic_ms - start, {}, request_token: start)
|
|
166
166
|
symbolize_keys_deep(result)
|
|
167
167
|
end
|
|
168
168
|
|
|
@@ -196,7 +196,11 @@ module SearchEngine
|
|
|
196
196
|
|
|
197
197
|
raise
|
|
198
198
|
ensure
|
|
199
|
-
|
|
199
|
+
if defined?(start)
|
|
200
|
+
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {},
|
|
201
|
+
request_token: start
|
|
202
|
+
)
|
|
203
|
+
end
|
|
200
204
|
end
|
|
201
205
|
|
|
202
206
|
private
|
|
@@ -15,7 +15,7 @@ module SearchEngine
|
|
|
15
15
|
|
|
16
16
|
symbolize_keys_deep(result)
|
|
17
17
|
ensure
|
|
18
|
-
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
18
|
+
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def list_api_keys
|
|
@@ -37,7 +37,7 @@ module SearchEngine
|
|
|
37
37
|
|
|
38
38
|
symbolize_keys_deep(result)
|
|
39
39
|
ensure
|
|
40
|
-
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
40
|
+
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def clear_cache
|
|
@@ -48,7 +48,7 @@ module SearchEngine
|
|
|
48
48
|
typesense.operations.perform('cache/clear')
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
instrument(:post, path, current_monotonic_ms - start, {})
|
|
51
|
+
instrument(:post, path, current_monotonic_ms - start, {}, request_token: start)
|
|
52
52
|
symbolize_keys_deep(result)
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -75,7 +75,7 @@ module SearchEngine
|
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
ensure
|
|
78
|
-
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
78
|
+
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# Return raw server statistics from Typesense.
|
|
@@ -101,7 +101,7 @@ module SearchEngine
|
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
ensure
|
|
104
|
-
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
104
|
+
instrument(:get, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
private
|
|
@@ -21,7 +21,7 @@ module SearchEngine
|
|
|
21
21
|
|
|
22
22
|
result = instrumented_search(collection, params_obj, cache_params, path, payload, start)
|
|
23
23
|
duration = current_monotonic_ms - start
|
|
24
|
-
instrument(:post, path, duration, cache_params)
|
|
24
|
+
instrument(:post, path, duration, cache_params, request_token: start)
|
|
25
25
|
log_success(:post, path, start, cache_params)
|
|
26
26
|
|
|
27
27
|
klass = begin
|
|
@@ -48,7 +48,7 @@ module SearchEngine
|
|
|
48
48
|
typesense.multi_search.perform({ searches: bodies }, common_params: cache_params)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
instrument(:post, path, current_monotonic_ms - start, cache_params)
|
|
51
|
+
instrument(:post, path, current_monotonic_ms - start, cache_params, request_token: start)
|
|
52
52
|
symbolize_keys_deep(result)
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -129,7 +129,7 @@ module SearchEngine
|
|
|
129
129
|
|
|
130
130
|
pinned = params[:pinned_hits]
|
|
131
131
|
hidden = params[:hidden_hits]
|
|
132
|
-
tags = params[:override_tags]
|
|
132
|
+
tags = params[:curation_tags] || params[:override_tags]
|
|
133
133
|
|
|
134
134
|
pinned_count =
|
|
135
135
|
case pinned
|
data/lib/search_engine/client.rb
CHANGED
|
@@ -12,6 +12,10 @@ module SearchEngine
|
|
|
12
12
|
# Provides single-search and federated multi-search while enforcing that cache
|
|
13
13
|
# knobs live in URL/common-params and not in per-search request bodies.
|
|
14
14
|
class Client
|
|
15
|
+
REQUEST_ERROR_INSTRUMENTED_KEY = :__se_request_error_instrumented_queue__
|
|
16
|
+
REQUEST_ERROR_INSTRUMENTED_MAX = 32
|
|
17
|
+
REQUEST_ERROR_INSTRUMENTED_TTL_MS = 60_000.0
|
|
18
|
+
|
|
15
19
|
# @param config [SearchEngine::Config]
|
|
16
20
|
# @param typesense_client [Object, nil] optional injected Typesense::Client (for tests)
|
|
17
21
|
def initialize(config: SearchEngine.config, typesense_client: nil)
|
|
@@ -163,7 +167,7 @@ module SearchEngine
|
|
|
163
167
|
end
|
|
164
168
|
|
|
165
169
|
# --- Admin: Synonyms ----------------------------------------------------
|
|
166
|
-
#
|
|
170
|
+
# We map per-collection synonym IDs to a dedicated synonym set.
|
|
167
171
|
|
|
168
172
|
# @param collection [String]
|
|
169
173
|
# @param id [String]
|
|
@@ -172,10 +176,10 @@ module SearchEngine
|
|
|
172
176
|
# @see `https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/synonyms-stopwords`
|
|
173
177
|
# @see `https://typesense.org/docs/latest/api/synonyms.html#upsert-a-synonym`
|
|
174
178
|
def synonyms_upsert(collection:, id:, terms:)
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
set = synonym_set_name_for_collection(collection)
|
|
180
|
+
synonym_set_item_request(
|
|
177
181
|
method: :put,
|
|
178
|
-
|
|
182
|
+
set: set,
|
|
179
183
|
id: id,
|
|
180
184
|
body_data: Array(terms)
|
|
181
185
|
)
|
|
@@ -183,23 +187,24 @@ module SearchEngine
|
|
|
183
187
|
|
|
184
188
|
# @return [Array<Hash>]
|
|
185
189
|
# @see `https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/synonyms-stopwords`
|
|
186
|
-
# @see `https://typesense.org/docs/latest/api/synonyms.html#list-all-synonyms-
|
|
190
|
+
# @see `https://typesense.org/docs/latest/api/synonyms.html#list-all-synonyms-in-a-synonym-set`
|
|
187
191
|
def synonyms_list(collection:)
|
|
188
|
-
|
|
189
|
-
|
|
192
|
+
set = synonym_set_name_for_collection(collection)
|
|
193
|
+
raw = synonym_set_item_request(
|
|
190
194
|
method: :get,
|
|
191
|
-
|
|
195
|
+
set: set
|
|
192
196
|
)
|
|
197
|
+
extract_synonym_items(raw)
|
|
193
198
|
end
|
|
194
199
|
|
|
195
200
|
# @return [Hash, nil]
|
|
196
201
|
# @see `https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/synonyms-stopwords`
|
|
197
202
|
# @see `https://typesense.org/docs/latest/api/synonyms.html#retrieve-a-synonym`
|
|
198
203
|
def synonyms_get(collection:, id:)
|
|
199
|
-
|
|
200
|
-
|
|
204
|
+
set = synonym_set_name_for_collection(collection)
|
|
205
|
+
synonym_set_item_request(
|
|
201
206
|
method: :get,
|
|
202
|
-
|
|
207
|
+
set: set,
|
|
203
208
|
id: id,
|
|
204
209
|
return_nil_on_404: true
|
|
205
210
|
)
|
|
@@ -209,10 +214,10 @@ module SearchEngine
|
|
|
209
214
|
# @see `https://nikita-shkoda.mintlify.app/projects/search-engine-for-typesense/synonyms-stopwords`
|
|
210
215
|
# @see `https://typesense.org/docs/latest/api/synonyms.html#delete-a-synonym`
|
|
211
216
|
def synonyms_delete(collection:, id:)
|
|
212
|
-
|
|
213
|
-
|
|
217
|
+
set = synonym_set_name_for_collection(collection)
|
|
218
|
+
synonym_set_item_request(
|
|
214
219
|
method: :delete,
|
|
215
|
-
|
|
220
|
+
set: set,
|
|
216
221
|
id: id
|
|
217
222
|
)
|
|
218
223
|
end
|
|
@@ -437,7 +442,88 @@ module SearchEngine
|
|
|
437
442
|
|
|
438
443
|
raise
|
|
439
444
|
ensure
|
|
440
|
-
instrument(method, path, (start ? (current_monotonic_ms - start) : 0.0), {})
|
|
445
|
+
instrument(method, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Internal helper for synonym set item CRUD.
|
|
449
|
+
#
|
|
450
|
+
# @param method [Symbol]
|
|
451
|
+
# @param set [String] synonym set name
|
|
452
|
+
# @param id [String, nil] synonym id
|
|
453
|
+
# @param body_data [Array<String>, nil]
|
|
454
|
+
# @param return_nil_on_404 [Boolean]
|
|
455
|
+
# @return [Hash, Array<Hash>, nil]
|
|
456
|
+
def synonym_set_item_request(method:, set:, id: nil, body_data: nil, return_nil_on_404: false)
|
|
457
|
+
set_name = set.to_s
|
|
458
|
+
sid = id.to_s if id
|
|
459
|
+
start = current_monotonic_ms
|
|
460
|
+
|
|
461
|
+
path = if sid
|
|
462
|
+
"/synonym_sets/#{escape_segment(set_name)}/synonyms/#{escape_segment(sid)}"
|
|
463
|
+
else
|
|
464
|
+
"/synonym_sets/#{escape_segment(set_name)}/synonyms"
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
request_body = if method == :put && body_data
|
|
468
|
+
{ synonyms: body_data }
|
|
469
|
+
else
|
|
470
|
+
{}
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
result = with_exception_mapping(method, path, {}, start) do
|
|
474
|
+
execute_synonym_set_request(typesense_api_call, method, path, request_body)
|
|
475
|
+
end
|
|
476
|
+
symbolize_keys_deep(result)
|
|
477
|
+
rescue Errors::Api => error
|
|
478
|
+
return nil if return_nil_on_404 && error.status.to_i == 404
|
|
479
|
+
|
|
480
|
+
raise
|
|
481
|
+
ensure
|
|
482
|
+
instrument(method, path, (start ? (current_monotonic_ms - start) : 0.0), {}, request_token: start)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def execute_synonym_set_request(api_call, method, path, request_body)
|
|
486
|
+
raise ArgumentError, 'Typesense client missing configuration' unless api_call
|
|
487
|
+
|
|
488
|
+
case method
|
|
489
|
+
when :get
|
|
490
|
+
api_call.get(path)
|
|
491
|
+
when :put
|
|
492
|
+
api_call.put(path, request_body)
|
|
493
|
+
when :delete
|
|
494
|
+
api_call.delete(path)
|
|
495
|
+
else
|
|
496
|
+
raise ArgumentError, "Unsupported method: #{method.inspect}"
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def extract_synonym_items(raw)
|
|
501
|
+
return [] if raw.nil?
|
|
502
|
+
return raw if raw.is_a?(Array)
|
|
503
|
+
|
|
504
|
+
if raw.is_a?(Hash)
|
|
505
|
+
list = raw[:synonyms] || raw['synonyms'] || raw[:items] || raw['items'] || raw[:results] || raw['results']
|
|
506
|
+
return Array(list) if list
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
Array(raw)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def synonym_set_name_for_collection(collection)
|
|
513
|
+
"#{collection}_synonyms_index"
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def typesense_api_call
|
|
517
|
+
@typesense_api_call ||= begin
|
|
518
|
+
require 'typesense'
|
|
519
|
+
ts = typesense
|
|
520
|
+
Typesense::ApiCall.new(ts.configuration) if ts.respond_to?(:configuration)
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def escape_segment(value)
|
|
525
|
+
require 'uri'
|
|
526
|
+
URI.encode_www_form_component(value.to_s)
|
|
441
527
|
end
|
|
442
528
|
|
|
443
529
|
# Execute the actual Typesense API call for admin resources.
|
|
@@ -481,7 +567,7 @@ module SearchEngine
|
|
|
481
567
|
Typesense::Client.new(
|
|
482
568
|
nodes: build_nodes,
|
|
483
569
|
api_key: config.api_key,
|
|
484
|
-
# typesense-ruby
|
|
570
|
+
# typesense-ruby uses a single connection timeout for both open+read
|
|
485
571
|
connection_timeout_seconds: read_timeout_seconds,
|
|
486
572
|
num_retries: safe_retry_attempts,
|
|
487
573
|
retry_interval_seconds: safe_retry_backoff,
|
|
@@ -628,12 +714,25 @@ module SearchEngine
|
|
|
628
714
|
def map_and_raise(error, method, path, cache_params, start_ms)
|
|
629
715
|
duration_ms = current_monotonic_ms - start_ms
|
|
630
716
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
717
|
+
if api_error?(error)
|
|
718
|
+
return handle_api_error(
|
|
719
|
+
error, method, path, cache_params, duration_ms, request_token: start_ms
|
|
720
|
+
)
|
|
721
|
+
end
|
|
722
|
+
if timeout_error?(error)
|
|
723
|
+
return handle_timeout_error(
|
|
724
|
+
error, method, path, cache_params, duration_ms, request_token: start_ms
|
|
725
|
+
)
|
|
726
|
+
end
|
|
727
|
+
if connection_error?(error)
|
|
728
|
+
return handle_connection_error(
|
|
729
|
+
error, method, path, cache_params, duration_ms, request_token: start_ms
|
|
730
|
+
)
|
|
731
|
+
end
|
|
634
732
|
|
|
635
733
|
# Unmapped error: instrument and re-raise as-is
|
|
636
|
-
instrument(method, path, duration_ms, cache_params, error_class: error.class.name)
|
|
734
|
+
instrument(method, path, duration_ms, cache_params, error_class: error.class.name, request_token: start_ms)
|
|
735
|
+
mark_request_error_instrumented(method, path, start_ms)
|
|
637
736
|
raise error
|
|
638
737
|
end
|
|
639
738
|
|
|
@@ -643,7 +742,7 @@ module SearchEngine
|
|
|
643
742
|
end
|
|
644
743
|
|
|
645
744
|
# Handle Typesense API errors by converting to SearchEngine::Errors::Api.
|
|
646
|
-
def handle_api_error(error, method, path, cache_params, duration_ms)
|
|
745
|
+
def handle_api_error(error, method, path, cache_params, duration_ms, request_token: nil)
|
|
647
746
|
status = if error.respond_to?(:http_code)
|
|
648
747
|
error.http_code
|
|
649
748
|
else
|
|
@@ -657,13 +756,22 @@ module SearchEngine
|
|
|
657
756
|
doc: Client::RequestBuilder::DOC_CLIENT_ERRORS,
|
|
658
757
|
details: { http_status: status, body: body.is_a?(String) ? body[0, 120] : body }
|
|
659
758
|
)
|
|
660
|
-
instrument(method, path, duration_ms, cache_params, error_class: err.class.name)
|
|
759
|
+
instrument(method, path, duration_ms, cache_params, error_class: err.class.name, request_token: request_token)
|
|
760
|
+
mark_request_error_instrumented(method, path, request_token)
|
|
661
761
|
raise err
|
|
662
762
|
end
|
|
663
763
|
|
|
664
764
|
# Handle timeout errors by converting to SearchEngine::Errors::Timeout.
|
|
665
|
-
def handle_timeout_error(error, method, path, cache_params, duration_ms)
|
|
666
|
-
instrument(
|
|
765
|
+
def handle_timeout_error(error, method, path, cache_params, duration_ms, request_token: nil)
|
|
766
|
+
instrument(
|
|
767
|
+
method,
|
|
768
|
+
path,
|
|
769
|
+
duration_ms,
|
|
770
|
+
cache_params,
|
|
771
|
+
error_class: Errors::Timeout.name,
|
|
772
|
+
request_token: request_token
|
|
773
|
+
)
|
|
774
|
+
mark_request_error_instrumented(method, path, request_token)
|
|
667
775
|
raise Errors::Timeout.new(
|
|
668
776
|
error.message,
|
|
669
777
|
doc: Client::RequestBuilder::DOC_CLIENT_ERRORS,
|
|
@@ -672,8 +780,16 @@ module SearchEngine
|
|
|
672
780
|
end
|
|
673
781
|
|
|
674
782
|
# Handle connection errors by converting to SearchEngine::Errors::Connection.
|
|
675
|
-
def handle_connection_error(error, method, path, cache_params, duration_ms)
|
|
676
|
-
instrument(
|
|
783
|
+
def handle_connection_error(error, method, path, cache_params, duration_ms, request_token: nil)
|
|
784
|
+
instrument(
|
|
785
|
+
method,
|
|
786
|
+
path,
|
|
787
|
+
duration_ms,
|
|
788
|
+
cache_params,
|
|
789
|
+
error_class: Errors::Connection.name,
|
|
790
|
+
request_token: request_token
|
|
791
|
+
)
|
|
792
|
+
mark_request_error_instrumented(method, path, request_token)
|
|
677
793
|
raise Errors::Connection.new(
|
|
678
794
|
error.message,
|
|
679
795
|
doc: Client::RequestBuilder::DOC_CLIENT_ERRORS,
|
|
@@ -705,8 +821,9 @@ module SearchEngine
|
|
|
705
821
|
500
|
|
706
822
|
end
|
|
707
823
|
|
|
708
|
-
def instrument(method, path, duration_ms, cache_params, error_class: nil)
|
|
824
|
+
def instrument(method, path, duration_ms, cache_params, error_class: nil, request_token: nil)
|
|
709
825
|
return unless defined?(ActiveSupport::Notifications)
|
|
826
|
+
return if error_class.nil? && consume_request_error_instrumented?(method, path, request_token)
|
|
710
827
|
|
|
711
828
|
ActiveSupport::Notifications.instrument(
|
|
712
829
|
'search_engine.request',
|
|
@@ -718,6 +835,44 @@ module SearchEngine
|
|
|
718
835
|
)
|
|
719
836
|
end
|
|
720
837
|
|
|
838
|
+
def mark_request_error_instrumented(method, path, request_token = nil)
|
|
839
|
+
return nil if request_token.nil?
|
|
840
|
+
|
|
841
|
+
queue = Thread.current[REQUEST_ERROR_INSTRUMENTED_KEY] ||= []
|
|
842
|
+
now = current_monotonic_ms
|
|
843
|
+
prune_request_error_instrumented_queue!(queue, now)
|
|
844
|
+
queue << { method: method.to_sym, path: path.to_s, token: request_token, at: now }
|
|
845
|
+
queue.shift while queue.size > REQUEST_ERROR_INSTRUMENTED_MAX
|
|
846
|
+
nil
|
|
847
|
+
rescue StandardError
|
|
848
|
+
nil
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
def consume_request_error_instrumented?(method, path, request_token = nil)
|
|
852
|
+
return false if request_token.nil?
|
|
853
|
+
|
|
854
|
+
queue = Thread.current[REQUEST_ERROR_INSTRUMENTED_KEY]
|
|
855
|
+
return false unless queue.is_a?(Array) && !queue.empty?
|
|
856
|
+
|
|
857
|
+
now = current_monotonic_ms
|
|
858
|
+
prune_request_error_instrumented_queue!(queue, now)
|
|
859
|
+
idx = queue.index do |entry|
|
|
860
|
+
entry[:method] == method.to_sym && entry[:path] == path.to_s && entry[:token] == request_token
|
|
861
|
+
end
|
|
862
|
+
return false unless idx
|
|
863
|
+
|
|
864
|
+
queue.delete_at(idx)
|
|
865
|
+
true
|
|
866
|
+
rescue StandardError
|
|
867
|
+
false
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
def prune_request_error_instrumented_queue!(queue, now_ms)
|
|
871
|
+
queue.reject! do |entry|
|
|
872
|
+
now_ms - entry[:at].to_f > REQUEST_ERROR_INSTRUMENTED_TTL_MS
|
|
873
|
+
end
|
|
874
|
+
end
|
|
875
|
+
|
|
721
876
|
def log_success(method, path, start_ms, cache_params)
|
|
722
877
|
return unless safe_logger
|
|
723
878
|
|
|
@@ -500,7 +500,7 @@ module SearchEngine
|
|
|
500
500
|
joins = Array(state[:joins]).flatten.compact
|
|
501
501
|
return relation if joins.empty?
|
|
502
502
|
|
|
503
|
-
# Guard: sorting or selection on joined fields not supported
|
|
503
|
+
# Guard: sorting or selection on joined fields not supported by client-side join fallback
|
|
504
504
|
orders = Array(state[:orders]).map(&:to_s)
|
|
505
505
|
if orders.any? { |o| o.start_with?('$') }
|
|
506
506
|
raise SearchEngine::Errors::InvalidOption.new(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'search_engine/indexer/import_response_parser'
|
|
4
5
|
|
|
5
6
|
module SearchEngine
|
|
6
7
|
class Indexer
|
|
@@ -131,62 +132,7 @@ module SearchEngine
|
|
|
131
132
|
end
|
|
132
133
|
|
|
133
134
|
def parse_import_response(raw)
|
|
134
|
-
|
|
135
|
-
return parse_from_array(raw) if raw.is_a?(Array)
|
|
136
|
-
|
|
137
|
-
[0, 0, []]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def parse_from_string(str)
|
|
141
|
-
success = 0
|
|
142
|
-
failure = 0
|
|
143
|
-
samples = []
|
|
144
|
-
str.each_line do |line|
|
|
145
|
-
line = line.strip
|
|
146
|
-
next if line.empty?
|
|
147
|
-
|
|
148
|
-
h = safe_parse_json(line)
|
|
149
|
-
unless h
|
|
150
|
-
failure += 1
|
|
151
|
-
samples << 'invalid-json-line'
|
|
152
|
-
next
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
if truthy?(h['success'] || h[:success])
|
|
156
|
-
success += 1
|
|
157
|
-
else
|
|
158
|
-
failure += 1
|
|
159
|
-
msg = h['error'] || h[:error] || h['message'] || h[:message]
|
|
160
|
-
samples << msg.to_s[0, 200] if msg
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
[success, failure, samples[0, 5]]
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def parse_from_array(arr)
|
|
167
|
-
success = 0
|
|
168
|
-
failure = 0
|
|
169
|
-
samples = []
|
|
170
|
-
arr.each do |h|
|
|
171
|
-
if h.is_a?(Hash) && truthy?(h['success'] || h[:success])
|
|
172
|
-
success += 1
|
|
173
|
-
else
|
|
174
|
-
failure += 1
|
|
175
|
-
msg = h.is_a?(Hash) ? (h['error'] || h[:error] || h['message'] || h[:message]) : nil
|
|
176
|
-
samples << msg.to_s[0, 200] if msg
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
[success, failure, samples[0, 5]]
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def safe_parse_json(line)
|
|
183
|
-
JSON.parse(line)
|
|
184
|
-
rescue StandardError
|
|
185
|
-
nil
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def truthy?(val)
|
|
189
|
-
val == true || val.to_s.downcase == 'true'
|
|
135
|
+
SearchEngine::Indexer::ImportResponseParser.parse(raw)
|
|
190
136
|
end
|
|
191
137
|
|
|
192
138
|
def monotonic_ms
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module SearchEngine
|
|
6
|
+
class Indexer
|
|
7
|
+
# Shared parser for Typesense import API responses.
|
|
8
|
+
#
|
|
9
|
+
# Accepts the two shapes returned by different versions of the Typesense Ruby
|
|
10
|
+
# client:
|
|
11
|
+
# - String (JSONL status lines)
|
|
12
|
+
# - Array<Hash> (already parsed per-document statuses)
|
|
13
|
+
#
|
|
14
|
+
# Returns a stable tuple:
|
|
15
|
+
# [success_count, failure_count, errors_sample]
|
|
16
|
+
module ImportResponseParser
|
|
17
|
+
MAX_ERROR_SAMPLES = 5
|
|
18
|
+
INVALID_JSON_LINE = 'invalid-json-line'
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# @param raw [String, Array, Object]
|
|
23
|
+
# @return [Array(Integer, Integer, Array<String>)]
|
|
24
|
+
def parse(raw)
|
|
25
|
+
return parse_from_string(raw) if raw.is_a?(String)
|
|
26
|
+
return parse_from_array(raw) if raw.is_a?(Array)
|
|
27
|
+
|
|
28
|
+
[0, 0, []]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_from_string(str)
|
|
32
|
+
success = 0
|
|
33
|
+
failure = 0
|
|
34
|
+
samples = []
|
|
35
|
+
|
|
36
|
+
str.each_line do |line|
|
|
37
|
+
row = line.strip
|
|
38
|
+
next if row.empty?
|
|
39
|
+
|
|
40
|
+
parsed = safe_parse_json(row)
|
|
41
|
+
unless parsed
|
|
42
|
+
failure += 1
|
|
43
|
+
samples << INVALID_JSON_LINE
|
|
44
|
+
next
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if truthy?(parsed['success'] || parsed[:success])
|
|
48
|
+
success += 1
|
|
49
|
+
else
|
|
50
|
+
failure += 1
|
|
51
|
+
msg = parsed['error'] || parsed[:error] || parsed['message'] || parsed[:message]
|
|
52
|
+
samples << msg.to_s[0, 200] if msg
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
[success, failure, samples[0, MAX_ERROR_SAMPLES]]
|
|
57
|
+
end
|
|
58
|
+
module_function :parse_from_string
|
|
59
|
+
|
|
60
|
+
def parse_from_array(arr)
|
|
61
|
+
success = 0
|
|
62
|
+
failure = 0
|
|
63
|
+
samples = []
|
|
64
|
+
|
|
65
|
+
arr.each do |entry|
|
|
66
|
+
if entry.is_a?(Hash) && truthy?(entry['success'] || entry[:success])
|
|
67
|
+
success += 1
|
|
68
|
+
else
|
|
69
|
+
failure += 1
|
|
70
|
+
msg = entry.is_a?(Hash) ? (entry['error'] || entry[:error] || entry['message'] || entry[:message]) : nil
|
|
71
|
+
samples << msg.to_s[0, 200] if msg
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
[success, failure, samples[0, MAX_ERROR_SAMPLES]]
|
|
76
|
+
end
|
|
77
|
+
module_function :parse_from_array
|
|
78
|
+
|
|
79
|
+
def safe_parse_json(line)
|
|
80
|
+
JSON.parse(line)
|
|
81
|
+
rescue StandardError
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
module_function :safe_parse_json
|
|
85
|
+
|
|
86
|
+
def truthy?(value)
|
|
87
|
+
value == true || value.to_s.downcase == 'true'
|
|
88
|
+
end
|
|
89
|
+
module_function :truthy?
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -4,6 +4,7 @@ require 'json'
|
|
|
4
4
|
require 'timeout'
|
|
5
5
|
require 'digest'
|
|
6
6
|
require 'time'
|
|
7
|
+
require 'search_engine/indexer/import_response_parser'
|
|
7
8
|
|
|
8
9
|
module SearchEngine
|
|
9
10
|
# Batch importer for streaming JSONL documents into a physical collection.
|
|
@@ -520,66 +521,7 @@ module SearchEngine
|
|
|
520
521
|
end
|
|
521
522
|
|
|
522
523
|
def parse_import_response(raw)
|
|
523
|
-
|
|
524
|
-
return parse_from_array(raw) if raw.is_a?(Array)
|
|
525
|
-
|
|
526
|
-
[0, 0, []]
|
|
527
|
-
end
|
|
528
|
-
|
|
529
|
-
def parse_from_string(str)
|
|
530
|
-
success = 0
|
|
531
|
-
failure = 0
|
|
532
|
-
samples = []
|
|
533
|
-
|
|
534
|
-
str.each_line do |line|
|
|
535
|
-
line = line.strip
|
|
536
|
-
next if line.empty?
|
|
537
|
-
|
|
538
|
-
h = safe_parse_json(line)
|
|
539
|
-
unless h
|
|
540
|
-
failure += 1
|
|
541
|
-
samples << 'invalid-json-line'
|
|
542
|
-
next
|
|
543
|
-
end
|
|
544
|
-
|
|
545
|
-
if truthy?(h['success'] || h[:success])
|
|
546
|
-
success += 1
|
|
547
|
-
else
|
|
548
|
-
failure += 1
|
|
549
|
-
msg = h['error'] || h[:error] || h['message'] || h[:message]
|
|
550
|
-
samples << msg.to_s[0, 200] if msg
|
|
551
|
-
end
|
|
552
|
-
end
|
|
553
|
-
|
|
554
|
-
[success, failure, samples[0, 5]]
|
|
555
|
-
end
|
|
556
|
-
|
|
557
|
-
def parse_from_array(arr)
|
|
558
|
-
success = 0
|
|
559
|
-
failure = 0
|
|
560
|
-
samples = []
|
|
561
|
-
|
|
562
|
-
arr.each do |h|
|
|
563
|
-
if h.is_a?(Hash) && truthy?(h['success'] || h[:success])
|
|
564
|
-
success += 1
|
|
565
|
-
else
|
|
566
|
-
failure += 1
|
|
567
|
-
msg = h.is_a?(Hash) ? (h['error'] || h[:error] || h['message'] || h[:message]) : nil
|
|
568
|
-
samples << msg.to_s[0, 200] if msg
|
|
569
|
-
end
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
[success, failure, samples[0, 5]]
|
|
573
|
-
end
|
|
574
|
-
|
|
575
|
-
def safe_parse_json(line)
|
|
576
|
-
JSON.parse(line)
|
|
577
|
-
rescue StandardError
|
|
578
|
-
nil
|
|
579
|
-
end
|
|
580
|
-
|
|
581
|
-
def truthy?(val)
|
|
582
|
-
val == true || val.to_s.downcase == 'true'
|
|
524
|
+
SearchEngine::Indexer::ImportResponseParser.parse(raw)
|
|
583
525
|
end
|
|
584
526
|
|
|
585
527
|
def safe_error_excerpt(error)
|
|
@@ -311,7 +311,7 @@ module SearchEngine
|
|
|
311
311
|
|
|
312
312
|
lines << " Pinned: #{pinned.join(', ')}" unless pinned.empty?
|
|
313
313
|
lines << " Hidden: #{hidden.join(', ')}" unless hidden.empty?
|
|
314
|
-
lines << "
|
|
314
|
+
lines << " Curation tags: #{tags.join(', ')}" unless tags.empty?
|
|
315
315
|
lines << " Filter curated hits: #{fch}" unless fch.nil?
|
|
316
316
|
lines
|
|
317
317
|
end
|
|
@@ -510,7 +510,7 @@ module SearchEngine
|
|
|
510
510
|
|
|
511
511
|
params[:pinned_hits] = pinned.join(',') if pinned.any?
|
|
512
512
|
params[:hidden_hits] = hidden.join(',') if hidden.any?
|
|
513
|
-
params[:
|
|
513
|
+
params[:curation_tags] = tags.join(',') if tags.any?
|
|
514
514
|
params[:filter_curated_hits] = fch unless fch.nil?
|
|
515
515
|
# Expose a compact curation meta segment for callers (not sent over HTTP)
|
|
516
516
|
params[:_curation] = { filter_curated_hits: fch } if cur.key?(:filter_curated_hits)
|
|
@@ -145,7 +145,7 @@ module SearchEngine
|
|
|
145
145
|
|
|
146
146
|
# Set multiple curation knobs in one call.
|
|
147
147
|
# @return [SearchEngine::Relation]
|
|
148
|
-
def curate(pin: nil, hide: nil, override_tags: nil, filter_curated_hits: :__unset__)
|
|
148
|
+
def curate(pin: nil, hide: nil, override_tags: nil, curation_tags: nil, filter_curated_hits: :__unset__)
|
|
149
149
|
spawn do |s|
|
|
150
150
|
cur = s[:curation] || { pinned: [], hidden: [], override_tags: [], filter_curated_hits: nil }
|
|
151
151
|
|
|
@@ -157,7 +157,12 @@ module SearchEngine
|
|
|
157
157
|
list = normalize_curation_ids(hide)
|
|
158
158
|
cur[:hidden] = list.each_with_object([]) { |t, acc| acc << t unless acc.include?(t) }
|
|
159
159
|
end
|
|
160
|
-
|
|
160
|
+
tags_input = if !curation_tags.nil?
|
|
161
|
+
curation_tags
|
|
162
|
+
else
|
|
163
|
+
override_tags
|
|
164
|
+
end
|
|
165
|
+
cur[:override_tags] = normalize_curation_tags(tags_input) unless tags_input.nil?
|
|
161
166
|
if filter_curated_hits != :__unset__
|
|
162
167
|
cur[:filter_curated_hits] =
|
|
163
168
|
filter_curated_hits.nil? ? nil : coerce_boolean_strict(filter_curated_hits, :filter_curated_hits)
|
|
@@ -820,7 +825,13 @@ module SearchEngine
|
|
|
820
825
|
|
|
821
826
|
pinned = normalize_curation_ids(value[:pinned] || value['pinned'])
|
|
822
827
|
hidden = normalize_curation_ids(value[:hidden] || value['hidden'])
|
|
823
|
-
|
|
828
|
+
raw_tags =
|
|
829
|
+
if value.key?(:curation_tags) || value.key?('curation_tags')
|
|
830
|
+
value[:curation_tags] || value['curation_tags']
|
|
831
|
+
else
|
|
832
|
+
value[:override_tags] || value['override_tags']
|
|
833
|
+
end
|
|
834
|
+
tags = normalize_curation_tags(raw_tags)
|
|
824
835
|
|
|
825
836
|
raw_fch = (value.key?(:filter_curated_hits) ? value[:filter_curated_hits] : value['filter_curated_hits'])
|
|
826
837
|
fch = raw_fch.nil? ? nil : coerce_boolean_strict(raw_fch, :filter_curated_hits)
|
data/lib/search_engine.rb
CHANGED
|
@@ -23,6 +23,7 @@ require 'search_engine/collection_resolver'
|
|
|
23
23
|
require 'search_engine/cascade'
|
|
24
24
|
require 'search_engine/indexer'
|
|
25
25
|
require 'search_engine/indexer/batch_planner'
|
|
26
|
+
require 'search_engine/indexer/import_response_parser'
|
|
26
27
|
require 'search_engine/indexer/import_dispatcher'
|
|
27
28
|
require 'search_engine/indexer/retry_policy'
|
|
28
29
|
require 'search_engine/indexer/bulk_import'
|
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: 1.0
|
|
4
|
+
version: 30.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nikita Shkoda
|
|
@@ -44,14 +44,14 @@ dependencies:
|
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
47
|
+
version: 5.0.0
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
54
|
+
version: 5.0.0
|
|
55
55
|
description: Rails::Engine providing a thin wrapper around Typesense with idiomatic
|
|
56
56
|
Rails integration.
|
|
57
57
|
email:
|
|
@@ -148,6 +148,7 @@ files:
|
|
|
148
148
|
- lib/search_engine/indexer/batch_planner.rb
|
|
149
149
|
- lib/search_engine/indexer/bulk_import.rb
|
|
150
150
|
- lib/search_engine/indexer/import_dispatcher.rb
|
|
151
|
+
- lib/search_engine/indexer/import_response_parser.rb
|
|
151
152
|
- lib/search_engine/indexer/retry_policy.rb
|
|
152
153
|
- lib/search_engine/instrumentation.rb
|
|
153
154
|
- lib/search_engine/joins/guard.rb
|