@topgunbuild/server 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.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _topgunbuild_core from '@topgunbuild/core';
2
- import { Timestamp, LWWRecord, ORMapRecord, Principal, EventJournalImpl, EventJournalConfig, JournalEvent, PermissionPolicy, ConsistencyLevel, ReplicationConfig, LWWMap, ORMap, PermissionType, MigrationConfig, MigrationStatus, MigrationMetrics, PartitionMap, PartitionInfo, PartitionChange, ReplicationLag, ReplicationHealth, ReplicationResult, EntryProcessorDef, EntryProcessorResult, HLC, MergeRejection, ConflictResolverDef, MergeContext, MergeResult, IndexedLWWMap, IndexedORMap } from '@topgunbuild/core';
2
+ import { Timestamp, LWWRecord, ORMapRecord, Principal, EventJournalImpl, EventJournalConfig, JournalEvent, PermissionPolicy, ConsistencyLevel, ReplicationConfig, FullTextIndexConfig, LWWMap, ORMap, PermissionType, MigrationConfig, MigrationStatus, MigrationMetrics, PartitionMap, PartitionInfo, PartitionChange, ReplicationLag, ReplicationHealth, ReplicationResult, ClusterReadOptions, MerkleTree, EntryProcessorDef, EntryProcessorResult, HLC, MergeRejection, ConflictResolverDef, MergeContext, MergeResult, IndexedLWWMap, IndexedORMap, SearchUpdateType, FTSSearchOptions, SearchRespPayload, SearchOptions } from '@topgunbuild/core';
3
3
  import { WebSocket } from 'ws';
4
4
  import { Pool, PoolConfig } from 'pg';
5
5
  import pino from 'pino';
@@ -1858,6 +1858,8 @@ interface ServerCoordinatorConfig {
1858
1858
  eventJournalEnabled?: boolean;
1859
1859
  /** Event journal configuration */
1860
1860
  eventJournalConfig?: Partial<Omit<EventJournalServiceConfig, 'pool'>>;
1861
+ /** Enable full-text search for specific maps */
1862
+ fullTextSearch?: Record<string, FullTextIndexConfig>;
1861
1863
  }
