@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.
- package/dist/generic/EntitySearchProvider.d.ts +54 -0
- package/dist/generic/EntitySearchProvider.d.ts.map +1 -0
- package/dist/generic/EntitySearchProvider.js +207 -0
- package/dist/generic/EntitySearchProvider.js.map +1 -0
- package/dist/generic/FullTextSearchProvider.d.ts +35 -0
- package/dist/generic/FullTextSearchProvider.d.ts.map +1 -0
- package/dist/generic/FullTextSearchProvider.js +89 -0
- package/dist/generic/FullTextSearchProvider.js.map +1 -0
- package/dist/generic/ISearchProvider.d.ts +90 -0
- package/dist/generic/ISearchProvider.d.ts.map +1 -0
- package/dist/generic/ISearchProvider.js +62 -0
- package/dist/generic/ISearchProvider.js.map +1 -0
- package/dist/generic/SearchEngine.d.ts +140 -0
- package/dist/generic/SearchEngine.d.ts.map +1 -0
- package/dist/generic/SearchEngine.js +446 -0
- package/dist/generic/SearchEngine.js.map +1 -0
- package/dist/generic/SearchEnricher.d.ts +69 -0
- package/dist/generic/SearchEnricher.d.ts.map +1 -0
- package/dist/generic/SearchEnricher.js +241 -0
- package/dist/generic/SearchEnricher.js.map +1 -0
- package/dist/generic/SearchFusion.d.ts +62 -0
- package/dist/generic/SearchFusion.d.ts.map +1 -0
- package/dist/generic/SearchFusion.js +147 -0
- package/dist/generic/SearchFusion.js.map +1 -0
- package/dist/generic/StorageSearchProvider.d.ts +87 -0
- package/dist/generic/StorageSearchProvider.d.ts.map +1 -0
- package/dist/generic/StorageSearchProvider.js +322 -0
- package/dist/generic/StorageSearchProvider.js.map +1 -0
- package/dist/generic/VectorSearchProvider.d.ts +65 -0
- package/dist/generic/VectorSearchProvider.d.ts.map +1 -0
- package/dist/generic/VectorSearchProvider.js +347 -0
- package/dist/generic/VectorSearchProvider.js.map +1 -0
- package/dist/generic/search.types.d.ts +151 -0
- package/dist/generic/search.types.d.ts.map +1 -0
- package/dist/generic/search.types.js +11 -0
- package/dist/generic/search.types.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -7
- 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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"}
|