@delali/sirannon-db 0.1.4 → 0.1.5

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.
@@ -0,0 +1,491 @@
1
+ import { C as ConflictResolver, h as ConflictContext, i as ConflictResolution, H as HLCTimestamp, R as ReplicationBatch, A as ApplyResult, j as SyncPhase, k as SyncTableManifest, I as InFlightBatch, P as PeerState, b as ForwardedTransactionResult, c as SyncBatch, d as SyncComplete, e as SyncAck, S as SyncRequest, l as ReplicationConfig, m as SyncState, n as ReplicationStatus, o as ReplicationErrorEvent, a as ReplicationAck, p as Topology, T as TopologyRole } from '../types-B2byqt0B.js';
2
+ export { F as ForwardedTransaction, N as NodeInfo, q as ReplicationChange, f as ReplicationTransport, g as TransportConfig } from '../types-B2byqt0B.js';
3
+ import { c as ReplicationGroupState } from '../types-BEu1I_9_.js';
4
+ export { A as AcquireControllerLeaseInput, a as AcquireControllerLeaseResult, h as AdmitNodeToInSyncSetInput, C as ClusterCoordinator, f as CompareAndAdvancePrimaryTermInput, g as CompareAndAdvancePrimaryTermResult, j as CoordinatorCompatibilityMetadata, k as CoordinatorLease, b as CoordinatorNodeSession, l as CoordinatorPrimary, e as CoordinatorWatchDisposer, P as PromoteEligibleReplicaInput, R as RegisterNodeSessionInput, d as ReplicationGroupWatcher, S as SetReplicationGroupStateInput, U as UpdateInSyncSetInput, i as UpdateNodeMaintenanceInput } from '../types-BEu1I_9_.js';
5
+ import { EventEmitter } from 'node:events';
6
+ import { C as ChangeTracker } from '../change-tracker-CFTQ9TSn.js';
7
+ import { D as Database } from '../database-BVY1GqE7.js';
8
+ import { a as SQLiteConnection } from '../types-BFSsG77t.js';
9
+ import { P as Params, k as QueryOptions, T as Transaction, E as ExecuteResult } from '../types-D-74JiXb.js';
10
+ import { S as SirannonError } from '../errors-C00ed08Q.js';
11
+ import '../types-BeozgNPr.js';
12
+
13
+ type ColumnVersionGetter = (table: string, rowId: string) => Promise<Map<string, {
14
+ hlc: string;
15
+ nodeId: string;
16
+ }>>;
17
+ /**
18
+ * Per-column conflict resolver that merges non-overlapping field changes.
19
+ *
20
+ * Given a conflict between a local and remote write on the same row, this
21
+ * resolver computes the set of columns each side changed relative to the
22
+ * common ancestor (oldData). If the changed column sets don't overlap, both
23
+ * sides' changes are merged into a single row, avoiding unnecessary data
24
+ * loss. When columns do overlap, per-column HLC versions (retrieved via the
25
+ * injected `getColumnVersions` callback) determine which side's value wins
26
+ * for each contested column. If no column version metadata exists, the
27
+ * resolver falls back to whole-row LWW.
28
+ */
29
+ declare class FieldMergeResolver implements ConflictResolver {
30
+ private readonly getColumnVersions;
31
+ private readonly lww;
32
+ constructor(getColumnVersions: ColumnVersionGetter);
33
+ resolve(ctx: ConflictContext): Promise<ConflictResolution>;
34
+ }
35
+
36
+ /**
37
+ * Last-Writer-Wins conflict resolver.
38
+ *
39
+ * Compares the remote HLC against the local HLC. The higher timestamp wins.
40
+ * When both timestamps are equal (concurrent writes within the same
41
+ * millisecond and logical tick), the tie is broken deterministically by
42
+ * comparing node IDs lexicographically, so that every node reaches the same
43
+ * resolution without coordination. This is the default resolver and the
44
+ * fallback used by PrimaryWinsResolver and FieldMergeResolver when they
45
+ * cannot make a more specific decision.
46
+ */
47
+ declare class LWWResolver implements ConflictResolver {
48
+ resolve(ctx: ConflictContext): ConflictResolution;
49
+ }
50
+
51
+ /**
52
+ * Conflict resolver that unconditionally favors the designated primary node.
53
+ *
54
+ * If the remote change originated from the primary, it is accepted. If the
55
+ * local change originated from the primary, the remote is rejected. When
56
+ * neither side is the primary (e.g., two replicas syncing through a relay),
57
+ * the decision falls back to LWW ordering. This resolver is designed for
58
+ * primary-replica topologies where the primary is the authoritative source of
59
+ * truth and replica-side writes should never override it.
60
+ */
61
+ declare class PrimaryWinsResolver implements ConflictResolver {
62
+ private readonly primaryNodeId;
63
+ private readonly lww;
64
+ constructor(primaryNodeId: string);
65
+ resolve(ctx: ConflictContext): ConflictResolution;
66
+ }
67
+
68
+ /**
69
+ * Hybrid Logical Clock (HLC) for causal ordering of events across nodes.
70
+ *
71
+ * Each timestamp is encoded as `{wallMs hex}-{logical hex}-{nodeId}`, which
72
+ * is directly comparable with string comparison while preserving both
73
+ * wall-clock proximity and causal ordering guarantees. The wall-clock
74
+ * component tracks real time (milliseconds since epoch), the logical counter
75
+ * disambiguates events that occur within the same millisecond, and the nodeId
76
+ * ties the timestamp to its origin.
77
+ *
78
+ * Call `now()` before persisting a local write to generate a monotonically
79
+ * increasing timestamp. Call `receive(remote)` when processing a remote
80
+ * change to merge the remote clock into the local state, ensuring the local
81
+ * clock is never behind any observed timestamp.
82
+ */
83
+ declare class HLC {
84
+ private wallMs;
85
+ private logical;
86
+ private readonly nodeId;
87
+ constructor(nodeId: string);
88
+ /** Generate a new HLC timestamp, advancing the clock. */
89
+ now(): string;
90
+ /** Merge a remote HLC timestamp into the local clock and return the updated value. */
91
+ receive(remote: string): string;
92
+ /** Lexicographic comparison of two encoded HLC strings. Returns -1, 0, or 1. */
93
+ static compare(a: string, b: string): number;
94
+ /** Parse an encoded HLC string into its wall-clock, logical, and nodeId components. */
95
+ static decode(hlc: string): HLCTimestamp;
96
+ private encode;
97
+ }
98
+
99
+ /**
100
+ * Persistent change log that bridges CDC events and the replication protocol.
101
+ *
102
+ * Composes specialised helpers: BatchReader (outbound batches), BatchApplier
103
+ * (inbound batches and conflict resolution), DumpOps (table dumps and
104
+ * manifests), SchemaOps (replication-table bootstrap, schema dump, wipe),
105
+ * StateOps (sequence and sync metadata), and PkResolver (cached primary-key
106
+ * lookups).
107
+ */
108
+ declare class ReplicationLog {
109
+ private readonly changesTable;
110
+ private readonly pkResolver;
111
+ private readonly state;
112
+ private readonly schema;
113
+ private readonly batchReader;
114
+ private readonly batchApplier;
115
+ private readonly dump;
116
+ constructor(conn: SQLiteConnection, localNodeId: string, hlc: HLC, changesTable?: string, tracker?: ChangeTracker);
117
+ ensureReplicationTables(): Promise<void>;
118
+ readBatch(afterSeq: bigint, batchSize: number): Promise<ReplicationBatch | null>;
119
+ stampChanges(tx: SQLiteConnection, afterSeq: bigint, txId: string): Promise<void>;
120
+ updateColumnVersions(tx: SQLiteConnection, afterSeq: bigint): Promise<void>;
121
+ applyBatch(batch: ReplicationBatch, resolver: ConflictResolver | ((table: string) => ConflictResolver)): Promise<ApplyResult>;
122
+ getLastAppliedSeq(fromNodeId: string): Promise<bigint>;
123
+ setLastAppliedSeq(fromNodeId: string, seq: bigint): Promise<void>;
124
+ getPeerAckedSeq(peerNodeId: string): Promise<bigint>;
125
+ getLocalSeq(): Promise<bigint>;
126
+ recoverMaxObservedHlc(): Promise<string | null>;
127
+ getMinAckedSeq(): Promise<bigint | null>;
128
+ registerActiveSyncSeq(seq: bigint): void;
129
+ unregisterActiveSyncSeq(seq: bigint): void;
130
+ dumpTable(table: string, batchSize: number): AsyncGenerator<ReplicationBatch>;
131
+ dumpTableOnConnection(conn: SQLiteConnection, table: string, batchSize: number): AsyncGenerator<{
132
+ rows: Record<string, unknown>[];
133
+ checksum: string;
134
+ isLast: boolean;
135
+ }>;
136
+ dumpSchema(conn: SQLiteConnection, excludePrefix?: string): Promise<string[]>;
137
+ getTablesInFkOrder(conn: SQLiteConnection): Promise<string[]>;
138
+ wipeTables(conn: SQLiteConnection, tables: string[], tracker: ChangeTracker): Promise<void>;
139
+ setSyncTableStatus(table: string, status: string, rowCount?: number, pkHash?: string): Promise<void>;
140
+ setSyncMeta(phase: SyncPhase, snapshotSeq?: bigint, sourcePeerId?: string, requestId?: string): Promise<void>;
141
+ getSyncState(): Promise<{
142
+ phase: SyncPhase;
143
+ completedTables: string[];
144
+ snapshotSeq: bigint | null;
145
+ sourcePeerId: string | null;
146
+ }>;
147
+ isSyncCompleted(): Promise<boolean>;
148
+ generateManifest(conn: SQLiteConnection, table: string): Promise<SyncTableManifest>;
149
+ verifyManifest(manifest: SyncTableManifest): Promise<boolean>;
150
+ }
151
+
152
+ /**
153
+ * Maintains in-memory state for every peer the local node communicates with.
154
+ *
155
+ * For each peer, PeerTracker records the last acknowledged sequence number,
156
+ * the last sent sequence number, pending batch count, and connection status.
157
+ * This state drives two key mechanisms:
158
+ *
159
+ * - **Back-pressure**: the sender loop in ReplicationEngine checks
160
+ * `pendingBatches` against the configured max before queuing more work
161
+ * for a given peer.
162
+ * - **Write concern**: callers can await `waitForMajority` or `waitForAll`
163
+ * with a sequence number and timeout. These methods resolve once enough
164
+ * peers have acknowledged that sequence, or reject with a
165
+ * WriteConcernError on timeout.
166
+ */
167
+ declare class PeerTracker {
168
+ private readonly peers;
169
+ private readonly waiters;
170
+ addPeer(nodeId: string): void;
171
+ removePeer(nodeId: string): void;
172
+ onAckReceived(nodeId: string, ackedSeq: bigint): void;
173
+ recordInFlightBatch(nodeId: string, batch: InFlightBatch): void;
174
+ expireTimedOutBatches(nodeId: string, nowMs: number, timeoutMs: number): boolean;
175
+ getPeerState(nodeId: string): PeerState | undefined;
176
+ connectedPeerCount(): number;
177
+ waitForMajority(seq: bigint, timeoutMs: number): Promise<void>;
178
+ waitForAll(seq: bigint, timeoutMs: number): Promise<void>;
179
+ waitForConfiguredMajority(seq: bigint, localNodeId: string, votingNodeIds: string[], timeoutMs: number): Promise<void>;
180
+ waitForConfiguredAll(seq: bigint, localNodeId: string, votingNodeIds: string[], timeoutMs: number): Promise<void>;
181
+ ackedConfiguredNodeIds(seq: bigint, localNodeId: string, votingNodeIds: string[]): string[];
182
+ allPeerStates(): PeerState[];
183
+ private countAcked;
184
+ private countAckedConfigured;
185
+ private checkWaiters;
186
+ }
187
+
188
+ declare class LocalExecutor {
189
+ private readonly engine;
190
+ /**
191
+ * Serialises every `executeTransactionLocally` call against itself.
192
+ *
193
+ * Reason: SQLite only allows one active transaction per connection; the
194
+ * writer pool exposes a single writer connection (see `ConnectionPool`).
195
+ * Two parallel `engine.transaction(fn)` callers therefore both reach
196
+ * `await conn.exec('BEGIN')` on the same connection, and the second
197
+ * `BEGIN` errors with "cannot start a transaction within a transaction".
198
+ * Chaining onto this promise turns the second caller into a strict
199
+ * follow-on without changing the public API. A rejection is swallowed at
200
+ * the chain level so a failed transaction never poisons the queue; the
201
+ * original error still surfaces to the caller that initiated it.
202
+ */
203
+ private transactionQueue;
204
+ constructor(engine: ReplicationEngine);
205
+ executeLocally(sql: string, params?: Params, options?: QueryOptions): Promise<{
206
+ changes: number;
207
+ lastInsertRowId: number | bigint;
208
+ }>;
209
+ executeForwardedLocally(statements: Array<{
210
+ sql: string;
211
+ params?: Params;
212
+ }>): Promise<ForwardedTransactionResult>;
213
+ executeTransactionLocally<T>(fn: (tx: Transaction) => Promise<T>, options?: QueryOptions): Promise<T>;
214
+ private runTransaction;
215
+ }
216
+
217
+ declare class SenderLoop {
218
+ private readonly engine;
219
+ private senderTimer;
220
+ constructor(engine: ReplicationEngine);
221
+ start(): void;
222
+ stop(): void;
223
+ private updatePruneBoundary;
224
+ private sendPendingBatches;
225
+ private shouldReplicateTo;
226
+ }
227
+
228
+ declare class SyncJoiner {
229
+ private readonly engine;
230
+ private catchUpCheckTimer;
231
+ constructor(engine: ReplicationEngine);
232
+ initiateSync(): Promise<void>;
233
+ handleSyncBatchReceived(batch: SyncBatch, fromPeerId: string): Promise<void>;
234
+ handleSyncCompleteReceived(complete: SyncComplete, fromPeerId: string): Promise<void>;
235
+ startCatchUpCheck(): void;
236
+ stopCatchUpCheck(): void;
237
+ private sendSyncAck;
238
+ private finishCatchUpAsReady;
239
+ }
240
+
241
+ interface ActiveSyncSession {
242
+ requestId: string;
243
+ joinerNodeId: string;
244
+ readConn: SQLiteConnection;
245
+ snapshotSeq: bigint;
246
+ tables: string[];
247
+ completedTables: Set<string>;
248
+ startedAt: number;
249
+ timeoutTimer: ReturnType<typeof setTimeout>;
250
+ aborted: boolean;
251
+ }
252
+ interface SyncAckWaiter {
253
+ resolve: (ack: SyncAck) => void;
254
+ timer: ReturnType<typeof setTimeout>;
255
+ }
256
+
257
+ declare class SyncServer {
258
+ private readonly engine;
259
+ readonly activeSyncs: Map<string, ActiveSyncSession>;
260
+ readonly syncAckWaiters: Map<string, SyncAckWaiter>;
261
+ constructor(engine: ReplicationEngine);
262
+ handleSyncRequest(request: SyncRequest, fromPeerId: string): Promise<void>;
263
+ handleSyncAckReceived(ack: SyncAck): void;
264
+ abortSyncSession(requestId: string): void;
265
+ abortAll(): void;
266
+ abortSessionsForPeer(peerId: string): void;
267
+ private waitForSyncAck;
268
+ private sendSyncBatchAndWaitForAck;
269
+ private serveSyncSession;
270
+ }
271
+
272
+ /**
273
+ * Coordinates replication for a single database node.
274
+ *
275
+ * State and dependencies are exposed as readable properties so that the
276
+ * collaborating modules in `./engine/` (LocalExecutor, SyncServer, SyncJoiner,
277
+ * SenderLoop, transport wiring) can operate against a shared, mutable engine
278
+ * instance without duplicating constructor wiring.
279
+ */
280
+ declare class ReplicationEngine extends EventEmitter {
281
+ readonly database: Database;
282
+ readonly writerConn: SQLiteConnection;
283
+ readonly config: ReplicationConfig;
284
+ readonly nodeId: string;
285
+ readonly hlc: HLC;
286
+ readonly log: ReplicationLog;
287
+ readonly peerTracker: PeerTracker;
288
+ readonly defaultResolver: ConflictResolver;
289
+ readonly tracker: ChangeTracker | undefined;
290
+ readonly snapshotConnectionFactory: (() => Promise<SQLiteConnection>) | undefined;
291
+ readonly batchSize: number;
292
+ readonly batchIntervalMs: number;
293
+ readonly maxClockDriftMs: number;
294
+ readonly maxPendingBatches: number;
295
+ readonly maxBatchChanges: number;
296
+ readonly ackTimeoutMs: number;
297
+ readonly initialSync: boolean;
298
+ readonly syncBatchSize: number;
299
+ readonly maxConcurrentSyncs: number;
300
+ readonly maxSyncDurationMs: number;
301
+ readonly maxSyncLagBeforeReady: number;
302
+ readonly syncAckTimeoutMs: number;
303
+ readonly catchUpDeadlineMs: number;
304
+ readonly resumeFromSeq: bigint | undefined;
305
+ running: boolean;
306
+ coordinatorState: ReplicationGroupState | null;
307
+ coordinatorAuthority: boolean;
308
+ controllerState: 'disabled' | 'standby' | 'active' | 'lost';
309
+ private nodeSessionLeaseId;
310
+ private controllerLeaseId;
311
+ private coordinatorWatchDisposer;
312
+ private coordinatorLeaseTimer;
313
+ private controllerTimer;
314
+ private coordinatorRejoinSyncStarting;
315
+ lastSentSeq: bigint;
316
+ lastLocalSeq: bigint;
317
+ highestSourceSeqSeen: bigint;
318
+ readonly appliedSeqByPeer: Map<string, bigint>;
319
+ readonly expectedBatchIndex: Map<string, number>;
320
+ syncState: SyncState;
321
+ readonly localExecutor: LocalExecutor;
322
+ readonly syncServer: SyncServer;
323
+ readonly syncJoiner: SyncJoiner;
324
+ readonly senderLoop: SenderLoop;
325
+ constructor(database: Database, writerConn: SQLiteConnection, config: ReplicationConfig);
326
+ start(): Promise<void>;
327
+ stop(): Promise<void>;
328
+ status(): ReplicationStatus;
329
+ getCurrentSeq(): bigint;
330
+ getAppliedSeq(peerId: string): bigint;
331
+ query<T>(sql: string, params?: Params, options?: QueryOptions): Promise<T[]>;
332
+ execute(sql: string, params?: Params, options?: QueryOptions): Promise<ExecuteResult>;
333
+ executeBatch(sql: string, paramsBatch: Params[], options?: QueryOptions): Promise<ExecuteResult[]>;
334
+ transaction<T>(fn: (tx: Transaction) => Promise<T>, options?: QueryOptions): Promise<T>;
335
+ forwardStatements(statements: Array<{
336
+ sql: string;
337
+ params?: Params;
338
+ }>, _options?: QueryOptions): Promise<ForwardedTransactionResult>;
339
+ startSenderLoop(): void;
340
+ emitError(event: ReplicationErrorEvent): void;
341
+ getResolver(table?: string): ConflictResolver;
342
+ checkClockDrift(remoteHlc: string): number;
343
+ refreshTriggersAfterDdl(): Promise<void>;
344
+ waitForWriteConcern(seq: bigint, wc: {
345
+ level: string;
346
+ timeoutMs?: number;
347
+ }): Promise<void>;
348
+ loadAppliedSeqs(): Promise<void>;
349
+ isCoordinatorMode(): boolean;
350
+ startCoordinatorMode(): Promise<void>;
351
+ prepareCoordinatorRejoinIfNeeded(): Promise<void>;
352
+ hasCoordinatorWriteAuthority(): boolean;
353
+ requiresCoordinatorRejoinSync(state?: ReplicationGroupState | null): boolean;
354
+ markCoordinatorSyncReady(): Promise<void>;
355
+ handleCoordinatorAckProgress(nodeId: string, ackedSeq: bigint): Promise<void>;
356
+ private removeLocalNodeFromCoordinatorInSyncSet;
357
+ verifyPrimaryAuthority(): Promise<ReplicationGroupState>;
358
+ assertInboundCoordinatorMessage(message: {
359
+ groupId?: string;
360
+ primaryTerm?: bigint;
361
+ }, fromPeerId: string, direction: 'batch' | 'ack' | 'forward' | 'sync-request' | 'sync-data'): Promise<void>;
362
+ decorateBatch(batch: ReplicationBatch): ReplicationBatch;
363
+ decorateAck(ack: ReplicationAck): ReplicationAck;
364
+ decorateForwardResult(result: ForwardedTransactionResult): ForwardedTransactionResult;
365
+ decorateSyncRequest(request: SyncRequest): SyncRequest;
366
+ decorateSyncBatch(batch: SyncBatch): SyncBatch;
367
+ decorateSyncComplete(complete: SyncComplete): SyncComplete;
368
+ decorateSyncAck(ack: SyncAck): SyncAck;
369
+ resolveWriteConcern(wc: {
370
+ level: string;
371
+ timeoutMs?: number;
372
+ } | undefined): {
373
+ level: string;
374
+ timeoutMs?: number;
375
+ } | undefined;
376
+ getCurrentPrimaryPeerId(): string | null;
377
+ private canAcceptLocalWrite;
378
+ private throwNotCurrentPrimary;
379
+ private assertReadConcern;
380
+ private refreshCoordinatorState;
381
+ private hasCurrentPrimaryAuthorityFor;
382
+ private getCoordinatorMessageFields;
383
+ private getForwardingPrimaryPeerId;
384
+ private updateCoordinatorProgressAfterWrite;
385
+ private startCoordinatorLeaseRenewal;
386
+ private startControllerLoop;
387
+ private controllerTick;
388
+ private runControllerPromotionCheck;
389
+ private handleCoordinatorStateUpdate;
390
+ private startCoordinatorRejoinSyncIfReady;
391
+ private handleFormerPrimaryDemotion;
392
+ private markLocalNodeRepairing;
393
+ private requiresCoordinatorRejoin;
394
+ private assertLocalCompatibility;
395
+ private evaluateFormerPrimaryHistory;
396
+ private stopCoordinatorTimers;
397
+ private stopCoordinatorMode;
398
+ private localCoordinatorPrimary;
399
+ private unrefTimer;
400
+ private getCoordinatorRuntimeStatus;
401
+ private errorDetails;
402
+ }
403
+
404
+ /** Base error for all replication-related failures. */
405
+ declare class ReplicationError extends SirannonError {
406
+ readonly details?: Record<string, unknown> | undefined;
407
+ constructor(message: string, code?: string, details?: Record<string, unknown> | undefined);
408
+ }
409
+ /** Thrown when a write conflict cannot be resolved automatically. */
410
+ declare class ConflictError extends ReplicationError {
411
+ readonly table: string;
412
+ readonly rowId: string;
413
+ constructor(message: string, table: string, rowId: string);
414
+ }
415
+ /** Thrown when inter-node communication fails. */
416
+ declare class TransportError extends ReplicationError {
417
+ constructor(message: string);
418
+ }
419
+ /** Thrown when an incoming replication batch fails integrity checks (checksum, schema, clock drift). */
420
+ declare class BatchValidationError extends ReplicationError {
421
+ constructor(message: string);
422
+ }
423
+ /** Thrown when a write-concern quorum is not met within the configured timeout. */
424
+ declare class WriteConcernError extends ReplicationError {
425
+ constructor(message: string);
426
+ }
427
+ declare class ReadConcernError extends ReplicationError {
428
+ constructor(message: string, details?: Record<string, unknown>);
429
+ }
430
+ /** Thrown when a write or routing operation violates the configured topology rules. */
431
+ declare class TopologyError extends ReplicationError {
432
+ constructor(message: string);
433
+ }
434
+ declare class CoordinatorError extends ReplicationError {
435
+ constructor(message: string, details?: Record<string, unknown>);
436
+ }
437
+ declare class AuthorityError extends ReplicationError {
438
+ constructor(message: string, code?: string, details?: Record<string, unknown>);
439
+ }
440
+ declare class StalePrimaryError extends AuthorityError {
441
+ constructor(message: string, details?: Record<string, unknown>);
442
+ }
443
+ declare class FailoverError extends ReplicationError {
444
+ constructor(message: string, code?: string, details?: Record<string, unknown>);
445
+ }
446
+ declare class NoSafePrimaryError extends FailoverError {
447
+ constructor(message: string, details?: Record<string, unknown>);
448
+ }
449
+ declare class NodeNotInSyncError extends ReplicationError {
450
+ constructor(message: string, details?: Record<string, unknown>);
451
+ }
452
+ declare class NodeDrainingError extends ReplicationError {
453
+ constructor(message: string, details?: Record<string, unknown>);
454
+ }
455
+ declare class ProtocolVersionMismatchError extends ReplicationError {
456
+ constructor(message: string, details?: Record<string, unknown>);
457
+ }
458
+ declare class UnsafeRecoveryRequiredError extends FailoverError {
459
+ constructor(message: string, details?: Record<string, unknown>);
460
+ }
461
+ /** Thrown for initial sync failures. */
462
+ declare class SyncError extends ReplicationError {
463
+ readonly requestId?: string | undefined;
464
+ constructor(message: string, requestId?: string | undefined);
465
+ }
466
+
467
+ /** Generate a cryptographically random 32-hex-character node identifier. */
468
+ declare function generateNodeId(): string;
469
+ /** Throw a ReplicationError if the given string is not a valid 32-hex-character node ID. */
470
+ declare function validateNodeId(id: string): void;
471
+
472
+ /**
473
+ * Single-writer topology with one primary and one or more read-only replicas.
474
+ *
475
+ * Only the primary node accepts writes; replicas reject writes with a
476
+ * TopologyError (or forward them when writeForwarding is enabled on the
477
+ * engine). The primary replicates outbound batches only to peers whose role
478
+ * is 'replica', and replicas only accept inbound batches from a peer whose
479
+ * role is 'primary'. Conflict resolution is not required because a single
480
+ * writer eliminates concurrent write conflicts by design.
481
+ */
482
+ declare class PrimaryReplicaTopology implements Topology {
483
+ readonly role: TopologyRole;
484
+ constructor(role: 'primary' | 'replica');
485
+ canWrite(): boolean;
486
+ shouldReplicateTo(_peerId: string, peerRole: TopologyRole): boolean;
487
+ shouldAcceptFrom(_peerId: string, peerRole: TopologyRole): boolean;
488
+ requiresConflictResolution(): boolean;
489
+ }
490
+
491
+ export { ApplyResult, AuthorityError, BatchValidationError, ConflictContext, ConflictError, ConflictResolution, ConflictResolver, CoordinatorError, FailoverError, FieldMergeResolver, ForwardedTransactionResult, HLC, HLCTimestamp, LWWResolver, NoSafePrimaryError, NodeDrainingError, NodeNotInSyncError, PeerState, PeerTracker, PrimaryReplicaTopology, PrimaryWinsResolver, ProtocolVersionMismatchError, ReadConcernError, ReplicationAck, ReplicationBatch, ReplicationConfig, ReplicationEngine, ReplicationError, ReplicationErrorEvent, ReplicationGroupState, ReplicationLog, ReplicationStatus, StalePrimaryError, SyncAck, SyncBatch, SyncComplete, SyncError, SyncPhase, SyncRequest, SyncState, SyncTableManifest, Topology, TopologyError, TopologyRole, TransportError, UnsafeRecoveryRequiredError, WriteConcernError, generateNodeId, validateNodeId };