@@ -143,25 +143,7 @@ export class LogicalReplicationClient {
143143 this . ackIntervalTimer = null ;
144144 }
145145 // Release leader lock if held
146- if ( this . leaderLock ) {
147- const [ releaseError ] = await tryCatch ( this . leaderLock . release ( ) ) ;
148-
149- if ( releaseError ) {
150- this . logger . error ( "Failed to release leader lock" , {
151- name : this . options . name ,
152- slotName : this . options . slotName ,
153- publicationName : this . options . publicationName ,
154- error : releaseError ,
155- } ) ;
156- } else {
157- this . logger . info ( "Released leader lock" , {
158- name : this . options . name ,
159- slotName : this . options . slotName ,
160- } ) ;
161- }
162-
163- this . leaderLock = null ;
164- }
146+ await this . #releaseLeaderLock( ) ;
165147
166148 this . connection ?. removeAllListeners ( ) ;
167149 this . connection = null ;
@@ -198,6 +180,24 @@ export class LogicalReplicationClient {
198180 return this ;
199181 }
200182
183+ public async teardown ( ) : Promise < boolean > {
184+ await this . stop ( ) ;
185+
186+ // Acquire the leaderLock
187+ const leaderLockAcquired = await this . #acquireLeaderLock( ) ;
188+
189+ if ( ! leaderLockAcquired ) {
190+ return false ;
191+ }
192+
193+ // Drop the slot
194+ const slotDropped = await this . #dropSlot( ) ;
195+
196+ await this . #releaseLeaderLock( ) ;
197+
198+ return slotDropped ;
199+ }
200+
201201 public async subscribe ( startLsn ?: string ) : Promise < this> {
202202 await this . stop ( ) ;
203203
@@ -212,28 +212,10 @@ export class LogicalReplicationClient {
212212 } ) ;
213213
214214 // 1. Leader election
215- try {
216- this . leaderLock = await this . redlock . acquire (
217- [ `logical-replication-client:${ this . options . name } ` ] ,
218- this . leaderLockTimeoutMs ,
219- {
220- retryCount : 60 ,
221- retryDelay : 1000 ,
222- retryJitter : 100 ,
223- }
224- ) ;
225- } catch ( err ) {
226- this . logger . error ( "Leader election failed" , {
227- name : this . options . name ,
228- table : this . options . table ,
229- slotName : this . options . slotName ,
230- publicationName : this . options . publicationName ,
231- startLsn,
232- error : err ,
233- } ) ;
215+ const leaderLockAcquired = await this . #acquireLeaderLock( ) ;
234216
217+ if ( ! leaderLockAcquired ) {
235218 this . events . emit ( "leaderElection" , false ) ;
236-
237219 return this . stop ( ) ;
238220 }
239221
@@ -481,6 +463,31 @@ export class LogicalReplicationClient {
481463 return res . rows [ 0 ] . exists ;
482464 }
483465
466+ async #dropSlot( ) : Promise < boolean > {
467+ if ( ! this . client ) {
468+ this . events . emit ( "error" , new LogicalReplicationClientError ( "Cannot drop slot" ) ) ;
469+ return false ;
470+ }
471+
472+ const [ dropError ] = await tryCatch (
473+ this . client . query ( `SELECT pg_drop_replication_slot('${ this . options . slotName } ');` )
474+ ) ;
475+
476+ if ( dropError ) {
477+ this . logger . error ( "Failed to drop slot" , {
478+ name : this . options . name ,
479+ table : this . options . table ,
480+ slotName : this . options . slotName ,
481+ publicationName : this . options . publicationName ,
482+ error : dropError ,
483+ } ) ;
484+
485+ this . events . emit ( "error" , dropError ) ;
486+ }
487+
488+ return true ;
489+ }
490+
484491 async #acknowledge( lsn : string ) : Promise < void > {
485492 if ( ! this . autoAcknowledge ) return ;
486493 this . events . emit ( "acknowledge" , { lsn } ) ;
@@ -520,6 +527,45 @@ export class LogicalReplicationClient {
520527 return true ;
521528 }
522529
530+ async #acquireLeaderLock( ) : Promise < boolean > {
531+ try {
532+ this . leaderLock = await this . redlock . acquire (
533+ [ `logical-replication-client:${ this . options . name } ` ] ,
534+ this . leaderLockTimeoutMs ,
535+ {
536+ retryCount : 60 ,
537+ retryDelay : 1000 ,
538+ retryJitter : 100 ,
539+ }
540+ ) ;
541+ } catch ( err ) {
542+ this . logger . error ( "Leader election failed" , {
543+ name : this . options . name ,
544+ table : this . options . table ,
545+ slotName : this . options . slotName ,
546+ publicationName : this . options . publicationName ,
547+ error : err ,
548+ } ) ;
549+
550+ return false ;
551+ }
552+
553+ return true ;
554+ }
555+
556+ async #releaseLeaderLock( ) {
557+ if ( ! this . leaderLock ) return ;
558+ const [ releaseError ] = await tryCatch ( this . leaderLock . release ( ) ) ;
559+ this . leaderLock = null ;
560+
561+ if ( releaseError ) {
562+ this . logger . error ( "Failed to release leader lock" , {
563+ name : this . options . name ,
564+ error : releaseError ,
565+ } ) ;
566+ }
567+ }
568+
523569 async #startLeaderLockHeartbeat( ) {
524570 if ( this . leaderLockHeartbeatTimer ) {
525571 clearInterval ( this . leaderLockHeartbeatTimer ) ;
0 commit comments