@memberjunction/search-engine 0.0.1 → 5.26.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,322 @@
1
+ /**
2
+ * @fileoverview Storage search provider for file storage accounts.
3
+ *
4
+ * Searches files across MJ File Storage accounts that have
5
+ * IncludeInGlobalSearch=true, using each provider's native search API.
6
+ * Results are permission-checked against FileStorageAccountPermission
7
+ * before being returned.
8
+ *
9
+ * @module @memberjunction/search-engine
10
+ */
11
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
12
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
13
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
14
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
15
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
16
+ };
17
+ import { LogError, LogStatus, RunView } from '@memberjunction/core';
18
+ import { NormalizeUUID, RegisterClass } from '@memberjunction/global';
19
+ import { FileStorageEngine } from '@memberjunction/storage';
20
+ import { BaseSearchProvider } from './ISearchProvider.js';
21
+ /**
22
+ * Provides file-level search across MJ File Storage accounts using each
23
+ * provider's native SearchFiles() API. Only accounts with
24
+ * IncludeInGlobalSearch=true and providers with SupportsSearch=true are searched.
25
+ *
26
+ * Permission checking is performed against FileStorageAccountPermission records.
27
+ * If no permission records exist for an account, it is accessible to everyone
28
+ * (backwards compatible). Otherwise, the user must have CanRead=true via a
29
+ * direct User permission, a Role permission, or an Everyone permission.
30
+ */
31
+ let StorageSearchProvider = class StorageSearchProvider extends BaseSearchProvider {
32
+ constructor() {
33
+ super(...arguments);
34
+ this.SourceType = 'storage';
35
+ this._available = false;
36
+ this._searchableAccounts = [];
37
+ this._permissions = [];
38
+ }
39
+ /**
40
+ * Whether this provider has at least one searchable storage account.
41
+ */
42
+ IsAvailable() {
43
+ return this._available;
44
+ }
45
+ /**
46
+ * Load storage accounts and permissions to determine availability.
47
+ * Must be called once during SearchEngine.Config().
48
+ *
49
+ * @param contextUser - The user context for database queries
50
+ */
51
+ async CheckAvailability(contextUser) {
52
+ try {
53
+ // Use cached accounts and providers from the engine — no RunView needed for these
54
+ await FileStorageEngine.Instance.Config(false, contextUser);
55
+ const allAccounts = FileStorageEngine.Instance.Accounts;
56
+ const allProviders = FileStorageEngine.Instance.Providers;
57
+ // Filter to accounts with IncludeInGlobalSearch and active providers that support search
58
+ const accounts = allAccounts.filter(a => a.Get('IncludeInGlobalSearch') === true);
59
+ const providers = allProviders.filter(p => p.IsActive && p.Get('SupportsSearch') === true);
60
+ // Permissions are user-context-dependent, so we still need a RunView for these
61
+ const rv = new RunView();
62
+ const permissionsResult = await rv.RunView({
63
+ EntityName: 'MJ: File Storage Account Permissions',
64
+ ResultType: 'entity_object'
65
+ }, contextUser);
66
+ this._permissions = permissionsResult.Success
67
+ ? permissionsResult.Results
68
+ : [];
69
+ // Build a set of provider IDs that support search
70
+ const searchProviderIDs = new Set(providers.map(p => NormalizeUUID(p.ID)));
71
+ // Filter accounts to those whose provider supports search
72
+ this._searchableAccounts = [];
73
+ for (const account of accounts) {
74
+ if (searchProviderIDs.has(NormalizeUUID(account.ProviderID))) {
75
+ const provider = providers.find(p => NormalizeUUID(p.ID) === NormalizeUUID(account.ProviderID));
76
+ if (provider) {
77
+ this._searchableAccounts.push({ Account: account, Provider: provider });
78
+ }
79
+ }
80
+ }
81
+ this._available = this._searchableAccounts.length > 0;
82
+ LogStatus(`StorageSearchProvider: Found ${this._searchableAccounts.length} searchable storage account(s)`);
83
+ }
84
+ catch (error) {
85
+ const msg = error instanceof Error ? error.message : String(error);
86
+ LogError(`StorageSearchProvider: CheckAvailability failed: ${msg}`);
87
+ this._available = false;
88
+ }
89
+ }
90
+ /**
91
+ * Execute a file search across all eligible storage accounts.
92
+ *
93
+ * @param query - The search query text
94
+ * @param topK - Maximum number of results to retrieve
95
+ * @param _filters - Optional filters (currently unused for storage search)
96
+ * @param contextUser - The user performing the search
97
+ * @returns Scored result items from storage search
98
+ */
99
+ async Search(query, topK, _filters, contextUser) {
100
+ if (!this._available || this._searchableAccounts.length === 0) {
101
+ return [];
102
+ }
103
+ const startTime = Date.now();
104
+ // Filter accounts by user permissions
105
+ const accessibleAccounts = this.filterAccountsByPermissions(this._searchableAccounts, contextUser);
106
+ if (accessibleAccounts.length === 0) {
107
+ LogStatus('StorageSearchProvider: User has no access to any searchable storage accounts');
108
+ return [];
109
+ }
110
+ // Distribute topK across accounts
111
+ const perAccountLimit = Math.max(3, Math.ceil(topK / accessibleAccounts.length));
112
+ // Search all accounts in parallel
113
+ const searchPromises = accessibleAccounts.map(entry => this.searchOneAccount(entry, query, perAccountLimit, contextUser));
114
+ const results = await Promise.all(searchPromises);
115
+ const allResults = results.flat();
116
+ // Sort by score descending and limit to topK
117
+ allResults.sort((a, b) => b.Score - a.Score);
118
+ const trimmed = allResults.slice(0, topK);
119
+ LogStatus(`StorageSearchProvider: Search complete in ${Date.now() - startTime}ms - ` +
120
+ `${trimmed.length} results from ${accessibleAccounts.length} account(s)`);
121
+ return trimmed;
122
+ }
123
+ /**
124
+ * Filter storage accounts by checking FileStorageAccountPermission records
125
+ * for the given user. If an account has no permission records, it is
126
+ * accessible to everyone (backwards compatible).
127
+ */
128
+ filterAccountsByPermissions(accounts, contextUser) {
129
+ const normalizedUserID = NormalizeUUID(contextUser.ID);
130
+ const userRoleIDs = new Set((contextUser.UserRoles ?? []).map(r => NormalizeUUID(r.RoleID)));
131
+ return accounts.filter(entry => {
132
+ const accountPerms = this._permissions.filter(p => NormalizeUUID(p.FileStorageAccountID) === NormalizeUUID(entry.Account.ID));
133
+ // No permission records means open access (backwards compatible)
134
+ if (accountPerms.length === 0) {
135
+ return true;
136
+ }
137
+ // Check if user has CanRead through any permission path
138
+ return accountPerms.some(perm => {
139
+ if (!perm.CanRead)
140
+ return false;
141
+ switch (perm.Type) {
142
+ case 'Everyone':
143
+ return true;
144
+ case 'User':
145
+ return perm.UserID != null &&
146
+ NormalizeUUID(perm.UserID) === normalizedUserID;
147
+ case 'Role':
148
+ return perm.RoleID != null &&
149
+ userRoleIDs.has(NormalizeUUID(perm.RoleID));
150
+ default:
151
+ return false;
152
+ }
153
+ });
154
+ });
155
+ }
156
+ /**
157
+ * Search a single storage account using the provider's native SearchFiles API.
158
+ */
159
+ async searchOneAccount(entry, query, maxResults, contextUser) {
160
+ try {
161
+ const driver = await FileStorageEngine.Instance.GetDriver(entry.Account.ID, contextUser);
162
+ if (!driver.IsConfigured) {
163
+ LogError(`StorageSearchProvider: Driver for account "${entry.Account.Name}" ` +
164
+ `is not configured, skipping`);
165
+ return [];
166
+ }
167
+ const searchOptions = {
168
+ maxResults,
169
+ searchContent: true
170
+ };
171
+ const resultSet = await driver.SearchFiles(query, searchOptions);
172
+ return this.convertResults(resultSet.results, entry.Account, query);
173
+ }
174
+ catch (error) {
175
+ const msg = error instanceof Error ? error.message : String(error);
176
+ LogError(`StorageSearchProvider: Error searching account "${entry.Account.Name}": ${msg}`);
177
+ return [];
178
+ }
179
+ }
180
+ /**
181
+ * Convert FileSearchResult items to SearchResultItem format with relevance scores.
182
+ */
183
+ convertResults(files, account, query) {
184
+ return files.map((file, index) => {
185
+ const score = this.calculateScore(file, query, index, files.length);
186
+ const snippet = this.buildSnippet(file, account);
187
+ return {
188
+ ID: `storage-${NormalizeUUID(account.ID)}-${file.objectId ?? file.path}`,
189
+ EntityName: account.Name,
190
+ RecordID: file.objectId ?? file.path,
191
+ SourceType: 'storage',
192
+ ResultType: 'storage-file',
193
+ Title: file.name,
194
+ Snippet: snippet,
195
+ Score: Math.round(score * 100) / 100,
196
+ ScoreBreakdown: { Storage: Math.round(score * 100) / 100 },
197
+ Tags: this.buildTags(file, account),
198
+ EntityIcon: this.getFileIcon(file),
199
+ RecordName: file.name,
200
+ MatchedAt: file.lastModified ?? new Date(),
201
+ RawMetadata: JSON.stringify({
202
+ accountId: account.ID,
203
+ accountName: account.Name,
204
+ path: file.path,
205
+ size: file.size,
206
+ contentType: file.contentType,
207
+ objectId: file.objectId
208
+ })
209
+ };
210
+ });
211
+ }
212
+ /**
213
+ * Calculate a relevance score for a file search result.
214
+ * Uses the provider's relevance score if available, otherwise
215
+ * falls back to a rank-based score with name-match boosting.
216
+ */
217
+ calculateScore(file, query, rankIndex, totalResults) {
218
+ // If the provider gave us a relevance score, use it (normalized to 0-0.95 range)
219
+ if (file.relevance != null && file.relevance > 0) {
220
+ return Math.min(file.relevance * 0.95, 0.95);
221
+ }
222
+ // Rank-based fallback: higher rank = higher score
223
+ const rankScore = totalResults > 1
224
+ ? 0.3 + (0.35 * (1 - rankIndex / totalResults))
225
+ : 0.5;
226
+ // Boost if query appears in the filename
227
+ const queryLower = query.toLowerCase();
228
+ const nameMatch = file.name.toLowerCase().includes(queryLower);
229
+ const nameBoost = nameMatch ? 0.25 : 0;
230
+ return Math.min(rankScore + nameBoost, 0.95);
231
+ }
232
+ /**
233
+ * Build a display snippet for a file search result.
234
+ */
235
+ buildSnippet(file, account) {
236
+ const parts = [];
237
+ // If provider returned a content excerpt, lead with that
238
+ if (file.excerpt) {
239
+ const cleanExcerpt = file.excerpt.replace(/<[^>]*>/g, '').trim();
240
+ if (cleanExcerpt.length > 0) {
241
+ parts.push(cleanExcerpt.length > 150 ? cleanExcerpt.substring(0, 150) + '...' : cleanExcerpt);
242
+ }
243
+ }
244
+ // Build metadata line: provider · path · size · modified
245
+ const meta = [];
246
+ meta.push(account.Name);
247
+ if (file.path && file.path !== file.name) {
248
+ // Show folder path without the filename
249
+ const folder = file.path.substring(0, file.path.lastIndexOf('/')) || '/';
250
+ meta.push(folder);
251
+ }
252
+ if (file.size > 0) {
253
+ meta.push(this.formatFileSize(file.size));
254
+ }
255
+ if (file.contentType) {
256
+ meta.push(file.contentType);
257
+ }
258
+ parts.push(meta.join(' · '));
259
+ return parts.join('\n');
260
+ }
261
+ /**
262
+ * Build tags for a file search result.
263
+ */
264
+ buildTags(_file, _account) {
265
+ // Storage files don't use MJ tags — file type and provider are shown
266
+ // in dedicated filter sections (File Type, Source) instead
267
+ return [];
268
+ }
269
+ /**
270
+ * Get an appropriate Font Awesome icon for a file based on its extension/content type.
271
+ */
272
+ getFileIcon(file) {
273
+ const ext = file.name.split('.').pop()?.toLowerCase() ?? '';
274
+ const type = file.contentType?.toLowerCase() ?? '';
275
+ // PDFs
276
+ if (ext === 'pdf' || type.includes('pdf'))
277
+ return 'fa-solid fa-file-pdf';
278
+ // Images
279
+ if (['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp'].includes(ext) || type.startsWith('image/'))
280
+ return 'fa-solid fa-file-image';
281
+ // Spreadsheets
282
+ if (['xlsx', 'xls', 'csv', 'tsv'].includes(ext) || type.includes('spreadsheet'))
283
+ return 'fa-solid fa-file-excel';
284
+ // Documents
285
+ if (['docx', 'doc', 'rtf', 'odt'].includes(ext) || type.includes('document') || type.includes('msword'))
286
+ return 'fa-solid fa-file-word';
287
+ // Presentations
288
+ if (['pptx', 'ppt', 'odp'].includes(ext) || type.includes('presentation'))
289
+ return 'fa-solid fa-file-powerpoint';
290
+ // Code / text
291
+ if (['txt', 'md', 'json', 'xml', 'yaml', 'yml', 'html', 'css', 'js', 'ts'].includes(ext) || type.startsWith('text/'))
292
+ return 'fa-solid fa-file-code';
293
+ // Archives
294
+ if (['zip', 'tar', 'gz', 'rar', '7z'].includes(ext) || type.includes('archive') || type.includes('zip'))
295
+ return 'fa-solid fa-file-zipper';
296
+ // Audio
297
+ if (['mp3', 'wav', 'ogg', 'flac', 'aac'].includes(ext) || type.startsWith('audio/'))
298
+ return 'fa-solid fa-file-audio';
299
+ // Video
300
+ if (['mp4', 'avi', 'mov', 'mkv', 'webm'].includes(ext) || type.startsWith('video/'))
301
+ return 'fa-solid fa-file-video';
302
+ // Default
303
+ return 'fa-solid fa-file';
304
+ }
305
+ /**
306
+ * Format a file size in bytes to a human-readable string.
307
+ */
308
+ formatFileSize(bytes) {
309
+ if (bytes < 1024)
310
+ return `${bytes} B`;
311
+ if (bytes < 1024 * 1024)
312
+ return `${(bytes / 1024).toFixed(1)} KB`;
313
+ if (bytes < 1024 * 1024 * 1024)
314
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
315
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
316
+ }
317
+ };
318
+ StorageSearchProvider = __decorate([
319
+ RegisterClass(BaseSearchProvider, 'StorageSearchProvider')
320
+ ], StorageSearchProvider);
321
+ export { StorageSearchProvider };
322
+ //# sourceMappingURL=StorageSearchProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageSearchProvider.js","sourceRoot":"","sources":["../../src/generic/StorageSearchProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @fileoverview Vector similarity search provider for the SearchEngine.
3
+ *
4
+ * Searches ALL configured vector indexes by grouping them by embedding model,
5
+ * embedding the query once per model, then querying all indexes for that model
6
+ * in parallel. Gracefully returns empty results when no vector indexes are
7
+ * configured.
8
+ *
9
+ * @module @memberjunction/search-engine
10
+ */
11
+ import { UserInfo } from '@memberjunction/core';
12
+ import { BaseSearchProvider } from './ISearchProvider.js';
13
+ import { SearchSource, SearchFilters, SearchResultItem } from './search.types.js';
14
+ export declare class VectorSearchProvider extends BaseSearchProvider {
15
+ readonly SourceType: SearchSource;
16
+ private available;
17
+ /** LRU cache for query embeddings. Key = `${modelDriverClass}::${query}`, Value = embedding vector */
18
+ private static EmbeddingCache;
19
+ private static readonly CACHE_MAX_SIZE;
20
+ private static readonly CACHE_TTL_MS;
21
+ /**
22
+ * Check and cache availability. Requires at least one vector index to be
23
+ * configured. Called by SearchEngine during Config().
24
+ */
25
+ CheckAvailability(contextUser: UserInfo): Promise<void>;
26
+ IsAvailable(): boolean;
27
+ /**
28
+ * Search all vector indexes. Groups indexes by embedding model, embeds the
29
+ * query once per model, then queries all indexes for that model in parallel.
30
+ */
31
+ Search(query: string, topK: number, filters: SearchFilters | undefined, contextUser: UserInfo): Promise<SearchResultItem[]>;
32
+ /** Retrieve a cached embedding if present and not expired, promoting it for LRU */
33
+ private getCachedEmbedding;
34
+ /** Store an embedding in the cache, evicting the oldest entry if at capacity */
35
+ private setCachedEmbedding;
36
+ /** Group vector indexes by their EmbeddingModelID */
37
+ private groupIndexesByModel;
38
+ /**
39
+ * Embed query with one model, then query all indexes for that model in parallel.
40
+ */
41
+ private embedAndQueryGroup;
42
+ /**
43
+ * Query a single vector index.
44
+ */
45
+ private queryOneIndex;
46
+ /** Convert vector DB matches to SearchResultItem[] */
47
+ private convertMatches;
48
+ /**
49
+ * Extract best display title from vector metadata using entity field metadata.
50
+ * Combines all IsNameField fields in Sequence order.
51
+ */
52
+ private extractDisplayTitle;
53
+ /** Extract best display snippet from vector metadata */
54
+ private extractDisplaySnippet;
55
+ /** Build metadata filter from SearchFilters for vector DB queries */
56
+ private buildMetadataFilter;
57
+ /**
58
+ * Extract a plain record ID from a CompositeKey URL segment string.
59
+ * Vector metadata stores RecordID in format "FieldName|Value" or "F1|V1||F2|V2".
60
+ * For deduplication with entity search results, we need just the value(s).
61
+ * Uses CompositeKey.SimpleLoadFromURLSegment for proper multi-field parsing.
62
+ */
63
+ private extractRecordIDFromCompositeKey;
64
+ }
65
+ //# sourceMappingURL=VectorSearchProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorSearchProvider.d.ts","sourceRoot":"","sources":["../../src/generic/VectorSearchProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAA0C,QAAQ,EAAgB,MAAM,sBAAsB,CAAC;AAMtG,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAoB,MAAM,gBAAgB,CAAC;AAYjG,qBACa,oBAAqB,SAAQ,kBAAkB;IACxD,SAAgB,UAAU,EAAE,YAAY,CAAY;IAEpD,OAAO,CAAC,SAAS,CAAS;IAE1B,sGAAsG;IACtG,OAAO,CAAC,MAAM,CAAC,cAAc,CAA0C;IACvE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAO;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAErD;;;OAGG;IACU,iBAAiB,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAe7D,WAAW,IAAI,OAAO;IAI7B;;;OAGG;IACU,MAAM,CACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqC9B,mFAAmF;IACnF,OAAO,CAAC,kBAAkB;IAa1B,gFAAgF;IAChF,OAAO,CAAC,kBAAkB;IAa1B,qDAAqD;IACrD,OAAO,CAAC,mBAAmB;IAc3B;;OAEG;YACW,kBAAkB;IAyDhC;;OAEG;YACW,aAAa;IA6C3B,sDAAsD;IACtD,OAAO,CAAC,cAAc;IAwCtB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA+B3B,wDAAwD;IACxD,OAAO,CAAC,qBAAqB;IAwB7B,qEAAqE;IACrE,OAAO,CAAC,mBAAmB;IAmB3B;;;;;OAKG;IACH,OAAO,CAAC,+BAA+B;CAoB1C"}