@topgunbuild/core 0.8.0 → 0.9.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/LICENSE +97 -0
- package/dist/index.d.mts +1224 -855
- package/dist/index.d.ts +1224 -855
- package/dist/index.js +413 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +412 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -11
package/dist/index.mjs
CHANGED
|
@@ -1973,6 +1973,95 @@ var Predicates = class {
|
|
|
1973
1973
|
static containsAny(attribute, values) {
|
|
1974
1974
|
return { op: "containsAny", attribute, value: values };
|
|
1975
1975
|
}
|
|
1976
|
+
// ============== Full-Text Search Predicates (Phase 12) ==============
|
|
1977
|
+
/**
|
|
1978
|
+
* Create a 'match' predicate for full-text search.
|
|
1979
|
+
* Uses BM25 scoring to find relevant documents.
|
|
1980
|
+
*
|
|
1981
|
+
* @param attribute - Field to search in
|
|
1982
|
+
* @param query - Search query string
|
|
1983
|
+
* @param options - Match options (minScore, boost, operator, fuzziness)
|
|
1984
|
+
*
|
|
1985
|
+
* @example
|
|
1986
|
+
* ```typescript
|
|
1987
|
+
* // Simple match
|
|
1988
|
+
* Predicates.match('title', 'machine learning')
|
|
1989
|
+
*
|
|
1990
|
+
* // With options
|
|
1991
|
+
* Predicates.match('body', 'neural networks', { minScore: 1.0, boost: 2.0 })
|
|
1992
|
+
* ```
|
|
1993
|
+
*/
|
|
1994
|
+
static match(attribute, query, options) {
|
|
1995
|
+
return { op: "match", attribute, query, matchOptions: options };
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Create a 'matchPhrase' predicate for exact phrase matching.
|
|
1999
|
+
* Matches documents containing the exact phrase (words in order).
|
|
2000
|
+
*
|
|
2001
|
+
* @param attribute - Field to search in
|
|
2002
|
+
* @param query - Phrase to match
|
|
2003
|
+
* @param slop - Word distance tolerance (0 = exact, 1 = allow 1 word between)
|
|
2004
|
+
*
|
|
2005
|
+
* @example
|
|
2006
|
+
* ```typescript
|
|
2007
|
+
* // Exact phrase
|
|
2008
|
+
* Predicates.matchPhrase('body', 'machine learning')
|
|
2009
|
+
*
|
|
2010
|
+
* // With slop (allows "machine deep learning")
|
|
2011
|
+
* Predicates.matchPhrase('body', 'machine learning', 1)
|
|
2012
|
+
* ```
|
|
2013
|
+
*/
|
|
2014
|
+
static matchPhrase(attribute, query, slop) {
|
|
2015
|
+
return { op: "matchPhrase", attribute, query, slop };
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Create a 'matchPrefix' predicate for prefix matching.
|
|
2019
|
+
* Matches documents where field starts with the given prefix.
|
|
2020
|
+
*
|
|
2021
|
+
* @param attribute - Field to search in
|
|
2022
|
+
* @param prefix - Prefix to match
|
|
2023
|
+
* @param maxExpansions - Maximum number of term expansions
|
|
2024
|
+
*
|
|
2025
|
+
* @example
|
|
2026
|
+
* ```typescript
|
|
2027
|
+
* // Match titles starting with "mach"
|
|
2028
|
+
* Predicates.matchPrefix('title', 'mach')
|
|
2029
|
+
*
|
|
2030
|
+
* // Limit expansions for performance
|
|
2031
|
+
* Predicates.matchPrefix('title', 'mach', 50)
|
|
2032
|
+
* ```
|
|
2033
|
+
*/
|
|
2034
|
+
static matchPrefix(attribute, prefix, maxExpansions) {
|
|
2035
|
+
return { op: "matchPrefix", attribute, prefix, maxExpansions };
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Create a multi-field match predicate.
|
|
2039
|
+
* Searches across multiple fields with optional per-field boosting.
|
|
2040
|
+
*
|
|
2041
|
+
* @param attributes - Fields to search in
|
|
2042
|
+
* @param query - Search query string
|
|
2043
|
+
* @param options - Options including per-field boost factors
|
|
2044
|
+
*
|
|
2045
|
+
* @example
|
|
2046
|
+
* ```typescript
|
|
2047
|
+
* // Search title and body
|
|
2048
|
+
* Predicates.multiMatch(['title', 'body'], 'machine learning')
|
|
2049
|
+
*
|
|
2050
|
+
* // With boosting (title 2x more important)
|
|
2051
|
+
* Predicates.multiMatch(['title', 'body'], 'machine learning', {
|
|
2052
|
+
* boost: { title: 2.0, body: 1.0 }
|
|
2053
|
+
* })
|
|
2054
|
+
* ```
|
|
2055
|
+
*/
|
|
2056
|
+
static multiMatch(attributes, query, options) {
|
|
2057
|
+
const children = attributes.map((attr) => ({
|
|
2058
|
+
op: "match",
|
|
2059
|
+
attribute: attr,
|
|
2060
|
+
query,
|
|
2061
|
+
matchOptions: options?.boost?.[attr] ? { boost: options.boost[attr] } : void 0
|
|
2062
|
+
}));
|
|
2063
|
+
return { op: "or", children };
|
|
2064
|
+
}
|
|
1976
2065
|
};
|
|
1977
2066
|
function evaluatePredicate(predicate, data) {
|
|
1978
2067
|
if (!data) return false;
|
|
@@ -3595,6 +3684,9 @@ function isSimpleQuery(query) {
|
|
|
3595
3684
|
function isLogicalQuery(query) {
|
|
3596
3685
|
return query.type === "and" || query.type === "or" || query.type === "not";
|
|
3597
3686
|
}
|
|
3687
|
+
function isFTSQuery(query) {
|
|
3688
|
+
return query.type === "match" || query.type === "matchPhrase" || query.type === "matchPrefix";
|
|
3689
|
+
}
|
|
3598
3690
|
|
|
3599
3691
|
// src/query/indexes/StandingQueryIndex.ts
|
|
3600
3692
|
var _StandingQueryIndex = class _StandingQueryIndex {
|
|
@@ -5853,11 +5945,48 @@ var QueryOptimizer = class {
|
|
|
5853
5945
|
if ("indexRegistry" in indexRegistryOrOptions) {
|
|
5854
5946
|
this.indexRegistry = indexRegistryOrOptions.indexRegistry;
|
|
5855
5947
|
this.standingQueryRegistry = indexRegistryOrOptions.standingQueryRegistry;
|
|
5948
|
+
this.fullTextIndexes = indexRegistryOrOptions.fullTextIndexes ?? /* @__PURE__ */ new Map();
|
|
5856
5949
|
} else {
|
|
5857
5950
|
this.indexRegistry = indexRegistryOrOptions;
|
|
5858
5951
|
this.standingQueryRegistry = standingQueryRegistry;
|
|
5952
|
+
this.fullTextIndexes = /* @__PURE__ */ new Map();
|
|
5859
5953
|
}
|
|
5860
5954
|
}
|
|
5955
|
+
/**
|
|
5956
|
+
* Register a full-text index for a field (Phase 12).
|
|
5957
|
+
*
|
|
5958
|
+
* @param field - Field name
|
|
5959
|
+
* @param index - FullTextIndex instance
|
|
5960
|
+
*/
|
|
5961
|
+
registerFullTextIndex(field, index) {
|
|
5962
|
+
this.fullTextIndexes.set(field, index);
|
|
5963
|
+
}
|
|
5964
|
+
/**
|
|
5965
|
+
* Unregister a full-text index (Phase 12).
|
|
5966
|
+
*
|
|
5967
|
+
* @param field - Field name
|
|
5968
|
+
*/
|
|
5969
|
+
unregisterFullTextIndex(field) {
|
|
5970
|
+
this.fullTextIndexes.delete(field);
|
|
5971
|
+
}
|
|
5972
|
+
/**
|
|
5973
|
+
* Get registered full-text index for a field (Phase 12).
|
|
5974
|
+
*
|
|
5975
|
+
* @param field - Field name
|
|
5976
|
+
* @returns FullTextIndex or undefined
|
|
5977
|
+
*/
|
|
5978
|
+
getFullTextIndex(field) {
|
|
5979
|
+
return this.fullTextIndexes.get(field);
|
|
5980
|
+
}
|
|
5981
|
+
/**
|
|
5982
|
+
* Check if a full-text index exists for a field (Phase 12).
|
|
5983
|
+
*
|
|
5984
|
+
* @param field - Field name
|
|
5985
|
+
* @returns True if FTS index exists
|
|
5986
|
+
*/
|
|
5987
|
+
hasFullTextIndex(field) {
|
|
5988
|
+
return this.fullTextIndexes.has(field);
|
|
5989
|
+
}
|
|
5861
5990
|
/**
|
|
5862
5991
|
* Optimize a query and return an execution plan.
|
|
5863
5992
|
*
|
|
@@ -5931,12 +6060,151 @@ var QueryOptimizer = class {
|
|
|
5931
6060
|
optimizeNode(query) {
|
|
5932
6061
|
if (isLogicalQuery(query)) {
|
|
5933
6062
|
return this.optimizeLogical(query);
|
|
6063
|
+
} else if (isFTSQuery(query)) {
|
|
6064
|
+
return this.optimizeFTS(query);
|
|
5934
6065
|
} else if (isSimpleQuery(query)) {
|
|
5935
6066
|
return this.optimizeSimple(query);
|
|
5936
6067
|
} else {
|
|
5937
6068
|
return { type: "full-scan", predicate: query };
|
|
5938
6069
|
}
|
|
5939
6070
|
}
|
|
6071
|
+
/**
|
|
6072
|
+
* Optimize a full-text search query (Phase 12).
|
|
6073
|
+
*/
|
|
6074
|
+
optimizeFTS(query) {
|
|
6075
|
+
const field = query.attribute;
|
|
6076
|
+
if (!this.hasFullTextIndex(field)) {
|
|
6077
|
+
return { type: "full-scan", predicate: query };
|
|
6078
|
+
}
|
|
6079
|
+
return this.buildFTSScanStep(query);
|
|
6080
|
+
}
|
|
6081
|
+
/**
|
|
6082
|
+
* Build an FTS scan step from a query node (Phase 12).
|
|
6083
|
+
*/
|
|
6084
|
+
buildFTSScanStep(query) {
|
|
6085
|
+
const field = query.attribute;
|
|
6086
|
+
switch (query.type) {
|
|
6087
|
+
case "match":
|
|
6088
|
+
return {
|
|
6089
|
+
type: "fts-scan",
|
|
6090
|
+
field,
|
|
6091
|
+
query: query.query,
|
|
6092
|
+
ftsType: "match",
|
|
6093
|
+
options: query.options,
|
|
6094
|
+
returnsScored: true,
|
|
6095
|
+
estimatedCost: this.estimateFTSCost(field)
|
|
6096
|
+
};
|
|
6097
|
+
case "matchPhrase":
|
|
6098
|
+
return {
|
|
6099
|
+
type: "fts-scan",
|
|
6100
|
+
field,
|
|
6101
|
+
query: query.query,
|
|
6102
|
+
ftsType: "matchPhrase",
|
|
6103
|
+
options: query.slop !== void 0 ? { fuzziness: query.slop } : void 0,
|
|
6104
|
+
returnsScored: true,
|
|
6105
|
+
estimatedCost: this.estimateFTSCost(field)
|
|
6106
|
+
};
|
|
6107
|
+
case "matchPrefix":
|
|
6108
|
+
return {
|
|
6109
|
+
type: "fts-scan",
|
|
6110
|
+
field,
|
|
6111
|
+
query: query.prefix,
|
|
6112
|
+
ftsType: "matchPrefix",
|
|
6113
|
+
options: query.maxExpansions !== void 0 ? { fuzziness: query.maxExpansions } : void 0,
|
|
6114
|
+
returnsScored: true,
|
|
6115
|
+
estimatedCost: this.estimateFTSCost(field)
|
|
6116
|
+
};
|
|
6117
|
+
default:
|
|
6118
|
+
throw new Error(`Unknown FTS query type: ${query.type}`);
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
/**
|
|
6122
|
+
* Estimate cost of FTS query based on index size (Phase 12).
|
|
6123
|
+
*/
|
|
6124
|
+
estimateFTSCost(field) {
|
|
6125
|
+
const index = this.fullTextIndexes.get(field);
|
|
6126
|
+
if (!index) {
|
|
6127
|
+
return Number.MAX_SAFE_INTEGER;
|
|
6128
|
+
}
|
|
6129
|
+
const docCount = index.getSize();
|
|
6130
|
+
return 50 + Math.log2(docCount + 1) * 10;
|
|
6131
|
+
}
|
|
6132
|
+
/**
|
|
6133
|
+
* Classify predicates by type for hybrid query planning (Phase 12).
|
|
6134
|
+
*
|
|
6135
|
+
* @param predicates - Array of predicates to classify
|
|
6136
|
+
* @returns Classified predicates
|
|
6137
|
+
*/
|
|
6138
|
+
classifyPredicates(predicates) {
|
|
6139
|
+
const result = {
|
|
6140
|
+
exactPredicates: [],
|
|
6141
|
+
rangePredicates: [],
|
|
6142
|
+
ftsPredicates: [],
|
|
6143
|
+
otherPredicates: []
|
|
6144
|
+
};
|
|
6145
|
+
for (const pred of predicates) {
|
|
6146
|
+
if (isFTSQuery(pred)) {
|
|
6147
|
+
result.ftsPredicates.push(pred);
|
|
6148
|
+
} else if (isSimpleQuery(pred)) {
|
|
6149
|
+
switch (pred.type) {
|
|
6150
|
+
case "eq":
|
|
6151
|
+
case "neq":
|
|
6152
|
+
case "in":
|
|
6153
|
+
result.exactPredicates.push(pred);
|
|
6154
|
+
break;
|
|
6155
|
+
case "gt":
|
|
6156
|
+
case "gte":
|
|
6157
|
+
case "lt":
|
|
6158
|
+
case "lte":
|
|
6159
|
+
case "between":
|
|
6160
|
+
result.rangePredicates.push(pred);
|
|
6161
|
+
break;
|
|
6162
|
+
default:
|
|
6163
|
+
result.otherPredicates.push(pred);
|
|
6164
|
+
}
|
|
6165
|
+
} else if (isLogicalQuery(pred)) {
|
|
6166
|
+
result.otherPredicates.push(pred);
|
|
6167
|
+
} else {
|
|
6168
|
+
result.otherPredicates.push(pred);
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
return result;
|
|
6172
|
+
}
|
|
6173
|
+
/**
|
|
6174
|
+
* Determine fusion strategy based on step types (Phase 12).
|
|
6175
|
+
*
|
|
6176
|
+
* Strategy selection:
|
|
6177
|
+
* - All binary (exact/range with no scores) → 'intersection'
|
|
6178
|
+
* - All scored (FTS) → 'score-filter' (filter by score, sort by score)
|
|
6179
|
+
* - Mixed (binary + scored) → 'rrf' (Reciprocal Rank Fusion)
|
|
6180
|
+
*
|
|
6181
|
+
* @param steps - Plan steps to fuse
|
|
6182
|
+
* @returns Fusion strategy
|
|
6183
|
+
*/
|
|
6184
|
+
determineFusionStrategy(steps) {
|
|
6185
|
+
const hasScored = steps.some((s) => this.stepReturnsScored(s));
|
|
6186
|
+
const hasBinary = steps.some((s) => !this.stepReturnsScored(s));
|
|
6187
|
+
if (hasScored && hasBinary) {
|
|
6188
|
+
return "rrf";
|
|
6189
|
+
} else if (hasScored) {
|
|
6190
|
+
return "score-filter";
|
|
6191
|
+
} else {
|
|
6192
|
+
return "intersection";
|
|
6193
|
+
}
|
|
6194
|
+
}
|
|
6195
|
+
/**
|
|
6196
|
+
* Check if a plan step returns scored results (Phase 12).
|
|
6197
|
+
*/
|
|
6198
|
+
stepReturnsScored(step) {
|
|
6199
|
+
switch (step.type) {
|
|
6200
|
+
case "fts-scan":
|
|
6201
|
+
return true;
|
|
6202
|
+
case "fusion":
|
|
6203
|
+
return step.returnsScored;
|
|
6204
|
+
default:
|
|
6205
|
+
return false;
|
|
6206
|
+
}
|
|
6207
|
+
}
|
|
5940
6208
|
/**
|
|
5941
6209
|
* Optimize a simple (attribute-based) query.
|
|
5942
6210
|
*/
|
|
@@ -6192,6 +6460,18 @@ var QueryOptimizer = class {
|
|
|
6192
6460
|
return this.estimateCost(step.source) + 10;
|
|
6193
6461
|
case "not":
|
|
6194
6462
|
return this.estimateCost(step.source) + 100;
|
|
6463
|
+
// Phase 12: FTS step types
|
|
6464
|
+
case "fts-scan":
|
|
6465
|
+
return step.estimatedCost;
|
|
6466
|
+
case "fusion":
|
|
6467
|
+
return step.steps.reduce((sum, s) => {
|
|
6468
|
+
const cost = this.estimateCost(s);
|
|
6469
|
+
if (cost === Number.MAX_SAFE_INTEGER) {
|
|
6470
|
+
return Number.MAX_SAFE_INTEGER;
|
|
6471
|
+
}
|
|
6472
|
+
return Math.min(sum + cost, Number.MAX_SAFE_INTEGER);
|
|
6473
|
+
}, 0) + 20;
|
|
6474
|
+
// Fusion overhead
|
|
6195
6475
|
default:
|
|
6196
6476
|
return Number.MAX_SAFE_INTEGER;
|
|
6197
6477
|
}
|
|
@@ -6212,6 +6492,12 @@ var QueryOptimizer = class {
|
|
|
6212
6492
|
return this.usesIndexes(step.source);
|
|
6213
6493
|
case "not":
|
|
6214
6494
|
return this.usesIndexes(step.source);
|
|
6495
|
+
// Phase 12: FTS step types
|
|
6496
|
+
case "fts-scan":
|
|
6497
|
+
return true;
|
|
6498
|
+
// FTS uses FullTextIndex
|
|
6499
|
+
case "fusion":
|
|
6500
|
+
return step.steps.some((s) => this.usesIndexes(s));
|
|
6215
6501
|
default:
|
|
6216
6502
|
return false;
|
|
6217
6503
|
}
|
|
@@ -7760,6 +8046,131 @@ var DefaultIndexingStrategy = class {
|
|
|
7760
8046
|
}
|
|
7761
8047
|
};
|
|
7762
8048
|
|
|
8049
|
+
// src/search/ReciprocalRankFusion.ts
|
|
8050
|
+
var ReciprocalRankFusion = class {
|
|
8051
|
+
constructor(config) {
|
|
8052
|
+
this.k = config?.k ?? 60;
|
|
8053
|
+
}
|
|
8054
|
+
/**
|
|
8055
|
+
* Merge multiple ranked result lists using RRF.
|
|
8056
|
+
*
|
|
8057
|
+
* Formula: RRF_score(d) = Σ 1 / (k + rank_i(d))
|
|
8058
|
+
*
|
|
8059
|
+
* @param resultSets - Array of ranked result lists from different search methods
|
|
8060
|
+
* @returns Merged results sorted by RRF score (descending)
|
|
8061
|
+
*/
|
|
8062
|
+
merge(resultSets) {
|
|
8063
|
+
const nonEmptySets = resultSets.filter((set) => set.length > 0);
|
|
8064
|
+
if (nonEmptySets.length === 0) {
|
|
8065
|
+
return [];
|
|
8066
|
+
}
|
|
8067
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
8068
|
+
for (const resultSet of nonEmptySets) {
|
|
8069
|
+
for (let rank = 0; rank < resultSet.length; rank++) {
|
|
8070
|
+
const result = resultSet[rank];
|
|
8071
|
+
const { docId, score, source } = result;
|
|
8072
|
+
const rrfContribution = 1 / (this.k + rank + 1);
|
|
8073
|
+
const existing = scoreMap.get(docId);
|
|
8074
|
+
if (existing) {
|
|
8075
|
+
existing.rrfScore += rrfContribution;
|
|
8076
|
+
existing.sources.add(source);
|
|
8077
|
+
existing.originalScores[source] = score;
|
|
8078
|
+
} else {
|
|
8079
|
+
scoreMap.set(docId, {
|
|
8080
|
+
rrfScore: rrfContribution,
|
|
8081
|
+
sources: /* @__PURE__ */ new Set([source]),
|
|
8082
|
+
originalScores: { [source]: score }
|
|
8083
|
+
});
|
|
8084
|
+
}
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
const merged = [];
|
|
8088
|
+
for (const [docId, data] of scoreMap) {
|
|
8089
|
+
merged.push({
|
|
8090
|
+
docId,
|
|
8091
|
+
score: data.rrfScore,
|
|
8092
|
+
source: Array.from(data.sources).sort().join("+"),
|
|
8093
|
+
originalScores: data.originalScores
|
|
8094
|
+
});
|
|
8095
|
+
}
|
|
8096
|
+
merged.sort((a, b) => b.score - a.score);
|
|
8097
|
+
return merged;
|
|
8098
|
+
}
|
|
8099
|
+
/**
|
|
8100
|
+
* Merge with weighted RRF for different method priorities.
|
|
8101
|
+
*
|
|
8102
|
+
* Weighted formula: RRF_score(d) = Σ weight_i * (1 / (k + rank_i(d)))
|
|
8103
|
+
*
|
|
8104
|
+
* @param resultSets - Array of ranked result lists
|
|
8105
|
+
* @param weights - Weights for each result set (same order as resultSets)
|
|
8106
|
+
* @returns Merged results sorted by weighted RRF score (descending)
|
|
8107
|
+
*
|
|
8108
|
+
* @example
|
|
8109
|
+
* ```typescript
|
|
8110
|
+
* const rrf = new ReciprocalRankFusion();
|
|
8111
|
+
*
|
|
8112
|
+
* // Prioritize exact matches (weight 2.0) over FTS (weight 1.0)
|
|
8113
|
+
* const merged = rrf.mergeWeighted(
|
|
8114
|
+
* [exactResults, ftsResults],
|
|
8115
|
+
* [2.0, 1.0]
|
|
8116
|
+
* );
|
|
8117
|
+
* ```
|
|
8118
|
+
*/
|
|
8119
|
+
mergeWeighted(resultSets, weights) {
|
|
8120
|
+
if (weights.length !== resultSets.length) {
|
|
8121
|
+
throw new Error(
|
|
8122
|
+
`Weights array length (${weights.length}) must match resultSets length (${resultSets.length})`
|
|
8123
|
+
);
|
|
8124
|
+
}
|
|
8125
|
+
const nonEmptyPairs = [];
|
|
8126
|
+
for (let i = 0; i < resultSets.length; i++) {
|
|
8127
|
+
if (resultSets[i].length > 0) {
|
|
8128
|
+
nonEmptyPairs.push({ resultSet: resultSets[i], weight: weights[i] });
|
|
8129
|
+
}
|
|
8130
|
+
}
|
|
8131
|
+
if (nonEmptyPairs.length === 0) {
|
|
8132
|
+
return [];
|
|
8133
|
+
}
|
|
8134
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
8135
|
+
for (const { resultSet, weight } of nonEmptyPairs) {
|
|
8136
|
+
for (let rank = 0; rank < resultSet.length; rank++) {
|
|
8137
|
+
const result = resultSet[rank];
|
|
8138
|
+
const { docId, score, source } = result;
|
|
8139
|
+
const rrfContribution = weight * (1 / (this.k + rank + 1));
|
|
8140
|
+
const existing = scoreMap.get(docId);
|
|
8141
|
+
if (existing) {
|
|
8142
|
+
existing.rrfScore += rrfContribution;
|
|
8143
|
+
existing.sources.add(source);
|
|
8144
|
+
existing.originalScores[source] = score;
|
|
8145
|
+
} else {
|
|
8146
|
+
scoreMap.set(docId, {
|
|
8147
|
+
rrfScore: rrfContribution,
|
|
8148
|
+
sources: /* @__PURE__ */ new Set([source]),
|
|
8149
|
+
originalScores: { [source]: score }
|
|
8150
|
+
});
|
|
8151
|
+
}
|
|
8152
|
+
}
|
|
8153
|
+
}
|
|
8154
|
+
const merged = [];
|
|
8155
|
+
for (const [docId, data] of scoreMap) {
|
|
8156
|
+
merged.push({
|
|
8157
|
+
docId,
|
|
8158
|
+
score: data.rrfScore,
|
|
8159
|
+
source: Array.from(data.sources).sort().join("+"),
|
|
8160
|
+
originalScores: data.originalScores
|
|
8161
|
+
});
|
|
8162
|
+
}
|
|
8163
|
+
merged.sort((a, b) => b.score - a.score);
|
|
8164
|
+
return merged;
|
|
8165
|
+
}
|
|
8166
|
+
/**
|
|
8167
|
+
* Get the k constant used for RRF calculation.
|
|
8168
|
+
*/
|
|
8169
|
+
getK() {
|
|
8170
|
+
return this.k;
|
|
8171
|
+
}
|
|
8172
|
+
};
|
|
8173
|
+
|
|
7763
8174
|
// src/IndexedLWWMap.ts
|
|
7764
8175
|
var IndexedLWWMap = class extends LWWMap {
|
|
7765
8176
|
constructor(hlc, options = {}) {
|
|
@@ -10439,6 +10850,7 @@ export {
|
|
|
10439
10850
|
QuerySubMessageSchema,
|
|
10440
10851
|
QueryUnsubMessageSchema,
|
|
10441
10852
|
RESOLVER_FORBIDDEN_PATTERNS,
|
|
10853
|
+
ReciprocalRankFusion,
|
|
10442
10854
|
RegisterResolverRequestSchema,
|
|
10443
10855
|
RegisterResolverResponseSchema,
|
|
10444
10856
|
Ringbuffer,
|