@memberjunction/search-engine 0.0.1 → 5.25.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 (42) hide show
  1. package/dist/generic/EntitySearchProvider.d.ts +54 -0
  2. package/dist/generic/EntitySearchProvider.d.ts.map +1 -0
  3. package/dist/generic/EntitySearchProvider.js +207 -0
  4. package/dist/generic/EntitySearchProvider.js.map +1 -0
  5. package/dist/generic/FullTextSearchProvider.d.ts +35 -0
  6. package/dist/generic/FullTextSearchProvider.d.ts.map +1 -0
  7. package/dist/generic/FullTextSearchProvider.js +89 -0
  8. package/dist/generic/FullTextSearchProvider.js.map +1 -0
  9. package/dist/generic/ISearchProvider.d.ts +90 -0
  10. package/dist/generic/ISearchProvider.d.ts.map +1 -0
  11. package/dist/generic/ISearchProvider.js +62 -0
  12. package/dist/generic/ISearchProvider.js.map +1 -0
  13. package/dist/generic/SearchEngine.d.ts +140 -0
  14. package/dist/generic/SearchEngine.d.ts.map +1 -0
  15. package/dist/generic/SearchEngine.js +446 -0
  16. package/dist/generic/SearchEngine.js.map +1 -0
  17. package/dist/generic/SearchEnricher.d.ts +69 -0
  18. package/dist/generic/SearchEnricher.d.ts.map +1 -0
  19. package/dist/generic/SearchEnricher.js +241 -0
  20. package/dist/generic/SearchEnricher.js.map +1 -0
  21. package/dist/generic/SearchFusion.d.ts +62 -0
  22. package/dist/generic/SearchFusion.d.ts.map +1 -0
  23. package/dist/generic/SearchFusion.js +147 -0
  24. package/dist/generic/SearchFusion.js.map +1 -0
  25. package/dist/generic/StorageSearchProvider.d.ts +87 -0
  26. package/dist/generic/StorageSearchProvider.d.ts.map +1 -0
  27. package/dist/generic/StorageSearchProvider.js +322 -0
  28. package/dist/generic/StorageSearchProvider.js.map +1 -0
  29. package/dist/generic/VectorSearchProvider.d.ts +65 -0
  30. package/dist/generic/VectorSearchProvider.d.ts.map +1 -0
  31. package/dist/generic/VectorSearchProvider.js +347 -0
  32. package/dist/generic/VectorSearchProvider.js.map +1 -0
  33. package/dist/generic/search.types.d.ts +151 -0
  34. package/dist/generic/search.types.d.ts.map +1 -0
  35. package/dist/generic/search.types.js +11 -0
  36. package/dist/generic/search.types.js.map +1 -0
  37. package/dist/index.d.ts +10 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +10 -0
  40. package/dist/index.js.map +1 -0
  41. package/package.json +32 -7
  42. package/README.md +0 -45
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @fileoverview SearchEngine singleton - the main entry point for multi-source search.
3
+ *
4
+ * Orchestrates search providers discovered from the SearchProvider metadata entity,
5
+ * fuses results with Reciprocal Rank Fusion (RRF), applies enrichment
6
+ * (entity icons, record names, tags), and filters by minimum score.
7
+ *
8
+ * Providers are loaded via @RegisterClass(BaseSearchProvider, DriverClass) and
9
+ * instantiated using the MJ ClassFactory based on active SearchProvider records.
10
+ *
11
+ * Uses BaseSingleton from @memberjunction/global for a truly global instance.
12
+ *
13
+ * @module @memberjunction/search-engine
14
+ */
15
+ import { UserInfo } from '@memberjunction/core';
16
+ import { SearchEngineBase } from '@memberjunction/core-entities';
17
+ import { BaseSingleton } from '@memberjunction/global';
18
+ import { SearchParams, SearchResult } from './search.types.js';
19
+ /**
20
+ * Configuration options for the SearchEngine.
21
+ */
22
+ export interface SearchEngineConfig {
23
+ /** Default maximum results if not specified in SearchParams (default: 20) */
24
+ DefaultMaxResults?: number;
25
+ }
26
+ /**
27
+ * Singleton search engine that orchestrates multi-source search with RRF fusion.
28
+ *
29
+ * Providers are discovered from the MJ: Search Providers entity. Each active
30
+ * provider's DriverClass is resolved via ClassFactory to create an instance,
31
+ * which is then initialized with the provider's config from the DB.
32
+ *
33
+ * Usage:
34
+ * ```typescript
35
+ * // Initialize once at server startup
36
+ * await SearchEngine.Instance.Config({}, contextUser);
37
+ *
38
+ * // Execute searches
39
+ * const result = await SearchEngine.Instance.Search({
40
+ * Query: 'quarterly revenue',
41
+ * MaxResults: 20,
42
+ * MinScore: 0.1
43
+ * }, contextUser);
44
+ * ```
45
+ */
46
+ export declare class SearchEngine extends BaseSingleton<SearchEngine> {
47
+ constructor();
48
+ /** Static accessor for the global singleton instance */
49
+ static get Instance(): SearchEngine;
50
+ private _configured;
51
+ private _providerEntries;
52
+ private _fusion;
53
+ private _enricher;
54
+ private _defaultMaxResults;
55
+ /** Access the cached provider metadata from SearchEngineBase */
56
+ protected get Base(): SearchEngineBase;
57
+ /**
58
+ * Initialize the search engine by reading active SearchProvider records
59
+ * from SearchEngineBase (which caches them via BaseEngine) and
60
+ * instantiating each via ClassFactory.
61
+ *
62
+ * Safe to call multiple times (no-ops if already configured unless forceRefresh=true).
63
+ *
64
+ * @param config - Engine configuration options
65
+ * @param contextUser - The user context for initialization
66
+ * @param forceRefresh - If true, re-initializes even if already configured
67
+ */
68
+ Config(config: SearchEngineConfig, contextUser: UserInfo, forceRefresh?: boolean): Promise<void>;
69
+ /**
70
+ * Execute a multi-source search with RRF fusion and optional enrichment.
71
+ *
72
+ * Steps:
73
+ * 1. Run all available providers in parallel
74
+ * 2. Fuse results with RRF
75
+ * 3. Deduplicate by EntityName+RecordID
76
+ * 4. Exclude redundant entity-sourced Content Items
77
+ * 5. Apply minimum score threshold
78
+ * 6. Enrich with icons, names, and tags (skipped in preview mode)
79
+ *
80
+ * @param params - Search parameters
81
+ * @param contextUser - The user performing the search
82
+ * @returns Aggregated search result
83
+ */
84
+ Search(params: SearchParams, contextUser: UserInfo): Promise<SearchResult>;
85
+ /**
86
+ * Quick preview search optimized for autocomplete / typeahead.
87
+ * Uses preview mode (no enrichment), limited to 8 results by default.
88
+ * Only runs providers that have SupportsPreview=true.
89
+ *
90
+ * @param query - The search query text
91
+ * @param maxResults - Maximum number of preview results (default: 8)
92
+ * @param contextUser - The user performing the search
93
+ * @returns Search result in preview mode
94
+ */
95
+ PreviewSearch(query: string, maxResults: number, contextUser: UserInfo): Promise<SearchResult>;
96
+ /**
97
+ * Instantiate a single provider from its SearchProvider metadata record,
98
+ * initialize it, check availability, and add to the active list if available.
99
+ */
100
+ private initializeProvider;
101
+ /**
102
+ * Run all available providers in parallel and return labeled result lists.
103
+ * When isPreview is true, only providers with SupportsPreview=true are included.
104
+ */
105
+ private executeProviders;
106
+ /**
107
+ * Count results contributed by each source before fusion.
108
+ */
109
+ private countSources;
110
+ /**
111
+ * Filter search results by entity-level and row-level security permissions.
112
+ *
113
+ * Groups results by entity for efficient permission checking:
114
+ * 1. Unknown entities are excluded (fail closed).
115
+ * 2. If the user lacks entity-level CanRead, all results for that entity are dropped.
116
+ * 3. If the user is exempt from RLS, all results pass through.
117
+ * 4. If RLS applies, a RunView validates which record IDs the user can read.
118
+ */
119
+ private filterByPermissions;
120
+ /**
121
+ * Group search result items by EntityName for batch permission checking.
122
+ */
123
+ private groupResultsByEntity;
124
+ /**
125
+ * Check permissions for a single entity's batch of results.
126
+ * Permitted results are pushed into the shared `permitted` array.
127
+ * On any error, results are excluded (fail closed).
128
+ */
129
+ private filterEntityResults;
130
+ /**
131
+ * Use RunView to validate which record IDs the user can see under RLS.
132
+ * Only results whose record IDs survive the RLS filter are added to `permitted`.
133
+ */
134
+ private filterByRowLevelSecurity;
135
+ /** Build an error SearchResult */
136
+ private buildErrorResult;
137
+ /** Build the list of active provider metadata for the response */
138
+ private buildProviderInfoList;
139
+ }
140
+ //# sourceMappingURL=SearchEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchEngine.d.ts","sourceRoot":"","sources":["../../src/generic/SearchEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAA4E,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC1H,OAAO,EAAE,gBAAgB,EAA0B,MAAM,+BAA+B,CAAC;AACzF,OAAO,EAAE,aAAa,EAA2B,MAAM,wBAAwB,CAAC;AAChF,OAAO,EACH,YAAY,EACZ,YAAY,EAIf,MAAM,gBAAgB,CAAC;AAMxB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAkBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,YAAa,SAAQ,aAAa,CAAC,YAAY,CAAC;;IAMzD,wDAAwD;IACxD,WAAkB,QAAQ,IAAI,YAAY,CAEzC;IAED,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,kBAAkB,CAAM;IAEhC,gEAAgE;IAChE,SAAS,KAAK,IAAI,IAAI,gBAAgB,CAErC;IAED;;;;;;;;;;OAUG;IACU,MAAM,CACf,MAAM,EAAE,kBAAuB,EAC/B,WAAW,EAAE,QAAQ,EACrB,YAAY,GAAE,OAAe,GAC9B,OAAO,CAAC,IAAI,CAAC;IA8BhB;;;;;;;;;;;;;;OAcG;IACU,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC;IA6DvF;;;;;;;;;OASG;IACU,aAAa,CACtB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAU,EACtB,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,YAAY,CAAC;IAYxB;;;OAGG;YACW,kBAAkB;IAyEhC;;;OAGG;YACW,gBAAgB;IAuC9B;;OAEG;IACH,OAAO,CAAC,YAAY;IAyBpB;;;;;;;;OAQG;YACW,mBAAmB;IAyBjC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;;;OAIG;YACW,mBAAmB;IAiDjC;;;OAGG;YACW,wBAAwB;IA2CtC,kCAAkC;IAClC,OAAO,CAAC,gBAAgB;IAYxB,kEAAkE;IAClE,OAAO,CAAC,qBAAqB;CAUhC"}
@@ -0,0 +1,446 @@
1
+ /**
2
+ * @fileoverview SearchEngine singleton - the main entry point for multi-source search.
3
+ *
4
+ * Orchestrates search providers discovered from the SearchProvider metadata entity,
5
+ * fuses results with Reciprocal Rank Fusion (RRF), applies enrichment
6
+ * (entity icons, record names, tags), and filters by minimum score.
7
+ *
8
+ * Providers are loaded via @RegisterClass(BaseSearchProvider, DriverClass) and
9
+ * instantiated using the MJ ClassFactory based on active SearchProvider records.
10
+ *
11
+ * Uses BaseSingleton from @memberjunction/global for a truly global instance.
12
+ *
13
+ * @module @memberjunction/search-engine
14
+ */
15
+ import { EntityPermissionType, LogError, LogStatus, Metadata, RunView } from '@memberjunction/core';
16
+ import { SearchEngineBase } from '@memberjunction/core-entities';
17
+ import { BaseSingleton, MJGlobal, NormalizeUUID } from '@memberjunction/global';
18
+ import { BaseSearchProvider } from './ISearchProvider.js';
19
+ import { SearchFusion } from './SearchFusion.js';
20
+ import { SearchEnricher } from './SearchEnricher.js';
21
+ import { FullTextSearchProvider } from './FullTextSearchProvider.js';
22
+ /**
23
+ * Singleton search engine that orchestrates multi-source search with RRF fusion.
24
+ *
25
+ * Providers are discovered from the MJ: Search Providers entity. Each active
26
+ * provider's DriverClass is resolved via ClassFactory to create an instance,
27
+ * which is then initialized with the provider's config from the DB.
28
+ *
29
+ * Usage:
30
+ * ```typescript
31
+ * // Initialize once at server startup
32
+ * await SearchEngine.Instance.Config({}, contextUser);
33
+ *
34
+ * // Execute searches
35
+ * const result = await SearchEngine.Instance.Search({
36
+ * Query: 'quarterly revenue',
37
+ * MaxResults: 20,
38
+ * MinScore: 0.1
39
+ * }, contextUser);
40
+ * ```
41
+ */
42
+ export class SearchEngine extends BaseSingleton {
43
+ // Constructor must be public to satisfy BaseSingleton.getInstance() constraint
44
+ constructor() {
45
+ super();
46
+ this._configured = false;
47
+ this._providerEntries = [];
48
+ this._fusion = new SearchFusion();
49
+ this._enricher = new SearchEnricher();
50
+ this._defaultMaxResults = 20;
51
+ }
52
+ /** Static accessor for the global singleton instance */
53
+ static get Instance() {
54
+ return super.getInstance();
55
+ }
56
+ /** Access the cached provider metadata from SearchEngineBase */
57
+ get Base() {
58
+ return SearchEngineBase.Instance;
59
+ }
60
+ /**
61
+ * Initialize the search engine by reading active SearchProvider records
62
+ * from SearchEngineBase (which caches them via BaseEngine) and
63
+ * instantiating each via ClassFactory.
64
+ *
65
+ * Safe to call multiple times (no-ops if already configured unless forceRefresh=true).
66
+ *
67
+ * @param config - Engine configuration options
68
+ * @param contextUser - The user context for initialization
69
+ * @param forceRefresh - If true, re-initializes even if already configured
70
+ */
71
+ async Config(config = {}, contextUser, forceRefresh = false) {
72
+ if (this._configured && !forceRefresh)
73
+ return;
74
+ this._defaultMaxResults = config.DefaultMaxResults ?? 20;
75
+ this._providerEntries = [];
76
+ // Ensure SearchEngineBase has loaded provider metadata
77
+ await this.Base.Config(forceRefresh, contextUser);
78
+ const providerRecords = this.Base.ActiveProviders;
79
+ if (providerRecords.length === 0) {
80
+ LogStatus('SearchEngine: No active search providers found in database');
81
+ this._configured = true;
82
+ return;
83
+ }
84
+ // Instantiate and initialize each provider via ClassFactory
85
+ for (const record of providerRecords) {
86
+ await this.initializeProvider(record, contextUser);
87
+ }
88
+ // Sort by priority (lower = higher priority)
89
+ this._providerEntries.sort((a, b) => a.Priority - b.Priority);
90
+ this._configured = true;
91
+ const names = this._providerEntries.map(e => e.Provider.SourceType);
92
+ LogStatus(`SearchEngine: Configured with ${this._providerEntries.length} provider(s): ${names.join(', ')}`);
93
+ }
94
+ /**
95
+ * Execute a multi-source search with RRF fusion and optional enrichment.
96
+ *
97
+ * Steps:
98
+ * 1. Run all available providers in parallel
99
+ * 2. Fuse results with RRF
100
+ * 3. Deduplicate by EntityName+RecordID
101
+ * 4. Exclude redundant entity-sourced Content Items
102
+ * 5. Apply minimum score threshold
103
+ * 6. Enrich with icons, names, and tags (skipped in preview mode)
104
+ *
105
+ * @param params - Search parameters
106
+ * @param contextUser - The user performing the search
107
+ * @returns Aggregated search result
108
+ */
109
+ async Search(params, contextUser) {
110
+ const startTime = Date.now();
111
+ try {
112
+ if (!params.Query.trim()) {
113
+ return this.buildErrorResult('Query cannot be empty', startTime);
114
+ }
115
+ // Ensure configured
116
+ if (!this._configured) {
117
+ await this.Config({}, contextUser);
118
+ }
119
+ const topK = params.MaxResults ?? this._defaultMaxResults;
120
+ const mode = params.Mode ?? 'full';
121
+ const isPreview = mode === 'preview';
122
+ // Step 1: Run all providers in parallel (respecting preview flag)
123
+ const labeledLists = await this.executeProviders(params.Query, topK, params.Filters, contextUser, isPreview);
124
+ const sourceCounts = this.countSources(labeledLists);
125
+ // Step 2: Fuse with RRF
126
+ let results = this._fusion.Fuse(labeledLists, topK);
127
+ // Step 3: Deduplicate
128
+ results = this._fusion.Deduplicate(results);
129
+ // Step 4: Exclude redundant Content Items
130
+ results = await this._enricher.ExcludeEntitySourcedContentItems(results, contextUser);
131
+ // Step 4.5: Filter by entity-level and row-level permissions
132
+ results = await this.filterByPermissions(results, contextUser);
133
+ // Step 5: Apply minimum score threshold
134
+ const scoreThreshold = params.MinScore ?? 0;
135
+ if (scoreThreshold > 0) {
136
+ results = results.filter(r => r.Score >= scoreThreshold);
137
+ }
138
+ // Step 6: Enrich (skip in preview mode)
139
+ if (!isPreview) {
140
+ await this._enricher.Enrich(results, contextUser);
141
+ }
142
+ LogStatus(`SearchEngine: Search complete in ${Date.now() - startTime}ms - ${results.length} results`);
143
+ return {
144
+ Success: true,
145
+ Results: results,
146
+ TotalCount: results.length,
147
+ ElapsedMs: Date.now() - startTime,
148
+ SourceCounts: sourceCounts,
149
+ Providers: this.buildProviderInfoList(),
150
+ };
151
+ }
152
+ catch (error) {
153
+ const msg = error instanceof Error ? error.message : String(error);
154
+ LogError(`SearchEngine: Search failed: ${msg}`);
155
+ return this.buildErrorResult(msg, startTime);
156
+ }
157
+ }
158
+ /**
159
+ * Quick preview search optimized for autocomplete / typeahead.
160
+ * Uses preview mode (no enrichment), limited to 8 results by default.
161
+ * Only runs providers that have SupportsPreview=true.
162
+ *
163
+ * @param query - The search query text
164
+ * @param maxResults - Maximum number of preview results (default: 8)
165
+ * @param contextUser - The user performing the search
166
+ * @returns Search result in preview mode
167
+ */
168
+ async PreviewSearch(query, maxResults = 8, contextUser) {
169
+ return this.Search({
170
+ Query: query,
171
+ MaxResults: maxResults,
172
+ Mode: 'preview'
173
+ }, contextUser);
174
+ }
175
+ // ────────────────────────────────────────────────────────────────
176
+ // Provider loading and initialization
177
+ // ────────────────────────────────────────────────────────────────
178
+ /**
179
+ * Instantiate a single provider from its SearchProvider metadata record,
180
+ * initialize it, check availability, and add to the active list if available.
181
+ */
182
+ async initializeProvider(record, contextUser) {
183
+ const driverClass = record.DriverClass;
184
+ try {
185
+ // Use ClassFactory to create an instance from the DriverClass key
186
+ const provider = MJGlobal.Instance.ClassFactory.CreateInstance(BaseSearchProvider, driverClass);
187
+ if (!provider) {
188
+ LogError(`SearchEngine: No registered class found for DriverClass "${driverClass}" (provider: ${record.Name})`);
189
+ return;
190
+ }
191
+ // Parse ProviderConfig JSON if present
192
+ let providerConfig = null;
193
+ if (record.ProviderConfig) {
194
+ try {
195
+ providerConfig = JSON.parse(record.ProviderConfig);
196
+ }
197
+ catch {
198
+ LogError(`SearchEngine: Invalid JSON in ProviderConfig for "${record.Name}"`);
199
+ }
200
+ }
201
+ // Build the config object from the metadata record
202
+ const config = {
203
+ Name: record.Name,
204
+ ProviderConfig: providerConfig,
205
+ CredentialID: record.CredentialID ?? null,
206
+ MaxResultsOverride: record.MaxResultsOverride ?? null,
207
+ SupportsPreview: record.SupportsPreview,
208
+ Priority: record.Priority,
209
+ };
210
+ // Initialize the provider
211
+ await provider.Initialize(config, contextUser);
212
+ // Special handling: FullTextSearchProvider needs the shared enricher
213
+ if (provider instanceof FullTextSearchProvider) {
214
+ provider.SetEnricher(this._enricher);
215
+ }
216
+ // Check availability
217
+ await provider.CheckAvailability(contextUser);
218
+ if (provider.IsAvailable()) {
219
+ this._providerEntries.push({
220
+ Provider: provider,
221
+ ID: record.ID,
222
+ DisplayName: record.DisplayName ?? record.Name,
223
+ Icon: record.Icon ?? 'fa-solid fa-circle',
224
+ Priority: record.Priority,
225
+ SupportsPreview: record.SupportsPreview,
226
+ MaxResultsOverride: record.MaxResultsOverride ?? null,
227
+ });
228
+ LogStatus(`SearchEngine: Provider "${record.Name}" (${driverClass}) enabled`);
229
+ }
230
+ else {
231
+ LogStatus(`SearchEngine: Provider "${record.Name}" (${driverClass}) not available`);
232
+ }
233
+ }
234
+ catch (error) {
235
+ const msg = error instanceof Error ? error.message : String(error);
236
+ LogError(`SearchEngine: Failed to initialize provider "${record.Name}" (${driverClass}): ${msg}`);
237
+ }
238
+ }
239
+ // ────────────────────────────────────────────────────────────────
240
+ // Search execution helpers
241
+ // ────────────────────────────────────────────────────────────────
242
+ /**
243
+ * Run all available providers in parallel and return labeled result lists.
244
+ * When isPreview is true, only providers with SupportsPreview=true are included.
245
+ */
246
+ async executeProviders(query, topK, filters, contextUser, isPreview) {
247
+ const entries = this._providerEntries.filter(e => {
248
+ if (!e.Provider.IsAvailable())
249
+ return false;
250
+ if (isPreview && !e.SupportsPreview)
251
+ return false;
252
+ return true;
253
+ });
254
+ if (entries.length === 0) {
255
+ LogStatus('SearchEngine: No providers available');
256
+ return [];
257
+ }
258
+ const promises = entries.map(async (entry) => {
259
+ try {
260
+ const providerTopK = entry.MaxResultsOverride ?? topK;
261
+ const results = await entry.Provider.Search(query, providerTopK, filters, contextUser);
262
+ // Stamp provider metadata onto each result
263
+ for (const r of results) {
264
+ r.ProviderId = entry.ID;
265
+ r.ProviderLabel = entry.DisplayName;
266
+ r.ProviderIcon = entry.Icon;
267
+ }
268
+ return { Source: entry.Provider.SourceType, Results: results };
269
+ }
270
+ catch (error) {
271
+ const msg = error instanceof Error ? error.message : String(error);
272
+ LogError(`SearchEngine: Provider "${entry.Provider.SourceType}" failed: ${msg}`);
273
+ return { Source: entry.Provider.SourceType, Results: [] };
274
+ }
275
+ });
276
+ return Promise.all(promises);
277
+ }
278
+ /**
279
+ * Count results contributed by each source before fusion.
280
+ */
281
+ countSources(lists) {
282
+ const counts = { Vector: 0, FullText: 0, Entity: 0, Storage: 0 };
283
+ for (const list of lists) {
284
+ switch (list.Source) {
285
+ case 'vector':
286
+ counts.Vector = list.Results.length;
287
+ break;
288
+ case 'fulltext':
289
+ counts.FullText = list.Results.length;
290
+ break;
291
+ case 'entity':
292
+ counts.Entity = list.Results.length;
293
+ break;
294
+ case 'storage':
295
+ counts.Storage = list.Results.length;
296
+ break;
297
+ }
298
+ }
299
+ return counts;
300
+ }
301
+ // ────────────────────────────────────────────────────────────────
302
+ // Permission filtering
303
+ // ────────────────────────────────────────────────────────────────
304
+ /**
305
+ * Filter search results by entity-level and row-level security permissions.
306
+ *
307
+ * Groups results by entity for efficient permission checking:
308
+ * 1. Unknown entities are excluded (fail closed).
309
+ * 2. If the user lacks entity-level CanRead, all results for that entity are dropped.
310
+ * 3. If the user is exempt from RLS, all results pass through.
311
+ * 4. If RLS applies, a RunView validates which record IDs the user can read.
312
+ */
313
+ async filterByPermissions(results, contextUser) {
314
+ if (results.length === 0)
315
+ return results;
316
+ // Storage file results have their own permission model (FileStorageAccountPermission)
317
+ // which is already checked by StorageSearchProvider — pass them through here
318
+ const storageResults = results.filter(r => r.ResultType === 'storage-file');
319
+ const entityResults = results.filter(r => r.ResultType !== 'storage-file');
320
+ const byEntity = this.groupResultsByEntity(entityResults);
321
+ const permitted = [...storageResults];
322
+ const promises = [];
323
+ for (const [entityName, groupResults] of byEntity) {
324
+ promises.push(this.filterEntityResults(entityName, groupResults, contextUser, permitted));
325
+ }
326
+ await Promise.all(promises);
327
+ return permitted;
328
+ }
329
+ /**
330
+ * Group search result items by EntityName for batch permission checking.
331
+ */
332
+ groupResultsByEntity(results) {
333
+ const byEntity = new Map();
334
+ for (const result of results) {
335
+ const list = byEntity.get(result.EntityName);
336
+ if (list) {
337
+ list.push(result);
338
+ }
339
+ else {
340
+ byEntity.set(result.EntityName, [result]);
341
+ }
342
+ }
343
+ return byEntity;
344
+ }
345
+ /**
346
+ * Check permissions for a single entity's batch of results.
347
+ * Permitted results are pushed into the shared `permitted` array.
348
+ * On any error, results are excluded (fail closed).
349
+ */
350
+ async filterEntityResults(entityName, entityResults, contextUser, permitted) {
351
+ try {
352
+ const md = new Metadata();
353
+ let entity = null;
354
+ try {
355
+ entity = md.EntityByName(entityName);
356
+ }
357
+ catch {
358
+ // EntityByName throws on unknown entity names — skip these results
359
+ return;
360
+ }
361
+ if (!entity)
362
+ return;
363
+ // Check entity-level read permission
364
+ const perms = entity.GetUserPermisions(contextUser);
365
+ if (!perms || !perms.CanRead)
366
+ return;
367
+ // Check RLS
368
+ if (entity.UserExemptFromRowLevelSecurity(contextUser, EntityPermissionType.Read)) {
369
+ // User exempt from RLS — all results for this entity are permitted
370
+ permitted.push(...entityResults);
371
+ return;
372
+ }
373
+ // User is NOT exempt from RLS — check if there's an actual RLS clause
374
+ const rlsClause = entity.GetUserRowLevelSecurityWhereClause(contextUser, EntityPermissionType.Read, '');
375
+ if (!rlsClause) {
376
+ // No RLS clause produced — results pass through
377
+ permitted.push(...entityResults);
378
+ return;
379
+ }
380
+ // RLS applies — validate record IDs via RunView
381
+ await this.filterByRowLevelSecurity(entity, entityResults, rlsClause, contextUser, permitted);
382
+ }
383
+ catch (error) {
384
+ // Fail closed — if anything goes wrong, exclude the results
385
+ const msg = error instanceof Error ? error.message : String(error);
386
+ LogError(`SearchEngine: Permission filtering failed for entity "${entityName}": ${msg}`);
387
+ }
388
+ }
389
+ /**
390
+ * Use RunView to validate which record IDs the user can see under RLS.
391
+ * Only results whose record IDs survive the RLS filter are added to `permitted`.
392
+ */
393
+ async filterByRowLevelSecurity(entity, entityResults, rlsClause, contextUser, permitted) {
394
+ const pkField = entity.FirstPrimaryKey;
395
+ if (!pkField) {
396
+ // Cannot verify without a primary key — exclude results
397
+ LogError(`SearchEngine: Entity "${entity.Name}" has no primary key, cannot apply RLS filter`);
398
+ return;
399
+ }
400
+ const pkFieldName = pkField.Name;
401
+ const recordIDs = entityResults.map(r => `'${r.RecordID.replace(/'/g, "''")}'`).join(',');
402
+ const filter = `${pkFieldName} IN (${recordIDs}) AND (${rlsClause})`;
403
+ const rv = new RunView();
404
+ const result = await rv.RunView({
405
+ EntityName: entity.Name,
406
+ ExtraFilter: filter,
407
+ Fields: [pkFieldName],
408
+ ResultType: 'simple'
409
+ }, contextUser);
410
+ if (!result.Success) {
411
+ // RunView failed — fail closed, exclude all results
412
+ LogError(`SearchEngine: RLS RunView failed for entity "${entity.Name}": ${result.ErrorMessage ?? 'unknown error'}`);
413
+ return;
414
+ }
415
+ const validIDs = new Set(result.Results.map(r => NormalizeUUID(String(r[pkFieldName]))));
416
+ for (const item of entityResults) {
417
+ if (validIDs.has(NormalizeUUID(item.RecordID))) {
418
+ permitted.push(item);
419
+ }
420
+ }
421
+ }
422
+ /** Build an error SearchResult */
423
+ buildErrorResult(message, startTime) {
424
+ return {
425
+ Success: false,
426
+ Results: [],
427
+ TotalCount: 0,
428
+ ElapsedMs: Date.now() - startTime,
429
+ SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },
430
+ Providers: [],
431
+ ErrorMessage: message,
432
+ };
433
+ }
434
+ /** Build the list of active provider metadata for the response */
435
+ buildProviderInfoList() {
436
+ return this._providerEntries.map(e => ({
437
+ ID: e.ID,
438
+ Name: e.DisplayName,
439
+ DisplayName: e.DisplayName,
440
+ Icon: e.Icon,
441
+ SourceType: e.Provider.SourceType,
442
+ Priority: e.Priority,
443
+ }));
444
+ }
445
+ }
446
+ //# sourceMappingURL=SearchEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchEngine.js","sourceRoot":"","sources":["../../src/generic/SearchEngine.ts"],"names":[],"mappings":"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"}