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.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +148 -0
  4. data/app/search_engine/search_engine/app_info.rb +11 -0
  5. data/app/search_engine/search_engine/index_partition_job.rb +170 -0
  6. data/lib/generators/search_engine/install/install_generator.rb +20 -0
  7. data/lib/generators/search_engine/install/templates/initializer.rb.tt +230 -0
  8. data/lib/generators/search_engine/model/model_generator.rb +86 -0
  9. data/lib/generators/search_engine/model/templates/model.rb.tt +12 -0
  10. data/lib/search-engine-for-typesense.rb +12 -0
  11. data/lib/search_engine/active_record_syncable.rb +247 -0
  12. data/lib/search_engine/admin/stopwords.rb +125 -0
  13. data/lib/search_engine/admin/synonyms.rb +125 -0
  14. data/lib/search_engine/admin.rb +12 -0
  15. data/lib/search_engine/ast/and.rb +52 -0
  16. data/lib/search_engine/ast/binary_op.rb +75 -0
  17. data/lib/search_engine/ast/eq.rb +19 -0
  18. data/lib/search_engine/ast/group.rb +18 -0
  19. data/lib/search_engine/ast/gt.rb +12 -0
  20. data/lib/search_engine/ast/gte.rb +12 -0
  21. data/lib/search_engine/ast/in.rb +28 -0
  22. data/lib/search_engine/ast/lt.rb +12 -0
  23. data/lib/search_engine/ast/lte.rb +12 -0
  24. data/lib/search_engine/ast/matches.rb +55 -0
  25. data/lib/search_engine/ast/node.rb +176 -0
  26. data/lib/search_engine/ast/not_eq.rb +13 -0
  27. data/lib/search_engine/ast/not_in.rb +24 -0
  28. data/lib/search_engine/ast/or.rb +52 -0
  29. data/lib/search_engine/ast/prefix.rb +51 -0
  30. data/lib/search_engine/ast/raw.rb +41 -0
  31. data/lib/search_engine/ast/unary_op.rb +43 -0
  32. data/lib/search_engine/ast.rb +101 -0
  33. data/lib/search_engine/base/creation.rb +727 -0
  34. data/lib/search_engine/base/deletion.rb +80 -0
  35. data/lib/search_engine/base/display_coercions.rb +36 -0
  36. data/lib/search_engine/base/hydration.rb +312 -0
  37. data/lib/search_engine/base/index_maintenance/cleanup.rb +202 -0
  38. data/lib/search_engine/base/index_maintenance/lifecycle.rb +251 -0
  39. data/lib/search_engine/base/index_maintenance/schema.rb +117 -0
  40. data/lib/search_engine/base/index_maintenance.rb +459 -0
  41. data/lib/search_engine/base/indexing_dsl.rb +255 -0
  42. data/lib/search_engine/base/joins.rb +479 -0
  43. data/lib/search_engine/base/model_dsl.rb +472 -0
  44. data/lib/search_engine/base/presets.rb +43 -0
  45. data/lib/search_engine/base/pretty_printer.rb +315 -0
  46. data/lib/search_engine/base/relation_delegation.rb +42 -0
  47. data/lib/search_engine/base/scopes.rb +113 -0
  48. data/lib/search_engine/base/updating.rb +92 -0
  49. data/lib/search_engine/base.rb +38 -0
  50. data/lib/search_engine/bulk.rb +284 -0
  51. data/lib/search_engine/cache.rb +33 -0
  52. data/lib/search_engine/cascade.rb +531 -0
  53. data/lib/search_engine/cli/doctor.rb +631 -0
  54. data/lib/search_engine/cli/support.rb +217 -0
  55. data/lib/search_engine/cli.rb +222 -0
  56. data/lib/search_engine/client/http_adapter.rb +63 -0
  57. data/lib/search_engine/client/request_builder.rb +92 -0
  58. data/lib/search_engine/client/services/base.rb +74 -0
  59. data/lib/search_engine/client/services/collections.rb +161 -0
  60. data/lib/search_engine/client/services/documents.rb +214 -0
  61. data/lib/search_engine/client/services/operations.rb +152 -0
  62. data/lib/search_engine/client/services/search.rb +190 -0
  63. data/lib/search_engine/client/services.rb +29 -0
  64. data/lib/search_engine/client.rb +765 -0
  65. data/lib/search_engine/client_options.rb +20 -0
  66. data/lib/search_engine/collection_resolver.rb +191 -0
  67. data/lib/search_engine/collections_graph.rb +330 -0
  68. data/lib/search_engine/compiled_params.rb +143 -0
  69. data/lib/search_engine/compiler.rb +383 -0
  70. data/lib/search_engine/config/observability.rb +27 -0
  71. data/lib/search_engine/config/presets.rb +92 -0
  72. data/lib/search_engine/config/selection.rb +16 -0
  73. data/lib/search_engine/config/typesense.rb +48 -0
  74. data/lib/search_engine/config/validators.rb +97 -0
  75. data/lib/search_engine/config.rb +917 -0
  76. data/lib/search_engine/console_helpers.rb +130 -0
  77. data/lib/search_engine/deletion.rb +103 -0
  78. data/lib/search_engine/dispatcher.rb +125 -0
  79. data/lib/search_engine/dsl/parser.rb +582 -0
  80. data/lib/search_engine/engine.rb +167 -0
  81. data/lib/search_engine/errors.rb +290 -0
  82. data/lib/search_engine/filters/sanitizer.rb +189 -0
  83. data/lib/search_engine/hydration/materializers.rb +808 -0
  84. data/lib/search_engine/hydration/selection_context.rb +96 -0
  85. data/lib/search_engine/indexer/batch_planner.rb +76 -0
  86. data/lib/search_engine/indexer/bulk_import.rb +626 -0
  87. data/lib/search_engine/indexer/import_dispatcher.rb +198 -0
  88. data/lib/search_engine/indexer/retry_policy.rb +103 -0
  89. data/lib/search_engine/indexer.rb +747 -0
  90. data/lib/search_engine/instrumentation.rb +308 -0
  91. data/lib/search_engine/joins/guard.rb +202 -0
  92. data/lib/search_engine/joins/resolver.rb +95 -0
  93. data/lib/search_engine/logging/color.rb +78 -0
  94. data/lib/search_engine/logging/format_helpers.rb +92 -0
  95. data/lib/search_engine/logging/partition_progress.rb +53 -0
  96. data/lib/search_engine/logging_subscriber.rb +388 -0
  97. data/lib/search_engine/mapper.rb +785 -0
  98. data/lib/search_engine/multi.rb +286 -0
  99. data/lib/search_engine/multi_result.rb +186 -0
  100. data/lib/search_engine/notifications/compact_logger.rb +675 -0
  101. data/lib/search_engine/observability.rb +162 -0
  102. data/lib/search_engine/operations.rb +58 -0
  103. data/lib/search_engine/otel.rb +227 -0
  104. data/lib/search_engine/partitioner.rb +128 -0
  105. data/lib/search_engine/ranking_plan.rb +118 -0
  106. data/lib/search_engine/registry.rb +158 -0
  107. data/lib/search_engine/relation/compiler.rb +711 -0
  108. data/lib/search_engine/relation/deletion.rb +37 -0
  109. data/lib/search_engine/relation/dsl/filters.rb +624 -0
  110. data/lib/search_engine/relation/dsl/selection.rb +240 -0
  111. data/lib/search_engine/relation/dsl.rb +903 -0
  112. data/lib/search_engine/relation/dx/dry_run.rb +59 -0
  113. data/lib/search_engine/relation/dx/friendly_where.rb +24 -0
  114. data/lib/search_engine/relation/dx.rb +231 -0
  115. data/lib/search_engine/relation/materializers.rb +118 -0
  116. data/lib/search_engine/relation/options.rb +138 -0
  117. data/lib/search_engine/relation/state.rb +274 -0
  118. data/lib/search_engine/relation/updating.rb +44 -0
  119. data/lib/search_engine/relation.rb +623 -0
  120. data/lib/search_engine/result.rb +664 -0
  121. data/lib/search_engine/schema.rb +1083 -0
  122. data/lib/search_engine/sources/active_record_source.rb +185 -0
  123. data/lib/search_engine/sources/base.rb +62 -0
  124. data/lib/search_engine/sources/lambda_source.rb +55 -0
  125. data/lib/search_engine/sources/sql_source.rb +196 -0
  126. data/lib/search_engine/sources.rb +71 -0
  127. data/lib/search_engine/stale_rules.rb +160 -0
  128. data/lib/search_engine/test/minitest_assertions.rb +57 -0
  129. data/lib/search_engine/test/offline_client.rb +134 -0
  130. data/lib/search_engine/test/rspec_matchers.rb +77 -0
  131. data/lib/search_engine/test/stub_client.rb +201 -0
  132. data/lib/search_engine/test.rb +66 -0
  133. data/lib/search_engine/test_autoload.rb +8 -0
  134. data/lib/search_engine/update.rb +35 -0
  135. data/lib/search_engine/version.rb +7 -0
  136. data/lib/search_engine.rb +332 -0
  137. data/lib/tasks/search_engine.rake +501 -0
  138. data/lib/tasks/search_engine_doctor.rake +16 -0
  139. 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