Skip to content

Service API ​

Service Injection ​

typescript
import { Injectable, Inject } from '@nestjs/common';
import { LEADER_SERVICE, ILeaderService } from '@nestjs-redisx/leader';

@Injectable()
export class LeadershipStatusService {
  constructor(
    @Inject(LEADER_SERVICE)
    private readonly leaderService: ILeaderService,
  ) {}

  // Synchronous local view — safe to call on every request.
  isThisInstanceTheLeader(): boolean {
    return this.leaderService.isLeader();
  }

  // Works on ANY instance: reads the election key from Redis.
  async whoLeads(): Promise<string | null> {
    return this.leaderService.getLeaderId();
  }

  // Execute singleton work programmatically (without a decorator).
  async maybeCompact(): Promise<number | undefined> {
    return this.leaderService.runIfLeader(async () => {
      // ...heavy singleton work...
      return 42;
    });
  }

  status() {
    return {
      instanceId: this.leaderService.instanceId,
      isLeader: this.leaderService.isLeader(),
      groups: this.leaderService.getGroups(),
    };
  }
}

instanceId ​

This process's identity — the value stored in the election key while it leads, and what getLeaderId() returns on other instances. Defaults to ${hostname}-${pid}-${random}; set instanceId in the options (e.g. the pod name) for stable identities in logs and dashboards.

isLeader() ​

typescript
isLeader(group?: string): boolean

Synchronous local lease view — no Redis round-trip, safe to call on every request or cron tick. Returns true only while the last confirmed lease is unexpired; a group with no running election returns false.

getLeaderId() ​

typescript
getLeaderId(group?: string): Promise<string | null>

Reads the election key from Redis — works on any instance, leader or follower. Returns null when nobody holds the lease. Throws LeaderStoreError when the store call fails (this is a direct API call, unlike the background loop which contains its errors). On a read-scaled cluster (scaleReads to replicas) this GET may be served by a replica and briefly lag the election — election correctness is unaffected (all lease mutations are master-side CAS scripts), only this observability read can be stale.

runIfLeader() ​

typescript
runIfLeader<T>(fn: () => T | Promise<T>, group?: string): Promise<T | undefined>

Executes fn only when this instance leads the group; resolves to undefined otherwise. The programmatic twin of @LeaderOnly.

stepDown() ​

typescript
stepDown(group?: string): Promise<void>

Voluntarily hand leadership over: release the lease (CAS delete) and abstain from candidacy for one ttlMs, so another instance wins instead of this one immediately re-acquiring. Emits onLost(group, 'stepdown') at demotion time — before the release lands, like the expired path (shutdown, by contrast, fires after its bounded release attempt, still before onModuleDestroy() resolves). After stepDown() resolves, this instance no longer claims a lease locally, and the key is cleared too — including a stale own lease left behind by a fail-safe demotion — provided the release lands: if the store call fails it is logged (see the cheat sheet below) and the key holds this instance's id for up to the remaining ttlMs; the next graceful shutdown retries the release. A no-op for groups without a running election.

typescript
import { Injectable, Inject } from '@nestjs/common';
import { LEADER_SERVICE, ILeaderService } from '@nestjs-redisx/leader';

@Injectable()
export class MaintenanceService {
  constructor(
    @Inject(LEADER_SERVICE)
    private readonly leaderService: ILeaderService,
  ) {}

  /**
   * Before planned maintenance of this node: release the lease and abstain
   * from candidacy for one ttlMs, so another instance takes over immediately
   * instead of waiting for the lease to expire.
   */
  async drainThisNode(): Promise<void> {
    await this.leaderService.stepDown();
    // Optionally also for named groups:
    await this.leaderService.stepDown('cleanup');
  }
}

getGroups() ​

typescript
getGroups(): string[]

Groups with running elections on this instance — the union of 'default', the configured groups, and every group referenced by @LeaderOnly decorators.

Semantics Cheat Sheet ​

CallRedis I/OOn store failure
isLeader()nonen/a (local state)
getLeaderId()GETthrows LeaderStoreError
runIfLeader()none (local check)n/a
stepDown()CAS DELlogged, local demotion still happens

Next Steps ​

  • Monitoring — counters and lifecycle callbacks
  • Failover — behavior on shutdown and crash

Released under the MIT License.