Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAGH,OAAO,EACL,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;;;AAEH,QAAQ;AACR,uCAkBoB;AAflB,uGAAA,WAAW,OAAA;AACX,yGAAA,aAAa,OAAA;AACb,oGAAA,QAAQ,OAAA;AACR,wGAAA,YAAY,OAAA;AAEZ,2GAAA,eAAe,OAAA;AAIf,0GAAA,cAAc,OAAA;AACd,4GAAA,gBAAgB,OAAA;AAChB,gHAAA,oBAAoB,OAAA;AACpB,4GAAA,gBAAgB,OAAA;AAKlB,eAAe;AACf,qDAM2B;AALzB,8GAAA,WAAW,OAAA;AACX,wHAAA,qBAAqB,OAAA;AAErB,gHAAA,aAAa,OAAA;AACb,gHAAA,aAAa,OAAA;AAGf,cAAc;AACd,mDAA8C;AAArC,4GAAA,UAAU,OAAA;AAEnB,eAAe;AACf,qDAAgE;AAAvD,8GAAA,WAAW,OAAA;AAAE,iHAAA,cAAc,OAAA"}

View File

@@ -0,0 +1,83 @@
/**
* @ruvector/replication - Data Replication and Synchronization
*
* A TypeScript implementation of data replication capabilities including:
* - Multi-node replica management
* - Synchronous, asynchronous, and semi-synchronous replication modes
* - Conflict resolution with vector clocks
* - Change data capture and streaming
* - Automatic failover and split-brain prevention
*
* @example
* ```typescript
* import {
* ReplicaSet,
* ReplicaRole,
* SyncManager,
* ReplicationLog,
* SyncMode,
* ChangeOperation,
* } from '@ruvector/replication';
*
* // Create a replica set
* const replicaSet = new ReplicaSet('my-cluster');
*
* // Add replicas
* replicaSet.addReplica('replica-1', '192.168.1.10:9001', ReplicaRole.Primary);
* replicaSet.addReplica('replica-2', '192.168.1.11:9001', ReplicaRole.Secondary);
* replicaSet.addReplica('replica-3', '192.168.1.12:9001', ReplicaRole.Secondary);
*
* // Create replication log and sync manager
* const log = new ReplicationLog('replica-1');
* const syncManager = new SyncManager(replicaSet, log);
*
* // Configure semi-sync replication
* syncManager.setSyncMode(SyncMode.SemiSync, 1);
*
* // Listen for events
* syncManager.on('changeReceived', (change) => {
* console.log(`Change: ${change.operation} on ${change.key}`);
* });
*
* // Record a change
* await syncManager.recordChange('user:123', ChangeOperation.Update, { name: 'Alice' });
* ```
*
* @packageDocumentation
*/
// Types
export {
ReplicaId,
LogicalClock,
ReplicaRole,
ReplicaStatus,
SyncMode,
HealthStatus,
Replica,
ChangeOperation,
ChangeEvent,
VectorClockValue,
LogEntry,
FailoverPolicy,
ReplicationError,
ReplicationErrorCode,
ReplicationEvent,
ReplicaSetConfig,
SyncConfig,
} from './types.js';
// Vector Clock
export {
VectorClock,
VectorClockComparison,
ConflictResolver,
LastWriteWins,
MergeFunction,
} from './vector-clock.js';
// Replica Set
export { ReplicaSet } from './replica-set.js';
// Sync Manager
export { SyncManager, ReplicationLog } from './sync-manager.js';

View File

@@ -0,0 +1,55 @@
/**
* Replica Set Management
* Manages a set of replicas for distributed data storage
*/
import EventEmitter from 'eventemitter3';
import { type Replica, type ReplicaId, type ReplicaSetConfig, ReplicaRole, ReplicaStatus } from './types.js';
/** Manages a set of replicas */
export declare class ReplicaSet extends EventEmitter {
private replicas;
private config;
private heartbeatTimer;
constructor(name: string, config?: Partial<ReplicaSetConfig>);
/** Get replica set name */
get name(): string;
/** Get the primary replica */
get primary(): Replica | undefined;
/** Get all secondary replicas */
get secondaries(): Replica[];
/** Get all active replicas */
get activeReplicas(): Replica[];
/** Get replica count */
get size(): number;
/** Check if quorum is met */
get hasQuorum(): boolean;
/** Add a replica to the set */
addReplica(id: ReplicaId, address: string, role: ReplicaRole): Replica;
/** Remove a replica from the set */
removeReplica(id: ReplicaId): boolean;
/** Get a replica by ID */
getReplica(id: ReplicaId): Replica | undefined;
/** Update replica status */
updateStatus(id: ReplicaId, status: ReplicaStatus): void;
/** Update replica lag */
updateLag(id: ReplicaId, lag: number): void;
/** Promote a secondary to primary */
promote(id: ReplicaId): void;
/** Trigger automatic failover */
private triggerFailover;
/** Start heartbeat monitoring */
startHeartbeat(): void;
/** Stop heartbeat monitoring */
stopHeartbeat(): void;
/** Get all replicas */
getAllReplicas(): Replica[];
/** Get replica set stats */
getStats(): {
total: number;
active: number;
syncing: number;
offline: number;
failed: number;
hasQuorum: boolean;
};
}
//# sourceMappingURL=replica-set.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"replica-set.d.ts","sourceRoot":"","sources":["replica-set.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,WAAW,EACX,aAAa,EAId,MAAM,YAAY,CAAC;AAWpB,gCAAgC;AAChC,qBAAa,UAAW,SAAQ,YAAY;IAC1C,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,cAAc,CAA+C;gBAEzD,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAK5D,2BAA2B;IAC3B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,8BAA8B;IAC9B,IAAI,OAAO,IAAI,OAAO,GAAG,SAAS,CAOjC;IAED,iCAAiC;IACjC,IAAI,WAAW,IAAI,OAAO,EAAE,CAI3B;IAED,8BAA8B;IAC9B,IAAI,cAAc,IAAI,OAAO,EAAE,CAE9B;IAED,wBAAwB;IACxB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,6BAA6B;IAC7B,IAAI,SAAS,IAAI,OAAO,CAGvB;IAED,+BAA+B;IAC/B,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO;IAyBtE,oCAAoC;IACpC,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO;IAerC,0BAA0B;IAC1B,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS;IAI9C,4BAA4B;IAC5B,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI;IA4BxD,yBAAyB;IACzB,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAQ3C,qCAAqC;IACrC,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAwB5B,iCAAiC;IACjC,OAAO,CAAC,eAAe;IAmBvB,iCAAiC;IACjC,cAAc,IAAI,IAAI;IAetB,gCAAgC;IAChC,aAAa,IAAI,IAAI;IAOrB,uBAAuB;IACvB,cAAc,IAAI,OAAO,EAAE;IAI3B,4BAA4B;IAC5B,QAAQ,IAAI;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;KACpB;CAWF"}

View File

