/** * @module @dotdo/postgres-shared/sync-primitives * * Common sync primitives for CDC/Electric integrations. * Provides: * - Delta compression for repeated column patterns * - Transaction batching for multi-row operations * - Bloom filter conflict detection * - Merge strategies (server-wins, client-wins, field-merge, CRDT-like) * - Sync progress tracking */ /** * Represents a single row change */ export interface RowChange> { /** Unique change ID (usually LSN) */ id: string; /** Table name */ table: string; /** Schema name */ schema: string; /** Operation type */ operation: 'INSERT' | 'UPDATE' | 'DELETE' | 'TRUNCATE'; /** New row data for INSERT/UPDATE */ newRow?: T | undefined; /** Old row data for UPDATE/DELETE */ oldRow?: T | undefined; /** Changed columns for UPDATE */ changedColumns?: string[] | undefined; /** Transaction ID */ xid?: number | undefined; /** Timestamp */ timestamp: Date; } /** * Delta-compressed change for efficient transmission */ export interface DeltaCompressedChange> { /** Change ID */ id: string; /** Reference to schema definition (index into column schema) */ schemaRef?: number; /** Operation code: 0=INSERT, 1=UPDATE, 2=DELETE, 3=TRUNCATE */ op: 0 | 1 | 2 | 3; /** New values (only changed fields for UPDATE) */ n?: Partial; /** Old values (only for UPDATE/DELETE, only included fields) */ o?: Partial; /** Changed column indices (instead of names) */ c?: number[]; /** Transaction ID */ x?: number; /** Timestamp delta from batch base (ms) */ td?: number; } /** * Batch of delta-compressed changes */ export interface DeltaCompressedBatch> { /** Schema definitions: array of column names per table */ schemas: Record; /** Base timestamp (ISO string) */ baseTs: string; /** Table name (if all changes are for same table) */ table?: string; /** Schema name (if all changes are for same schema) */ schema?: string; /** Compressed changes */ changes: DeltaCompressedChange[]; } /** * Merge strategy types */ export type MergeStrategy = 'server-wins' | 'client-wins' | 'field-merge' | 'timestamp-wins' | 'custom'; /** * Merge conflict information */ export interface MergeConflict<_T = Record> { /** Conflicting field name */ field: string; /** Server value */ serverValue: unknown; /** Client value */ clientValue: unknown; /** Base value (before both changes) */ baseValue?: unknown; /** Server timestamp */ serverTimestamp?: Date; /** Client timestamp */ clientTimestamp?: Date; } /** * Merge result */ export interface MergeResult> { /** Resolved row */ resolved: T; /** Which strategy was applied */ strategy: MergeStrategy; /** Conflicts that were detected */ conflicts: MergeConflict[]; /** Fields that required resolution */ resolvedFields: string[]; /** Whether any conflicts occurred */ hadConflicts: boolean; } /** * Custom merge resolver function */ export type MergeResolver> = (conflict: MergeConflict, context: { serverRow: T; clientRow: T; baseRow?: T; }) => unknown; /** * Merge configuration */ export interface MergeConfig> { /** Default merge strategy */ strategy: MergeStrategy; /** Field-specific strategies */ fieldStrategies?: Record; /** Custom resolver for 'custom' strategy */ customResolver?: MergeResolver; /** Timestamp field name for timestamp-wins strategy */ timestampField?: string; /** Counter fields for CRDT-like increment merge */ counterFields?: string[]; /** Set fields for CRDT-like set union merge */ setFields?: string[]; } /** * Sync progress state */ export interface SyncProgress { /** Current LSN position */ currentLsn: string; /** Target LSN (if known, e.g., from snapshot) */ targetLsn?: string; /** Total changes processed */ changesProcessed: number; /** Changes in current batch */ batchSize: number; /** Start time */ startedAt: Date; /** Last update time */ updatedAt: Date; /** Estimated completion percentage (0-100) */ progress: number; /** Current phase */ phase: 'initial-sync' | 'streaming' | 'catching-up' | 'idle'; /** Error if any */ error?: string; /** Bytes transferred */ bytesTransferred: number; /** Throughput (changes/second) */ throughput: number; } /** * Transaction batch for multi-row operations */ export interface TransactionBatch> { /** Transaction ID */ xid: number; /** Changes in this transaction */ changes: RowChange[]; /** Batch start LSN */ startLsn: string; /** Batch end LSN */ endLsn: string; /** Transaction timestamp */ timestamp: Date; /** Whether transaction is committed */ committed: boolean; } declare const OP_MAP: Record; declare const REVERSE_OP_MAP: Record; /** * Delta compression encoder for CDC changes. * Reduces payload size by: * - Using numeric operation codes * - Storing column schemas separately * - Using column indices instead of names * - Storing timestamps as deltas from batch base * - Only including changed fields for updates */ export declare class DeltaCompressor> { private schemaCache; private schemaIndex; private nextSchemaIndex; /** * Get or create schema for a table */ private getOrCreateSchema; /** * Compress a single change */ compressChange(change: RowChange, baseTimestamp: Date): DeltaCompressedChange; /** * Compress a batch of changes */ compressBatch(changes: RowChange[], table?: string, schema?: string): DeltaCompressedBatch; /** * Clear the schema cache (call when switching tables/schemas) */ clearCache(): void; /** * Get compression statistics */ getStats(): { schemasCount: number; columnCount: number; }; } /** * Decompress a batch of delta-compressed changes */ export declare function decompressBatch>(batch: DeltaCompressedBatch): RowChange[]; /** * Calculate compression ratio for a batch */ export declare function calculateCompressionRatio(original: RowChange[], compressed: DeltaCompressedBatch): number; /** * Batches changes by transaction for atomic application. * Ensures all changes in a transaction are applied together. */ export declare class TransactionBatcher> { private pendingBatches; private completedBatches; private maxPendingBatches; private flushCallback?; constructor(options?: { /** Maximum pending batches before forced flush */ maxPendingBatches?: number; /** Callback when a batch is ready to flush */ onFlush?: (batch: TransactionBatch) => void | Promise; }); /** * Add a change to the appropriate transaction batch */ addChange(change: RowChange, isLastInTransaction: boolean): TransactionBatch | null; /** * Force flush the oldest incomplete batch */ private forceFlushOldest; /** * Get all completed batches and clear them */ flushCompleted(): TransactionBatch[]; /** * Get pending batch count */ getPendingCount(): number; /** * Get completed batch count */ getCompletedCount(): number; /** * Force complete all pending batches */ forceCompleteAll(): TransactionBatch[]; /** * Clear all batches */ clear(): void; } /** * Simple Bloom filter implementation for efficient conflict detection. * Used to quickly check if a row ID has been modified locally before * applying remote changes. */ export declare class BloomFilter { private bitArray; private size; private hashCount; /** * Create a new Bloom filter * @param expectedItems Expected number of items to store * @param falsePositiveRate Desired false positive rate (default 0.01 = 1%) */ constructor(expectedItems: number, falsePositiveRate?: number); /** * Generate hash values for an item */ private hash; /** * FNV-1a hash function * @see https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function */ private fnv1a; /** * Simple Murmur3-like hash function * @see https://en.wikipedia.org/wiki/MurmurHash */ private murmur3; /** * Add an item to the filter */ add(item: string): void; /** * Check if an item might be in the filter * Returns true if item might exist (may be false positive) * Returns false if item definitely doesn't exist */ mightContain(item: string): boolean; /** * Merge another filter into this one (union operation) */ merge(other: BloomFilter): void; /** * Clear the filter */ clear(): void; /** * Get approximate number of items in filter * Uses formula: -(m/k) * ln(1 - X/m) where X is set bits */ approximateCount(): number; /** * Export filter state for serialization */ export(): { size: number; hashCount: number; data: string; }; /** * Import filter state from serialization */ static import(state: { size: number; hashCount: number; data: string; }): BloomFilter; } /** * Conflict detector using Bloom filter for efficient local change tracking */ export declare class ConflictDetector> { private localChanges; private changeTimestamps; private primaryKeyField; constructor(options?: { /** Expected number of local changes to track */ expectedChanges?: number; /** False positive rate for Bloom filter */ falsePositiveRate?: number; /** Primary key field name */ primaryKeyField?: string; }); /** * Track a local change */ trackLocalChange(row: T): void; /** * Check if a remote change might conflict with local changes * Returns true if there MIGHT be a conflict (Bloom filter positive) */ mightConflict(row: T): boolean; /** * Get the timestamp of a tracked local change (if tracked exactly) */ getLocalChangeTime(row: T): number | undefined; /** * Clear all tracked changes */ clear(): void; /** * Get approximate number of tracked changes */ approximateCount(): number; /** * Get row key for conflict detection */ private getRowKey; } /** * Merge engine for resolving conflicts between server and client changes. * Supports multiple strategies and CRDT-like merging for specific fields. */ export declare class MergeEngine> { private strategy; private fieldStrategies; private customResolver?; private timestampField; private counterFields; private setFields; constructor(config: MergeConfig); /** * Merge server and client changes */ merge(serverRow: T, clientRow: T, baseRow?: T): MergeResult; /** * Resolve a single field conflict */ private resolveField; /** * Merge by timestamp (latest wins) */ private mergeByTimestamp; /** * CRDT-like counter merge (add deltas from base) */ private mergeCounter; /** * CRDT-like set merge (union of changes) */ private mergeSet; /** * Check if two values are equal */ private valuesEqual; } /** * Tracks sync progress and provides estimates for UI feedback. */ export declare class SyncProgressTracker { private progress; private recentChanges; private throughputWindowMs; constructor(initialLsn?: string); /** * Update progress with a new batch of changes */ recordBatch(lsn: string, changeCount: number, bytesTransferred: number): void; /** * Set target LSN for progress calculation */ setTargetLsn(lsn: string): void; /** * Set the sync phase */ setPhase(phase: SyncProgress['phase']): void; /** * Set an error */ setError(error: string): void; /** * Clear error */ clearError(): void; /** * Get current progress snapshot */ getProgress(): SyncProgress; /** * Update progress estimate based on LSN comparison */ private updateProgressEstimate; /** * Parse PostgreSQL LSN string to bigint */ private parseLsn; /** * Get estimated time remaining in milliseconds */ getEstimatedTimeRemaining(): number | null; /** * Reset progress */ reset(): void; } export { OP_MAP, REVERSE_OP_MAP, }; //# sourceMappingURL=sync-primitives.d.ts.map