@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.js
CHANGED
|
@@ -133,6 +133,7 @@ __export(index_exports, {
|
|
|
133
133
|
QuerySubMessageSchema: () => QuerySubMessageSchema,
|
|
134
134
|
QueryUnsubMessageSchema: () => QueryUnsubMessageSchema,
|
|
135
135
|
RESOLVER_FORBIDDEN_PATTERNS: () => RESOLVER_FORBIDDEN_PATTERNS,
|
|
136
|
+
ReciprocalRankFusion: () => ReciprocalRankFusion,
|
|
136
137
|
RegisterResolverRequestSchema: () => RegisterResolverRequestSchema,
|
|
137
138
|
RegisterResolverResponseSchema: () => RegisterResolverResponseSchema,
|
|
138
139
|
Ringbuffer: () => Ringbuffer,
|
|
@@ -2171,6 +2172,95 @@ var Predicates = class {
|
|
|
2171
2172
|
static containsAny(attribute, values) {
|
|
2172
2173
|
return { op: "containsAny", attribute, value: values };
|
|
2173
2174
|
}
|
|
2175
|
+
// ============== Full-Text Search Predicates (Phase 12) ==============
|
|
2176
|
+
/**
|
|
2177
|
+
* Create a 'match' predicate for full-text search.
|
|
2178
|
+
* Uses BM25 scoring to find relevant documents.
|
|
2179
|
+
*
|
|
2180
|
+
* @param attribute - Field to search in
|
|
2181
|
+
* @param query - Search query string
|
|
2182
|
+
* @param options - Match options (minScore, boost, operator, fuzziness)
|
|
2183
|
+
*
|
|
2184
|
+
* @example
|
|
2185
|
+
* ```typescript
|
|
2186
|
+
* // Simple match
|
|
2187
|
+
* Predicates.match('title', 'machine learning')
|
|
2188
|
+
*
|
|
2189
|
+
* // With options
|
|
2190
|
+
* Predicates.match('body', 'neural networks', { minScore: 1.0, boost: 2.0 })
|
|
2191
|
+
* ```
|
|
2192
|
+
*/
|
|
2193
|
+
static match(attribute, query, options) {
|
|
2194
|
+
return { op: "match", attribute, query, matchOptions: options };
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Create a 'matchPhrase' predicate for exact phrase matching.
|
|
2198
|
+
* Matches documents containing the exact phrase (words in order).
|
|
2199
|
+
*
|
|
2200
|
+
* @param attribute - Field to search in
|
|
2201
|
+
* @param query - Phrase to match
|
|
2202
|
+
* @param slop - Word distance tolerance (0 = exact, 1 = allow 1 word between)
|
|
2203
|
+
*
|
|
2204
|
+
* @example
|
|
2205
|
+
* ```typescript
|
|
2206
|
+
* // Exact phrase
|
|
2207
|
+
* Predicates.matchPhrase('body', 'machine learning')
|
|
2208
|
+
*
|
|
2209
|
+
* // With slop (allows "machine deep learning")
|
|
2210
|
+
* Predicates.matchPhrase('body', 'machine learning', 1)
|
|
2211
|
+
* ```
|
|
2212
|
+
*/
|
|
2213
|
+
static matchPhrase(attribute, query, slop) {
|
|
2214
|
+
return { op: "matchPhrase", attribute, query, slop };
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Create a 'matchPrefix' predicate for prefix matching.
|
|
2218
|
+
* Matches documents where field starts with the given prefix.
|
|
2219
|
+
*
|
|
2220
|
+
* @param attribute - Field to search in
|
|
2221
|
+
* @param prefix - Prefix to match
|
|
2222
|
+
* @param maxExpansions - Maximum number of term expansions
|
|
2223
|
+
*
|
|
2224
|
+
* @example
|
|
2225
|
+
* ```typescript
|
|
2226
|
+
* // Match titles starting with "mach"
|
|
2227
|
+
* Predicates.matchPrefix('title', 'mach')
|
|
2228
|
+
*
|
|
2229
|
+
* // Limit expansions for performance
|
|
2230
|
+
* Predicates.matchPrefix('title', 'mach', 50)
|
|
2231
|
+
* ```
|
|
2232
|
+
*/
|
|
2233
|
+
static matchPrefix(attribute, prefix, maxExpansions) {
|
|
2234
|
+
return { op: "matchPrefix", attribute, prefix, maxExpansions };
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Create a multi-field match predicate.
|
|
2238
|
+
* Searches across multiple fields with optional per-field boosting.
|
|
2239
|
+
*
|
|
2240
|
+
* @param attributes - Fields to search in
|
|
2241
|
+
* @param query - Search query string
|
|
2242
|
+
* @param options - Options including per-field boost factors
|
|
2243
|
+
*
|
|
2244
|
+
* @example
|
|
2245
|
+
* ```typescript
|
|
2246
|
+
* // Search title and body
|
|
2247
|
+
* Predicates.multiMatch(['title', 'body'], 'machine learning')
|
|
2248
|
+
*
|
|
2249
|
+
* // With boosting (title 2x more important)
|
|
2250
|
+
* Predicates.multiMatch(['title', 'body'], 'machine learning', {
|
|
2251
|
+
* boost: { title: 2.0, body: 1.0 }
|
|
2252
|
+
* })
|
|
2253
|
+
* ```
|
|
2254
|
+
*/
|
|
2255
|
+
static multiMatch(attributes, query, options) {
|
|
2256
|
+
const children = attributes.map((attr) => ({
|
|
2257
|
+
op: "match",
|
|
2258
|
+
attribute: attr,
|
|
2259
|
+
query,
|
|
2260
|
+
matchOptions: options?.boost?.[attr] ? { boost: options.boost[attr] } : void 0
|
|
2261
|
+
}));
|
|
2262
|
+
return { op: "or", children };
|
|
2263
|
+
}
|
|
2174
2264
|
};
|
|
2175
2265
|
function evaluatePredicate(predicate, data) {
|
|
2176
2266
|
if (!data) return false;
|
|
@@ -3793,6 +3883,9 @@ function isSimpleQuery(query) {
|
|
|
3793
3883
|
function isLogicalQuery(query) {
|
|
3794
3884
|
return query.type === "and" || query.type === "or" || query.type === "not";
|
|
3795
3885
|
}
|
|
3886
|
+
function isFTSQuery(query) {
|
|
3887
|
+
return query.type === "match" || query.type === "matchPhrase" || query.type === "matchPrefix";
|
|
3888
|
+
}
|
|
3796
3889
|
|
|
3797
3890
|
// src/query/indexes/StandingQueryIndex.ts
|
|
3798
3891
|
var _StandingQueryIndex = class _StandingQueryIndex {
|
|
@@ -6051,11 +6144,48 @@ var QueryOptimizer = class {
|
|
|
6051
6144
|
if ("indexRegistry" in indexRegistryOrOptions) {
|
|
6052
6145
|
this.indexRegistry = indexRegistryOrOptions.indexRegistry;
|
|
6053
6146
|
this.standingQueryRegistry = indexRegistryOrOptions.standingQueryRegistry;
|
|
6147
|
+
this.fullTextIndexes = indexRegistryOrOptions.fullTextIndexes ?? /* @__PURE__ */ new Map();
|
|
6054
6148
|
} else {
|
|
6055
6149
|
this.indexRegistry = indexRegistryOrOptions;
|
|
6056
6150
|
this.standingQueryRegistry = standingQueryRegistry;
|
|
6151
|
+
this.fullTextIndexes = /* @__PURE__ */ new Map();
|
|
6057
6152
|
}
|
|
6058
6153
|
}
|
|
6154
|
+
/**
|
|
6155
|
+
* Register a full-text index for a field (Phase 12).
|
|
6156
|
+
*
|
|
6157
|
+
* @param field - Field name
|
|
6158
|
+
* @param index - FullTextIndex instance
|
|
6159
|
+
*/
|
|
6160
|
+
registerFullTextIndex(field, index) {
|
|
6161
|
+
this.fullTextIndexes.set(field, index);
|
|
6162
|
+
}
|
|
6163
|
+
/**
|
|
6164
|
+
* Unregister a full-text index (Phase 12).
|
|
6165
|
+
*
|
|
6166
|
+
* @param field - Field name
|
|
6167
|
+
*/
|
|
6168
|
+
unregisterFullTextIndex(field) {
|
|
6169
|
+
this.fullTextIndexes.delete(field);
|
|
6170
|
+
}
|
|
6171
|
+
/**
|
|
6172
|
+
* Get registered full-text index for a field (Phase 12).
|
|
6173
|
+
*
|
|
6174
|
+
* @param field - Field name
|
|
6175
|
+
* @returns FullTextIndex or undefined
|
|
6176
|
+
*/
|
|
6177
|
+
getFullTextIndex(field) {
|
|
6178
|
+
return this.fullTextIndexes.get(field);
|
|
6179
|
+
}
|
|
6180
|
+
/**
|
|
6181
|
+
* Check if a full-text index exists for a field (Phase 12).
|
|
6182
|
+
*
|
|
6183
|
+
* @param field - Field name
|
|
6184
|
+
* @returns True if FTS index exists
|
|
6185
|
+
*/
|
|
6186
|
+
hasFullTextIndex(field) {
|
|
6187
|
+
return this.fullTextIndexes.has(field);
|
|
6188
|
+
}
|
|
6059
6189
|
/**
|
|
6060
6190
|
* Optimize a query and return an execution plan.
|
|
6061
6191
|
*
|
|
@@ -6129,12 +6259,151 @@ var QueryOptimizer = class {
|
|
|
6129
6259
|
optimizeNode(query) {
|
|
6130
6260
|
if (isLogicalQuery(query)) {
|
|
6131
6261
|
return this.optimizeLogical(query);
|
|
6262
|
+
} else if (isFTSQuery(query)) {
|
|
6263
|
+
return this.optimizeFTS(query);
|
|
6132
6264
|
} else if (isSimpleQuery(query)) {
|
|
6133
6265
|
return this.optimizeSimple(query);
|
|
6134
6266
|
} else {
|
|
6135
6267
|
return { type: "full-scan", predicate: query };
|
|
6136
6268
|
}
|
|
6137
6269
|
}
|
|
6270
|
+
/**
|
|
6271
|
+
* Optimize a full-text search query (Phase 12).
|
|
6272
|
+
*/
|
|
6273
|
+
optimizeFTS(query) {
|
|
6274
|
+
const field = query.attribute;
|
|
6275
|
+
if (!this.hasFullTextIndex(field)) {
|
|
6276
|
+
return { type: "full-scan", predicate: query };
|
|
6277
|
+
}
|
|
6278
|
+
return this.buildFTSScanStep(query);
|
|
6279
|
+
}
|
|
6280
|
+
/**
|
|
6281
|
+
* Build an FTS scan step from a query node (Phase 12).
|
|
6282
|
+
*/
|
|
6283
|
+
buildFTSScanStep(query) {
|
|
6284
|
+
const field = query.attribute;
|
|
6285
|
+
switch (query.type) {
|
|
6286
|
+
case "match":
|
|
6287
|
+
return {
|
|
6288
|
+
type: "fts-scan",
|
|
6289
|
+
field,
|
|
6290
|
+
query: query.query,
|
|
6291
|
+
ftsType: "match",
|
|
6292
|
+
options: query.options,
|
|
6293
|
+
returnsScored: true,
|
|
6294
|
+
estimatedCost: this.estimateFTSCost(field)
|
|
6295
|
+
};
|
|
6296
|
+
case "matchPhrase":
|
|
6297
|
+
return {
|
|
6298
|
+
type: "fts-scan",
|
|
6299
|
+
field,
|
|
6300
|
+
query: query.query,
|
|
6301
|
+
ftsType: "matchPhrase",
|
|
6302
|
+
options: query.slop !== void 0 ? { fuzziness: query.slop } : void 0,
|
|
6303
|
+
returnsScored: true,
|
|
6304
|
+
estimatedCost: this.estimateFTSCost(field)
|
|
6305
|
+
};
|
|
6306
|
+
case "matchPrefix":
|
|
6307
|
+
return {
|
|
6308
|
+
type: "fts-scan",
|
|
6309
|
+
field,
|
|
6310
|
+
query: query.prefix,
|
|
6311
|
+
ftsType: "matchPrefix",
|
|
6312
|
+
options: query.maxExpansions !== void 0 ? { fuzziness: query.maxExpansions } : void 0,
|
|
6313
|
+
returnsScored: true,
|
|
6314
|
+
estimatedCost: this.estimateFTSCost(field)
|
|
6315
|
+
};
|
|
6316
|
+
default:
|
|
6317
|
+
throw new Error(`Unknown FTS query type: ${query.type}`);
|
|
6318
|
+
}
|
|
6319
|
+
}
|
|
6320
|
+
/**
|
|
6321
|
+
* Estimate cost of FTS query based on index size (Phase 12).
|
|
6322
|
+
*/
|
|
6323
|
+
estimateFTSCost(field) {
|
|
6324
|
+
const index = this.fullTextIndexes.get(field);
|
|
6325
|
+
if (!index) {
|
|
6326
|
+
return Number.MAX_SAFE_INTEGER;
|
|
6327
|
+
}
|
|
6328
|
+
const docCount = index.getSize();
|
|
6329
|
+
return 50 + Math.log2(docCount + 1) * 10;
|
|
6330
|
+
}
|
|
6331
|
+
/**
|
|
6332
|
+
* Classify predicates by type for hybrid query planning (Phase 12).
|
|
6333
|
+
*
|
|
6334
|
+
* @param predicates - Array of predicates to classify
|
|
6335
|
+
* @returns Classified predicates
|
|
6336
|
+
*/
|
|
6337
|
+
classifyPredicates(predicates) {
|
|
6338
|
+
const result = {
|
|
6339
|
+
exactPredicates: [],
|
|
6340
|
+
rangePredicates: [],
|
|
6341
|
+
ftsPredicates: [],
|
|
6342
|
+
otherPredicates: []
|
|
6343
|
+
};
|
|
6344
|
+
for (const pred of predicates) {
|
|
6345
|
+
if (isFTSQuery(pred)) {
|
|
6346
|
+
result.ftsPredicates.push(pred);
|
|
6347
|
+
} else if (isSimpleQuery(pred)) {
|
|
6348
|
+
switch (pred.type) {
|
|
6349
|
+
case "eq":
|
|
6350
|
+
case "neq":
|
|
6351
|
+
case "in":
|
|
6352
|
+
result.exactPredicates.push(pred);
|
|
6353
|
+
break;
|
|
6354
|
+
case "gt":
|
|
6355
|
+
case "gte":
|
|
6356
|
+
case "lt":
|
|
6357
|
+
case "lte":
|
|
6358
|
+
case "between":
|
|
6359
|
+
result.rangePredicates.push(pred);
|
|
6360
|
+
break;
|
|
6361
|
+
default:
|
|
6362
|
+
result.otherPredicates.push(pred);
|
|
6363
|
+
}
|
|
6364
|
+
} else if (isLogicalQuery(pred)) {
|
|
6365
|
+
result.otherPredicates.push(pred);
|
|
6366
|
+
} else {
|
|
6367
|
+
result.otherPredicates.push(pred);
|
|
6368
|
+
}
|
|
6369
|
+
}
|
|
6370
|
+
return result;
|
|
6371
|
+
}
|
|
6372
|
+
/**
|
|
6373
|
+
* Determine fusion strategy based on step types (Phase 12).
|
|
6374
|
+
*
|
|
6375
|
+
* Strategy selection:
|
|
6376
|
+
* - All binary (exact/range with no scores) → 'intersection'
|
|
6377
|
+
* - All scored (FTS) → 'score-filter' (filter by score, sort by score)
|
|
6378
|
+
* - Mixed (binary + scored) → 'rrf' (Reciprocal Rank Fusion)
|
|
6379
|
+
*
|
|
6380
|
+
* @param steps - Plan steps to fuse
|
|
6381
|
+
* @returns Fusion strategy
|
|
6382
|
+
*/
|
|
6383
|
+
determineFusionStrategy(steps) {
|
|
6384
|
+
const hasScored = steps.some((s) => this.stepReturnsScored(s));
|
|
6385
|
+
const hasBinary = steps.some((s) => !this.stepReturnsScored(s));
|
|
6386
|
+
if (hasScored && hasBinary) {
|
|
6387
|
+
return "rrf";
|
|
6388
|
+
} else if (hasScored) {
|
|
6389
|
+
return "score-filter";
|
|
6390
|
+
} else {
|
|
6391
|
+
return "intersection";
|
|
6392
|
+
}
|
|
6393
|
+
}
|
|
6394
|
+
/**
|
|
6395
|
+
* Check if a plan step returns scored results (Phase 12).
|
|
6396
|
+
*/
|
|
6397
|
+
stepReturnsScored(step) {
|
|
6398
|
+
switch (step.type) {
|
|
6399
|
+
case "fts-scan":
|
|
6400
|
+
return true;
|
|
6401
|
+
case "fusion":
|
|
6402
|
+
return step.returnsScored;
|
|
6403
|
+
default:
|
|
6404
|
+
return false;
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6138
6407
|
/**
|
|
6139
6408
|
* Optimize a simple (attribute-based) query.
|
|
6140
6409
|
*/
|
|
@@ -6390,6 +6659,18 @@ var QueryOptimizer = class {
|
|
|
6390
6659
|
return this.estimateCost(step.source) + 10;
|
|
6391
6660
|
case "not":
|
|
6392
6661
|
return this.estimateCost(step.source) + 100;
|
|
6662
|
+
// Phase 12: FTS step types
|
|
6663
|
+
case "fts-scan":
|
|
6664
|
+
return step.estimatedCost;
|
|
6665
|
+
case "fusion":
|
|
6666
|
+
return step.steps.reduce((sum, s) => {
|
|
6667
|
+
const cost = this.estimateCost(s);
|
|
6668
|
+
if (cost === Number.MAX_SAFE_INTEGER) {
|
|
6669
|
+
return Number.MAX_SAFE_INTEGER;
|
|
6670
|
+
}
|
|
6671
|
+
return Math.min(sum + cost, Number.MAX_SAFE_INTEGER);
|
|
6672
|
+
}, 0) + 20;
|
|
6673
|
+
// Fusion overhead
|
|
6393
6674
|
default:
|
|
6394
6675
|
return Number.MAX_SAFE_INTEGER;
|
|
6395
6676
|
}
|
|
@@ -6410,6 +6691,12 @@ var QueryOptimizer = class {
|
|
|
6410
6691
|
return this.usesIndexes(step.source);
|
|
6411
6692
|
case "not":
|
|
6412
6693
|
return this.usesIndexes(step.source);
|
|
6694
|
+
// Phase 12: FTS step types
|
|
6695
|
+
case "fts-scan":
|
|
6696
|
+
return true;
|
|
6697
|
+
// FTS uses FullTextIndex
|
|
6698
|
+
case "fusion":
|
|
6699
|
+
return step.steps.some((s) => this.usesIndexes(s));
|
|
6413
6700
|
default:
|
|
6414
6701
|
return false;
|
|
6415
6702
|
}
|
|
@@ -7958,6 +8245,131 @@ var DefaultIndexingStrategy = class {
|
|
|
7958
8245
|
}
|
|
7959
8246
|
};
|
|
7960
8247
|
|
|
8248
|
+
// src/search/ReciprocalRankFusion.ts
|
|
8249
|
+
var ReciprocalRankFusion = class {
|
|
8250
|
+
constructor(config) {
|
|
8251
|
+
this.k = config?.k ?? 60;
|
|
8252
|
+
}
|
|
8253
|
+
/**
|
|
8254
|
+
* Merge multiple ranked result lists using RRF.
|
|
8255
|
+
*
|
|
8256
|
+
* Formula: RRF_score(d) = Σ 1 / (k + rank_i(d))
|
|
8257
|
+
*
|
|
8258
|
+
* @param resultSets - Array of ranked result lists from different search methods
|
|
8259
|
+
* @returns Merged results sorted by RRF score (descending)
|
|
8260
|
+
*/
|
|
8261
|
+
merge(resultSets) {
|
|
8262
|
+
const nonEmptySets = resultSets.filter((set) => set.length > 0);
|
|
8263
|
+
if (nonEmptySets.length === 0) {
|
|
8264
|
+
return [];
|
|
8265
|
+
}
|
|
8266
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
8267
|
+
for (const resultSet of nonEmptySets) {
|
|
8268
|
+
for (let rank = 0; rank < resultSet.length; rank++) {
|
|
8269
|
+
const result = resultSet[rank];
|
|
8270
|
+
const { docId, score, source } = result;
|
|
8271
|
+
const rrfContribution = 1 / (this.k + rank + 1);
|
|
8272
|
+
const existing = scoreMap.get(docId);
|
|
8273
|
+
if (existing) {
|
|
8274
|
+
existing.rrfScore += rrfContribution;
|
|
8275
|
+
existing.sources.add(source);
|
|
8276
|
+
existing.originalScores[source] = score;
|
|
8277
|
+
} else {
|
|
8278
|
+
scoreMap.set(docId, {
|
|
8279
|
+
rrfScore: rrfContribution,
|
|
8280
|
+
sources: /* @__PURE__ */ new Set([source]),
|
|
8281
|
+
originalScores: { [source]: score }
|
|
8282
|
+
});
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
}
|
|
8286
|
+
const merged = [];
|
|
8287
|
+
for (const [docId, data] of scoreMap) {
|
|
8288
|
+
merged.push({
|
|
8289
|
+
docId,
|
|
8290
|
+
score: data.rrfScore,
|
|
8291
|
+
source: Array.from(data.sources).sort().join("+"),
|
|
8292
|
+
originalScores: data.originalScores
|
|
8293
|
+
});
|
|
8294
|
+
}
|
|
8295
|
+
merged.sort((a, b) => b.score - a.score);
|
|
8296
|
+
return merged;
|
|
8297
|
+
}
|
|
8298
|
+
/**
|
|
8299
|
+
* Merge with weighted RRF for different method priorities.
|
|
8300
|
+
*
|
|
8301
|
+
* Weighted formula: RRF_score(d) = Σ weight_i * (1 / (k + rank_i(d)))
|
|
8302
|
+
*
|
|
8303
|
+
* @param resultSets - Array of ranked result lists
|
|
8304
|
+
* @param weights - Weights for each result set (same order as resultSets)
|
|
8305
|
+
* @returns Merged results sorted by weighted RRF score (descending)
|
|
8306
|
+
*
|
|
8307
|
+
* @example
|
|
8308
|
+
* ```typescript
|
|
8309
|
+
* const rrf = new ReciprocalRankFusion();
|
|
8310
|
+
*
|
|
8311
|
+
* // Prioritize exact matches (weight 2.0) over FTS (weight 1.0)
|
|
8312
|
+
* const merged = rrf.mergeWeighted(
|
|
8313
|
+
* [exactResults, ftsResults],
|
|
8314
|
+
* [2.0, 1.0]
|
|
8315
|
+
* );
|
|
8316
|
+
* ```
|
|
8317
|
+
*/
|
|
8318
|
+
mergeWeighted(resultSets, weights) {
|
|
8319
|
+
if (weights.length !== resultSets.length) {
|
|
8320
|
+
throw new Error(
|
|
8321
|
+
`Weights array length (${weights.length}) must match resultSets length (${resultSets.length})`
|
|
8322
|
+
);
|
|
8323
|
+
}
|
|
8324
|
+
const nonEmptyPairs = [];
|
|
8325
|
+
for (let i = 0; i < resultSets.length; i++) {
|
|
8326
|
+
if (resultSets[i].length > 0) {
|
|
8327
|
+
nonEmptyPairs.push({ resultSet: resultSets[i], weight: weights[i] });
|
|
8328
|
+
}
|
|
8329
|
+
}
|
|
8330
|
+
if (nonEmptyPairs.length === 0) {
|
|
8331
|
+
return [];
|
|
8332
|
+
}
|
|
8333
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
8334
|
+
for (const { resultSet, weight } of nonEmptyPairs) {
|
|
8335
|
+
for (let rank = 0; rank < resultSet.length; rank++) {
|
|
8336
|
+
const result = resultSet[rank];
|
|
8337
|
+
const { docId, score, source } = result;
|
|
8338
|
+
const rrfContribution = weight * (1 / (this.k + rank + 1));
|
|
8339
|
+
const existing = scoreMap.get(docId);
|
|
8340
|
+
if (existing) {
|
|
8341
|
+
existing.rrfScore += rrfContribution;
|
|
8342
|
+
existing.sources.add(source);
|
|
8343
|
+
existing.originalScores[source] = score;
|
|
8344
|
+
} else {
|
|
8345
|
+
scoreMap.set(docId, {
|
|
8346
|
+
rrfScore: rrfContribution,
|
|
8347
|
+
sources: /* @__PURE__ */ new Set([source]),
|
|
8348
|
+
originalScores: { [source]: score }
|
|
8349
|
+
});
|
|
8350
|
+
}
|
|
8351
|
+
}
|
|
8352
|
+
}
|
|
8353
|
+
const merged = [];
|
|
8354
|
+
for (const [docId, data] of scoreMap) {
|
|
8355
|
+
merged.push({
|
|
8356
|
+
docId,
|
|
8357
|
+
score: data.rrfScore,
|
|
8358
|
+
source: Array.from(data.sources).sort().join("+"),
|
|
8359
|
+
originalScores: data.originalScores
|
|
8360
|
+
});
|
|
8361
|
+
}
|
|
8362
|
+
merged.sort((a, b) => b.score - a.score);
|
|
8363
|
+
return merged;
|
|
8364
|
+
}
|
|
8365
|
+
/**
|
|
8366
|
+
* Get the k constant used for RRF calculation.
|
|
8367
|
+
*/
|
|
8368
|
+
getK() {
|
|
8369
|
+
return this.k;
|
|
8370
|
+
}
|
|
8371
|
+
};
|
|
8372
|
+
|
|
7961
8373
|
// src/IndexedLWWMap.ts
|
|
7962
8374
|
var IndexedLWWMap = class extends LWWMap {
|
|
7963
8375
|
constructor(hlc, options = {}) {
|
|
@@ -10638,6 +11050,7 @@ var IndexedORMap = class extends ORMap {
|
|
|
10638
11050
|
QuerySubMessageSchema,
|
|
10639
11051
|
QueryUnsubMessageSchema,
|
|
10640
11052
|
RESOLVER_FORBIDDEN_PATTERNS,
|
|
11053
|
+
ReciprocalRankFusion,
|
|
10641
11054
|
RegisterResolverRequestSchema,
|
|
10642
11055
|
RegisterResolverResponseSchema,
|
|
10643
11056
|
Ringbuffer,
|