@@ -0,0 +1,204 @@
"use strict";
/**
* Replica Set Management
* Manages a set of replicas for distributed data storage
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReplicaSet = void 0;
const eventemitter3_1 = __importDefault(require("eventemitter3"));
const types_js_1 = require("./types.js");
/** Default configuration */
const DEFAULT_CONFIG = {
name: 'default',
minQuorum: 2,
heartbeatInterval: 1000,
healthCheckTimeout: 5000,
failoverPolicy: types_js_1.FailoverPolicy.Automatic,
};
/** Manages a set of replicas */
class ReplicaSet extends eventemitter3_1.default {
constructor(name, config) {
super();
this.replicas = new Map();
this.heartbeatTimer = null;
this.config = { ...DEFAULT_CONFIG, name, ...config };
}
/** Get replica set name */
get name() {
return this.config.name;
}
/** Get the primary replica */
get primary() {
for (const replica of this.replicas.values()) {
if (replica.role === types_js_1.ReplicaRole.Primary && replica.status === types_js_1.ReplicaStatus.Active) {
return replica;
}
}
return undefined;
}
/** Get all secondary replicas */
get secondaries() {
return Array.from(this.replicas.values()).filter((r) => r.role === types_js_1.ReplicaRole.Secondary && r.status === types_js_1.ReplicaStatus.Active);
}
/** Get all active replicas */
get activeReplicas() {
return Array.from(this.replicas.values()).filter((r) => r.status === types_js_1.ReplicaStatus.Active);
}
/** Get replica count */
get size() {
return this.replicas.size;
}
/** Check if quorum is met */
get hasQuorum() {
const activeCount = this.activeReplicas.length;
return activeCount >= this.config.minQuorum;
}
/** Add a replica to the set */
addReplica(id, address, role) {
if (this.replicas.has(id)) {
throw new Error(`Replica ${id} already exists`);
}
// Check if adding a primary when one exists
if (role === types_js_1.ReplicaRole.Primary && this.primary) {
throw new Error('Primary already exists in replica set');
}
const replica = {
id,
address,
role,
status: types_js_1.ReplicaStatus.Active,
lastSeen: Date.now(),
lag: 0,
};
this.replicas.set(id, replica);
this.emit(types_js_1.ReplicationEvent.ReplicaAdded, replica);
return replica;
}
/** Remove a replica from the set */
removeReplica(id) {
const replica = this.replicas.get(id);
if (!replica)
return false;
this.replicas.delete(id);
this.emit(types_js_1.ReplicationEvent.ReplicaRemoved, replica);
// If primary was removed, trigger failover
if (replica.role === types_js_1.ReplicaRole.Primary && this.config.failoverPolicy === types_js_1.FailoverPolicy.Automatic) {
this.triggerFailover();
}
return true;
}
/** Get a replica by ID */
getReplica(id) {
return this.replicas.get(id);
}
/** Update replica status */
updateStatus(id, status) {
const replica = this.replicas.get(id);
if (!replica) {
throw types_js_1.ReplicationError.replicaNotFound(id);
}
const previousStatus = replica.status;
replica.status = status;
replica.lastSeen = Date.now();
if (previousStatus !== status) {
this.emit(types_js_1.ReplicationEvent.ReplicaStatusChanged, {
replica,
previousStatus,
newStatus: status,
});
// Check for failover conditions
if (replica.role === types_js_1.ReplicaRole.Primary &&
status === types_js_1.ReplicaStatus.Failed &&
this.config.failoverPolicy === types_js_1.FailoverPolicy.Automatic) {
this.triggerFailover();
}
}
}
/** Update replica lag */
updateLag(id, lag) {
const replica = this.replicas.get(id);
if (replica) {
replica.lag = lag;
replica.lastSeen = Date.now();
}
}
/** Promote a secondary to primary */
promote(id) {
const replica = this.replicas.get(id);
if (!replica) {
throw types_js_1.ReplicationError.replicaNotFound(id);
}
if (replica.role === types_js_1.ReplicaRole.Primary) {
return; // Already primary
}
// Demote current primary
const currentPrimary = this.primary;
if (currentPrimary) {
currentPrimary.role = types_js_1.ReplicaRole.Secondary;
}
// Promote new primary
replica.role = types_js_1.ReplicaRole.Primary;
this.emit(types_js_1.ReplicationEvent.PrimaryChanged, {
previousPrimary: currentPrimary?.id,
newPrimary: id,
});
}
/** Trigger automatic failover */
triggerFailover() {
this.emit(types_js_1.ReplicationEvent.FailoverStarted, {});
// Find the best candidate (lowest lag, active secondary)
const candidates = this.secondaries
.filter((r) => r.status === types_js_1.ReplicaStatus.Active)
.sort((a, b) => a.lag - b.lag);
if (candidates.length === 0) {
this.emit(types_js_1.ReplicationEvent.Error, types_js_1.ReplicationError.noPrimary());
return;
}
const newPrimary = candidates[0];
this.promote(newPrimary.id);
this.emit(types_js_1.ReplicationEvent.FailoverCompleted, { newPrimary: newPrimary.id });
}
/** Start heartbeat monitoring */
startHeartbeat() {
if (this.heartbeatTimer)
return;
this.heartbeatTimer = setInterval(() => {
const now = Date.now();
for (const replica of this.replicas.values()) {
if (now - replica.lastSeen > this.config.healthCheckTimeout) {
if (replica.status === types_js_1.ReplicaStatus.Active) {
this.updateStatus(replica.id, types_js_1.ReplicaStatus.Offline);
}
}
}
}, this.config.heartbeatInterval);
}
/** Stop heartbeat monitoring */
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
/** Get all replicas */
getAllReplicas() {
return Array.from(this.replicas.values());
}
/** Get replica set stats */
getStats() {
const replicas = Array.from(this.replicas.values());
return {
total: replicas.length,
active: replicas.filter((r) => r.status === types_js_1.ReplicaStatus.Active).length,
syncing: replicas.filter((r) => r.status === types_js_1.ReplicaStatus.Syncing).length,
offline: replicas.filter((r) => r.status === types_js_1.ReplicaStatus.Offline).length,
failed: replicas.filter((r) => r.status === types_js_1.ReplicaStatus.Failed).length,
hasQuorum: this.hasQuorum,
};
}
}
exports.ReplicaSet = ReplicaSet;
//# sourceMappingURL=replica-set.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,254 @@
/**
* Replica Set Management
* Manages a set of replicas for distributed data storage
*/
import EventEmitter from 'eventemitter3';
import {
type Replica,
type ReplicaId,
type ReplicaSetConfig,
ReplicaRole,
ReplicaStatus,
ReplicationError,
ReplicationEvent,
FailoverPolicy,
} from './types.js';
/** Default configuration */
const DEFAULT_CONFIG: ReplicaSetConfig = {
name: 'default',
minQuorum: 2,
heartbeatInterval: 1000,
healthCheckTimeout: 5000,
failoverPolicy: FailoverPolicy.Automatic,
};
/** Manages a set of replicas */
export class ReplicaSet extends EventEmitter {
private replicas: Map<ReplicaId, Replica> = new Map();
private config: ReplicaSetConfig;
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
constructor(name: string, config?: Partial<ReplicaSetConfig>) {
super();
this.config = { ...DEFAULT_CONFIG, name, ...config };
}
/** Get replica set name */
get name(): string {
return this.config.name;
}
/** Get the primary replica */
get primary(): Replica | undefined {
for (const replica of this.replicas.values()) {
if (replica.role === ReplicaRole.Primary && replica.status === ReplicaStatus.Active) {
return replica;
}
}
return undefined;
}
/** Get all secondary replicas */
get secondaries(): Replica[] {
return Array.from(this.replicas.values()).filter(
(r) => r.role === ReplicaRole.Secondary && r.status === ReplicaStatus.Active,
);
}
/** Get all active replicas */
get activeReplicas(): Replica[] {
return Array.from(this.replicas.values()).filter((r) => r.status === ReplicaStatus.Active);
}
/** Get replica count */
get size(): number {
return this.replicas.size;
}
/** Check if quorum is met */
get hasQuorum(): boolean {
const activeCount = this.activeReplicas.length;
return activeCount >= this.config.minQuorum;
}
/** Add a replica to the set */
addReplica(id: ReplicaId, address: string, role: ReplicaRole): Replica {
if (this.replicas.has(id)) {
throw new Error(`Replica ${id} already exists`);
}
// Check if adding a primary when one exists
if (role === ReplicaRole.Primary && this.primary) {
throw new Error('Primary already exists in replica set');
}
const replica: Replica = {
id,
address,
role,
status: ReplicaStatus.Active,
lastSeen: Date.now(),
lag: 0,
};
this.replicas.set(id, replica);
this.emit(ReplicationEvent.ReplicaAdded, replica);
return replica;
}
/** Remove a replica from the set */
removeReplica(id: ReplicaId): boolean {
const replica = this.replicas.get(id);
if (!replica) return false;
this.replicas.delete(id);
this.emit(ReplicationEvent.ReplicaRemoved, replica);
// If primary was removed, trigger failover
if (replica.role === ReplicaRole.Primary && this.config.failoverPolicy === FailoverPolicy.Automatic) {
this.triggerFailover();
}
return true;
}
/** Get a replica by ID */
getReplica(id: ReplicaId): Replica | undefined {
return this.replicas.get(id);
}
/** Update replica status */
updateStatus(id: ReplicaId, status: ReplicaStatus): void {
const replica = this.replicas.get(id);
if (!replica) {
throw ReplicationError.replicaNotFound(id);
}
const previousStatus = replica.status;
replica.status = status;
replica.lastSeen = Date.now();
if (previousStatus !== status) {
this.emit(ReplicationEvent.ReplicaStatusChanged, {
replica,
previousStatus,
newStatus: status,
});
// Check for failover conditions
if (
replica.role === ReplicaRole.Primary &&
status === ReplicaStatus.Failed &&
this.config.failoverPolicy === FailoverPolicy.Automatic
) {
this.triggerFailover();
}
}
}
/** Update replica lag */
updateLag(id: ReplicaId, lag: number): void {
const replica = this.replicas.get(id);
if (replica) {
replica.lag = lag;
replica.lastSeen = Date.now();
}
}
/** Promote a secondary to primary */
promote(id: ReplicaId): void {
const replica = this.replicas.get(id);
if (!replica) {
throw ReplicationError.replicaNotFound(id);
}
if (replica.role === ReplicaRole.Primary) {
return; // Already primary
}
// Demote current primary
const currentPrimary = this.primary;
if (currentPrimary) {
currentPrimary.role = ReplicaRole.Secondary;
}
// Promote new primary
replica.role = ReplicaRole.Primary;
this.emit(ReplicationEvent.PrimaryChanged, {
previousPrimary: currentPrimary?.id,
newPrimary: id,
});
}
/** Trigger automatic failover */
private triggerFailover(): void {
this.emit(ReplicationEvent.FailoverStarted, {});
// Find the best candidate (lowest lag, active secondary)
const candidates = this.secondaries
.filter((r) => r.status === ReplicaStatus.Active)
.sort((a, b) => a.lag - b.lag);
if (candidates.length === 0) {
this.emit(ReplicationEvent.Error, ReplicationError.noPrimary());
return;
}
const newPrimary = candidates[0];
this.promote(newPrimary.id);
this.emit(ReplicationEvent.FailoverCompleted, { newPrimary: newPrimary.id });
}
/** Start heartbeat monitoring */
startHeartbeat(): void {
if (this.heartbeatTimer) return;
this.heartbeatTimer = setInterval(() => {
const now = Date.now();
for (const replica of this.replicas.values()) {
if (now - replica.lastSeen > this.config.healthCheckTimeout) {
if (replica.status === ReplicaStatus.Active) {
this.updateStatus(replica.id, ReplicaStatus.Offline);
}
}
}
}, this.config.heartbeatInterval);
}
/** Stop heartbeat monitoring */
stopHeartbeat(): void {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
/** Get all replicas */
getAllReplicas(): Replica[] {
return Array.from(this.replicas.values());
}
/** Get replica set stats */
getStats(): {
total: number;
active: number;
syncing: number;
offline: number;
failed: number;
hasQuorum: boolean;
} {
const replicas = Array.from(this.replicas.values());
return {
total: replicas.length,
active: replicas.filter((r) => r.status === ReplicaStatus.Active).length,
syncing: replicas.filter((r) => r.status === ReplicaStatus.Syncing).length,
offline: replicas.filter((r) => r.status === ReplicaStatus.Offline).length,
failed: replicas.filter((r) => r.status === ReplicaStatus.Failed).length,
hasQuorum: this.hasQuorum,
};
}
}

View File

@@ -0,0 +1,65 @@
/**
* Sync Manager Implementation
* Manages data synchronization across replicas
*/
import EventEmitter from 'eventemitter3';
import { type ReplicaId, type SyncConfig, type LogEntry, SyncMode, ChangeOperation } from './types.js';
import { VectorClock, type ConflictResolver } from './vector-clock.js';
import type { ReplicaSet } from './replica-set.js';
/** Replication log for tracking changes */
export declare class ReplicationLog<T = unknown> {
private entries;
private sequence;
private readonly replicaId;
private vectorClock;
constructor(replicaId: ReplicaId);
/** Get the current sequence number */
get currentSequence(): number;
/** Get the current vector clock */
get clock(): VectorClock;
/** Append an entry to the log */
append(data: T): LogEntry<T>;
/** Get entries since a sequence number */
getEntriesSince(sequence: number, limit?: number): LogEntry<T>[];
/** Get entry by ID */
getEntry(id: string): LogEntry<T> | undefined;
/** Get all entries */
getAllEntries(): LogEntry<T>[];
/** Apply entries from another replica */
applyEntries(entries: LogEntry<T>[]): void;
/** Clear the log */
clear(): void;
}
/** Manages synchronization across replicas */
export declare class SyncManager<T = unknown> extends EventEmitter {
private readonly replicaSet;
private readonly log;
private config;
private conflictResolver;
private pendingChanges;
private syncTimer;
constructor(replicaSet: ReplicaSet, log: ReplicationLog<T>, config?: Partial<SyncConfig>);
/** Set sync mode */
setSyncMode(mode: SyncMode, minReplicas?: number): void;
/** Set custom conflict resolver */
setConflictResolver(resolver: ConflictResolver<T>): void;
/** Record a change for replication */
recordChange(key: string, operation: ChangeOperation, value?: T, previousValue?: T): Promise<void>;
/** Sync a change to all replicas */
private syncAll;
/** Sync to minimum number of replicas (semi-sync) */
private syncMinimum;
/** Start background sync for async mode */
startBackgroundSync(interval?: number): void;
/** Stop background sync */
stopBackgroundSync(): void;
/** Resolve a conflict between local and remote values */
resolveConflict(local: T, remote: T, localClock: VectorClock, remoteClock: VectorClock): T;
/** Get sync statistics */
getStats(): {
pendingChanges: number;
lastSequence: number;
syncMode: SyncMode;
};
}
//# sourceMappingURL=sync-manager.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["sync-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,QAAQ,EAEb,QAAQ,EAGR,eAAe,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAiB,MAAM,mBAAmB,CAAC;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AASnD,2CAA2C;AAC3C,qBAAa,cAAc,CAAC,CAAC,GAAG,OAAO;IACrC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,WAAW,CAAc;gBAErB,SAAS,EAAE,SAAS;IAKhC,sCAAsC;IACtC,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,mCAAmC;IACnC,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,iCAAiC;IACjC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAgB5B,0CAA0C;IAC1C,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE;IAKhE,sBAAsB;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS;IAI7C,sBAAsB;IACtB,aAAa,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE;IAI9B,yCAAyC;IACzC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI;IAQ1C,oBAAoB;IACpB,KAAK,IAAI,IAAI;CAKd;AAED,8CAA8C;AAC9C,qBAAa,WAAW,CAAC,CAAC,GAAG,OAAO,CAAE,SAAQ,YAAY;IACxD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,SAAS,CAA+C;gBAG9D,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,EACtB,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC;IAU9B,oBAAoB;IACpB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAOvD,mCAAmC;IACnC,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI;IAIxD,sCAAsC;IAChC,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,eAAe,EAC1B,KAAK,CAAC,EAAE,CAAC,EACT,aAAa,CAAC,EAAE,CAAC,GAChB,OAAO,CAAC,IAAI,CAAC;IAmChB,oCAAoC;YACtB,OAAO;IAWrB,qDAAqD;YACvC,WAAW;IAgBzB,2CAA2C;IAC3C,mBAAmB,CAAC,QAAQ,GAAE,MAAa,GAAG,IAAI;IAalD,2BAA2B;IAC3B,kBAAkB,IAAI,IAAI;IAO1B,yDAAyD;IACzD,eAAe,CACb,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,CAAC,EACT,UAAU,EAAE,WAAW,EACvB,WAAW,EAAE,WAAW,GACvB,CAAC;IAgBJ,0BAA0B;IAC1B,QAAQ,IAAI;QACV,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,QAAQ,CAAC;KACpB;CAOF"}

View File

@@ -0,0 +1,201 @@
"use strict";
/**
* Sync Manager Implementation
* Manages data synchronization across replicas
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SyncManager = exports.ReplicationLog = void 0;
const eventemitter3_1 = __importDefault(require("eventemitter3"));
const types_js_1 = require("./types.js");
const vector_clock_js_1 = require("./vector-clock.js");
/** Default sync configuration */
const DEFAULT_SYNC_CONFIG = {
mode: types_js_1.SyncMode.Asynchronous,
batchSize: 100,
maxLag: 5000,
};
/** Replication log for tracking changes */
class ReplicationLog {
constructor(replicaId) {
this.entries = [];
this.sequence = 0;
this.replicaId = replicaId;
this.vectorClock = new vector_clock_js_1.VectorClock();
}
/** Get the current sequence number */
get currentSequence() {
return this.sequence;
}
/** Get the current vector clock */
get clock() {
return this.vectorClock.clone();
}
/** Append an entry to the log */
append(data) {
this.sequence++;
this.vectorClock.increment(this.replicaId);
const entry = {
id: `${this.replicaId}-${this.sequence}`,
sequence: this.sequence,
data,
timestamp: Date.now(),
vectorClock: this.vectorClock.getValue(),
};
this.entries.push(entry);
return entry;
}
/** Get entries since a sequence number */
getEntriesSince(sequence, limit) {
const filtered = this.entries.filter((e) => e.sequence > sequence);
return limit ? filtered.slice(0, limit) : filtered;
}
/** Get entry by ID */
getEntry(id) {
return this.entries.find((e) => e.id === id);
}
/** Get all entries */
getAllEntries() {
return [...this.entries];
}
/** Apply entries from another replica */
applyEntries(entries) {
for (const entry of entries) {
const entryClock = new vector_clock_js_1.VectorClock(entry.vectorClock);
this.vectorClock.merge(entryClock);
}
// Note: In a real implementation, entries would be merged properly
}
/** Clear the log */
clear() {
this.entries = [];
this.sequence = 0;
this.vectorClock = new vector_clock_js_1.VectorClock();
}
}
exports.ReplicationLog = ReplicationLog;
/** Manages synchronization across replicas */
class SyncManager extends eventemitter3_1.default {
constructor(replicaSet, log, config) {
super();
this.pendingChanges = [];
this.syncTimer = null;
this.replicaSet = replicaSet;
this.log = log;
this.config = { ...DEFAULT_SYNC_CONFIG, ...config };
// Default to timestamp-based resolution
this.conflictResolver = new vector_clock_js_1.LastWriteWins();
}
/** Set sync mode */
setSyncMode(mode, minReplicas) {
this.config.mode = mode;
if (minReplicas !== undefined) {
this.config.minReplicas = minReplicas;
}
}
/** Set custom conflict resolver */
setConflictResolver(resolver) {
this.conflictResolver = resolver;
}
/** Record a change for replication */
async recordChange(key, operation, value, previousValue) {
const primary = this.replicaSet.primary;
if (!primary) {
throw types_js_1.ReplicationError.noPrimary();
}
const entry = this.log.append({ key, operation, value, previousValue });
const change = {
id: entry.id,
operation,
key,
value,
previousValue,
timestamp: entry.timestamp,
sourceReplica: primary.id,
vectorClock: entry.vectorClock,
};
this.emit(types_js_1.ReplicationEvent.ChangeReceived, change);
// Handle based on sync mode
switch (this.config.mode) {
case types_js_1.SyncMode.Synchronous:
await this.syncAll(change);
break;
case types_js_1.SyncMode.SemiSync:
await this.syncMinimum(change);
break;
case types_js_1.SyncMode.Asynchronous:
this.pendingChanges.push(change);
break;
}
}
/** Sync a change to all replicas */
async syncAll(change) {
const secondaries = this.replicaSet.secondaries;
if (secondaries.length === 0)
return;
this.emit(types_js_1.ReplicationEvent.SyncStarted, { replicas: secondaries.map((r) => r.id) });
// In a real implementation, this would send to all replicas
// For now, we just emit the completion event
this.emit(types_js_1.ReplicationEvent.SyncCompleted, { change, replicas: secondaries.map((r) => r.id) });
}
/** Sync to minimum number of replicas (semi-sync) */
async syncMinimum(change) {
const minReplicas = this.config.minReplicas ?? 1;
const secondaries = this.replicaSet.secondaries;
if (secondaries.length < minReplicas) {
throw types_js_1.ReplicationError.quorumNotMet(minReplicas, secondaries.length);
}
// Sync to minimum number of replicas
const targetReplicas = secondaries.slice(0, minReplicas);
this.emit(types_js_1.ReplicationEvent.SyncStarted, { replicas: targetReplicas.map((r) => r.id) });
// In a real implementation, this would wait for acknowledgments
this.emit(types_js_1.ReplicationEvent.SyncCompleted, { change, replicas: targetReplicas.map((r) => r.id) });
}
/** Start background sync for async mode */
startBackgroundSync(interval = 1000) {
if (this.syncTimer)
return;
this.syncTimer = setInterval(async () => {
if (this.pendingChanges.length > 0) {
const batch = this.pendingChanges.splice(0, this.config.batchSize);
for (const change of batch) {
await this.syncAll(change);
}
}
}, interval);
}
/** Stop background sync */
stopBackgroundSync() {
if (this.syncTimer) {
clearInterval(this.syncTimer);
this.syncTimer = null;
}
}
/** Resolve a conflict between local and remote values */
resolveConflict(local, remote, localClock, remoteClock) {
// Check for causal relationship
if (localClock.happensBefore(remoteClock)) {
return remote; // Remote is newer
}
else if (localClock.happensAfter(remoteClock)) {
return local; // Local is newer
}
// Concurrent - need conflict resolution
this.emit(types_js_1.ReplicationEvent.ConflictDetected, { local, remote });
const resolved = this.conflictResolver.resolve(local, remote, localClock, remoteClock);
this.emit(types_js_1.ReplicationEvent.ConflictResolved, { local, remote, resolved });
return resolved;
}
/** Get sync statistics */
getStats() {
return {
pendingChanges: this.pendingChanges.length,
lastSequence: this.log.currentSequence,
syncMode: this.config.mode,
};
}
}
exports.SyncManager = SyncManager;
//# sourceMappingURL=sync-manager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,260 @@
/**
* Sync Manager Implementation
* Manages data synchronization across replicas
*/
import EventEmitter from 'eventemitter3';
import {
type ReplicaId,
type SyncConfig,
type LogEntry,
type ChangeEvent,
SyncMode,
ReplicationError,
ReplicationEvent,
ChangeOperation,
} from './types.js';
import { VectorClock, type ConflictResolver, LastWriteWins } from './vector-clock.js';
import type { ReplicaSet } from './replica-set.js';
/** Default sync configuration */
const DEFAULT_SYNC_CONFIG: SyncConfig = {
mode: SyncMode.Asynchronous,
batchSize: 100,
maxLag: 5000,
};
/** Replication log for tracking changes */
export class ReplicationLog<T = unknown> {
private entries: LogEntry<T>[] = [];
private sequence = 0;
private readonly replicaId: ReplicaId;
private vectorClock: VectorClock;
constructor(replicaId: ReplicaId) {
this.replicaId = replicaId;
this.vectorClock = new VectorClock();
}
/** Get the current sequence number */
get currentSequence(): number {
return this.sequence;
}
/** Get the current vector clock */
get clock(): VectorClock {
return this.vectorClock.clone();
}
/** Append an entry to the log */
append(data: T): LogEntry<T> {
this.sequence++;
this.vectorClock.increment(this.replicaId);
const entry: LogEntry<T> = {
id: `${this.replicaId}-${this.sequence}`,
sequence: this.sequence,
data,
timestamp: Date.now(),
vectorClock: this.vectorClock.getValue(),
};
this.entries.push(entry);
return entry;
}
/** Get entries since a sequence number */
getEntriesSince(sequence: number, limit?: number): LogEntry<T>[] {
const filtered = this.entries.filter((e) => e.sequence > sequence);
return limit ? filtered.slice(0, limit) : filtered;
}
/** Get entry by ID */
getEntry(id: string): LogEntry<T> | undefined {
return this.entries.find((e) => e.id === id);
}
/** Get all entries */
getAllEntries(): LogEntry<T>[] {
return [...this.entries];
}
/** Apply entries from another replica */
applyEntries(entries: LogEntry<T>[]): void {
for (const entry of entries) {
const entryClock = new VectorClock(entry.vectorClock);
this.vectorClock.merge(entryClock);
}
// Note: In a real implementation, entries would be merged properly
}
/** Clear the log */
clear(): void {
this.entries = [];
this.sequence = 0;
this.vectorClock = new VectorClock();
}
}
/** Manages synchronization across replicas */
export class SyncManager<T = unknown> extends EventEmitter {
private readonly replicaSet: ReplicaSet;
private readonly log: ReplicationLog<T>;
private config: SyncConfig;
private conflictResolver: ConflictResolver<T>;
private pendingChanges: ChangeEvent<T>[] = [];
private syncTimer: ReturnType<typeof setInterval> | null = null;
constructor(
replicaSet: ReplicaSet,
log: ReplicationLog<T>,
config?: Partial<SyncConfig>,
) {
super();
this.replicaSet = replicaSet;
this.log = log;
this.config = { ...DEFAULT_SYNC_CONFIG, ...config };
// Default to timestamp-based resolution
this.conflictResolver = new LastWriteWins() as unknown as ConflictResolver<T>;
}
/** Set sync mode */
setSyncMode(mode: SyncMode, minReplicas?: number): void {
this.config.mode = mode;
if (minReplicas !== undefined) {
this.config.minReplicas = minReplicas;
}
}
/** Set custom conflict resolver */
setConflictResolver(resolver: ConflictResolver<T>): void {
this.conflictResolver = resolver;
}
/** Record a change for replication */
async recordChange(
key: string,
operation: ChangeOperation,
value?: T,
previousValue?: T,
): Promise<void> {
const primary = this.replicaSet.primary;
if (!primary) {
throw ReplicationError.noPrimary();
}
const entry = this.log.append({ key, operation, value, previousValue } as unknown as T);
const change: ChangeEvent<T> = {
id: entry.id,
operation,
key,
value,
previousValue,
timestamp: entry.timestamp,
sourceReplica: primary.id,
vectorClock: entry.vectorClock,
};
this.emit(ReplicationEvent.ChangeReceived, change);
// Handle based on sync mode
switch (this.config.mode) {
case SyncMode.Synchronous:
await this.syncAll(change);
break;
case SyncMode.SemiSync:
await this.syncMinimum(change);
break;
case SyncMode.Asynchronous:
this.pendingChanges.push(change);
break;
}
}
/** Sync a change to all replicas */
private async syncAll(change: ChangeEvent<T>): Promise<void> {
const secondaries = this.replicaSet.secondaries;
if (secondaries.length === 0) return;
this.emit(ReplicationEvent.SyncStarted, { replicas: secondaries.map((r) => r.id) });
// In a real implementation, this would send to all replicas
// For now, we just emit the completion event
this.emit(ReplicationEvent.SyncCompleted, { change, replicas: secondaries.map((r) => r.id) });
}
/** Sync to minimum number of replicas (semi-sync) */
private async syncMinimum(change: ChangeEvent<T>): Promise<void> {
const minReplicas = this.config.minReplicas ?? 1;
const secondaries = this.replicaSet.secondaries;
if (secondaries.length < minReplicas) {
throw ReplicationError.quorumNotMet(minReplicas, secondaries.length);
}
// Sync to minimum number of replicas
const targetReplicas = secondaries.slice(0, minReplicas);
this.emit(ReplicationEvent.SyncStarted, { replicas: targetReplicas.map((r) => r.id) });
// In a real implementation, this would wait for acknowledgments
this.emit(ReplicationEvent.SyncCompleted, { change, replicas: targetReplicas.map((r) => r.id) });
}
/** Start background sync for async mode */
startBackgroundSync(interval: number = 1000): void {
if (this.syncTimer) return;
this.syncTimer = setInterval(async () => {
if (this.pendingChanges.length > 0) {
const batch = this.pendingChanges.splice(0, this.config.batchSize);
for (const change of batch) {
await this.syncAll(change);
}
}
}, interval);
}
/** Stop background sync */
stopBackgroundSync(): void {
if (this.syncTimer) {
clearInterval(this.syncTimer);
this.syncTimer = null;
}
}
/** Resolve a conflict between local and remote values */
resolveConflict(
local: T,
remote: T,
localClock: VectorClock,
remoteClock: VectorClock,
): T {
// Check for causal relationship
if (localClock.happensBefore(remoteClock)) {
return remote; // Remote is newer
} else if (localClock.happensAfter(remoteClock)) {
return local; // Local is newer
}
// Concurrent - need conflict resolution
this.emit(ReplicationEvent.ConflictDetected, { local, remote });
const resolved = this.conflictResolver.resolve(local, remote, localClock, remoteClock);
this.emit(ReplicationEvent.ConflictResolved, { local, remote, resolved });
return resolved;
}
/** Get sync statistics */
getStats(): {
pendingChanges: number;
lastSequence: number;
syncMode: SyncMode;
} {
return {
pendingChanges: this.pendingChanges.length,
lastSequence: this.log.currentSequence,
syncMode: this.config.mode,
};
}
}

View File

@@ -0,0 +1,158 @@
/**
* Replication Types
* Data replication and synchronization types
*/
/** Unique identifier for a replica */
export type ReplicaId = string;
/** Logical timestamp for ordering events */
export type LogicalClock = number;
/** Role of a replica in the set */
export declare enum ReplicaRole {
Primary = "primary",
Secondary = "secondary",
Arbiter = "arbiter"
}
/** Status of a replica */
export declare enum ReplicaStatus {
Active = "active",
Syncing = "syncing",
Offline = "offline",
Failed = "failed"
}
/** Synchronization mode */
export declare enum SyncMode {
/** All replicas must confirm before commit */
Synchronous = "synchronous",
/** Commit immediately, replicate in background */
Asynchronous = "asynchronous",
/** Wait for minimum number of replicas */
SemiSync = "semi-sync"
}
/** Health status of a replica */
export declare enum HealthStatus {
Healthy = "healthy",
Degraded = "degraded",
Unhealthy = "unhealthy",
Unknown = "unknown"
}
/** Replica information */
export interface Replica {
id: ReplicaId;
address: string;
role: ReplicaRole;
status: ReplicaStatus;
lastSeen: number;
lag: number;
}
/** Change operation type */
export declare enum ChangeOperation {
Insert = "insert",
Update = "update",
Delete = "delete"
}
/** Change event for CDC */
export interface ChangeEvent<T = unknown> {
/** Unique event ID */
id: string;
/** Operation type */
operation: ChangeOperation;
/** Affected key/path */
key: string;
/** New value (for insert/update) */
value?: T;
/** Previous value (for update/delete) */
previousValue?: T;
/** Timestamp of the change */
timestamp: number;
/** Source replica */
sourceReplica: ReplicaId;
/** Vector clock for ordering */
vectorClock: VectorClockValue;
}
/** Vector clock entry for a single node */
export type VectorClockValue = Map<ReplicaId, LogicalClock>;
/** Log entry for replication */
export interface LogEntry<T = unknown> {
/** Unique entry ID */
id: string;
/** Sequence number */
sequence: number;
/** Operation data */
data: T;
/** Timestamp */
timestamp: number;
/** Vector clock */
vectorClock: VectorClockValue;
}
/** Failover policy */
export declare enum FailoverPolicy {
/** Automatic failover with quorum */
Automatic = "automatic",
/** Manual intervention required */
Manual = "manual",
/** Priority-based failover */
Priority = "priority"
}
/** Replication error types */
export declare class ReplicationError extends Error {
readonly code: ReplicationErrorCode;
constructor(message: string, code: ReplicationErrorCode);
static replicaNotFound(id: string): ReplicationError;
static noPrimary(): ReplicationError;
static timeout(operation: string): ReplicationError;
static quorumNotMet(needed: number, available: number): ReplicationError;
static splitBrain(): ReplicationError;
static conflictResolution(reason: string): ReplicationError;
}
export declare enum ReplicationErrorCode {
ReplicaNotFound = "REPLICA_NOT_FOUND",
NoPrimary = "NO_PRIMARY",
Timeout = "TIMEOUT",
SyncFailed = "SYNC_FAILED",
ConflictResolution = "CONFLICT_RESOLUTION",
FailoverFailed = "FAILOVER_FAILED",
Network = "NETWORK",
QuorumNotMet = "QUORUM_NOT_MET",
SplitBrain = "SPLIT_BRAIN",
InvalidState = "INVALID_STATE"
}
/** Events emitted by replication components */
export declare enum ReplicationEvent {
ReplicaAdded = "replicaAdded",
ReplicaRemoved = "replicaRemoved",
ReplicaStatusChanged = "replicaStatusChanged",
PrimaryChanged = "primaryChanged",
ChangeReceived = "changeReceived",
SyncStarted = "syncStarted",
SyncCompleted = "syncCompleted",
ConflictDetected = "conflictDetected",
ConflictResolved = "conflictResolved",
FailoverStarted = "failoverStarted",
FailoverCompleted = "failoverCompleted",
Error = "error"
}
/** Configuration for replica set */
export interface ReplicaSetConfig {
/** Cluster name */
name: string;
/** Minimum replicas for quorum */
minQuorum: number;
/** Heartbeat interval in milliseconds */
heartbeatInterval: number;
/** Timeout for health checks in milliseconds */
healthCheckTimeout: number;
/** Failover policy */
failoverPolicy: FailoverPolicy;
}
/** Configuration for sync manager */
export interface SyncConfig {
/** Sync mode */
mode: SyncMode;
/** Minimum replicas for semi-sync */
minReplicas?: number;
/** Batch size for streaming changes */
batchSize: number;
/** Maximum lag before triggering catchup */
maxLag: number;
}
//# sourceMappingURL=types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,114 @@
"use strict";
/**
* Replication Types
* Data replication and synchronization types
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReplicationEvent = exports.ReplicationErrorCode = exports.ReplicationError = exports.FailoverPolicy = exports.ChangeOperation = exports.HealthStatus = exports.SyncMode = exports.ReplicaStatus = exports.ReplicaRole = void 0;
/** Role of a replica in the set */
var ReplicaRole;
(function (ReplicaRole) {
ReplicaRole["Primary"] = "primary";
ReplicaRole["Secondary"] = "secondary";
ReplicaRole["Arbiter"] = "arbiter";
})(ReplicaRole || (exports.ReplicaRole = ReplicaRole = {}));
/** Status of a replica */
var ReplicaStatus;
(function (ReplicaStatus) {
ReplicaStatus["Active"] = "active";
ReplicaStatus["Syncing"] = "syncing";
ReplicaStatus["Offline"] = "offline";
ReplicaStatus["Failed"] = "failed";
})(ReplicaStatus || (exports.ReplicaStatus = ReplicaStatus = {}));
/** Synchronization mode */
var SyncMode;
(function (SyncMode) {
/** All replicas must confirm before commit */
SyncMode["Synchronous"] = "synchronous";
/** Commit immediately, replicate in background */
SyncMode["Asynchronous"] = "asynchronous";
/** Wait for minimum number of replicas */
SyncMode["SemiSync"] = "semi-sync";
})(SyncMode || (exports.SyncMode = SyncMode = {}));
/** Health status of a replica */
var HealthStatus;
(function (HealthStatus) {
HealthStatus["Healthy"] = "healthy";
HealthStatus["Degraded"] = "degraded";
HealthStatus["Unhealthy"] = "unhealthy";
HealthStatus["Unknown"] = "unknown";
})(HealthStatus || (exports.HealthStatus = HealthStatus = {}));
/** Change operation type */
var ChangeOperation;
(function (ChangeOperation) {
ChangeOperation["Insert"] = "insert";
ChangeOperation["Update"] = "update";
ChangeOperation["Delete"] = "delete";
})(ChangeOperation || (exports.ChangeOperation = ChangeOperation = {}));
/** Failover policy */
var FailoverPolicy;
(function (FailoverPolicy) {
/** Automatic failover with quorum */
FailoverPolicy["Automatic"] = "automatic";
/** Manual intervention required */
FailoverPolicy["Manual"] = "manual";
/** Priority-based failover */
FailoverPolicy["Priority"] = "priority";
})(FailoverPolicy || (exports.FailoverPolicy = FailoverPolicy = {}));
/** Replication error types */
class ReplicationError extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = 'ReplicationError';
}
static replicaNotFound(id) {
return new ReplicationError(`Replica not found: ${id}`, ReplicationErrorCode.ReplicaNotFound);
}
static noPrimary() {
return new ReplicationError('No primary replica available', ReplicationErrorCode.NoPrimary);
}
static timeout(operation) {
return new ReplicationError(`Replication timeout: ${operation}`, ReplicationErrorCode.Timeout);
}
static quorumNotMet(needed, available) {
return new ReplicationError(`Quorum not met: needed ${needed}, got ${available}`, ReplicationErrorCode.QuorumNotMet);
}
static splitBrain() {
return new ReplicationError('Split-brain detected', ReplicationErrorCode.SplitBrain);
}
static conflictResolution(reason) {
return new ReplicationError(`Conflict resolution failed: ${reason}`, ReplicationErrorCode.ConflictResolution);
}
}
exports.ReplicationError = ReplicationError;
var ReplicationErrorCode;
(function (ReplicationErrorCode) {
ReplicationErrorCode["ReplicaNotFound"] = "REPLICA_NOT_FOUND";
ReplicationErrorCode["NoPrimary"] = "NO_PRIMARY";
ReplicationErrorCode["Timeout"] = "TIMEOUT";
ReplicationErrorCode["SyncFailed"] = "SYNC_FAILED";
ReplicationErrorCode["ConflictResolution"] = "CONFLICT_RESOLUTION";
ReplicationErrorCode["FailoverFailed"] = "FAILOVER_FAILED";
ReplicationErrorCode["Network"] = "NETWORK";
ReplicationErrorCode["QuorumNotMet"] = "QUORUM_NOT_MET";
ReplicationErrorCode["SplitBrain"] = "SPLIT_BRAIN";
ReplicationErrorCode["InvalidState"] = "INVALID_STATE";
})(ReplicationErrorCode || (exports.ReplicationErrorCode = ReplicationErrorCode = {}));
/** Events emitted by replication components */
var ReplicationEvent;
(function (ReplicationEvent) {
ReplicationEvent["ReplicaAdded"] = "replicaAdded";
ReplicationEvent["ReplicaRemoved"] = "replicaRemoved";
ReplicationEvent["ReplicaStatusChanged"] = "replicaStatusChanged";
ReplicationEvent["PrimaryChanged"] = "primaryChanged";
ReplicationEvent["ChangeReceived"] = "changeReceived";
ReplicationEvent["SyncStarted"] = "syncStarted";
ReplicationEvent["SyncCompleted"] = "syncCompleted";
ReplicationEvent["ConflictDetected"] = "conflictDetected";
ReplicationEvent["ConflictResolved"] = "conflictResolved";
ReplicationEvent["FailoverStarted"] = "failoverStarted";
ReplicationEvent["FailoverCompleted"] = "failoverCompleted";
ReplicationEvent["Error"] = "error";
})(ReplicationEvent || (exports.ReplicationEvent = ReplicationEvent = {}));
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,205 @@
/**
* Replication Types
* Data replication and synchronization types
*/
import type EventEmitter from 'eventemitter3';
/** Unique identifier for a replica */
export type ReplicaId = string;
/** Logical timestamp for ordering events */
export type LogicalClock = number;
/** Role of a replica in the set */
export enum ReplicaRole {
Primary = 'primary',
Secondary = 'secondary',
Arbiter = 'arbiter',
}
/** Status of a replica */
export enum ReplicaStatus {
Active = 'active',
Syncing = 'syncing',
Offline = 'offline',
Failed = 'failed',
}
/** Synchronization mode */
export enum SyncMode {
/** All replicas must confirm before commit */
Synchronous = 'synchronous',
/** Commit immediately, replicate in background */
Asynchronous = 'asynchronous',
/** Wait for minimum number of replicas */
SemiSync = 'semi-sync',
}
/** Health status of a replica */
export enum HealthStatus {
Healthy = 'healthy',
Degraded = 'degraded',
Unhealthy = 'unhealthy',
Unknown = 'unknown',
}
/** Replica information */
export interface Replica {
id: ReplicaId;
address: string;
role: ReplicaRole;
status: ReplicaStatus;
lastSeen: number;
lag: number; // Replication lag in milliseconds
}
/** Change operation type */
export enum ChangeOperation {
Insert = 'insert',
Update = 'update',
Delete = 'delete',
}
/** Change event for CDC */
export interface ChangeEvent<T = unknown> {
/** Unique event ID */
id: string;
/** Operation type */
operation: ChangeOperation;
/** Affected key/path */
key: string;
/** New value (for insert/update) */
value?: T;
/** Previous value (for update/delete) */
previousValue?: T;
/** Timestamp of the change */
timestamp: number;
/** Source replica */
sourceReplica: ReplicaId;
/** Vector clock for ordering */
vectorClock: VectorClockValue;
}
/** Vector clock entry for a single node */
export type VectorClockValue = Map<ReplicaId, LogicalClock>;
/** Log entry for replication */
export interface LogEntry<T = unknown> {
/** Unique entry ID */
id: string;
/** Sequence number */
sequence: number;
/** Operation data */
data: T;
/** Timestamp */
timestamp: number;
/** Vector clock */
vectorClock: VectorClockValue;
}
/** Failover policy */
export enum FailoverPolicy {
/** Automatic failover with quorum */
Automatic = 'automatic',
/** Manual intervention required */
Manual = 'manual',
/** Priority-based failover */
Priority = 'priority',
}
/** Replication error types */
export class ReplicationError extends Error {
constructor(
message: string,
public readonly code: ReplicationErrorCode,
) {
super(message);
this.name = 'ReplicationError';
}
static replicaNotFound(id: string): ReplicationError {
return new ReplicationError(`Replica not found: ${id}`, ReplicationErrorCode.ReplicaNotFound);
}
static noPrimary(): ReplicationError {
return new ReplicationError('No primary replica available', ReplicationErrorCode.NoPrimary);
}
static timeout(operation: string): ReplicationError {
return new ReplicationError(`Replication timeout: ${operation}`, ReplicationErrorCode.Timeout);
}
static quorumNotMet(needed: number, available: number): ReplicationError {
return new ReplicationError(
`Quorum not met: needed ${needed}, got ${available}`,
ReplicationErrorCode.QuorumNotMet,
);
}
static splitBrain(): ReplicationError {
return new ReplicationError('Split-brain detected', ReplicationErrorCode.SplitBrain);
}
static conflictResolution(reason: string): ReplicationError {
return new ReplicationError(
`Conflict resolution failed: ${reason}`,
ReplicationErrorCode.ConflictResolution,
);
}
}
export enum ReplicationErrorCode {
ReplicaNotFound = 'REPLICA_NOT_FOUND',
NoPrimary = 'NO_PRIMARY',
Timeout = 'TIMEOUT',
SyncFailed = 'SYNC_FAILED',
ConflictResolution = 'CONFLICT_RESOLUTION',
FailoverFailed = 'FAILOVER_FAILED',
Network = 'NETWORK',
QuorumNotMet = 'QUORUM_NOT_MET',
SplitBrain = 'SPLIT_BRAIN',
InvalidState = 'INVALID_STATE',
}
/** Events emitted by replication components */
export enum ReplicationEvent {
ReplicaAdded = 'replicaAdded',
ReplicaRemoved = 'replicaRemoved',
ReplicaStatusChanged = 'replicaStatusChanged',
PrimaryChanged = 'primaryChanged',
ChangeReceived = 'changeReceived',
SyncStarted = 'syncStarted',
SyncCompleted = 'syncCompleted',
ConflictDetected = 'conflictDetected',
ConflictResolved = 'conflictResolved',
FailoverStarted = 'failoverStarted',
FailoverCompleted = 'failoverCompleted',
Error = 'error',
}
/** Configuration for replica set */
export interface ReplicaSetConfig {
/** Cluster name */
name: string;
/** Minimum replicas for quorum */
minQuorum: number;
/** Heartbeat interval in milliseconds */
heartbeatInterval: number;
/** Timeout for health checks in milliseconds */
healthCheckTimeout: number;
/** Failover policy */
failoverPolicy: FailoverPolicy;
}
/** Configuration for sync manager */
export interface SyncConfig {
/** Sync mode */
mode: SyncMode;
/** Minimum replicas for semi-sync */
minReplicas?: number;
/** Batch size for streaming changes */
batchSize: number;
/** Maximum lag before triggering catchup */
maxLag: number;
}

