@topgunbuild/client 0.7.0 → 0.8.1

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { LWWRecord, ORMapRecord, PredicateNode, ConflictResolverDef, MergeRejection, Timestamp, LWWMap, ORMap, HLC, EntryProcessorDef, EntryProcessorResult, PNCounter, PNCounterState, JournalEvent, JournalEventType, NodeHealth, ConnectionPoolConfig, PartitionRouterConfig, PartitionMap, ClusterClientConfig } from '@topgunbuild/core';
1
+ import { LWWRecord, ORMapRecord, PredicateNode, ConflictResolverDef, MergeRejection, Timestamp, LWWMap, ORMap, HLC, EntryProcessorDef, EntryProcessorResult, SearchOptions, PNCounter, PNCounterState, JournalEvent, JournalEventType, NodeHealth, ConnectionPoolConfig, PartitionRouterConfig, PartitionMap, ClusterClientConfig } from '@topgunbuild/core';
2
2
  export { LWWMap, LWWRecord, PredicateNode, Predicates } from '@topgunbuild/core';
3
3
  import pino from 'pino';
4
4
 
@@ -188,6 +188,148 @@ declare class QueryHandle<T> {
188
188
  getMapName(): string;
189
189
  }
190
190
 
191
+ /**
192
+ * HybridQueryHandle - Query handle for hybrid FTS + filter queries
193
+ *
194
+ * Extends QueryHandle functionality to support:
195
+ * - FTS predicates (match, matchPhrase, matchPrefix)
196
+ * - Score-based sorting (_score field)
197
+ * - Hybrid queries combining FTS with traditional filters
198
+ *
199
+ * Part of Phase 12: Unified Search
200
+ *
201
+ * @module HybridQueryHandle
202
+ */
203
+
204
+ /**
205
+ * Filter options for hybrid queries.
206
+ */
207
+ interface HybridQueryFilter {
208
+ /** Traditional where clause filters */
209
+ where?: Record<string, any>;
210
+ /** Predicate tree (can include FTS predicates) */
211
+ predicate?: PredicateNode;
212
+ /** Sort configuration - use '_score' for FTS relevance sorting */
213
+ sort?: Record<string, 'asc' | 'desc'>;
214
+ /** Maximum results */
215
+ limit?: number;
216
+ /** Skip N results */
217
+ offset?: number;
218
+ }
219
+ /**
220
+ * Result item with score for hybrid queries.
221
+ */
222
+ interface HybridResultItem<T> {
223
+ /** The document */
224
+ value: T;
225
+ /** Unique key */
226
+ _key: string;
227
+ /** Relevance score (only for FTS queries) */
228
+ _score?: number;
229
+ /** Matched terms (only for FTS queries) */
230
+ _matchedTerms?: string[];
231
+ }
232
+ /**
233
+ * Source of query results.
234
+ */
235
+ type HybridResultSource = 'local' | 'server';
236
+ /**
237
+ * HybridQueryHandle manages hybrid queries that combine FTS with filters.
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * // Create hybrid query: FTS + filter
242
+ * const handle = new HybridQueryHandle(syncEngine, 'articles', {
243
+ * predicate: Predicates.and(
244
+ * Predicates.match('body', 'machine learning'),
245
+ * Predicates.equal('category', 'tech')
246
+ * ),
247
+ * sort: { _score: 'desc' },
248
+ * limit: 20
249
+ * });
250
+ *
251
+ * // Subscribe to results
252
+ * handle.subscribe((results) => {
253
+ * results.forEach(r => console.log(`${r._key}: ${r._score}`));
254
+ * });
255
+ * ```
256
+ */
257
+ declare class HybridQueryHandle<T> {
258
+ readonly id: string;
259
+ private syncEngine;
260
+ private mapName;
261
+ private filter;
262
+ private listeners;
263
+ private currentResults;
264
+ private changeTracker;
265
+ private pendingChanges;
266
+ private changeListeners;
267
+ private hasReceivedServerData;
268
+ constructor(syncEngine: SyncEngine, mapName: string, filter?: HybridQueryFilter);
269
+ /**
270
+ * Subscribe to query results.
271
+ */
272
+ subscribe(callback: (results: HybridResultItem<T>[]) => void): () => void;
273
+ private loadInitialLocalData;
274
+ /**
275
+ * Called by SyncEngine with query results.
276
+ */
277
+ onResult(items: Array<{
278
+ key: string;
279
+ value: T;
280
+ score?: number;
281
+ matchedTerms?: string[];
282
+ }>, source?: HybridResultSource): void;
283
+ /**
284
+ * Called by SyncEngine on live update.
285
+ */
286
+ onUpdate(key: string, value: T | null, score?: number, matchedTerms?: string[]): void;
287
+ /**
288
+ * Subscribe to change events.
289
+ */
290
+ onChanges(listener: (changes: ChangeEvent<T>[]) => void): () => void;
291
+ /**
292
+ * Get and clear pending changes.
293
+ */
294
+ consumeChanges(): ChangeEvent<T>[];
295
+ /**
296
+ * Get last change without consuming.
297
+ */
298
+ getLastChange(): ChangeEvent<T> | null;
299
+ /**
300
+ * Get all pending changes without consuming.
301
+ */
302
+ getPendingChanges(): ChangeEvent<T>[];
303
+ /**
304
+ * Clear all pending changes.
305
+ */
306
+ clearChanges(): void;
307
+ /**
308
+ * Reset change tracker.
309
+ */
310
+ resetChangeTracker(): void;
311
+ private computeAndNotifyChanges;
312
+ private notifyChangeListeners;
313
+ private notify;
314
+ /**
315
+ * Get sorted results with _key and _score.
316
+ */
317
+ private getSortedResults;
318
+ /**
319
+ * Get the filter configuration.
320
+ */
321
+ getFilter(): HybridQueryFilter;
322
+ /**
323
+ * Get the map name.
324
+ */
325
+ getMapName(): string;
326
+ /**
327
+ * Check if this query contains FTS predicates.
328
+ */
329
+ hasFTSPredicate(): boolean;
330
+ private containsFTS;
331
+ }
332
+
191
333
  type TopicCallback = (data: any, context: {
192
334
  timestamp: number;
193
335
  publisherId?: string;
@@ -639,6 +781,15 @@ declare class ConflictResolverClient {
639
781
  get rejectionListenerCount(): number;
640
782
  }
641
783
 
784
+ /**
785
+ * Search result item from server.
786
+ */
787
+ interface SearchResult<T> {
788
+ key: string;
789
+ value: T;
790
+ score: number;
791
+ matchedTerms: string[];
792
+ }
642
793
  interface HeartbeatConfig {
643
794
  intervalMs: number;
644
795
  timeoutMs: number;
@@ -1026,11 +1177,76 @@ declare class SyncEngine {
1026
1177
  * Called internally when a message is received.
1027
1178
  */
1028
1179
  private emitMessage;
1180
+ /** Pending search requests by requestId */
1181
+ private pendingSearchRequests;
1182
+ /** Default timeout for search requests (ms) */
1183
+ private static readonly SEARCH_TIMEOUT;
1184
+ /**
1185
+ * Perform a one-shot BM25 search on the server.
1186
+ *
1187
+ * @param mapName Name of the map to search
1188
+ * @param query Search query text
1189
+ * @param options Search options (limit, minScore, boost)
1190
+ * @returns Promise resolving to search results
1191
+ */
1192
+ search<T>(mapName: string, query: string, options?: SearchOptions): Promise<SearchResult<T>[]>;
1193
+ /**
1194
+ * Handle search response from server.
1195
+ */
1196
+ private handleSearchResponse;
1029
1197
  /**
1030
1198
  * Get the conflict resolver client for registering custom resolvers
1031
1199
  * and subscribing to merge rejection events.
1032
1200
  */
1033
1201
  getConflictResolverClient(): ConflictResolverClient;
1202
+ /** Active hybrid query subscriptions */
1203
+ private hybridQueries;
1204
+ /**
1205
+ * Subscribe to a hybrid query (FTS + filter combination).
1206
+ */
1207
+ subscribeToHybridQuery<T>(query: HybridQueryHandle<T>): void;
1208
+ /**
1209
+ * Unsubscribe from a hybrid query.
1210
+ */
1211
+ unsubscribeFromHybridQuery(queryId: string): void;
1212
+ /**
1213
+ * Send hybrid query subscription to server.
1214
+ */
1215
+ private sendHybridQuerySubscription;
1216
+ /**
1217
+ * Run a local hybrid query (FTS + filter combination).
1218
+ * For FTS predicates, returns results with score = 0 (local-only mode).
1219
+ * Server provides actual FTS scoring.
1220
+ */
1221
+ runLocalHybridQuery<T>(mapName: string, filter: HybridQueryFilter): Promise<Array<{
1222
+ key: string;
1223
+ value: T;
1224
+ score?: number;
1225
+ matchedTerms?: string[];
1226
+ }>>;
1227
+ /**
1228
+ * Handle hybrid query response from server.
1229
+ */
1230
+ handleHybridQueryResponse(payload: {
1231
+ subscriptionId: string;
1232
+ results: Array<{
1233
+ key: string;
1234
+ value: unknown;
1235
+ score: number;
1236
+ matchedTerms: string[];
1237
+ }>;
1238
+ }): void;
1239
+ /**
1240
+ * Handle hybrid query delta update from server.
1241
+ */
1242
+ handleHybridQueryDelta(payload: {
1243
+ subscriptionId: string;
1244
+ key: string;
1245
+ value: unknown | null;
1246
+ score?: number;
1247
+ matchedTerms?: string[];
1248
+ type: 'ENTER' | 'UPDATE' | 'LEAVE';
1249
+ }): void;
1034
1250
  }
1035
1251
 
1036
1252
  interface ILock {
@@ -1209,6 +1425,140 @@ declare class EventJournalReader {
1209
1425
  private generateRequestId;
1210
1426
  }
1211
1427
 
1428
+ /**
1429
+ * SearchHandle - Client-side Live Search Subscription Handle
1430
+ *
1431
+ * Manages a live search subscription with delta updates.
1432
+ * Part of Phase 11.1b: Live Search Subscriptions.
1433
+ *
1434
+ * @module SearchHandle
1435
+ */
1436
+
1437
+ /**
1438
+ * Callback type for result change notifications.
1439
+ */
1440
+ type SearchResultsCallback<T> = (results: SearchResult<T>[]) => void;
1441
+ /**
1442
+ * SearchHandle manages a live search subscription.
1443
+ *
1444
+ * Provides:
1445
+ * - Initial results on subscription
1446
+ * - Real-time delta updates (ENTER/UPDATE/LEAVE)
1447
+ * - Sorted results by relevance score
1448
+ * - Query update without re-subscribing
1449
+ *
1450
+ * @example
1451
+ * ```typescript
1452
+ * const handle = client.searchSubscribe<Article>('articles', 'machine learning', {
1453
+ * limit: 20,
1454
+ * minScore: 0.5
1455
+ * });
1456
+ *
1457
+ * // Subscribe to results
1458
+ * const unsubscribe = handle.subscribe((results) => {
1459
+ * console.log('Results updated:', results.length);
1460
+ * });
1461
+ *
1462
+ * // Get current snapshot
1463
+ * const snapshot = handle.getResults();
1464
+ *
1465
+ * // Update query
1466
+ * handle.setQuery('deep learning');
1467
+ *
1468
+ * // Cleanup
1469
+ * handle.dispose();
1470
+ * ```
1471
+ */
1472
+ declare class SearchHandle<T = unknown> {
1473
+ /** Map name being searched */
1474
+ readonly mapName: string;
1475
+ /** Current search query */
1476
+ private _query;
1477
+ /** Search options */
1478
+ private _options?;
1479
+ /** Unique subscription ID */
1480
+ private subscriptionId;
1481
+ /** Current results map (key → result) */
1482
+ private results;
1483
+ /** Result change listeners */
1484
+ private listeners;
1485
+ /** Whether the handle has been disposed */
1486
+ private disposed;
1487
+ /** Reference to SyncEngine */
1488
+ private syncEngine;
1489
+ /** Handler for all messages (SEARCH_RESP and SEARCH_UPDATE) */
1490
+ private messageHandler;
1491
+ constructor(syncEngine: SyncEngine, mapName: string, query: string, options?: SearchOptions);
1492
+ /**
1493
+ * Handle incoming messages (both SEARCH_RESP and SEARCH_UPDATE).
1494
+ */
1495
+ private handleMessage;
1496
+ /**
1497
+ * Get the current query string.
1498
+ */
1499
+ get query(): string;
1500
+ /**
1501
+ * Subscribe to result changes.
1502
+ * Callback is immediately called with current results.
1503
+ *
1504
+ * @param callback - Function called with updated results
1505
+ * @returns Unsubscribe function
1506
+ */
1507
+ subscribe(callback: SearchResultsCallback<T>): () => void;
1508
+ /**
1509
+ * Get current results snapshot sorted by score (highest first).
1510
+ *
1511
+ * @returns Array of search results
1512
+ */
1513
+ getResults(): SearchResult<T>[];
1514
+ /**
1515
+ * Get result count.
1516
+ */
1517
+ get size(): number;
1518
+ /**
1519
+ * Update the search query.
1520
+ * Triggers a new subscription with the updated query.
1521
+ *
1522
+ * @param query - New query string
1523
+ */
1524
+ setQuery(query: string): void;
1525
+ /**
1526
+ * Update search options.
1527
+ *
1528
+ * @param options - New search options
1529
+ */
1530
+ setOptions(options: SearchOptions): void;
1531
+ /**
1532
+ * Dispose of the handle and cleanup resources.
1533
+ * After disposal, the handle cannot be used.
1534
+ */
1535
+ dispose(): void;
1536
+ /**
1537
+ * Check if handle is disposed.
1538
+ */
1539
+ isDisposed(): boolean;
1540
+ /**
1541
+ * Send SEARCH_SUB message to server.
1542
+ */
1543
+ private sendSubscribe;
1544
+ /**
1545
+ * Send SEARCH_UNSUB message to server.
1546
+ */
1547
+ private sendUnsubscribe;
1548
+ /**
1549
+ * Handle SEARCH_RESP message (initial results).
1550
+ */
1551
+ private handleSearchResponse;
1552
+ /**
1553
+ * Handle SEARCH_UPDATE message (delta updates).
1554
+ */
1555
+ private handleSearchUpdate;
1556
+ /**
1557
+ * Notify all listeners of result changes.
1558
+ */
1559
+ private notifyListeners;
1560
+ }
1561
+
1212
1562
  /**
1213
1563
  * Cluster mode configuration for TopGunClient.
1214
1564
  * When provided, the client connects to multiple nodes with partition-aware routing.
@@ -1418,6 +1768,107 @@ declare class TopGunClient {
1418
1768
  * ```
1419
1769
  */
1420
1770
  onBackpressure(event: 'backpressure:high' | 'backpressure:low' | 'backpressure:paused' | 'backpressure:resumed' | 'operation:dropped', listener: (data?: BackpressureThresholdEvent | OperationDroppedEvent) => void): () => void;
1771
+ /**
1772
+ * Perform a one-shot BM25 search on the server.
1773
+ *
1774
+ * Searches the specified map using BM25 ranking algorithm.
1775
+ * Requires FTS to be enabled for the map on the server.
1776
+ *
1777
+ * @param mapName Name of the map to search
1778
+ * @param query Search query text
1779
+ * @param options Search options
1780
+ * @returns Promise resolving to search results sorted by relevance
1781
+ *
1782
+ * @example
1783
+ * ```typescript
1784
+ * const results = await client.search<Article>('articles', 'machine learning', {
1785
+ * limit: 20,
1786
+ * minScore: 0.5,
1787
+ * boost: { title: 2.0, body: 1.0 }
1788
+ * });
1789
+ *
1790
+ * for (const result of results) {
1791
+ * console.log(`${result.key}: ${result.value.title} (score: ${result.score})`);
1792
+ * }
1793
+ * ```
1794
+ */
1795
+ search<T>(mapName: string, query: string, options?: {
1796
+ limit?: number;
1797
+ minScore?: number;
1798
+ boost?: Record<string, number>;
1799
+ }): Promise<Array<{
1800
+ key: string;
1801
+ value: T;
1802
+ score: number;
1803
+ matchedTerms: string[];
1804
+ }>>;
1805
+ /**
1806
+ * Subscribe to live search results with real-time updates.
1807
+ *
1808
+ * Unlike the one-shot `search()` method, `searchSubscribe()` returns a handle
1809
+ * that receives delta updates (ENTER/UPDATE/LEAVE) when documents change.
1810
+ * This is ideal for live search UIs that need to reflect data changes.
1811
+ *
1812
+ * @param mapName Name of the map to search
1813
+ * @param query Search query text
1814
+ * @param options Search options (limit, minScore, boost)
1815
+ * @returns SearchHandle for managing the subscription
1816
+ *
1817
+ * @example
1818
+ * ```typescript
1819
+ * const handle = client.searchSubscribe<Article>('articles', 'machine learning', {
1820
+ * limit: 20,
1821
+ * minScore: 0.5
1822
+ * });
1823
+ *
1824
+ * // Subscribe to result changes
1825
+ * const unsubscribe = handle.subscribe((results) => {
1826
+ * setSearchResults(results);
1827
+ * });
1828
+ *
1829
+ * // Update query dynamically
1830
+ * handle.setQuery('deep learning');
1831
+ *
1832
+ * // Get current snapshot
1833
+ * const snapshot = handle.getResults();
1834
+ *
1835
+ * // Cleanup when done
1836
+ * handle.dispose();
1837
+ * ```
1838
+ */
1839
+ searchSubscribe<T>(mapName: string, query: string, options?: SearchOptions): SearchHandle<T>;
1840
+ /**
1841
+ * Create a hybrid query combining FTS with traditional filters.
1842
+ *
1843
+ * Hybrid queries allow combining full-text search predicates (match, matchPhrase, matchPrefix)
1844
+ * with traditional filter predicates (eq, gt, lt, contains, etc.) in a single query.
1845
+ * Results include relevance scores for FTS ranking.
1846
+ *
1847
+ * @param mapName Name of the map to query
1848
+ * @param filter Hybrid query filter with predicate, where, sort, limit, offset
1849
+ * @returns HybridQueryHandle for managing the subscription
1850
+ *
1851
+ * @example
1852
+ * ```typescript
1853
+ * import { Predicates } from '@topgunbuild/core';
1854
+ *
1855
+ * // Hybrid query: FTS + filter
1856
+ * const handle = client.hybridQuery<Article>('articles', {
1857
+ * predicate: Predicates.and(
1858
+ * Predicates.match('body', 'machine learning'),
1859
+ * Predicates.equal('category', 'tech')
1860
+ * ),
1861
+ * sort: { _score: 'desc' },
1862
+ * limit: 20
1863
+ * });
1864
+ *
1865
+ * // Subscribe to results
1866
+ * handle.subscribe((results) => {
1867
+ * results.forEach(r => console.log(`${r._key}: score=${r._score}`));
1868
+ * });
1869
+ * ```
1870
+ */
1871
+ hybridQuery<T>(mapName: string, filter?: HybridQueryFilter): HybridQueryHandle<T>;
1421
1872
  /**
1422
1873
  * Execute an entry processor on a single key atomically.
1423
1874
  *
@@ -2254,4 +2705,4 @@ declare class SingleServerProvider implements IConnectionProvider {
2254
2705
 
2255
2706
  declare const logger: pino.Logger<never, boolean>;
2256
2707
 
2257
- export { type BackoffConfig, type BackpressureConfig, BackpressureError, type BackpressureStatus, type BackpressureStrategy, type BackpressureThresholdEvent, type ChangeEvent, ChangeTracker, type CircuitState, ClusterClient, type ClusterClientEvents, type ClusterRoutingMode, ConflictResolverClient, type ConnectionEventHandler, ConnectionPool, type ConnectionPoolEvents, type ConnectionProviderEvent, DEFAULT_BACKPRESSURE_CONFIG, DEFAULT_CLUSTER_CONFIG, EncryptedStorageAdapter, EventJournalReader, type HeartbeatConfig, type IConnectionProvider, IDBAdapter, type IStorageAdapter, type JournalEventData, type JournalSubscribeOptions, type OpLogEntry, type OperationDroppedEvent, PNCounterHandle, PartitionRouter, type PartitionRouterEvents, type QueryFilter, QueryHandle, type QueryResultItem, type QueryResultSource, type RegisterResult, type ResolverInfo, type RoutingMetrics, type RoutingResult, SingleServerProvider, type SingleServerProviderConfig, type StateChangeEvent, type StateChangeListener, SyncEngine, type SyncEngineConfig, SyncState, SyncStateMachine, type SyncStateMachineConfig, TopGun, TopGunClient, type TopGunClientConfig, type TopGunClusterConfig, type TopicCallback, TopicHandle, VALID_TRANSITIONS, isValidTransition, logger };
2708
+ export { type BackoffConfig, type BackpressureConfig, BackpressureError, type BackpressureStatus, type BackpressureStrategy, type BackpressureThresholdEvent, type ChangeEvent, ChangeTracker, type CircuitState, ClusterClient, type ClusterClientEvents, type ClusterRoutingMode, ConflictResolverClient, type ConnectionEventHandler, ConnectionPool, type ConnectionPoolEvents, type ConnectionProviderEvent, DEFAULT_BACKPRESSURE_CONFIG, DEFAULT_CLUSTER_CONFIG, EncryptedStorageAdapter, EventJournalReader, type HeartbeatConfig, type HybridQueryFilter, HybridQueryHandle, type HybridResultItem, type HybridResultSource, type IConnectionProvider, IDBAdapter, type IStorageAdapter, type JournalEventData, type JournalSubscribeOptions, type OpLogEntry, type OperationDroppedEvent, PNCounterHandle, PartitionRouter, type PartitionRouterEvents, type QueryFilter, QueryHandle, type QueryResultItem, type QueryResultSource, type RegisterResult, type ResolverInfo, type RoutingMetrics, type RoutingResult, SearchHandle, type SearchResult, type SearchResultsCallback, SingleServerProvider, type SingleServerProviderConfig, type StateChangeEvent, type StateChangeListener, SyncEngine, type SyncEngineConfig, SyncState, SyncStateMachine, type SyncStateMachineConfig, TopGun, TopGunClient, type TopGunClientConfig, type TopGunClusterConfig, type TopicCallback, TopicHandle, VALID_TRANSITIONS, isValidTransition, logger };