@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,69 @@
1
+ /**
2
+ * @fileoverview Enrichment logic for search results.
3
+ *
4
+ * Adds entity icons, record names, and tags to search results.
5
+ * Skippable for preview mode where speed is more important than enrichment.
6
+ *
7
+ * @module @memberjunction/search-engine
8
+ */
9
+ import { UserInfo } from '@memberjunction/core';
10
+ import { SearchResultItem } from './search.types.js';
11
+ /**
12
+ * Enriches search results with entity icons, record names, and tags.
13
+ * Also handles filtering of redundant Content Item results that originate
14
+ * from entity-type content sources.
15
+ */
16
+ export declare class SearchEnricher {
17
+ /**
18
+ * Apply full enrichment to search results: entity icons, record names, and tags.
19
+ *
20
+ * @param results - The search results to enrich (mutated in place)
21
+ * @param contextUser - The user performing the search
22
+ */
23
+ Enrich(results: SearchResultItem[], contextUser: UserInfo): Promise<void>;
24
+ /**
25
+ * Enrich results with tags from both TaggedItems and ContentItemTags entities.
26
+ *
27
+ * @param results - The search results to enrich (mutated in place)
28
+ * @param contextUser - The user performing the search
29
+ */
30
+ EnrichWithTags(results: SearchResultItem[], contextUser: UserInfo): Promise<void>;
31
+ /**
32
+ * Filter results to only include those with at least one tag matching the specified list.
33
+ * Uses case-insensitive comparison.
34
+ */
35
+ FilterByTags(results: SearchResultItem[], requiredTags: string[]): SearchResultItem[];
36
+ /**
37
+ * Remove Content Item results that originated from Entity-type content sources.
38
+ * These are redundant because the underlying entity records are already
39
+ * vectorized and searchable directly.
40
+ *
41
+ * @param results - All search results
42
+ * @param contextUser - The user performing the search
43
+ * @returns Filtered results without redundant Content Item entries
44
+ */
45
+ ExcludeEntitySourcedContentItems(results: SearchResultItem[], contextUser: UserInfo): Promise<SearchResultItem[]>;
46
+ /**
47
+ * Add entity icons for results that don't already have them from vector metadata.
48
+ */
49
+ private addEntityIcons;
50
+ /**
51
+ * Resolve record names for results that don't already have them.
52
+ * Vector results from enriched metadata should already have names;
53
+ * this handles FTS and entity results.
54
+ */
55
+ private resolveRecordNames;
56
+ /**
57
+ * Build EntityRecordNameInput entries for batch name resolution.
58
+ */
59
+ private buildNameInputs;
60
+ /**
61
+ * Load tags from the TaggedItems entity for non-Content-Item results.
62
+ */
63
+ private loadTaggedItemTags;
64
+ /**
65
+ * Load tags from the ContentItemTag entity for Content Item results.
66
+ */
67
+ private loadContentItemTags;
68
+ }
69
+ //# sourceMappingURL=SearchEnricher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchEnricher.d.ts","sourceRoot":"","sources":["../../src/generic/SearchEnricher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAIH,QAAQ,EAGX,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;GAIG;AACH,qBAAa,cAAc;IACvB;;;;;OAKG;IACU,MAAM,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtF;;;;;OAKG;IACU,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9F;;;OAGG;IACI,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,gBAAgB,EAAE;IAO5F;;;;;;;;OAQG;IACU,gCAAgC,CACzC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAmC9B;;OAEG;IACH,OAAO,CAAC,cAAc;IAWtB;;;;OAIG;YACW,kBAAkB;IAgChC;;OAEG;IACH,OAAO,CAAC,eAAe;IAwBvB;;OAEG;YACW,kBAAkB;IAoDhC;;OAEG;YACW,mBAAmB;CA4BpC"}
@@ -0,0 +1,241 @@
1
+ /**
2
+ * @fileoverview Enrichment logic for search results.
3
+ *
4
+ * Adds entity icons, record names, and tags to search results.
5
+ * Skippable for preview mode where speed is more important than enrichment.
6
+ *
7
+ * @module @memberjunction/search-engine
8
+ */
9
+ import { LogError, Metadata, RunView, EntityRecordNameInput, CompositeKey } from '@memberjunction/core';
10
+ import { UUIDsEqual } from '@memberjunction/global';
11
+ import { KnowledgeHubMetadataEngine } from '@memberjunction/core-entities';
12
+ /**
13
+ * Enriches search results with entity icons, record names, and tags.
14
+ * Also handles filtering of redundant Content Item results that originate
15
+ * from entity-type content sources.
16
+ */
17
+ export class SearchEnricher {
18
+ /**
19
+ * Apply full enrichment to search results: entity icons, record names, and tags.
20
+ *
21
+ * @param results - The search results to enrich (mutated in place)
22
+ * @param contextUser - The user performing the search
23
+ */
24
+ async Enrich(results, contextUser) {
25
+ if (results.length === 0)
26
+ return;
27
+ const md = new Metadata();
28
+ this.addEntityIcons(results, md);
29
+ await this.resolveRecordNames(results, md, contextUser);
30
+ }
31
+ /**
32
+ * Enrich results with tags from both TaggedItems and ContentItemTags entities.
33
+ *
34
+ * @param results - The search results to enrich (mutated in place)
35
+ * @param contextUser - The user performing the search
36
+ */
37
+ async EnrichWithTags(results, contextUser) {
38
+ if (results.length === 0)
39
+ return;
40
+ try {
41
+ const contentItemResults = results.filter(r => r.EntityName === 'MJ: Content Items');
42
+ const generalResults = results.filter(r => r.EntityName !== 'MJ: Content Items');
43
+ await Promise.all([
44
+ this.loadTaggedItemTags(generalResults, contextUser),
45
+ this.loadContentItemTags(contentItemResults, contextUser)
46
+ ]);
47
+ }
48
+ catch (error) {
49
+ LogError(`SearchEnricher: Error enriching results with tags: ${error}`);
50
+ // Non-fatal - results still usable without tags
51
+ }
52
+ }
53
+ /**
54
+ * Filter results to only include those with at least one tag matching the specified list.
55
+ * Uses case-insensitive comparison.
56
+ */
57
+ FilterByTags(results, requiredTags) {
58
+ const lowerTags = new Set(requiredTags.map(t => t.toLowerCase()));
59
+ return results.filter(r => r.Tags.some(t => lowerTags.has(t.toLowerCase())));
60
+ }
61
+ /**
62
+ * Remove Content Item results that originated from Entity-type content sources.
63
+ * These are redundant because the underlying entity records are already
64
+ * vectorized and searchable directly.
65
+ *
66
+ * @param results - All search results
67
+ * @param contextUser - The user performing the search
68
+ * @returns Filtered results without redundant Content Item entries
69
+ */
70
+ async ExcludeEntitySourcedContentItems(results, contextUser) {
71
+ const contentItemResults = results.filter(r => r.EntityName === 'MJ: Content Items');
72
+ if (contentItemResults.length === 0)
73
+ return results;
74
+ await KnowledgeHubMetadataEngine.Instance.Config(false, contextUser);
75
+ const engine = KnowledgeHubMetadataEngine.Instance;
76
+ const entitySourceType = engine.ContentSourceTypes.find(st => st.Name === 'Entity');
77
+ if (!entitySourceType)
78
+ return results;
79
+ const entitySourceIDs = new Set(engine.ContentSources
80
+ .filter(cs => UUIDsEqual(cs.ContentSourceTypeID, entitySourceType.ID))
81
+ .map(cs => cs.ID.toLowerCase()));
82
+ if (entitySourceIDs.size === 0)
83
+ return results;
84
+ return results.filter(r => {
85
+ if (r.EntityName !== 'MJ: Content Items')
86
+ return true;
87
+ if (!r.RawMetadata)
88
+ return true;
89
+ try {
90
+ const meta = JSON.parse(r.RawMetadata);
91
+ const sourceID = meta.ContentSourceID;
92
+ if (!sourceID)
93
+ return true;
94
+ return !entitySourceIDs.has(sourceID.toLowerCase());
95
+ }
96
+ catch {
97
+ return true;
98
+ }
99
+ });
100
+ }
101
+ // ────────────────────────────────────────────────────────────────
102
+ // Private helpers
103
+ // ────────────────────────────────────────────────────────────────
104
+ /**
105
+ * Add entity icons for results that don't already have them from vector metadata.
106
+ */
107
+ addEntityIcons(results, md) {
108
+ for (const result of results) {
109
+ if (!result.EntityIcon) {
110
+ const entity = md.Entities.find(e => e.Name === result.EntityName);
111
+ if (entity?.Icon) {
112
+ result.EntityIcon = entity.Icon;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ /**
118
+ * Resolve record names for results that don't already have them.
119
+ * Vector results from enriched metadata should already have names;
120
+ * this handles FTS and entity results.
121
+ */
122
+ async resolveRecordNames(results, md, contextUser) {
123
+ const needsName = results.filter(r => !r.RecordName || r.RecordName === `${r.EntityName} Record`);
124
+ if (needsName.length === 0)
125
+ return;
126
+ try {
127
+ const indexedInputs = this.buildNameInputs(needsName, results, md);
128
+ if (indexedInputs.length === 0)
129
+ return;
130
+ const names = await md.GetEntityRecordNames(indexedInputs.map(ir => ir.Input), contextUser);
131
+ for (let i = 0; i < names.length; i++) {
132
+ if (names[i].RecordName) {
133
+ const resultIndex = indexedInputs[i].ResultIndex;
134
+ results[resultIndex].RecordName = names[i].RecordName;
135
+ results[resultIndex].Title = names[i].RecordName;
136
+ }
137
+ }
138
+ }
139
+ catch (error) {
140
+ LogError(`SearchEnricher: Error resolving record names: ${error}`);
141
+ // Non-fatal - results still usable without names
142
+ }
143
+ }
144
+ /**
145
+ * Build EntityRecordNameInput entries for batch name resolution.
146
+ */
147
+ buildNameInputs(needsName, allResults, md) {
148
+ const indexed = [];
149
+ for (const r of needsName) {
150
+ const resultIndex = allResults.indexOf(r);
151
+ const entity = md.Entities.find(e => e.Name === r.EntityName);
152
+ if (!entity)
153
+ continue;
154
+ const key = new CompositeKey();
155
+ key.LoadFromURLSegment(entity, r.RecordID);
156
+ const input = new EntityRecordNameInput();
157
+ input.EntityName = r.EntityName;
158
+ input.CompositeKey = key;
159
+ indexed.push({ ResultIndex: resultIndex, Input: input });
160
+ }
161
+ return indexed;
162
+ }
163
+ /**
164
+ * Load tags from the TaggedItems entity for non-Content-Item results.
165
+ */
166
+ async loadTaggedItemTags(results, contextUser) {
167
+ if (results.length === 0)
168
+ return;
169
+ const md = new Metadata();
170
+ const entityIdMap = new Map();
171
+ for (const r of results) {
172
+ if (!entityIdMap.has(r.EntityName)) {
173
+ const entityInfo = md.Entities.find(e => e.Name === r.EntityName);
174
+ if (entityInfo) {
175
+ entityIdMap.set(r.EntityName, entityInfo.ID);
176
+ }
177
+ }
178
+ }
179
+ if (entityIdMap.size === 0)
180
+ return;
181
+ const conditions = [];
182
+ for (const r of results) {
183
+ const entityID = entityIdMap.get(r.EntityName);
184
+ if (!entityID)
185
+ continue;
186
+ conditions.push(`(EntityID='${entityID}' AND RecordID='${r.RecordID}')`);
187
+ }
188
+ if (conditions.length === 0)
189
+ return;
190
+ const rv = new RunView();
191
+ const tagResult = await rv.RunView({
192
+ EntityName: 'MJ: Tagged Items',
193
+ ExtraFilter: conditions.join(' OR '),
194
+ Fields: ['EntityID', 'RecordID', 'Tag'],
195
+ ResultType: 'simple'
196
+ }, contextUser);
197
+ if (!tagResult.Success)
198
+ return;
199
+ const tagMap = new Map();
200
+ for (const ti of tagResult.Results) {
201
+ const key = `${ti.EntityID}::${ti.RecordID}`;
202
+ const tags = tagMap.get(key) ?? [];
203
+ tags.push(ti.Tag);
204
+ tagMap.set(key, tags);
205
+ }
206
+ for (const r of results) {
207
+ const entityID = entityIdMap.get(r.EntityName);
208
+ if (!entityID)
209
+ continue;
210
+ const key = `${entityID}::${r.RecordID}`;
211
+ r.Tags = tagMap.get(key) ?? [];
212
+ }
213
+ }
214
+ /**
215
+ * Load tags from the ContentItemTag entity for Content Item results.
216
+ */
217
+ async loadContentItemTags(results, contextUser) {
218
+ if (results.length === 0)
219
+ return;
220
+ const recordIDs = results.map(r => `'${r.RecordID}'`);
221
+ const rv = new RunView();
222
+ const tagResult = await rv.RunView({
223
+ EntityName: 'MJ: Content Item Tags',
224
+ ExtraFilter: `ItemID IN (${recordIDs.join(',')})`,
225
+ Fields: ['ItemID', 'Tag'],
226
+ ResultType: 'simple'
227
+ }, contextUser);
228
+ if (!tagResult.Success)
229
+ return;
230
+ const tagMap = new Map();
231
+ for (const ti of tagResult.Results) {
232
+ const tags = tagMap.get(ti.ItemID) ?? [];
233
+ tags.push(ti.Tag);
234
+ tagMap.set(ti.ItemID, tags);
235
+ }
236
+ for (const r of results) {
237
+ r.Tags = tagMap.get(r.RecordID) ?? tagMap.get(r.RecordID.toLowerCase()) ?? [];
238
+ }
239
+ }
240
+ }
241
+ //# sourceMappingURL=SearchEnricher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchEnricher.js","sourceRoot":"","sources":["../../src/generic/SearchEnricher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACH,QAAQ,EACR,QAAQ,EACR,OAAO,EAEP,qBAAqB,EACrB,YAAY,EACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAG3E;;;;GAIG;AACH,MAAM,OAAO,cAAc;IACvB;;;;;OAKG;IACI,KAAK,CAAC,MAAM,CAAC,OAA2B,EAAE,WAAqB;QAClE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEjC,MAAM,EAAE,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjC,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC;IAC5D,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,cAAc,CAAC,OAA2B,EAAE,WAAqB;QAC1E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEjC,IAAI,CAAC;YACD,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,mBAAmB,CAAC,CAAC;YACrF,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,mBAAmB,CAAC,CAAC;YAEjF,MAAM,OAAO,CAAC,GAAG,CAAC;gBACd,IAAI,CAAC,kBAAkB,CAAC,cAAc,EAAE,WAAW,CAAC;gBACpD,IAAI,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,WAAW,CAAC;aAC5D,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,sDAAsD,KAAK,EAAE,CAAC,CAAC;YACxE,gDAAgD;QACpD,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,YAAY,CAAC,OAA2B,EAAE,YAAsB;QACnE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAClE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACtB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CACnD,CAAC;IACN,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,gCAAgC,CACzC,OAA2B,EAC3B,WAAqB;QAErB,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,mBAAmB,CAAC,CAAC;QACrF,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QAEpD,MAAM,0BAA0B,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,0BAA0B,CAAC,QAAQ,CAAC;QAEnD,MAAM,gBAAgB,GAAG,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;QACpF,IAAI,CAAC,gBAAgB;YAAE,OAAO,OAAO,CAAC;QAEtC,MAAM,eAAe,GAAG,IAAI,GAAG,CAC3B,MAAM,CAAC,cAAc;aAChB,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,EAAE,CAAC,CAAC;aACrE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CACtC,CAAC;QACF,IAAI,eAAe,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QAE/C,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;YACtB,IAAI,CAAC,CAAC,UAAU,KAAK,mBAAmB;gBAAE,OAAO,IAAI,CAAC;YACtD,IAAI,CAAC,CAAC,CAAC,WAAW;gBAAE,OAAO,IAAI,CAAC;YAChC,IAAI,CAAC;gBACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAA2B,CAAC;gBACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC;gBACtC,IAAI,CAAC,QAAQ;oBAAE,OAAO,IAAI,CAAC;gBAC3B,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YACxD,CAAC;YAAC,MAAM,CAAC;gBACL,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAED,mEAAmE;IACnE,kBAAkB;IAClB,mEAAmE;IAEnE;;OAEG;IACK,cAAc,CAAC,OAA2B,EAAE,EAAY;QAC5D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;gBACrB,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,UAAU,CAAC,CAAC;gBACnE,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;oBACf,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC;gBACpC,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,kBAAkB,CAC5B,OAA2B,EAC3B,EAAY,EACZ,WAAqB;QAErB,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACjC,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,CAAC,UAAU,SAAS,CAC7D,CAAC;QACF,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEnC,IAAI,CAAC;YACD,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;YACnE,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAEvC,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,oBAAoB,CACvC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,EACjC,WAAW,CACd,CAAC;YAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACpC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;oBACtB,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;oBACjD,OAAO,CAAC,WAAW,CAAC,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;oBACtD,OAAO,CAAC,WAAW,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACrD,CAAC;YACL,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,iDAAiD,KAAK,EAAE,CAAC,CAAC;YACnE,iDAAiD;QACrD,CAAC;IACL,CAAC;IAED;;OAEG;IACK,eAAe,CACnB,SAA6B,EAC7B,UAA8B,EAC9B,EAAY;QAEZ,MAAM,OAAO,GAA4D,EAAE,CAAC;QAE5E,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YACxB,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;YAC9D,IAAI,CAAC,MAAM;gBAAE,SAAS;YAEtB,MAAM,GAAG,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,GAAG,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;YAE3C,MAAM,KAAK,GAAG,IAAI,qBAAqB,EAAE,CAAC;YAC1C,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;YAChC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,kBAAkB,CAC5B,OAA2B,EAC3B,WAAqB;QAErB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEjC,MAAM,EAAE,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC;gBACjC,MAAM,UAAU,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;gBAClE,IAAI,UAAU,EAAE,CAAC;oBACb,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;gBACjD,CAAC;YACL,CAAC;QACL,CAAC;QACD,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAEnC,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACtB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YAC/C,IAAI,CAAC,QAAQ;gBAAE,SAAS;YACxB,UAAU,CAAC,IAAI,CAAC,cAAc,QAAQ,mBAAmB,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEpC,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAAsD;YACpF,UAAU,EAAE,kBAAkB;YAC9B,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;YACpC,MAAM,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC;YACvC,UAAU,EAAE,QAAQ;SACvB,EAAE,WAAW,CAAC,CAAC;QAEhB,IAAI,CAAC,SAAS,CAAC,OAAO;YAAE,OAAO;QAE/B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC3C,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YAClB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC1B,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACtB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YAC/C,IAAI,CAAC,QAAQ;gBAAE,SAAS;YACxB,MAAM,GAAG,GAAG,GAAG,QAAQ,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;YACzC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACnC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,mBAAmB,CAC7B,OAA2B,EAC3B,WAAqB;QAErB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEjC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;QACtD,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAAkC;YAChE,UAAU,EAAE,uBAAuB;YACnC,WAAW,EAAE,cAAc,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG;YACjD,MAAM,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;YACzB,UAAU,EAAE,QAAQ;SACvB,EAAE,WAAW,CAAC,CAAC;QAEhB,IAAI,CAAC,SAAS,CAAC,OAAO;YAAE,OAAO;QAE/B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC3C,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YAClB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAChC,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACtB,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;QAClF,CAAC;IACL,CAAC;CACJ"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @fileoverview Search result fusion using Reciprocal Rank Fusion (RRF).
3
+ *
4
+ * Takes ranked lists from vector, full-text, and entity search providers,
5
+ * applies RRF to produce a unified ranking, deduplicates by EntityName+RecordID,
6
+ * and normalizes scores when only a single source has results.
7
+ *
8
+ * @module @memberjunction/search-engine
9
+ */
10
+ import { SearchResultItem, SearchSource } from './search.types.js';
11
+ /**
12
+ * A labeled list of search results from a single source.
13
+ */
14
+ export interface LabeledResultList {
15
+ /** Which search source produced these results */
16
+ Source: SearchSource;
17
+ /** The results, sorted by descending relevance */
18
+ Results: SearchResultItem[];
19
+ }
20
+ /**
21
+ * Handles fusion and deduplication of multi-source search results
22
+ * using Reciprocal Rank Fusion.
23
+ */
24
+ export declare class SearchFusion {
25
+ /**
26
+ * Fuse multiple ranked result lists using RRF, deduplicate, and return
27
+ * the top results up to maxResults.
28
+ *
29
+ * When only one source has results, scores are normalized relative to
30
+ * the top result so the best match appears at ~95% rather than raw
31
+ * cosine similarity (~40-50%).
32
+ *
33
+ * @param lists - Labeled result lists from each search source
34
+ * @param maxResults - Maximum number of results to return
35
+ * @returns Fused, deduplicated, and ranked results
36
+ */
37
+ Fuse(lists: LabeledResultList[], maxResults: number): SearchResultItem[];
38
+ /**
39
+ * Deduplicate results by EntityName+RecordID.
40
+ * When the same record appears from multiple sources (e.g., vector + entity),
41
+ * prefer entity results (they have richer metadata like snippets and field data)
42
+ * and merge the score breakdowns. The final score is the maximum across sources.
43
+ */
44
+ Deduplicate(results: SearchResultItem[]): SearchResultItem[];
45
+ /**
46
+ * Apply RRF across multiple result lists.
47
+ * Maps results to ScoredCandidate[], runs ComputeRRF, then maps back.
48
+ */
49
+ private applyRRF;
50
+ /**
51
+ * Normalize scores when only one search source returned results.
52
+ * Scales scores relative to the top result so the best match shows
53
+ * ~95% instead of raw cosine similarity (~40-50%).
54
+ */
55
+ private normalizeScores;
56
+ /**
57
+ * Create a fallback SearchResultItem for a fused candidate that has
58
+ * no matching full result item (defensive).
59
+ */
60
+ private createFallbackItem;
61
+ }
62
+ //# sourceMappingURL=SearchFusion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchFusion.d.ts","sourceRoot":"","sources":["../../src/generic/SearchFusion.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAwB,MAAM,gBAAgB,CAAC;AAEtF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,iDAAiD;IACjD,MAAM,EAAE,YAAY,CAAC;IACrB,kDAAkD;IAClD,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC/B;AAED;;;GAGG;AACH,qBAAa,YAAY;IACrB;;;;;;;;;;;OAWG;IACI,IAAI,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAc/E;;;;;OAKG;IACI,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE;IAkCnE;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAkChB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAavB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;CAe7B"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @fileoverview Search result fusion using Reciprocal Rank Fusion (RRF).
3
+ *
4
+ * Takes ranked lists from vector, full-text, and entity search providers,
5
+ * applies RRF to produce a unified ranking, deduplicates by EntityName+RecordID,
6
+ * and normalizes scores when only a single source has results.
7
+ *
8
+ * @module @memberjunction/search-engine
9
+ */
10
+ import { ComputeRRF } from '@memberjunction/core';
11
+ /**
12
+ * Handles fusion and deduplication of multi-source search results
13
+ * using Reciprocal Rank Fusion.
14
+ */
15
+ export class SearchFusion {
16
+ /**
17
+ * Fuse multiple ranked result lists using RRF, deduplicate, and return
18
+ * the top results up to maxResults.
19
+ *
20
+ * When only one source has results, scores are normalized relative to
21
+ * the top result so the best match appears at ~95% rather than raw
22
+ * cosine similarity (~40-50%).
23
+ *
24
+ * @param lists - Labeled result lists from each search source
25
+ * @param maxResults - Maximum number of results to return
26
+ * @returns Fused, deduplicated, and ranked results
27
+ */
28
+ Fuse(lists, maxResults) {
29
+ // Collect only lists that have results
30
+ const nonEmpty = lists.filter(l => l.Results.length > 0);
31
+ if (nonEmpty.length === 0)
32
+ return [];
33
+ // Single source: return as-is (no normalization needed, scores are native to that source)
34
+ if (nonEmpty.length === 1) {
35
+ return nonEmpty[0].Results.slice(0, maxResults);
36
+ }
37
+ // Multiple sources: apply RRF
38
+ return this.applyRRF(nonEmpty, maxResults);
39
+ }
40
+ /**
41
+ * Deduplicate results by EntityName+RecordID.
42
+ * When the same record appears from multiple sources (e.g., vector + entity),
43
+ * prefer entity results (they have richer metadata like snippets and field data)
44
+ * and merge the score breakdowns. The final score is the maximum across sources.
45
+ */
46
+ Deduplicate(results) {
47
+ const seen = new Map();
48
+ for (const result of results) {
49
+ const key = `${result.EntityName}::${result.RecordID}`;
50
+ const existing = seen.get(key);
51
+ if (!existing) {
52
+ seen.set(key, result);
53
+ }
54
+ else {
55
+ // Merge: keep the better result, combine score breakdowns
56
+ const mergedBreakdown = { ...existing.ScoreBreakdown, ...result.ScoreBreakdown };
57
+ const maxScore = Math.max(existing.Score, result.Score);
58
+ // Prefer 'entity' source type (richer data) over 'vector'
59
+ const preferred = existing.SourceType === 'entity' ? existing :
60
+ result.SourceType === 'entity' ? result :
61
+ result.Score > existing.Score ? result : existing;
62
+ seen.set(key, {
63
+ ...preferred,
64
+ Score: maxScore,
65
+ ScoreBreakdown: mergedBreakdown
66
+ });
67
+ }
68
+ }
69
+ // Ensure Score is the max of all ScoreBreakdown values (handles cases
70
+ // where dedup didn't merge but breakdown was set from a single source)
71
+ const deduplicated = Array.from(seen.values()).map(r => {
72
+ const breakdownValues = Object.values(r.ScoreBreakdown).filter((v) => typeof v === 'number' && v > 0);
73
+ const breakdownMax = breakdownValues.length > 0 ? Math.max(...breakdownValues) : 0;
74
+ return breakdownMax > r.Score ? { ...r, Score: breakdownMax } : r;
75
+ });
76
+ return deduplicated.sort((a, b) => b.Score - a.Score);
77
+ }
78
+ /**
79
+ * Apply RRF across multiple result lists.
80
+ * Maps results to ScoredCandidate[], runs ComputeRRF, then maps back.
81
+ */
82
+ applyRRF(lists, maxResults) {
83
+ // Build ScoredCandidate arrays for each source
84
+ const rankedLists = lists.map(list => list.Results.map((r, i) => ({
85
+ ID: r.RecordID,
86
+ Score: r.Score,
87
+ Rank: i + 1
88
+ })));
89
+ // Run RRF fusion
90
+ const fused = ComputeRRF(rankedLists);
91
+ // Build a lookup from RecordID to full result item (prefer first occurrence)
92
+ const resultMap = new Map();
93
+ for (const list of lists) {
94
+ for (const r of list.Results) {
95
+ if (!resultMap.has(r.RecordID)) {
96
+ resultMap.set(r.RecordID, r);
97
+ }
98
+ }
99
+ }
100
+ // Map fused candidates back to full result items
101
+ return fused.slice(0, maxResults).map(candidate => {
102
+ const item = resultMap.get(candidate.ID);
103
+ if (item) {
104
+ return { ...item, Score: candidate.Score };
105
+ }
106
+ // Fallback (shouldn't happen in practice)
107
+ return this.createFallbackItem(candidate);
108
+ });
109
+ }
110
+ /**
111
+ * Normalize scores when only one search source returned results.
112
+ * Scales scores relative to the top result so the best match shows
113
+ * ~95% instead of raw cosine similarity (~40-50%).
114
+ */
115
+ normalizeScores(results) {
116
+ if (results.length === 0)
117
+ return results;
118
+ const maxScore = results[0].Score; // Results are already sorted desc
119
+ if (maxScore <= 0)
120
+ return results;
121
+ const scaleFactor = 0.95 / maxScore;
122
+ return results.map(r => ({
123
+ ...r,
124
+ Score: Math.min(0.99, r.Score * scaleFactor)
125
+ }));
126
+ }
127
+ /**
128
+ * Create a fallback SearchResultItem for a fused candidate that has
129
+ * no matching full result item (defensive).
130
+ */
131
+ createFallbackItem(candidate) {
132
+ return {
133
+ ID: candidate.ID,
134
+ EntityName: 'Unknown',
135
+ RecordID: candidate.ID,
136
+ SourceType: 'fused',
137
+ ResultType: 'entity-record',
138
+ Title: 'Unknown',
139
+ Snippet: '',
140
+ Score: candidate.Score,
141
+ ScoreBreakdown: {},
142
+ Tags: [],
143
+ MatchedAt: new Date()
144
+ };
145
+ }
146
+ }
147
+ //# sourceMappingURL=SearchFusion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchFusion.js","sourceRoot":"","sources":["../../src/generic/SearchFusion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
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
+ import { UserInfo } from '@memberjunction/core';
12
+ import { BaseSearchProvider } from './ISearchProvider.js';
13
+ import { SearchSource, SearchFilters, SearchResultItem } from './search.types.js';
14
+ /**
15
+ * Provides file-level search across MJ File Storage accounts using each
16
+ * provider's native SearchFiles() API. Only accounts with
17
+ * IncludeInGlobalSearch=true and providers with SupportsSearch=true are searched.
18
+ *
19
+ * Permission checking is performed against FileStorageAccountPermission records.
20
+ * If no permission records exist for an account, it is accessible to everyone
21
+ * (backwards compatible). Otherwise, the user must have CanRead=true via a
22
+ * direct User permission, a Role permission, or an Everyone permission.
23
+ */
24
+ export declare class StorageSearchProvider extends BaseSearchProvider {
25
+ readonly SourceType: SearchSource;
26
+ private _available;
27
+ private _searchableAccounts;
28
+ private _permissions;
29
+ /**
30
+ * Whether this provider has at least one searchable storage account.
31
+ */
32
+ IsAvailable(): boolean;
33
+ /**
34
+ * Load storage accounts and permissions to determine availability.
35
+ * Must be called once during SearchEngine.Config().
36
+ *
37
+ * @param contextUser - The user context for database queries
38
+ */
39
+ CheckAvailability(contextUser: UserInfo): Promise<void>;
40
+ /**
41
+ * Execute a file search across all eligible storage accounts.
42
+ *
43
+ * @param query - The search query text
44
+ * @param topK - Maximum number of results to retrieve
45
+ * @param _filters - Optional filters (currently unused for storage search)
46
+ * @param contextUser - The user performing the search
47
+ * @returns Scored result items from storage search
48
+ */
49
+ Search(query: string, topK: number, _filters: SearchFilters | undefined, contextUser: UserInfo): Promise<SearchResultItem[]>;
50
+ /**
51
+ * Filter storage accounts by checking FileStorageAccountPermission records
52
+ * for the given user. If an account has no permission records, it is
53
+ * accessible to everyone (backwards compatible).
54
+ */
55
+ private filterAccountsByPermissions;
56
+ /**
57
+ * Search a single storage account using the provider's native SearchFiles API.
58
+ */
59
+ private searchOneAccount;
60
+ /**
61
+ * Convert FileSearchResult items to SearchResultItem format with relevance scores.
62
+ */
63
+ private convertResults;
64
+ /**
65
+ * Calculate a relevance score for a file search result.
66
+ * Uses the provider's relevance score if available, otherwise
67
+ * falls back to a rank-based score with name-match boosting.
68
+ */
69
+ private calculateScore;
70
+ /**
71
+ * Build a display snippet for a file search result.
72
+ */
73
+ private buildSnippet;
74
+ /**
75
+ * Build tags for a file search result.
76
+ */
77
+ private buildTags;
78
+ /**
79
+ * Get an appropriate Font Awesome icon for a file based on its extension/content type.
80
+ */
81
+ private getFileIcon;
82
+ /**
83
+ * Format a file size in bytes to a human-readable string.
84
+ */
85
+ private formatFileSize;
86
+ }
87
+ //# sourceMappingURL=StorageSearchProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageSearchProvider.d.ts","sourceRoot":"","sources":["../../src/generic/StorageSearchProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAgC,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAY9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAoB,MAAM,gBAAgB,CAAC;AAWjG;;;;;;;;;GASG;AACH,qBACa,qBAAsB,SAAQ,kBAAkB;IACzD,SAAgB,UAAU,EAAE,YAAY,CAAa;IAErD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,mBAAmB,CAA2B;IACtD,OAAO,CAAC,YAAY,CAA8C;IAElE;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;;;;OAKG;IACU,iBAAiB,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAmDpE;;;;;;;;OAQG;IACU,MAAM,CACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAyC9B;;;;OAIG;IACH,OAAO,CAAC,2BAA2B;IAuCnC;;OAEG;YACW,gBAAgB;IAiC9B;;OAEG;IACH,OAAO,CAAC,cAAc;IAmCtB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAwBtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAiCpB;;OAEG;IACH,OAAO,CAAC,SAAS;IASjB;;OAEG;IACH,OAAO,CAAC,WAAW;IAkCnB;;OAEG;IACH,OAAO,CAAC,cAAc;CAMzB"}