View File

@@ -0,0 +1,63 @@
/**
* Vector Clock Implementation
* For conflict detection and resolution in distributed systems
*/
import type { ReplicaId, LogicalClock, VectorClockValue } from './types.js';
/** Comparison result between vector clocks */
export declare enum VectorClockComparison {
/** First happens before second */
Before = "before",
/** First happens after second */
After = "after",
/** Clocks are concurrent (no causal relationship) */
Concurrent = "concurrent",
/** Clocks are equal */
Equal = "equal"
}
/** Vector clock for tracking causality in distributed systems */
export declare class VectorClock {
private clock;
constructor(initial?: VectorClockValue | Map<ReplicaId, LogicalClock>);
/** Get the clock value for a replica */
get(replicaId: ReplicaId): LogicalClock;
/** Increment the clock for a replica */
increment(replicaId: ReplicaId): void;
/** Update with a received clock (merge) */
merge(other: VectorClock): void;
/** Create a copy of this clock */
clone(): VectorClock;
/** Get the clock value as a Map */
getValue(): VectorClockValue;
/** Compare two vector clocks */
compare(other: VectorClock): VectorClockComparison;
/** Check if this clock happens before another */
happensBefore(other: VectorClock): boolean;
/** Check if this clock happens after another */
happensAfter(other: VectorClock): boolean;
/** Check if clocks are concurrent (no causal relationship) */
isConcurrent(other: VectorClock): boolean;
/** Serialize to JSON */
toJSON(): Record<string, number>;
/** Create from JSON */
static fromJSON(json: Record<string, number>): VectorClock;
/** Create a new vector clock with a single entry */
static single(replicaId: ReplicaId, time?: LogicalClock): VectorClock;
}
/** Conflict resolver interface */
export interface ConflictResolver<T> {
/** Resolve a conflict between two values */
resolve(local: T, remote: T, localClock: VectorClock, remoteClock: VectorClock): T;
}
/** Last-write-wins conflict resolver */
export declare class LastWriteWins<T extends {
timestamp: number;
}> implements ConflictResolver<T> {
resolve(local: T, remote: T): T;
}
/** Custom merge function conflict resolver */
export declare class MergeFunction<T> implements ConflictResolver<T> {
private mergeFn;
constructor(mergeFn: (local: T, remote: T) => T);
resolve(local: T, remote: T): T;
}
//# sourceMappingURL=vector-clock.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"vector-clock.d.ts","sourceRoot":"","sources":["vector-clock.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE5E,8CAA8C;AAC9C,oBAAY,qBAAqB;IAC/B,kCAAkC;IAClC,MAAM,WAAW;IACjB,iCAAiC;IACjC,KAAK,UAAU;IACf,qDAAqD;IACrD,UAAU,eAAe;IACzB,uBAAuB;IACvB,KAAK,UAAU;CAChB;AAED,iEAAiE;AACjE,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAA+B;gBAEhC,OAAO,CAAC,EAAE,gBAAgB,GAAG,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC;IAIrE,wCAAwC;IACxC,GAAG,CAAC,SAAS,EAAE,SAAS,GAAG,YAAY;IAIvC,wCAAwC;IACxC,SAAS,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAKrC,2CAA2C;IAC3C,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAO/B,kCAAkC;IAClC,KAAK,IAAI,WAAW;IAIpB,mCAAmC;IACnC,QAAQ,IAAI,gBAAgB;IAI5B,gCAAgC;IAChC,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,qBAAqB;IA6BlD,iDAAiD;IACjD,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO;IAI1C,gDAAgD;IAChD,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO;IAIzC,8DAA8D;IAC9D,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO;IAIzC,wBAAwB;IACxB,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAQhC,uBAAuB;IACvB,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW;IAQ1D,oDAAoD;IACpD,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,GAAE,YAAgB,GAAG,WAAW;CAKzE;AAED,kCAAkC;AAClC,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,4CAA4C;IAC5C,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,GAAG,CAAC,CAAC;CACpF;AAED,wCAAwC;AACxC,qBAAa,aAAa,CAAC,CAAC,SAAS;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAE,YAAW,gBAAgB,CAAC,CAAC,CAAC;IACxF,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC;CAGhC;AAED,8CAA8C;AAC9C,qBAAa,aAAa,CAAC,CAAC,CAAE,YAAW,gBAAgB,CAAC,CAAC,CAAC;IAC9C,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC;IAEvD,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC;CAGhC"}