1862
1864
  declare class ServerCoordinator {
1863
1865
  private httpServer;
@@ -1902,12 +1904,22 @@ declare class ServerCoordinator {
1902
1904
  private conflictResolverHandler;
1903
1905
  private eventJournalService?;
1904
1906
  private journalSubscriptions;
1907
+ private partitionReassigner?;
1908
+ private readReplicaHandler?;
1909
+ private merkleTreeManager?;
1910
+ private repairScheduler?;
1911
+ private searchCoordinator;
1905
1912
  private readonly _nodeId;
1906
1913
  private _actualPort;
1907
1914
  private _actualClusterPort;
1908
1915
  private _readyPromise;
1909
1916
  private _readyResolve;
1910
1917
  constructor(config: ServerCoordinatorConfig);
1918
+ /**
1919
+ * Populate FTS indexes from existing map data.
1920
+ * Called after storage initialization.
1921
+ */
1922
+ private backfillSearchIndexes;
1911
1923
  /** Wait for server to be fully ready (ports assigned) */
1912
1924
  ready(): Promise<void>;
1913
1925
  /**
@@ -1943,6 +1955,54 @@ declare class ServerCoordinator {
1943
1955
  getTaskletSchedulerStats(): TaskletSchedulerStats;
1944
1956
  /** Get tasklet scheduler for scheduling long-running operations */
1945
1957
  getTaskletScheduler(): TaskletScheduler;
1958
+ /**
1959
+ * Enable full-text search for a map.
1960
+ * Can be called at runtime to enable FTS dynamically.
1961
+ *
1962
+ * @param mapName - Name of the map to enable FTS for
1963
+ * @param config - FTS configuration (fields, tokenizer, bm25 options)
1964
+ */
1965
+ enableFullTextSearch(mapName: string, config: FullTextIndexConfig): void;
1966
+ /**
1967
+ * Disable full-text search for a map.
1968
+ *
1969
+ * @param mapName - Name of the map to disable FTS for
1970
+ */
1971
+ disableFullTextSearch(mapName: string): void;
1972
+ /**
1973
+ * Check if full-text search is enabled for a map.
1974
+ *
1975
+ * @param mapName - Name of the map to check
1976
+ * @returns True if FTS is enabled
1977
+ */
1978
+ isFullTextSearchEnabled(mapName: string): boolean;
1979
+ /**
1980
+ * Get FTS index statistics for a map.
1981
+ *
1982
+ * @param mapName - Name of the map
1983
+ * @returns Index stats or null if FTS not enabled
1984
+ */
1985
+ getFullTextSearchStats(mapName: string): {
1986
+ documentCount: number;
1987
+ fields: string[];
1988
+ } | null;
1989
+ /**
1990
+ * Phase 10.02: Graceful cluster departure
1991
+ *
1992
+ * Notifies the cluster that this node is leaving and allows time for:
1993
+ * 1. Pending replication to complete
1994
+ * 2. Other nodes to detect departure
1995
+ * 3. Partition reassignment to begin
1996
+ */
1997
+ private gracefulClusterDeparture;
1998
+ /**
1999
+ * Get list of partition IDs owned by this node
2000
+ */
2001
+ private getOwnedPartitions;
2002
+ /**
2003
+ * Wait for replication pipeline to flush pending operations
2004
+ */
2005
+ private waitForReplicationFlush;
1946
2006
  shutdown(): Promise<void>;
1947
2007
  private handleConnection;
1948
2008
  private handleMessage;
@@ -2052,6 +2112,16 @@ declare class ServerCoordinator {
2052
2112
  * Use this for queries to avoid returning empty results during initial load.
2053
2113
  */
2054
2114
  getMapAsync(name: string, typeHint?: 'LWW' | 'OR'): Promise<LWWMap<string, any> | ORMap<string, any>>;
2115
+ /**
2116
+ * Phase 10.04: Get local record for anti-entropy repair
2117
+ * Returns the LWWRecord for a key, used by RepairScheduler
2118
+ */
2119
+ private getLocalRecord;
2120
+ /**
2121
+ * Phase 10.04: Apply repaired record from anti-entropy repair
2122
+ * Used by RepairScheduler to apply resolved conflicts
2123
+ */
2124
+ private applyRepairRecord;
2055
2125
  private loadMapFromStorage;
2056
2126
  private startGarbageCollection;
2057
2127
  /**
@@ -2347,6 +2417,31 @@ interface FailureDetectorConfig {
2347
2417
  /** Initial heartbeat interval estimate (ms). Default: 1000 */
2348
2418
  initialHeartbeatIntervalMs: number;
2349
2419
  }
2420
+ declare const DEFAULT_FAILURE_DETECTOR_CONFIG: FailureDetectorConfig;
2421
+ interface NodeState {
2422
+ /** Last heartbeat timestamp */
2423
+ lastHeartbeat: number;
2424
+ /** Heartbeat interval history for phi calculation */
2425
+ intervalHistory: number[];
2426
+ /** Whether node is currently suspected */
2427
+ isSuspected: boolean;
2428
+ /** Timestamp when suspicion started */
2429
+ suspicionStartTime?: number;
2430
+ /** Whether failure has been confirmed */
2431
+ isConfirmedFailed: boolean;
2432
+ }
2433
+ interface FailureDetectorEvents {
2434
+ nodeSuspected: {
2435
+ nodeId: string;
2436
+ phi: number;
2437
+ };
2438
+ nodeRecovered: {
2439
+ nodeId: string;
2440
+ };
2441
+ nodeConfirmedFailed: {
2442
+ nodeId: string;
2443
+ };
2444
+ }
2350
2445
  declare class FailureDetector extends EventEmitter {
2351
2446
  private config;
2352
2447
  private nodeStates;
@@ -2460,7 +2555,7 @@ interface ClusterMember {
2460
2555
  isSelf: boolean;
2461
2556
  }
2462
2557
  interface ClusterMessage {
2463
- type: 'HELLO' | 'OP_FORWARD' | 'PARTITION_UPDATE' | 'HEARTBEAT' | 'CLUSTER_EVENT' | 'CLUSTER_QUERY_EXEC' | 'CLUSTER_QUERY_RESP' | 'CLUSTER_GC_REPORT' | 'CLUSTER_GC_COMMIT' | 'CLUSTER_LOCK_REQ' | 'CLUSTER_LOCK_RELEASE' | 'CLUSTER_LOCK_GRANTED' | 'CLUSTER_LOCK_RELEASED' | 'CLUSTER_CLIENT_DISCONNECTED' | 'CLUSTER_TOPIC_PUB';
2558
+ type: 'HELLO' | 'MEMBER_LIST' | 'OP_FORWARD' | 'PARTITION_UPDATE' | 'HEARTBEAT' | 'CLUSTER_EVENT' | 'CLUSTER_QUERY_EXEC' | 'CLUSTER_QUERY_RESP' | 'CLUSTER_GC_REPORT' | 'CLUSTER_GC_COMMIT' | 'CLUSTER_LOCK_REQ' | 'CLUSTER_LOCK_RELEASE' | 'CLUSTER_LOCK_GRANTED' | 'CLUSTER_LOCK_RELEASED' | 'CLUSTER_CLIENT_DISCONNECTED' | 'CLUSTER_TOPIC_PUB' | 'CLUSTER_MERKLE_ROOT_REQ' | 'CLUSTER_MERKLE_ROOT_RESP' | 'CLUSTER_MERKLE_BUCKETS_REQ' | 'CLUSTER_MERKLE_BUCKETS_RESP' | 'CLUSTER_MERKLE_KEYS_REQ' | 'CLUSTER_MERKLE_KEYS_RESP' | 'CLUSTER_REPAIR_DATA_REQ' | 'CLUSTER_REPAIR_DATA_RESP';
2464
2559
  senderId: string;
2465
2560
  payload: any;
2466
2561
  }
@@ -2501,6 +2596,21 @@ declare class ClusterManager extends EventEmitter {
2501
2596
  * Handle incoming heartbeat from a peer.
2502
2597
  */
2503
2598
  private handleHeartbeat;
2599
+ /**
2600
+ * Send current member list to a specific node (gossip protocol).
2601
+ * Called when a new node joins to propagate cluster topology.
2602
+ */
2603
+ private sendMemberList;
2604
+ /**
2605
+ * Broadcast member list to all connected nodes.
2606
+ * Called when cluster membership changes.
2607
+ */
2608
+ private broadcastMemberList;
2609
+ /**
2610
+ * Handle incoming member list from a peer (gossip protocol).
2611
+ * Attempts to connect to unknown members.
2612
+ */
2613
+ private handleMemberList;
2504
2614
  /**
2505
2615
  * Handle confirmed node failure.
2506
2616
  */
@@ -3187,6 +3297,505 @@ declare class ClusterCoordinator extends EventEmitter {
3187
3297
  private setupEventHandlers;
3188
3298
  }
3189
3299
 
3300
+ /**
3301
+ * PartitionReassigner - Automatic Partition Failover
3302
+ *
3303
+ * Handles automatic reassignment of partitions when nodes fail:
3304
+ * - Promotes backup nodes to owners
3305
+ * - Assigns new backups from remaining cluster
3306
+ * - Coordinates with MigrationManager for data transfer
3307
+ * - Broadcasts partition table updates
3308
+ *
3309
+ * This is Phase 10.02 of the TopGun cluster enhancements.
3310
+ */
3311
+
3312
+ interface PartitionReassignerConfig {
3313
+ /** Delay before reassigning partitions after failure detection (ms). Default: 1000 */
3314
+ reassignmentDelayMs: number;
3315
+ /** Maximum concurrent partition transfers. Default: 10 */
3316
+ maxConcurrentTransfers: number;
3317
+ /** Enable automatic backup promotion. Default: true */
3318
+ autoPromoteBackups: boolean;
3319
+ /** Enable automatic new backup assignment. Default: true */
3320
+ autoAssignNewBackups: boolean;
3321
+ }
3322
+ declare const DEFAULT_REASSIGNER_CONFIG: PartitionReassignerConfig;
3323
+ interface ReassignmentEvent {
3324
+ type: 'backup-promoted' | 'new-backup-assigned' | 'reassignment-complete';
3325
+ partitionId: number;
3326
+ previousOwner?: string;
3327
+ newOwner?: string;
3328
+ backups?: string[];
3329
+ failedNodeId?: string;
3330
+ }
3331
+ interface FailoverStatus {
3332
+ inProgress: boolean;
3333
+ failedNodeId?: string;
3334
+ partitionsReassigned: number;
3335
+ partitionsPending: number;
3336
+ startedAt?: number;
3337
+ completedAt?: number;
3338
+ }
3339
+ declare class PartitionReassigner extends EventEmitter {
3340
+ private config;
3341
+ private clusterManager;
3342
+ private partitionService;
3343
+ private failoverInProgress;
3344
+ private currentFailedNode?;
3345
+ private reassignmentStartTime?;
3346
+ private partitionsReassigned;
3347
+ private pendingReassignments;
3348
+ private reassignmentTimer?;
3349
+ constructor(clusterManager: ClusterManager, partitionService: PartitionService, config?: Partial<PartitionReassignerConfig>);
3350
+ private setupEventHandlers;
3351
+ /**
3352
+ * Handle a node failure - initiates failover process
3353
+ */
3354
+ private handleNodeFailure;
3355
+ /**
3356
+ * Handle a graceful node departure
3357
+ */
3358
+ private handleNodeDeparture;
3359
+ /**
3360
+ * Execute the failover process for a failed node
3361
+ */
3362
+ private executeFailover;
3363
+ /**
3364
+ * Find all partitions that need reassignment
3365
+ */
3366
+ private findOrphanedPartitions;
3367
+ /**
3368
+ * Reassign a single partition
3369
+ */
3370
+ private reassignPartition;
3371
+ /**
3372
+ * Select backup nodes for a partition
3373
+ */
3374
+ private selectBackups;
3375
+ /**
3376
+ * Complete the failover process
3377
+ */
3378
+ private completeFailover;
3379
+ /**
3380
+ * Get current failover status
3381
+ */
3382
+ getStatus(): FailoverStatus;
3383
+ /**
3384
+ * Check if failover is in progress
3385
+ */
3386
+ isFailoverInProgress(): boolean;
3387
+ /**
3388
+ * Force immediate reassignment (for testing/manual intervention)
3389
+ */
3390
+ forceReassignment(failedNodeId: string): void;
3391
+ /**
3392
+ * Stop any pending reassignment
3393
+ */
3394
+ stop(): void;
3395
+ }
3396
+
3397
+ /**
3398
+ * ReadReplicaHandler - Read Scaling via Replicas
3399
+ *
3400
+ * Phase 10.03: Enables reading from backup nodes to:
3401
+ * - Scale read throughput linearly with replicas
3402
+ * - Reduce latency by reading from nearest replica
3403
+ * - Provide availability during owner unavailability
3404
+ *
3405
+ * Supports three consistency levels for reads:
3406
+ * - STRONG: Read from partition owner (current behavior)
3407
+ * - EVENTUAL: Read from any replica (owner or backup)
3408
+ * - LOCAL: Read from local node if it's a replica
3409
+ */
3410
+
3411
+ interface ReadReplicaConfig {
3412
+ /** Default consistency for reads. Default: STRONG */
3413
+ defaultConsistency: ConsistencyLevel;
3414
+ /** Maximum staleness for eventual reads in ms. Default: 5000 */
3415
+ maxStalenessMs: number;
3416
+ /** Prefer local replica over remote. Default: true */
3417
+ preferLocalReplica: boolean;
3418
+ /** Load balancing strategy for replica selection. Default: 'latency-based' */
3419
+ loadBalancing: 'round-robin' | 'least-connections' | 'latency-based';
3420
+ }
3421
+ declare const DEFAULT_READ_REPLICA_CONFIG: ReadReplicaConfig;
3422
+ interface ReadResult<T> {
3423
+ value: T | null;
3424
+ version?: Timestamp;
3425
+ source: string;
3426
+ isOwner: boolean;
3427
+ staleness?: number;
3428
+ }
3429
+ interface ReadRequest {
3430
+ mapName: string;
3431
+ key: string;
3432
+ options?: ClusterReadOptions;
3433
+ }
3434
+ declare class ReadReplicaHandler extends EventEmitter {
3435
+ private config;
3436
+ private partitionService;
3437
+ private clusterManager;
3438
+ private lagTracker?;
3439
+ private nodeId;
3440
+ private roundRobinCounters;
3441
+ constructor(partitionService: PartitionService, clusterManager: ClusterManager, nodeId: string, lagTracker?: LagTracker, config?: Partial<ReadReplicaConfig>);
3442
+ /**
3443
+ * Determine if a read request can be served locally
3444
+ */
3445
+ canServeLocally(request: ReadRequest): boolean;
3446
+ /**
3447
+ * Determine which node should handle the read
3448
+ */
3449
+ selectReadNode(request: ReadRequest): string | null;
3450
+ /**
3451
+ * Select replica using configured load balancing strategy
3452
+ */
3453
+ private selectByStrategy;
3454
+ /**
3455
+ * Round-robin selection
3456
+ */
3457
+ private selectRoundRobin;
3458
+ /**
3459
+ * Latency-based selection using lag tracker
3460
+ */
3461
+ private selectByLatency;
3462
+ /**
3463
+ * Get estimated staleness for a node in ms
3464
+ */
3465
+ private getNodeStaleness;
3466
+ /**
3467
+ * Check if a node is alive in the cluster
3468
+ */
3469
+ private isNodeAlive;
3470
+ /**
3471
+ * Select first alive backup from list
3472
+ */
3473
+ private selectAliveBackup;
3474
+ /**
3475
+ * Create read response metadata
3476
+ */
3477
+ createReadMetadata(key: string, options?: ClusterReadOptions): {
3478
+ source: string;
3479
+ isOwner: boolean;
3480
+ consistency: ConsistencyLevel;
3481
+ };
3482
+ /**
3483
+ * Check if local node should forward read to owner
3484
+ */
3485
+ shouldForwardRead(request: ReadRequest): boolean;
3486
+ /**
3487
+ * Get metrics for monitoring
3488
+ */
3489
+ getMetrics(): {
3490
+ defaultConsistency: ConsistencyLevel;
3491
+ preferLocalReplica: boolean;
3492
+ loadBalancing: string;
3493
+ roundRobinPartitions: number;
3494
+ };
3495
+ }
3496
+
3497
+ /**
3498
+ * MerkleTreeManager - Per-Partition Merkle Tree Management
3499
+ *
3500
+ * Phase 10.04: Manages Merkle trees for each partition to enable:
3501
+ * - Efficient delta sync between nodes
3502
+ * - Anti-entropy repair detection
3503
+ * - Incremental updates on writes
3504
+ *
3505
+ * Each partition maintains its own Merkle tree for independent
3506
+ * consistency checking and repair.
3507
+ */
3508
+
3509
+ interface MerkleTreeManagerConfig {
3510
+ /** Tree depth for Merkle trees. Default: 3 */
3511
+ treeDepth: number;
3512
+ /** Enable automatic tree updates on write. Default: true */
3513
+ autoUpdate: boolean;
3514
+ /** Lazy initialization of trees. Default: true */
3515
+ lazyInit: boolean;
3516
+ }
3517
+ declare const DEFAULT_MERKLE_TREE_CONFIG: MerkleTreeManagerConfig;
3518
+ interface MerkleComparisonResult {
3519
+ partitionId: number;
3520
+ localRoot: number;
3521
+ remoteRoot: number;
3522
+ needsSync: boolean;
3523
+ differingBuckets: string[];
3524
+ }
3525
+ interface PartitionMerkleInfo {
3526
+ partitionId: number;
3527
+ rootHash: number;
3528
+ keyCount: number;
3529
+ lastUpdated: number;
3530
+ }
3531
+ declare class MerkleTreeManager extends EventEmitter {
3532
+ private config;
3533
+ private trees;
3534
+ private keyCounts;
3535
+ private lastUpdated;
3536
+ private nodeId;
3537
+ constructor(nodeId: string, config?: Partial<MerkleTreeManagerConfig>);
3538
+ /**
3539
+ * Get or create a Merkle tree for a partition
3540
+ */
3541
+ getTree(partitionId: number): MerkleTree;
3542
+ /**
3543
+ * Build tree for a partition from existing data
3544
+ */
3545
+ buildTree(partitionId: number, records: Map<string, LWWRecord<any>>): void;
3546
+ /**
3547
+ * Incrementally update tree when a record changes
3548
+ */
3549
+ updateRecord(partitionId: number, key: string, record: LWWRecord<any>): void;
3550
+ /**
3551
+ * Remove a key from the tree (e.g., after GC)
3552
+ */
3553
+ removeRecord(partitionId: number, key: string): void;
3554
+ /**
3555
+ * Get the path prefix for a key in the Merkle tree
3556
+ */
3557
+ private getKeyPath;
3558
+ /**
3559
+ * Get root hash for a partition
3560
+ */
3561
+ getRootHash(partitionId: number): number;
3562
+ /**
3563
+ * Compare local tree with remote root hash
3564
+ */
3565
+ compareWithRemote(partitionId: number, remoteRoot: number): MerkleComparisonResult;
3566
+ /**
3567
+ * Find buckets that differ between local and remote tree
3568
+ * Note: This is a simplified version - full implementation would
3569
+ * need to exchange bucket hashes with the remote node
3570
+ */
3571
+ private findDifferingBuckets;
3572
+ /**
3573
+ * Recursively collect all leaf bucket paths
3574
+ */
3575
+ private collectLeafBuckets;
3576
+ /**
3577
+ * Get bucket hashes for a partition at a given path
3578
+ */
3579
+ getBuckets(partitionId: number, path: string): Record<string, number>;
3580
+ /**
3581
+ * Get keys in a specific bucket
3582
+ */
3583
+ getKeysInBucket(partitionId: number, path: string): string[];
3584
+ /**
3585
+ * Get all keys across all buckets for a partition
3586
+ */
3587
+ getAllKeys(partitionId: number): string[];
3588
+ /**
3589
+ * Recursively collect all keys from the tree
3590
+ */
3591
+ private collectAllKeys;
3592
+ /**
3593
+ * Get info about all managed partitions
3594
+ */
3595
+ getPartitionInfos(): PartitionMerkleInfo[];
3596
+ /**
3597
+ * Get info for a specific partition
3598
+ */
3599
+ getPartitionInfo(partitionId: number): PartitionMerkleInfo | null;
3600
+ /**
3601
+ * Clear tree for a partition (e.g., after migration)
3602
+ */
3603
+ clearPartition(partitionId: number): void;
3604
+ /**
3605
+ * Clear all trees
3606
+ */
3607
+ clearAll(): void;
3608
+ /**
3609
+ * Get metrics for monitoring
3610
+ */
3611
+ getMetrics(): {
3612
+ totalPartitions: number;
3613
+ totalKeys: number;
3614
+ averageKeysPerPartition: number;
3615
+ };
3616
+ /**
3617
+ * Serialize tree state for network transfer
3618
+ */
3619
+ serializeTree(partitionId: number): {
3620
+ rootHash: number;
3621
+ buckets: Record<string, Record<string, number>>;
3622
+ } | null;
3623
+ private collectBucketsAtDepth;
3624
+ }
3625
+
3626
+ /**
3627
+ * RepairScheduler - Anti-Entropy Repair System
3628
+ *
3629
+ * Phase 10.04: Proactively detects and repairs data inconsistencies:
3630
+ * - Periodic scanning of partitions
3631
+ * - Merkle tree-based difference detection
3632
+ * - LWW conflict resolution
3633
+ * - Throttled repair execution
3634
+ *
3635
+ * Based on Cassandra/Dynamo anti-entropy patterns.
3636
+ */
3637
+
3638
+ interface RepairConfig {
3639
+ /** Enable anti-entropy repair. Default: true */
3640
+ enabled: boolean;
3641
+ /** Interval between full scans in ms. Default: 3600000 (1 hour) */
3642
+ scanIntervalMs: number;
3643
+ /** Keys per repair batch. Default: 1000 */
3644
+ repairBatchSize: number;
3645
+ /** Maximum concurrent partition repairs. Default: 2 */
3646
+ maxConcurrentRepairs: number;
3647
+ /** Delay between batches in ms. Default: 100 */
3648
+ throttleMs: number;
3649
+ /** Prioritize recently modified partitions. Default: true */
3650
+ prioritizeRecent: boolean;
3651
+ /** Timeout for network requests in ms. Default: 5000 */
3652
+ requestTimeoutMs: number;
3653
+ }
3654
+ declare const DEFAULT_REPAIR_CONFIG: RepairConfig;
3655
+ interface RepairTask {
3656
+ partitionId: number;
3657
+ replicaNodeId: string;
3658
+ priority: 'high' | 'normal' | 'low';
3659
+ scheduledAt: number;
3660
+ }
3661
+ interface RepairResult {
3662
+ partitionId: number;
3663
+ replicaNodeId: string;
3664
+ keysScanned: number;
3665
+ keysRepaired: number;
3666
+ durationMs: number;
3667
+ success: boolean;
3668
+ error?: string;
3669
+ }
3670
+ interface RepairMetrics {
3671
+ scansCompleted: number;
3672
+ repairsExecuted: number;
3673
+ keysRepaired: number;
3674
+ errorsEncountered: number;
3675
+ lastScanTime?: number;
3676
+ averageRepairDurationMs: number;
3677
+ }
3678
+ type RecordGetter = (key: string) => LWWRecord<any> | undefined;
3679
+ type RecordSetter = (key: string, record: LWWRecord<any>) => void;
3680
+ declare class RepairScheduler extends EventEmitter {
3681
+ private config;
3682
+ private merkleManager;
3683
+ private clusterManager;
3684
+ private partitionService;
3685
+ private nodeId;
3686
+ private repairQueue;
3687
+ private activeRepairs;
3688
+ private scanTimer?;
3689
+ private processTimer?;
3690
+ private started;
3691
+ private pendingRequests;
3692
+ private metrics;
3693
+ private getRecord?;
3694
+ private setRecord?;
3695
+ constructor(merkleManager: MerkleTreeManager, clusterManager: ClusterManager, partitionService: PartitionService, nodeId: string, config?: Partial<RepairConfig>);
3696
+ /**
3697
+ * Set data access callbacks
3698
+ */
3699
+ setDataAccessors(getRecord: RecordGetter, setRecord: RecordSetter): void;
3700
+ /**
3701
+ * Setup network message handlers
3702
+ */
3703
+ private setupNetworkHandlers;
3704
+ /**
3705
+ * Handle incoming cluster messages
3706
+ */
3707
+ private handleClusterMessage;
3708
+ private handleMerkleRootReq;
3709
+ private handleMerkleBucketsReq;
3710
+ private handleMerkleKeysReq;
3711
+ private handleRepairDataReq;
3712
+ private handleResponse;
3713
+ /**
3714
+ * Start the repair scheduler
3715
+ */
3716
+ start(): void;
3717
+ /**
3718
+ * Stop the repair scheduler
3719
+ */
3720
+ stop(): void;
3721
+ /**
3722
+ * Schedule a full scan of all owned partitions
3723
+ */
3724
+ scheduleFullScan(): void;
3725
+ /**
3726
+ * Schedule repair for a specific partition
3727
+ */
3728
+ schedulePartitionRepair(partitionId: number, priority?: 'high' | 'normal' | 'low'): void;
3729
+ /**
3730
+ * Sort repair queue by priority
3731
+ */
3732
+ private sortRepairQueue;
3733
+ /**
3734
+ * Process the repair queue
3735
+ */
3736
+ private processRepairQueue;
3737
+ /**
3738
+ * Execute repair for a partition-replica pair
3739
+ */
3740
+ private executeRepair;
3741
+ /**
3742
+ * Send a request and wait for response
3743
+ */
3744
+ private sendRequest;
3745
+ /**
3746
+ * Request Merkle root from remote node
3747
+ */
3748
+ private requestRemoteMerkleRoot;
3749
+ /**
3750
+ * Find keys that differ between local and remote using bucket exchange
3751
+ */
3752
+ private findDifferences;
3753
+ /**
3754
+ * Repair a single key
3755
+ */
3756
+ private repairKey;
3757
+ /**
3758
+ * Resolve conflict between two records using LWW
3759
+ */
3760
+ resolveConflict<T>(a: LWWRecord<T> | undefined, b: LWWRecord<T> | undefined): LWWRecord<T> | null;
3761
+ /**
3762
+ * Compare two timestamps
3763
+ */
3764
+ private compareTimestamps;
3765
+ /**
3766
+ * Get partitions owned by this node
3767
+ */
3768
+ private getOwnedPartitions;
3769
+ /**
3770
+ * Get partitions where this node is a backup
3771
+ */
3772
+ private getReplicaPartitions;
3773
+ /**
3774
+ * Update average repair duration
3775
+ */
3776
+ private updateAverageRepairDuration;
3777
+ /**
3778
+ * Get repair metrics
3779
+ */
3780
+ getMetrics(): RepairMetrics;
3781
+ /**
3782
+ * Get repair queue status
3783
+ */
3784
+ getQueueStatus(): {
3785
+ queueLength: number;
3786
+ activeRepairs: number;
3787
+ maxConcurrent: number;
3788
+ };
3789
+ /**
3790
+ * Force immediate repair for a partition
3791
+ */
3792
+ forceRepair(partitionId: number): void;
3793
+ /**
3794
+ * Sleep utility
3795
+ */
3796
+ private sleep;
3797
+ }
3798
+
3190
3799
  /**
3191
3800
  * Configuration for the processor sandbox.
3192
3801
  */
@@ -3872,4 +4481,266 @@ declare class MapFactory {
3872
4481
  getConfig(): ServerIndexConfig;
3873
4482
  }
3874
4483
 
3875
- export { BufferPool, type BufferPoolConfig, type BufferPoolStats, type ClusterConfig, ClusterCoordinator, type ClusterCoordinatorConfig, type ClusterCoordinatorEvents, ClusterManager, type ClusterMember, type ClusterMessage, type CoalescingPreset, type CoalescingWriterMetrics, type CoalescingWriterOptions, ConflictResolverHandler, type ConflictResolverHandlerConfig, ConflictResolverService, type ConflictResolverServiceConfig, type ConnectionContext, ConnectionRateLimiter, DEFAULT_CLUSTER_COORDINATOR_CONFIG, DEFAULT_CONFLICT_RESOLVER_CONFIG, DEFAULT_INDEX_CONFIG, DEFAULT_JOURNAL_SERVICE_CONFIG, DEFAULT_LAG_TRACKER_CONFIG, DEFAULT_SANDBOX_CONFIG, EntryProcessorHandler, type EntryProcessorHandlerConfig, EventJournalService, type EventJournalServiceConfig, type ExportOptions, FilterTasklet, ForEachTasklet, type IInterceptor, type IServerStorage, type IndexDefinition, IteratorTasklet, type IteratorTaskletConfig, type LagInfo, LagTracker, type LagTrackerConfig, LockManager, type Logger, MapFactory, type MapIndexConfig, MapTasklet, MapWithResolver, type MapWithResolverConfig, MemoryServerAdapter, type MergeWithResolverResult, MigrationManager, type NativeModuleStatus, type NativeStats, type ORMapTombstones, type ORMapValue, ObjectPool, type ObjectPoolConfig, type ObjectPoolStats, type OpContext, type PartitionDistribution, PartitionService, type PartitionServiceConfig, type PartitionServiceEvents, type PooledEventPayload, type PooledMessage, type PooledRecord, type PooledTimestamp, PostgresAdapter, type PostgresAdapterOptions, ProcessorSandbox, type ProcessorSandboxConfig, type ProgressState, RateLimitInterceptor, type RateLimiterConfig, type RateLimiterStats, ReduceTasklet, ReplicationPipeline, SecurityManager, ServerCoordinator, type ServerCoordinatorConfig, type ServerIndexConfig, type ServerOp, type SetWithResolverResult, type StorageValue, type Tasklet, TaskletScheduler, type TaskletSchedulerConfig, type TaskletSchedulerStats, TimestampInterceptor, coalescingPresets, createEventPayloadPool, createMessagePool, createRecordPool, createTimestampPool, getCoalescingPreset, getGlobalBufferPool, getGlobalEventPayloadPool, getGlobalMessagePool, getGlobalRecordPool, getGlobalTimestampPool, getNativeModuleStatus, getNativeStats, logNativeStatus, logger, mergeWithDefaults, setGlobalBufferPool, setGlobalEventPayloadPool, setGlobalMessagePool, setGlobalRecordPool, setGlobalTimestampPool, validateIndexConfig };
4484
+ /**
4485
+ * SearchCoordinator - Server-side Full-Text Search Handler
4486
+ *
4487
+ * Manages FullTextIndex instances per map and handles search requests.
4488
+ * Part of Phase 11.1a: Server-side BM25 Search.
4489
+ * Phase 11.1b: Live Search Subscriptions with delta updates.
4490
+ *
4491
+ * @module search/SearchCoordinator
4492
+ */
4493
+
4494
+ /**
4495
+ * Result item returned from server search.
4496
+ */
4497
+ interface ServerSearchResult {
4498
+ key: string;
4499
+ value: unknown;
4500
+ score: number;
4501
+ matchedTerms: string[];
4502
+ }
4503
+ /**
4504
+ * Configuration for enabling search on a map.
4505
+ */
4506
+ interface SearchConfig extends FullTextIndexConfig {
4507
+ }
4508
+ /**
4509
+ * Callback type for sending updates to clients.
4510
+ */
4511
+ type SendUpdateCallback = (clientId: string, subscriptionId: string, key: string, value: unknown, score: number, matchedTerms: string[], type: SearchUpdateType) => void;
4512
+ /**
4513
+ * Batched update for a single document change.
4514
+ */
4515
+ interface BatchedUpdate {
4516
+ key: string;
4517
+ value: unknown;
4518
+ score: number;
4519
+ matchedTerms: string[];
4520
+ type: SearchUpdateType;
4521
+ }
4522
+ /**
4523
+ * Callback type for sending batched updates to clients.
4524
+ */
4525
+ type SendBatchUpdateCallback = (clientId: string, subscriptionId: string, updates: BatchedUpdate[]) => void;
4526
+ /**
4527
+ * SearchCoordinator manages full-text search indexes for the server.
4528
+ *
4529
+ * Responsibilities:
4530
+ * - Maintain FullTextIndex per enabled map
4531
+ * - Execute one-shot search queries
4532
+ * - Update indexes when data changes
4533
+ *
4534
+ * @example
4535
+ * ```typescript
4536
+ * const searchCoordinator = new SearchCoordinator();
4537
+ *
4538
+ * // Enable FTS for a map
4539
+ * searchCoordinator.enableSearch('articles', {
4540
+ * fields: ['title', 'body'],
4541
+ * tokenizer: { minLength: 2 },
4542
+ * bm25: { k1: 1.2, b: 0.75 }
4543
+ * });
4544
+ *
4545
+ * // Search
4546
+ * const results = searchCoordinator.search('articles', 'machine learning', {
4547
+ * limit: 20,
4548
+ * boost: { title: 2.0 }
4549
+ * });
4550
+ * ```
4551
+ */
4552
+ declare class SearchCoordinator {
4553
+ /** Map name → FullTextIndex */
4554
+ private readonly indexes;
4555
+ /** Map name → FullTextIndexConfig (for reference) */
4556
+ private readonly configs;
4557
+ /** Callback to get document value by key (injected by ServerCoordinator) */
4558
+ private getDocumentValue?;
4559
+ /** Subscription ID → SearchSubscription */
4560
+ private readonly subscriptions;
4561
+ /** Map name → Set of subscription IDs */
4562
+ private readonly subscriptionsByMap;
4563
+ /** Client ID → Set of subscription IDs */
4564
+ private readonly subscriptionsByClient;
4565
+ /** Callback for sending updates to clients */
4566
+ private sendUpdate?;
4567
+ /** Callback for sending batched updates to clients */
4568
+ private sendBatchUpdate?;
4569
+ /** Queue of pending notifications per map */
4570
+ private readonly pendingNotifications;
4571
+ /** Timer for batching notifications */
4572
+ private notificationTimer;
4573
+ /** Batch interval in milliseconds (~1 frame at 60fps) */
4574
+ private readonly BATCH_INTERVAL;
4575
+ constructor();
4576
+ /**
4577
+ * Set the callback for sending updates to clients.
4578
+ * Called by ServerCoordinator during initialization.
4579
+ */
4580
+ setSendUpdateCallback(callback: SendUpdateCallback): void;
4581
+ /**
4582
+ * Set the callback for sending batched updates to clients.
4583
+ * When set, notifications are batched within BATCH_INTERVAL (16ms) window.
4584
+ * Called by ServerCoordinator during initialization.
4585
+ *
4586
+ * @param callback - Function to call with batched updates
4587
+ */
4588
+ setSendBatchUpdateCallback(callback: SendBatchUpdateCallback): void;
4589
+ /**
4590
+ * Set the callback for retrieving document values.
4591
+ * Called by ServerCoordinator during initialization.
4592
+ */
4593
+ setDocumentValueGetter(getter: (mapName: string, key: string) => unknown | undefined): void;
4594
+ /**
4595
+ * Enable full-text search for a map.
4596
+ *
4597
+ * @param mapName - Name of the map to enable FTS for
4598
+ * @param config - FTS configuration (fields, tokenizer, bm25 options)
4599
+ */
4600
+ enableSearch(mapName: string, config: SearchConfig): void;
4601
+ /**
4602
+ * Disable full-text search for a map.
4603
+ *
4604
+ * @param mapName - Name of the map to disable FTS for
4605
+ */
4606
+ disableSearch(mapName: string): void;
4607
+ /**
4608
+ * Check if FTS is enabled for a map.
4609
+ */
4610
+ isSearchEnabled(mapName: string): boolean;
4611
+ /**
4612
+ * Get enabled map names.
4613
+ */
4614
+ getEnabledMaps(): string[];
4615
+ /**
4616
+ * Execute a one-shot search query.
4617
+ *
4618
+ * @param mapName - Name of the map to search
4619
+ * @param query - Search query text
4620
+ * @param options - Search options (limit, minScore, boost)
4621
+ * @returns Search response payload
4622
+ */
4623
+ search(mapName: string, query: string, options?: FTSSearchOptions): SearchRespPayload;
4624
+ /**
4625
+ * Handle document set/update.
4626
+ * Called by ServerCoordinator when data changes.
4627
+ *
4628
+ * @param mapName - Name of the map
4629
+ * @param key - Document key
4630
+ * @param value - Document value
4631
+ */
4632
+ onDataChange(mapName: string, key: string, value: Record<string, unknown> | null | undefined, changeType: 'add' | 'update' | 'remove'): void;
4633
+ /**
4634
+ * Build index from existing map entries.
4635
+ * Called when FTS is enabled for a map that already has data.
4636
+ *
4637
+ * @param mapName - Name of the map
4638
+ * @param entries - Iterator of [key, value] tuples
4639
+ */
4640
+ buildIndexFromEntries(mapName: string, entries: Iterable<[string, Record<string, unknown> | null]>): void;
4641
+ /**
4642
+ * Get index statistics for monitoring.
4643
+ */
4644
+ getIndexStats(mapName: string): {
4645
+ documentCount: number;
4646
+ fields: string[];
4647
+ } | null;
4648
+ /**
4649
+ * Clear all indexes (for testing or shutdown).
4650
+ */
4651
+ clear(): void;
4652
+ /**
4653
+ * Subscribe to live search results.
4654
+ * Returns initial results and tracks the subscription for delta updates.
4655
+ *
4656
+ * @param clientId - ID of the subscribing client
4657
+ * @param subscriptionId - Unique subscription identifier
4658
+ * @param mapName - Name of the map to search
4659
+ * @param query - Search query text
4660
+ * @param options - Search options (limit, minScore, boost)
4661
+ * @returns Initial search results
4662
+ */
4663
+ subscribe(clientId: string, subscriptionId: string, mapName: string, query: string, options?: SearchOptions): ServerSearchResult[];
4664
+ /**
4665
+ * Unsubscribe from a live search.
4666
+ *
4667
+ * @param subscriptionId - Subscription to remove
4668
+ */
4669
+ unsubscribe(subscriptionId: string): void;
4670
+ /**
4671
+ * Unsubscribe all subscriptions for a client.
4672
+ * Called when a client disconnects.
4673
+ *
4674
+ * @param clientId - ID of the disconnected client
4675
+ */
4676
+ unsubscribeClient(clientId: string): void;
4677
+ /**
4678
+ * Get the number of active subscriptions.
4679
+ */
4680
+ getSubscriptionCount(): number;
4681
+ /**
4682
+ * Notify subscribers about a document change.
4683
+ * Computes delta (ENTER/UPDATE/LEAVE) for each affected subscription.
4684
+ *
4685
+ * @param mapName - Name of the map that changed
4686
+ * @param key - Document key that changed
4687
+ * @param value - New document value (null if removed)
4688
+ * @param changeType - Type of change
4689
+ */
4690
+ private notifySubscribers;
4691
+ /**
4692
+ * Score a single document against a subscription's query.
4693
+ *
4694
+ * OPTIMIZED: O(Q × D) complexity instead of O(N) full index scan.
4695
+ * Uses pre-tokenized queryTerms and FullTextIndex.scoreSingleDocument().
4696
+ *
4697
+ * @param subscription - The subscription containing query and cached queryTerms
4698
+ * @param key - Document key
4699
+ * @param value - Document value
4700
+ * @param index - The FullTextIndex for this map
4701
+ * @returns Scored result or null if document doesn't match
4702
+ */
4703
+ private scoreDocument;
4704
+ /**
4705
+ * Queue a notification for batched processing.
4706
+ * Notifications are collected and processed together after BATCH_INTERVAL.
4707
+ *
4708
+ * @param mapName - Name of the map that changed
4709
+ * @param key - Document key that changed
4710
+ * @param value - New document value (null if removed)
4711
+ * @param changeType - Type of change
4712
+ */
4713
+ queueNotification(mapName: string, key: string, value: Record<string, unknown> | null, changeType: 'add' | 'update' | 'remove'): void;
4714
+ /**
4715
+ * Schedule a flush of pending notifications.
4716
+ * Uses setTimeout to batch notifications within BATCH_INTERVAL window.
4717
+ */
4718
+ private scheduleNotificationFlush;
4719
+ /**
4720
+ * Flush all pending notifications.
4721
+ * Processes each map's notifications and sends batched updates.
4722
+ */
4723
+ flushNotifications(): void;
4724
+ /**
4725
+ * Process batched notifications for a single map.
4726
+ * Computes updates for each subscription and sends as a batch.
4727
+ *
4728
+ * @param mapName - Name of the map
4729
+ * @param notifications - Array of pending notifications
4730
+ */
4731
+ private processBatchedNotifications;
4732
+ /**
4733
+ * Compute the update for a single document change against a subscription.
4734
+ * Returns null if no update is needed.
4735
+ *
4736
+ * @param subscription - The subscription to check
4737
+ * @param key - Document key
4738
+ * @param value - Document value (null if removed)
4739
+ * @param changeType - Type of change
4740
+ * @param index - The FullTextIndex for this map
4741
+ * @returns BatchedUpdate or null
4742
+ */
4743
+ private computeSubscriptionUpdate;
4744
+ }
4745
+
4746
+ export { BufferPool, type BufferPoolConfig, type BufferPoolStats, type ClusterConfig, ClusterCoordinator, type ClusterCoordinatorConfig, type ClusterCoordinatorEvents, ClusterManager, type ClusterMember, type ClusterMessage, type CoalescingPreset, type CoalescingWriterMetrics, type CoalescingWriterOptions, ConflictResolverHandler, type ConflictResolverHandlerConfig, ConflictResolverService, type ConflictResolverServiceConfig, type ConnectionContext, ConnectionRateLimiter, DEFAULT_CLUSTER_COORDINATOR_CONFIG, DEFAULT_CONFLICT_RESOLVER_CONFIG, DEFAULT_FAILURE_DETECTOR_CONFIG, DEFAULT_INDEX_CONFIG, DEFAULT_JOURNAL_SERVICE_CONFIG, DEFAULT_LAG_TRACKER_CONFIG, DEFAULT_MERKLE_TREE_CONFIG, DEFAULT_READ_REPLICA_CONFIG, DEFAULT_REASSIGNER_CONFIG, DEFAULT_REPAIR_CONFIG, DEFAULT_SANDBOX_CONFIG, EntryProcessorHandler, type EntryProcessorHandlerConfig, EventJournalService, type EventJournalServiceConfig, type ExportOptions, type FailoverStatus, FailureDetector, type FailureDetectorConfig, type FailureDetectorEvents, FilterTasklet, ForEachTasklet, type IInterceptor, type IServerStorage, type IndexDefinition, IteratorTasklet, type IteratorTaskletConfig, type LagInfo, LagTracker, type LagTrackerConfig, LockManager, type Logger, MapFactory, type MapIndexConfig, MapTasklet, MapWithResolver, type MapWithResolverConfig, MemoryServerAdapter, type MergeWithResolverResult, type MerkleComparisonResult, MerkleTreeManager, type MerkleTreeManagerConfig, MigrationManager, type NativeModuleStatus, type NativeStats, type NodeState, type ORMapTombstones, type ORMapValue, ObjectPool, type ObjectPoolConfig, type ObjectPoolStats, type OpContext, type PartitionDistribution, type PartitionMerkleInfo, PartitionReassigner, type PartitionReassignerConfig, PartitionService, type PartitionServiceConfig, type PartitionServiceEvents, type PooledEventPayload, type PooledMessage, type PooledRecord, type PooledTimestamp, PostgresAdapter, type PostgresAdapterOptions, ProcessorSandbox, type ProcessorSandboxConfig, type ProgressState, RateLimitInterceptor, type RateLimiterConfig, type RateLimiterStats, type ReadReplicaConfig, ReadReplicaHandler, type ReadRequest, type ReadResult, type ReassignmentEvent, ReduceTasklet, type RepairConfig, type RepairMetrics, type RepairResult, RepairScheduler, type RepairTask, ReplicationPipeline, type SearchConfig, SearchCoordinator, SecurityManager, ServerCoordinator, type ServerCoordinatorConfig, type ServerIndexConfig, type ServerOp, type ServerSearchResult, type SetWithResolverResult, type StorageValue, type Tasklet, TaskletScheduler, type TaskletSchedulerConfig, type TaskletSchedulerStats, TimestampInterceptor, coalescingPresets, createEventPayloadPool, createMessagePool, createRecordPool, createTimestampPool, getCoalescingPreset, getGlobalBufferPool, getGlobalEventPayloadPool, getGlobalMessagePool, getGlobalRecordPool, getGlobalTimestampPool, getNativeModuleStatus, getNativeStats, logNativeStatus, logger, mergeWithDefaults, setGlobalBufferPool, setGlobalEventPayloadPool, setGlobalMessagePool, setGlobalRecordPool, setGlobalTimestampPool, validateIndexConfig };