search-engine-for-typesense 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +148 -0
- data/app/search_engine/search_engine/app_info.rb +11 -0
- data/app/search_engine/search_engine/index_partition_job.rb +170 -0
- data/lib/generators/search_engine/install/install_generator.rb +20 -0
- data/lib/generators/search_engine/install/templates/initializer.rb.tt +230 -0
- data/lib/generators/search_engine/model/model_generator.rb +86 -0
- data/lib/generators/search_engine/model/templates/model.rb.tt +12 -0
- data/lib/search-engine-for-typesense.rb +12 -0
- data/lib/search_engine/active_record_syncable.rb +247 -0
- data/lib/search_engine/admin/stopwords.rb +125 -0
- data/lib/search_engine/admin/synonyms.rb +125 -0
- data/lib/search_engine/admin.rb +12 -0
- data/lib/search_engine/ast/and.rb +52 -0
- data/lib/search_engine/ast/binary_op.rb +75 -0
- data/lib/search_engine/ast/eq.rb +19 -0
- data/lib/search_engine/ast/group.rb +18 -0
- data/lib/search_engine/ast/gt.rb +12 -0
- data/lib/search_engine/ast/gte.rb +12 -0
- data/lib/search_engine/ast/in.rb +28 -0
- data/lib/search_engine/ast/lt.rb +12 -0
- data/lib/search_engine/ast/lte.rb +12 -0
- data/lib/search_engine/ast/matches.rb +55 -0
- data/lib/search_engine/ast/node.rb +176 -0
- data/lib/search_engine/ast/not_eq.rb +13 -0
- data/lib/search_engine/ast/not_in.rb +24 -0
- data/lib/search_engine/ast/or.rb +52 -0
- data/lib/search_engine/ast/prefix.rb +51 -0
- data/lib/search_engine/ast/raw.rb +41 -0
- data/lib/search_engine/ast/unary_op.rb +43 -0
- data/lib/search_engine/ast.rb +101 -0
- data/lib/search_engine/base/creation.rb +727 -0
- data/lib/search_engine/base/deletion.rb +80 -0
- data/lib/search_engine/base/display_coercions.rb +36 -0
- data/lib/search_engine/base/hydration.rb +312 -0
- data/lib/search_engine/base/index_maintenance/cleanup.rb +202 -0
- data/lib/search_engine/base/index_maintenance/lifecycle.rb +251 -0
- data/lib/search_engine/base/index_maintenance/schema.rb +117 -0
- data/lib/search_engine/base/index_maintenance.rb +459 -0
- data/lib/search_engine/base/indexing_dsl.rb +255 -0
- data/lib/search_engine/base/joins.rb +479 -0
- data/lib/search_engine/base/model_dsl.rb +472 -0
- data/lib/search_engine/base/presets.rb +43 -0
- data/lib/search_engine/base/pretty_printer.rb +315 -0
- data/lib/search_engine/base/relation_delegation.rb +42 -0
- data/lib/search_engine/base/scopes.rb +113 -0
- data/lib/search_engine/base/updating.rb +92 -0
- data/lib/search_engine/base.rb +38 -0
- data/lib/search_engine/bulk.rb +284 -0
- data/lib/search_engine/cache.rb +33 -0
- data/lib/search_engine/cascade.rb +531 -0
- data/lib/search_engine/cli/doctor.rb +631 -0
- data/lib/search_engine/cli/support.rb +217 -0
- data/lib/search_engine/cli.rb +222 -0
- data/lib/search_engine/client/http_adapter.rb +63 -0
- data/lib/search_engine/client/request_builder.rb +92 -0
- data/lib/search_engine/client/services/base.rb +74 -0
- data/lib/search_engine/client/services/collections.rb +161 -0
- data/lib/search_engine/client/services/documents.rb +214 -0
- data/lib/search_engine/client/services/operations.rb +152 -0
- data/lib/search_engine/client/services/search.rb +190 -0
- data/lib/search_engine/client/services.rb +29 -0
- data/lib/search_engine/client.rb +765 -0
- data/lib/search_engine/client_options.rb +20 -0
- data/lib/search_engine/collection_resolver.rb +191 -0
- data/lib/search_engine/collections_graph.rb +330 -0
- data/lib/search_engine/compiled_params.rb +143 -0
- data/lib/search_engine/compiler.rb +383 -0
- data/lib/search_engine/config/observability.rb +27 -0
- data/lib/search_engine/config/presets.rb +92 -0
- data/lib/search_engine/config/selection.rb +16 -0
- data/lib/search_engine/config/typesense.rb +48 -0
- data/lib/search_engine/config/validators.rb +97 -0
- data/lib/search_engine/config.rb +917 -0
- data/lib/search_engine/console_helpers.rb +130 -0
- data/lib/search_engine/deletion.rb +103 -0
- data/lib/search_engine/dispatcher.rb +125 -0
- data/lib/search_engine/dsl/parser.rb +582 -0
- data/lib/search_engine/engine.rb +167 -0
- data/lib/search_engine/errors.rb +290 -0
- data/lib/search_engine/filters/sanitizer.rb +189 -0
- data/lib/search_engine/hydration/materializers.rb +808 -0
- data/lib/search_engine/hydration/selection_context.rb +96 -0
- data/lib/search_engine/indexer/batch_planner.rb +76 -0
- data/lib/search_engine/indexer/bulk_import.rb +626 -0
- data/lib/search_engine/indexer/import_dispatcher.rb +198 -0
- data/lib/search_engine/indexer/retry_policy.rb +103 -0
- data/lib/search_engine/indexer.rb +747 -0
- data/lib/search_engine/instrumentation.rb +308 -0
- data/lib/search_engine/joins/guard.rb +202 -0
- data/lib/search_engine/joins/resolver.rb +95 -0
- data/lib/search_engine/logging/color.rb +78 -0
- data/lib/search_engine/logging/format_helpers.rb +92 -0
- data/lib/search_engine/logging/partition_progress.rb +53 -0
- data/lib/search_engine/logging_subscriber.rb +388 -0
- data/lib/search_engine/mapper.rb +785 -0
- data/lib/search_engine/multi.rb +286 -0
- data/lib/search_engine/multi_result.rb +186 -0
- data/lib/search_engine/notifications/compact_logger.rb +675 -0
- data/lib/search_engine/observability.rb +162 -0
- data/lib/search_engine/operations.rb +58 -0
- data/lib/search_engine/otel.rb +227 -0
- data/lib/search_engine/partitioner.rb +128 -0
- data/lib/search_engine/ranking_plan.rb +118 -0
- data/lib/search_engine/registry.rb +158 -0
- data/lib/search_engine/relation/compiler.rb +711 -0
- data/lib/search_engine/relation/deletion.rb +37 -0
- data/lib/search_engine/relation/dsl/filters.rb +624 -0
- data/lib/search_engine/relation/dsl/selection.rb +240 -0
- data/lib/search_engine/relation/dsl.rb +903 -0
- data/lib/search_engine/relation/dx/dry_run.rb +59 -0
- data/lib/search_engine/relation/dx/friendly_where.rb +24 -0
- data/lib/search_engine/relation/dx.rb +231 -0
- data/lib/search_engine/relation/materializers.rb +118 -0
- data/lib/search_engine/relation/options.rb +138 -0
- data/lib/search_engine/relation/state.rb +274 -0
- data/lib/search_engine/relation/updating.rb +44 -0
- data/lib/search_engine/relation.rb +623 -0
- data/lib/search_engine/result.rb +664 -0
- data/lib/search_engine/schema.rb +1083 -0
- data/lib/search_engine/sources/active_record_source.rb +185 -0
- data/lib/search_engine/sources/base.rb +62 -0
- data/lib/search_engine/sources/lambda_source.rb +55 -0
- data/lib/search_engine/sources/sql_source.rb +196 -0
- data/lib/search_engine/sources.rb +71 -0
- data/lib/search_engine/stale_rules.rb +160 -0
- data/lib/search_engine/test/minitest_assertions.rb +57 -0
- data/lib/search_engine/test/offline_client.rb +134 -0
- data/lib/search_engine/test/rspec_matchers.rb +77 -0
- data/lib/search_engine/test/stub_client.rb +201 -0
- data/lib/search_engine/test.rb +66 -0
- data/lib/search_engine/test_autoload.rb +8 -0
- data/lib/search_engine/update.rb +35 -0
- data/lib/search_engine/version.rb +7 -0
- data/lib/search_engine.rb +332 -0
- data/lib/tasks/search_engine.rake +501 -0
- data/lib/tasks/search_engine_doctor.rake +16 -0
- metadata +225 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Internal SearchEngine registry and APIs for model mapping.
|
|
4
|
+
module SearchEngine
|
|
5
|
+
# Internal registry for mapping Typesense collection names to model classes.
|
|
6
|
+
#
|
|
7
|
+
# Exposes stable module-level APIs on {SearchEngine}:
|
|
8
|
+
# - {SearchEngine.register_collection!}
|
|
9
|
+
# - {SearchEngine.collection_for}
|
|
10
|
+
#
|
|
11
|
+
# The registry uses a copy-on-write Hash guarded by a small Mutex. Reads are
|
|
12
|
+
# lock-free and writes are atomic. This makes it safe under concurrency and
|
|
13
|
+
# friendly to Rails code reloading in development.
|
|
14
|
+
module Registry
|
|
15
|
+
class << self
|
|
16
|
+
# @return [Hash{String=>Class}] frozen snapshot of the registry
|
|
17
|
+
def mapping
|
|
18
|
+
@mapping ||= {}.freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Mutex] global write mutex for registry updates
|
|
22
|
+
def mutex
|
|
23
|
+
@mutex ||= Mutex.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Replace the current mapping with a new frozen Hash.
|
|
27
|
+
# @param new_map [Hash{String=>Class}]
|
|
28
|
+
# @return [void]
|
|
29
|
+
def replace!(new_map)
|
|
30
|
+
@mapping = new_map.freeze
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Reset to an empty mapping (used by tests).
|
|
35
|
+
# @api private
|
|
36
|
+
# @return [void]
|
|
37
|
+
def __reset_for_tests!
|
|
38
|
+
mutex.synchronize { replace!({}) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Top-level APIs for configuration and model registry.
|
|
45
|
+
module SearchEngine
|
|
46
|
+
class << self
|
|
47
|
+
# Register a model class for a given Typesense collection name.
|
|
48
|
+
#
|
|
49
|
+
# Idempotent when re-registering with the same class or with a class that
|
|
50
|
+
# has the same name (to support Rails code reloading). If an existing
|
|
51
|
+
# mapping is present for the collection name and points to a different
|
|
52
|
+
# class (by class name), an ArgumentError is raised.
|
|
53
|
+
#
|
|
54
|
+
# @param name [#to_s] Typesense collection name
|
|
55
|
+
# @param klass [Class] model class to associate
|
|
56
|
+
# @return [Class] the registered class
|
|
57
|
+
# @raise [ArgumentError] when attempting to change an existing mapping to a different class
|
|
58
|
+
def register_collection!(name, klass)
|
|
59
|
+
normalized_name = name.to_s
|
|
60
|
+
raise ArgumentError, 'collection name must be non-empty' if normalized_name.strip.empty?
|
|
61
|
+
raise ArgumentError, 'klass must be a Class' unless klass.is_a?(Class)
|
|
62
|
+
|
|
63
|
+
Registry.mutex.synchronize do
|
|
64
|
+
current = Registry.mapping[normalized_name]
|
|
65
|
+
|
|
66
|
+
return write_mapping!(normalized_name, klass) if mapping_idempotent?(current, klass)
|
|
67
|
+
|
|
68
|
+
raise_conflict_if_needed!(normalized_name, current, klass)
|
|
69
|
+
write_mapping!(normalized_name, klass)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Resolve the model class for a given Typesense collection name.
|
|
74
|
+
#
|
|
75
|
+
# @param name [#to_s]
|
|
76
|
+
# @return [Class]
|
|
77
|
+
# @raise [ArgumentError] when the collection is not registered
|
|
78
|
+
def collection_for(name)
|
|
79
|
+
normalized_name = name.to_s
|
|
80
|
+
klass = Registry.mapping[normalized_name]
|
|
81
|
+
return klass if klass
|
|
82
|
+
|
|
83
|
+
# Lazy autoload fallback: try to resolve a namespaced model constant
|
|
84
|
+
# based on the collection name and trigger its registration.
|
|
85
|
+
begin
|
|
86
|
+
begin
|
|
87
|
+
require 'active_support/inflector'
|
|
88
|
+
rescue StandardError
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
demod = if defined?(ActiveSupport::Inflector)
|
|
93
|
+
ActiveSupport::Inflector.classify(normalized_name)
|
|
94
|
+
else
|
|
95
|
+
# Minimal classify fallback: singularize by dropping trailing 's' and camelize tokens
|
|
96
|
+
base = normalized_name.end_with?('s') ? normalized_name[0..-2] : normalized_name
|
|
97
|
+
base.split('_').map { |p| p[0] ? p[0].upcase + p[1..] : '' }.join
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
const_name = "SearchEngine::#{demod}"
|
|
101
|
+
# Trigger autoload; ignore when constant is missing
|
|
102
|
+
Object.const_get(const_name)
|
|
103
|
+
rescue NameError
|
|
104
|
+
# constant not found; proceed to error
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Re-check after potential autoload/registration
|
|
108
|
+
klass = Registry.mapping[normalized_name]
|
|
109
|
+
return klass if klass
|
|
110
|
+
|
|
111
|
+
message = 'Unregistered collection: ' \
|
|
112
|
+
"'#{normalized_name}'. " \
|
|
113
|
+
'Define a model inheriting from SearchEngine::Base and call ' \
|
|
114
|
+
"`collection '#{normalized_name}'`."
|
|
115
|
+
raise ArgumentError, message
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def mapping_idempotent?(current, new_klass)
|
|
121
|
+
return false unless current
|
|
122
|
+
|
|
123
|
+
current == new_klass || safe_class_name(current) == safe_class_name(new_klass)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def raise_conflict_if_needed!(name, current, new_klass)
|
|
127
|
+
return unless current
|
|
128
|
+
|
|
129
|
+
old_name = safe_class_name(current)
|
|
130
|
+
new_name = safe_class_name(new_klass)
|
|
131
|
+
return if old_name == new_name
|
|
132
|
+
|
|
133
|
+
message = "Collection '#{name}' already registered to #{old_name}; " \
|
|
134
|
+
"cannot re-register to #{new_name}"
|
|
135
|
+
raise ArgumentError, message
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def write_mapping!(name, klass)
|
|
139
|
+
new_map = Registry.mapping.dup
|
|
140
|
+
new_map[name] = klass
|
|
141
|
+
Registry.replace!(new_map)
|
|
142
|
+
klass
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def safe_class_name(klass)
|
|
146
|
+
klass.respond_to?(:name) && klass.name ? klass.name : klass.to_s
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Clear the registry (intended for test suites).
|
|
150
|
+
# @api private
|
|
151
|
+
# @return [void]
|
|
152
|
+
def __reset_registry_for_tests!
|
|
153
|
+
Registry.__reset_for_tests!
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private :__reset_registry_for_tests!
|
|
157
|
+
end
|
|
158
|
+
end
|