@topgunbuild/client 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +231 -2
- package/dist/index.d.ts +231 -2
- package/dist/index.js +363 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +362 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ __export(index_exports, {
|
|
|
45
45
|
PartitionRouter: () => PartitionRouter,
|
|
46
46
|
Predicates: () => import_core9.Predicates,
|
|
47
47
|
QueryHandle: () => QueryHandle,
|
|
48
|
+
SearchHandle: () => SearchHandle,
|
|
48
49
|
SingleServerProvider: () => SingleServerProvider,
|
|
49
50
|
SyncEngine: () => SyncEngine,
|
|
50
51
|
SyncState: () => SyncState,
|
|
@@ -769,6 +770,11 @@ var _SyncEngine = class _SyncEngine {
|
|
|
769
770
|
// ============================================
|
|
770
771
|
/** Message listeners for journal and other generic messages */
|
|
771
772
|
this.messageListeners = /* @__PURE__ */ new Set();
|
|
773
|
+
// ============================================
|
|
774
|
+
// Full-Text Search Methods (Phase 11.1a)
|
|
775
|
+
// ============================================
|
|
776
|
+
/** Pending search requests by requestId */
|
|
777
|
+
this.pendingSearchRequests = /* @__PURE__ */ new Map();
|
|
772
778
|
if (!config.serverUrl && !config.connectionProvider) {
|
|
773
779
|
throw new Error("SyncEngine requires either serverUrl or connectionProvider");
|
|
774
780
|
}
|
|
@@ -1654,6 +1660,21 @@ var _SyncEngine = class _SyncEngine {
|
|
|
1654
1660
|
this.conflictResolverClient.handleMergeRejected(message);
|
|
1655
1661
|
break;
|
|
1656
1662
|
}
|
|
1663
|
+
// ============ Full-Text Search Message Handlers (Phase 11.1a) ============
|
|
1664
|
+
case "SEARCH_RESP": {
|
|
1665
|
+
logger.debug({ requestId: message.payload?.requestId, resultCount: message.payload?.results?.length }, "Received SEARCH_RESP");
|
|
1666
|
+
this.handleSearchResponse(message.payload);
|
|
1667
|
+
break;
|
|
1668
|
+
}
|
|
1669
|
+
// ============ Live Search Message Handlers (Phase 11.1b) ============
|
|
1670
|
+
case "SEARCH_UPDATE": {
|
|
1671
|
+
logger.debug({
|
|
1672
|
+
subscriptionId: message.payload?.subscriptionId,
|
|
1673
|
+
key: message.payload?.key,
|
|
1674
|
+
type: message.payload?.type
|
|
1675
|
+
}, "Received SEARCH_UPDATE");
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1657
1678
|
}
|
|
1658
1679
|
if (message.timestamp) {
|
|
1659
1680
|
this.hlc.update(message.timestamp);
|
|
@@ -2417,6 +2438,65 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2417
2438
|
}
|
|
2418
2439
|
}
|
|
2419
2440
|
}
|
|
2441
|
+
/**
|
|
2442
|
+
* Perform a one-shot BM25 search on the server.
|
|
2443
|
+
*
|
|
2444
|
+
* @param mapName Name of the map to search
|
|
2445
|
+
* @param query Search query text
|
|
2446
|
+
* @param options Search options (limit, minScore, boost)
|
|
2447
|
+
* @returns Promise resolving to search results
|
|
2448
|
+
*/
|
|
2449
|
+
async search(mapName, query, options) {
|
|
2450
|
+
if (!this.isAuthenticated()) {
|
|
2451
|
+
throw new Error("Not connected to server");
|
|
2452
|
+
}
|
|
2453
|
+
const requestId = crypto.randomUUID();
|
|
2454
|
+
return new Promise((resolve, reject) => {
|
|
2455
|
+
const timeout = setTimeout(() => {
|
|
2456
|
+
this.pendingSearchRequests.delete(requestId);
|
|
2457
|
+
reject(new Error("Search request timed out"));
|
|
2458
|
+
}, _SyncEngine.SEARCH_TIMEOUT);
|
|
2459
|
+
this.pendingSearchRequests.set(requestId, {
|
|
2460
|
+
resolve: (results) => {
|
|
2461
|
+
clearTimeout(timeout);
|
|
2462
|
+
resolve(results);
|
|
2463
|
+
},
|
|
2464
|
+
reject: (error) => {
|
|
2465
|
+
clearTimeout(timeout);
|
|
2466
|
+
reject(error);
|
|
2467
|
+
},
|
|
2468
|
+
timeout
|
|
2469
|
+
});
|
|
2470
|
+
const sent = this.sendMessage({
|
|
2471
|
+
type: "SEARCH",
|
|
2472
|
+
payload: {
|
|
2473
|
+
requestId,
|
|
2474
|
+
mapName,
|
|
2475
|
+
query,
|
|
2476
|
+
options
|
|
2477
|
+
}
|
|
2478
|
+
});
|
|
2479
|
+
if (!sent) {
|
|
2480
|
+
this.pendingSearchRequests.delete(requestId);
|
|
2481
|
+
clearTimeout(timeout);
|
|
2482
|
+
reject(new Error("Failed to send search request"));
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Handle search response from server.
|
|
2488
|
+
*/
|
|
2489
|
+
handleSearchResponse(payload) {
|
|
2490
|
+
const pending = this.pendingSearchRequests.get(payload.requestId);
|
|
2491
|
+
if (pending) {
|
|
2492
|
+
this.pendingSearchRequests.delete(payload.requestId);
|
|
2493
|
+
if (payload.error) {
|
|
2494
|
+
pending.reject(new Error(payload.error));
|
|
2495
|
+
} else {
|
|
2496
|
+
pending.resolve(payload.results);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2420
2500
|
// ============================================
|
|
2421
2501
|
// Conflict Resolver Client (Phase 5.05)
|
|
2422
2502
|
// ============================================
|
|
@@ -2430,6 +2510,8 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2430
2510
|
};
|
|
2431
2511
|
/** Default timeout for entry processor requests (ms) */
|
|
2432
2512
|
_SyncEngine.PROCESSOR_TIMEOUT = 3e4;
|
|
2513
|
+
/** Default timeout for search requests (ms) */
|
|
2514
|
+
_SyncEngine.SEARCH_TIMEOUT = 3e4;
|
|
2433
2515
|
var SyncEngine = _SyncEngine;
|
|
2434
2516
|
|
|
2435
2517
|
// src/TopGunClient.ts
|
|
@@ -3095,6 +3177,216 @@ var EventJournalReader = class {
|
|
|
3095
3177
|
}
|
|
3096
3178
|
};
|
|
3097
3179
|
|
|
3180
|
+
// src/SearchHandle.ts
|
|
3181
|
+
var SearchHandle = class {
|
|
3182
|
+
constructor(syncEngine, mapName, query, options) {
|
|
3183
|
+
/** Current results map (key → result) */
|
|
3184
|
+
this.results = /* @__PURE__ */ new Map();
|
|
3185
|
+
/** Result change listeners */
|
|
3186
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
3187
|
+
/** Whether the handle has been disposed */
|
|
3188
|
+
this.disposed = false;
|
|
3189
|
+
this.syncEngine = syncEngine;
|
|
3190
|
+
this.mapName = mapName;
|
|
3191
|
+
this._query = query;
|
|
3192
|
+
this._options = options;
|
|
3193
|
+
this.subscriptionId = crypto.randomUUID();
|
|
3194
|
+
this.messageHandler = this.handleMessage.bind(this);
|
|
3195
|
+
this.syncEngine.on("message", this.messageHandler);
|
|
3196
|
+
this.sendSubscribe();
|
|
3197
|
+
}
|
|
3198
|
+
/**
|
|
3199
|
+
* Handle incoming messages (both SEARCH_RESP and SEARCH_UPDATE).
|
|
3200
|
+
*/
|
|
3201
|
+
handleMessage(message) {
|
|
3202
|
+
if (message.type === "SEARCH_RESP") {
|
|
3203
|
+
this.handleSearchResponse(message);
|
|
3204
|
+
} else if (message.type === "SEARCH_UPDATE") {
|
|
3205
|
+
this.handleSearchUpdate(message);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
/**
|
|
3209
|
+
* Get the current query string.
|
|
3210
|
+
*/
|
|
3211
|
+
get query() {
|
|
3212
|
+
return this._query;
|
|
3213
|
+
}
|
|
3214
|
+
/**
|
|
3215
|
+
* Subscribe to result changes.
|
|
3216
|
+
* Callback is immediately called with current results.
|
|
3217
|
+
*
|
|
3218
|
+
* @param callback - Function called with updated results
|
|
3219
|
+
* @returns Unsubscribe function
|
|
3220
|
+
*/
|
|
3221
|
+
subscribe(callback) {
|
|
3222
|
+
if (this.disposed) {
|
|
3223
|
+
throw new Error("SearchHandle has been disposed");
|
|
3224
|
+
}
|
|
3225
|
+
this.listeners.add(callback);
|
|
3226
|
+
callback(this.getResults());
|
|
3227
|
+
return () => {
|
|
3228
|
+
this.listeners.delete(callback);
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
/**
|
|
3232
|
+
* Get current results snapshot sorted by score (highest first).
|
|
3233
|
+
*
|
|
3234
|
+
* @returns Array of search results
|
|
3235
|
+
*/
|
|
3236
|
+
getResults() {
|
|
3237
|
+
return Array.from(this.results.values()).sort((a, b) => b.score - a.score);
|
|
3238
|
+
}
|
|
3239
|
+
/**
|
|
3240
|
+
* Get result count.
|
|
3241
|
+
*/
|
|
3242
|
+
get size() {
|
|
3243
|
+
return this.results.size;
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* Update the search query.
|
|
3247
|
+
* Triggers a new subscription with the updated query.
|
|
3248
|
+
*
|
|
3249
|
+
* @param query - New query string
|
|
3250
|
+
*/
|
|
3251
|
+
setQuery(query) {
|
|
3252
|
+
if (this.disposed) {
|
|
3253
|
+
throw new Error("SearchHandle has been disposed");
|
|
3254
|
+
}
|
|
3255
|
+
if (query === this._query) {
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
this.sendUnsubscribe();
|
|
3259
|
+
this.results.clear();
|
|
3260
|
+
this._query = query;
|
|
3261
|
+
this.subscriptionId = crypto.randomUUID();
|
|
3262
|
+
this.sendSubscribe();
|
|
3263
|
+
this.notifyListeners();
|
|
3264
|
+
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Update search options.
|
|
3267
|
+
*
|
|
3268
|
+
* @param options - New search options
|
|
3269
|
+
*/
|
|
3270
|
+
setOptions(options) {
|
|
3271
|
+
if (this.disposed) {
|
|
3272
|
+
throw new Error("SearchHandle has been disposed");
|
|
3273
|
+
}
|
|
3274
|
+
this.sendUnsubscribe();
|
|
3275
|
+
this.results.clear();
|
|
3276
|
+
this._options = options;
|
|
3277
|
+
this.subscriptionId = crypto.randomUUID();
|
|
3278
|
+
this.sendSubscribe();
|
|
3279
|
+
this.notifyListeners();
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* Dispose of the handle and cleanup resources.
|
|
3283
|
+
* After disposal, the handle cannot be used.
|
|
3284
|
+
*/
|
|
3285
|
+
dispose() {
|
|
3286
|
+
if (this.disposed) {
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
this.disposed = true;
|
|
3290
|
+
this.sendUnsubscribe();
|
|
3291
|
+
this.syncEngine.off("message", this.messageHandler);
|
|
3292
|
+
this.results.clear();
|
|
3293
|
+
this.listeners.clear();
|
|
3294
|
+
}
|
|
3295
|
+
/**
|
|
3296
|
+
* Check if handle is disposed.
|
|
3297
|
+
*/
|
|
3298
|
+
isDisposed() {
|
|
3299
|
+
return this.disposed;
|
|
3300
|
+
}
|
|
3301
|
+
/**
|
|
3302
|
+
* Send SEARCH_SUB message to server.
|
|
3303
|
+
*/
|
|
3304
|
+
sendSubscribe() {
|
|
3305
|
+
this.syncEngine.send({
|
|
3306
|
+
type: "SEARCH_SUB",
|
|
3307
|
+
payload: {
|
|
3308
|
+
subscriptionId: this.subscriptionId,
|
|
3309
|
+
mapName: this.mapName,
|
|
3310
|
+
query: this._query,
|
|
3311
|
+
options: this._options
|
|
3312
|
+
}
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
/**
|
|
3316
|
+
* Send SEARCH_UNSUB message to server.
|
|
3317
|
+
*/
|
|
3318
|
+
sendUnsubscribe() {
|
|
3319
|
+
this.syncEngine.send({
|
|
3320
|
+
type: "SEARCH_UNSUB",
|
|
3321
|
+
payload: {
|
|
3322
|
+
subscriptionId: this.subscriptionId
|
|
3323
|
+
}
|
|
3324
|
+
});
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* Handle SEARCH_RESP message (initial results).
|
|
3328
|
+
*/
|
|
3329
|
+
handleSearchResponse(message) {
|
|
3330
|
+
if (message.type !== "SEARCH_RESP") return;
|
|
3331
|
+
if (message.payload?.requestId !== this.subscriptionId) return;
|
|
3332
|
+
const { results } = message.payload;
|
|
3333
|
+
if (Array.isArray(results)) {
|
|
3334
|
+
for (const result of results) {
|
|
3335
|
+
this.results.set(result.key, {
|
|
3336
|
+
key: result.key,
|
|
3337
|
+
value: result.value,
|
|
3338
|
+
score: result.score,
|
|
3339
|
+
matchedTerms: result.matchedTerms || []
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
this.notifyListeners();
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
/**
|
|
3346
|
+
* Handle SEARCH_UPDATE message (delta updates).
|
|
3347
|
+
*/
|
|
3348
|
+
handleSearchUpdate(message) {
|
|
3349
|
+
if (message.type !== "SEARCH_UPDATE") return;
|
|
3350
|
+
if (message.payload?.subscriptionId !== this.subscriptionId) return;
|
|
3351
|
+
const { key, value, score, matchedTerms, type } = message.payload;
|
|
3352
|
+
switch (type) {
|
|
3353
|
+
case "ENTER":
|
|
3354
|
+
this.results.set(key, {
|
|
3355
|
+
key,
|
|
3356
|
+
value,
|
|
3357
|
+
score,
|
|
3358
|
+
matchedTerms: matchedTerms || []
|
|
3359
|
+
});
|
|
3360
|
+
break;
|
|
3361
|
+
case "UPDATE":
|
|
3362
|
+
const existing = this.results.get(key);
|
|
3363
|
+
if (existing) {
|
|
3364
|
+
existing.score = score;
|
|
3365
|
+
existing.matchedTerms = matchedTerms || [];
|
|
3366
|
+
existing.value = value;
|
|
3367
|
+
}
|
|
3368
|
+
break;
|
|
3369
|
+
case "LEAVE":
|
|
3370
|
+
this.results.delete(key);
|
|
3371
|
+
break;
|
|
3372
|
+
}
|
|
3373
|
+
this.notifyListeners();
|
|
3374
|
+
}
|
|
3375
|
+
/**
|
|
3376
|
+
* Notify all listeners of result changes.
|
|
3377
|
+
*/
|
|
3378
|
+
notifyListeners() {
|
|
3379
|
+
const results = this.getResults();
|
|
3380
|
+
for (const listener of this.listeners) {
|
|
3381
|
+
try {
|
|
3382
|
+
listener(results);
|
|
3383
|
+
} catch (err) {
|
|
3384
|
+
console.error("SearchHandle listener error:", err);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
};
|
|
3389
|
+
|
|
3098
3390
|
// src/cluster/ClusterClient.ts
|
|
3099
3391
|
var import_core6 = require("@topgunbuild/core");
|
|
3100
3392
|
|
|
@@ -4908,6 +5200,76 @@ var TopGunClient = class {
|
|
|
4908
5200
|
return this.syncEngine.onBackpressure(event, listener);
|
|
4909
5201
|
}
|
|
4910
5202
|
// ============================================
|
|
5203
|
+
// Full-Text Search API (Phase 11.1a)
|
|
5204
|
+
// ============================================
|
|
5205
|
+
/**
|
|
5206
|
+
* Perform a one-shot BM25 search on the server.
|
|
5207
|
+
*
|
|
5208
|
+
* Searches the specified map using BM25 ranking algorithm.
|
|
5209
|
+
* Requires FTS to be enabled for the map on the server.
|
|
5210
|
+
*
|
|
5211
|
+
* @param mapName Name of the map to search
|
|
5212
|
+
* @param query Search query text
|
|
5213
|
+
* @param options Search options
|
|
5214
|
+
* @returns Promise resolving to search results sorted by relevance
|
|
5215
|
+
*
|
|
5216
|
+
* @example
|
|
5217
|
+
* ```typescript
|
|
5218
|
+
* const results = await client.search<Article>('articles', 'machine learning', {
|
|
5219
|
+
* limit: 20,
|
|
5220
|
+
* minScore: 0.5,
|
|
5221
|
+
* boost: { title: 2.0, body: 1.0 }
|
|
5222
|
+
* });
|
|
5223
|
+
*
|
|
5224
|
+
* for (const result of results) {
|
|
5225
|
+
* console.log(`${result.key}: ${result.value.title} (score: ${result.score})`);
|
|
5226
|
+
* }
|
|
5227
|
+
* ```
|
|
5228
|
+
*/
|
|
5229
|
+
async search(mapName, query, options) {
|
|
5230
|
+
return this.syncEngine.search(mapName, query, options);
|
|
5231
|
+
}
|
|
5232
|
+
// ============================================
|
|
5233
|
+
// Live Search API (Phase 11.1b)
|
|
5234
|
+
// ============================================
|
|
5235
|
+
/**
|
|
5236
|
+
* Subscribe to live search results with real-time updates.
|
|
5237
|
+
*
|
|
5238
|
+
* Unlike the one-shot `search()` method, `searchSubscribe()` returns a handle
|
|
5239
|
+
* that receives delta updates (ENTER/UPDATE/LEAVE) when documents change.
|
|
5240
|
+
* This is ideal for live search UIs that need to reflect data changes.
|
|
5241
|
+
*
|
|
5242
|
+
* @param mapName Name of the map to search
|
|
5243
|
+
* @param query Search query text
|
|
5244
|
+
* @param options Search options (limit, minScore, boost)
|
|
5245
|
+
* @returns SearchHandle for managing the subscription
|
|
5246
|
+
*
|
|
5247
|
+
* @example
|
|
5248
|
+
* ```typescript
|
|
5249
|
+
* const handle = client.searchSubscribe<Article>('articles', 'machine learning', {
|
|
5250
|
+
* limit: 20,
|
|
5251
|
+
* minScore: 0.5
|
|
5252
|
+
* });
|
|
5253
|
+
*
|
|
5254
|
+
* // Subscribe to result changes
|
|
5255
|
+
* const unsubscribe = handle.subscribe((results) => {
|
|
5256
|
+
* setSearchResults(results);
|
|
5257
|
+
* });
|
|
5258
|
+
*
|
|
5259
|
+
* // Update query dynamically
|
|
5260
|
+
* handle.setQuery('deep learning');
|
|
5261
|
+
*
|
|
5262
|
+
* // Get current snapshot
|
|
5263
|
+
* const snapshot = handle.getResults();
|
|
5264
|
+
*
|
|
5265
|
+
* // Cleanup when done
|
|
5266
|
+
* handle.dispose();
|
|
5267
|
+
* ```
|
|
5268
|
+
*/
|
|
5269
|
+
searchSubscribe(mapName, query, options) {
|
|
5270
|
+
return new SearchHandle(this.syncEngine, mapName, query, options);
|
|
5271
|
+
}
|
|
5272
|
+
// ============================================
|
|
4911
5273
|
// Entry Processor API (Phase 5.03)
|
|
4912
5274
|
// ============================================
|
|
4913
5275
|
/**
|
|
@@ -5521,6 +5883,7 @@ var import_core9 = require("@topgunbuild/core");
|
|
|
5521
5883
|
PartitionRouter,
|
|
5522
5884
|
Predicates,
|
|
5523
5885
|
QueryHandle,
|
|
5886
|
+
SearchHandle,
|
|
5524
5887
|
SingleServerProvider,
|
|
5525
5888
|
SyncEngine,
|
|
5526
5889
|
SyncState,
|