View File

@@ -0,0 +1,131 @@
"use strict";
/**
* Vector Clock Implementation
* For conflict detection and resolution in distributed systems
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MergeFunction = exports.LastWriteWins = exports.VectorClock = exports.VectorClockComparison = void 0;
/** Comparison result between vector clocks */
var VectorClockComparison;
(function (VectorClockComparison) {
/** First happens before second */
VectorClockComparison["Before"] = "before";
/** First happens after second */
VectorClockComparison["After"] = "after";
/** Clocks are concurrent (no causal relationship) */
VectorClockComparison["Concurrent"] = "concurrent";
/** Clocks are equal */
VectorClockComparison["Equal"] = "equal";
})(VectorClockComparison || (exports.VectorClockComparison = VectorClockComparison = {}));
/** Vector clock for tracking causality in distributed systems */
class VectorClock {
constructor(initial) {
this.clock = new Map(initial);
}
/** Get the clock value for a replica */
get(replicaId) {
return this.clock.get(replicaId) ?? 0;
}
/** Increment the clock for a replica */
increment(replicaId) {
const current = this.get(replicaId);
this.clock.set(replicaId, current + 1);
}
/** Update with a received clock (merge) */
merge(other) {
for (const [replicaId, otherTime] of other.clock) {
const myTime = this.get(replicaId);
this.clock.set(replicaId, Math.max(myTime, otherTime));
}
}
/** Create a copy of this clock */
clone() {
return new VectorClock(new Map(this.clock));
}
/** Get the clock value as a Map */
getValue() {
return new Map(this.clock);
}
/** Compare two vector clocks */
compare(other) {
let isLess = false;
let isGreater = false;
// Get all unique replica IDs
const allReplicas = new Set([...this.clock.keys(), ...other.clock.keys()]);
for (const replicaId of allReplicas) {
const myTime = this.get(replicaId);
const otherTime = other.get(replicaId);
if (myTime < otherTime) {
isLess = true;
}
else if (myTime > otherTime) {
isGreater = true;
}
}
if (isLess && isGreater) {
return VectorClockComparison.Concurrent;
}
else if (isLess) {
return VectorClockComparison.Before;
}
else if (isGreater) {
return VectorClockComparison.After;
}
else {
return VectorClockComparison.Equal;
}
}
/** Check if this clock happens before another */
happensBefore(other) {
return this.compare(other) === VectorClockComparison.Before;
}
/** Check if this clock happens after another */
happensAfter(other) {
return this.compare(other) === VectorClockComparison.After;
}
/** Check if clocks are concurrent (no causal relationship) */
isConcurrent(other) {
return this.compare(other) === VectorClockComparison.Concurrent;
}
/** Serialize to JSON */
toJSON() {
const obj = {};
for (const [key, value] of this.clock) {
obj[key] = value;
}
return obj;
}
/** Create from JSON */
static fromJSON(json) {
const clock = new VectorClock();
for (const [key, value] of Object.entries(json)) {
clock.clock.set(key, value);
}
return clock;
}
/** Create a new vector clock with a single entry */
static single(replicaId, time = 1) {
const clock = new VectorClock();
clock.clock.set(replicaId, time);
return clock;
}
}
exports.VectorClock = VectorClock;
/** Last-write-wins conflict resolver */
class LastWriteWins {
resolve(local, remote) {
return local.timestamp >= remote.timestamp ? local : remote;
}
}
exports.LastWriteWins = LastWriteWins;
/** Custom merge function conflict resolver */
class MergeFunction {
constructor(mergeFn) {
this.mergeFn = mergeFn;
}
resolve(local, remote) {
return this.mergeFn(local, remote);
}
}
exports.MergeFunction = MergeFunction;
//# sourceMappingURL=vector-clock.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"vector-clock.js","sourceRoot":"","sources":["vector-clock.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,148 @@
/**
* Vector Clock Implementation
* For conflict detection and resolution in distributed systems
*/
import type { ReplicaId, LogicalClock, VectorClockValue } from './types.js';
/** Comparison result between vector clocks */
export enum VectorClockComparison {
/** First happens before second */
Before = 'before',
/** First happens after second */
After = 'after',
/** Clocks are concurrent (no causal relationship) */
Concurrent = 'concurrent',
/** Clocks are equal */
Equal = 'equal',
}
/** Vector clock for tracking causality in distributed systems */
export class VectorClock {
private clock: Map<ReplicaId, LogicalClock>;
constructor(initial?: VectorClockValue | Map<ReplicaId, LogicalClock>) {
this.clock = new Map(initial);
}
/** Get the clock value for a replica */
get(replicaId: ReplicaId): LogicalClock {
return this.clock.get(replicaId) ?? 0;
}
/** Increment the clock for a replica */
increment(replicaId: ReplicaId): void {
const current = this.get(replicaId);
this.clock.set(replicaId, current + 1);
}
/** Update with a received clock (merge) */
merge(other: VectorClock): void {
for (const [replicaId, otherTime] of other.clock) {
const myTime = this.get(replicaId);
this.clock.set(replicaId, Math.max(myTime, otherTime));
}
}
/** Create a copy of this clock */
clone(): VectorClock {
return new VectorClock(new Map(this.clock));
}
/** Get the clock value as a Map */
getValue(): VectorClockValue {
return new Map(this.clock);
}
/** Compare two vector clocks */
compare(other: VectorClock): VectorClockComparison {
let isLess = false;
let isGreater = false;
// Get all unique replica IDs
const allReplicas = new Set([...this.clock.keys(), ...other.clock.keys()]);
for (const replicaId of allReplicas) {
const myTime = this.get(replicaId);
const otherTime = other.get(replicaId);
if (myTime < otherTime) {
isLess = true;
} else if (myTime > otherTime) {
isGreater = true;
}
}
if (isLess && isGreater) {
return VectorClockComparison.Concurrent;
} else if (isLess) {
return VectorClockComparison.Before;
} else if (isGreater) {
return VectorClockComparison.After;
} else {
return VectorClockComparison.Equal;
}
}
/** Check if this clock happens before another */
happensBefore(other: VectorClock): boolean {
return this.compare(other) === VectorClockComparison.Before;
}
/** Check if this clock happens after another */
happensAfter(other: VectorClock): boolean {
return this.compare(other) === VectorClockComparison.After;
}
/** Check if clocks are concurrent (no causal relationship) */
isConcurrent(other: VectorClock): boolean {
return this.compare(other) === VectorClockComparison.Concurrent;
}
/** Serialize to JSON */
toJSON(): Record<string, number> {
const obj: Record<string, number> = {};
for (const [key, value] of this.clock) {
obj[key] = value;
}
return obj;
}
/** Create from JSON */
static fromJSON(json: Record<string, number>): VectorClock {
const clock = new VectorClock();
for (const [key, value] of Object.entries(json)) {
clock.clock.set(key, value);
}
return clock;
}
/** Create a new vector clock with a single entry */
static single(replicaId: ReplicaId, time: LogicalClock = 1): VectorClock {
const clock = new VectorClock();
clock.clock.set(replicaId, time);
return clock;
}
}
/** Conflict resolver interface */
export interface ConflictResolver<T> {
/** Resolve a conflict between two values */
resolve(local: T, remote: T, localClock: VectorClock, remoteClock: VectorClock): T;
}
/** Last-write-wins conflict resolver */
export class LastWriteWins<T extends { timestamp: number }> implements ConflictResolver<T> {
resolve(local: T, remote: T): T {
return local.timestamp >= remote.timestamp ? local : remote;
}
}
/** Custom merge function conflict resolver */
export class MergeFunction<T> implements ConflictResolver<T> {
constructor(private mergeFn: (local: T, remote: T) => T) {}
resolve(local: T, remote: T): T {
return this.mergeFn(local, remote);
}
}