UNPKG

5.9 kBTypeScriptView Raw
1/// <reference types="node" />
2import { EventEmitter } from "events";
3import { Redis as IORedisClient, Cluster as IORedisCluster } from "ioredis";
4declare type Client = IORedisClient | IORedisCluster;
5export declare type ClientExecutionResult = {
6 client: Client;
7 vote: "for";
8 value: number;
9} | {
10 client: Client;
11 vote: "against";
12 error: Error;
13};
14export declare type ExecutionStats = {
15 readonly membershipSize: number;
16 readonly quorumSize: number;
17 readonly votesFor: Set<Client>;
18 readonly votesAgainst: Map<Client, Error>;
19};
20export declare type ExecutionResult = {
21 attempts: ReadonlyArray<Promise<ExecutionStats>>;
22};
23/**
24 *
25 */
26export interface Settings {
27 readonly driftFactor: number;
28 readonly retryCount: number;
29 readonly retryDelay: number;
30 readonly retryJitter: number;
31 readonly automaticExtensionThreshold: number;
32}
33export declare class ResourceLockedError extends Error {
34 readonly message: string;
35 constructor(message: string);
36}
37export declare class ExecutionError extends Error {
38 readonly message: string;
39 readonly attempts: ReadonlyArray<Promise<ExecutionStats>>;
40 constructor(message: string, attempts: ReadonlyArray<Promise<ExecutionStats>>);
41}
42export declare class Lock {
43 readonly redlock: Redlock;
44 readonly resources: string[];
45 readonly value: string;
46 readonly attempts: ReadonlyArray<Promise<ExecutionStats>>;
47 expiration: number;
48 constructor(redlock: Redlock, resources: string[], value: string, attempts: ReadonlyArray<Promise<ExecutionStats>>, expiration: number);
49 release(): Promise<ExecutionResult>;
50 extend(duration: number): Promise<Lock>;
51}
52export declare type RedlockAbortSignal = AbortSignal & {
53 error?: Error;
54};
55/**
56 * A redlock object is instantiated with an array of at least one redis client
57 * and an optional `options` object. Properties of the Redlock object should NOT
58 * be changed after it is first used, as doing so could have unintended
59 * consequences for live locks.
60 */
61export default class Redlock extends EventEmitter {
62 readonly clients: Set<Client>;
63 readonly settings: Settings;
64 readonly scripts: {
65 readonly acquireScript: {
66 value: string;
67 hash: string;
68 };
69 readonly extendScript: {
70 value: string;
71 hash: string;
72 };
73 readonly releaseScript: {
74 value: string;
75 hash: string;
76 };
77 };
78 constructor(clients: Iterable<Client>, settings?: Partial<Settings>, scripts?: {
79 readonly acquireScript?: string | ((script: string) => string);
80 readonly extendScript?: string | ((script: string) => string);
81 readonly releaseScript?: string | ((script: string) => string);
82 });
83 /**
84 * Generate a sha1 hash compatible with redis evalsha.
85 */
86 private _hash;
87 /**
88 * Generate a cryptographically random string.
89 */
90 private _random;
91 /**
92 * This method runs `.quit()` on all client connections.
93 */
94 quit(): Promise<void>;
95 /**
96 * This method acquires a locks on the resources for the duration specified by
97 * the `duration`.
98 */
99 acquire(resources: string[], duration: number, settings?: Partial<Settings>): Promise<Lock>;
100 /**
101 * This method unlocks the provided lock from all servers still persisting it.
102 * It will fail with an error if it is unable to release the lock on a quorum
103 * of nodes, but will make no attempt to restore the lock in the case of a
104 * failure to release. It is safe to re-attempt a release or to ignore the
105 * error, as the lock will automatically expire after its timeout.
106 */
107 release(lock: Lock, settings?: Partial<Settings>): Promise<ExecutionResult>;
108 /**
109 * This method extends a valid lock by the provided `duration`.
110 */
111 extend(existing: Lock, duration: number, settings?: Partial<Settings>): Promise<Lock>;
112 /**
113 * Execute a script on all clients. The resulting promise is resolved or
114 * rejected as soon as this quorum is reached; the resolution or rejection
115 * will contains a `stats` property that is resolved once all votes are in.
116 */
117 private _execute;
118 private _attemptOperation;
119 private _attemptOperationOnClient;
120 /**
121 * Wrap and execute a routine in the context of an auto-extending lock,
122 * returning a promise of the routine's value. In the case that auto-extension
123 * fails, an AbortSignal will be updated to indicate that abortion of the
124 * routine is in order, and to pass along the encountered error.
125 *
126 * @example
127 * ```ts
128 * await redlock.using([senderId, recipientId], 5000, { retryCount: 5 }, async (signal) => {
129 * const senderBalance = await getBalance(senderId);
130 * const recipientBalance = await getBalance(recipientId);
131 *
132 * if (senderBalance < amountToSend) {
133 * throw new Error("Insufficient balance.");
134 * }
135 *
136 * // The abort signal will be true if:
137 * // 1. the above took long enough that the lock needed to be extended
138 * // 2. redlock was unable to extend the lock
139 * //
140 * // In such a case, exclusivity can no longer be guaranteed for further
141 * // operations, and should be handled as an exceptional case.
142 * if (signal.aborted) {
143 * throw signal.error;
144 * }
145 *
146 * await setBalances([
147 * {id: senderId, balance: senderBalance - amountToSend},
148 * {id: recipientId, balance: recipientBalance + amountToSend},
149 * ]);
150 * });
151 * ```
152 */
153 using<T>(resources: string[], duration: number, settings: Partial<Settings>, routine?: (signal: RedlockAbortSignal) => Promise<T>): Promise<T>;
154 using<T>(resources: string[], duration: number, routine: (signal: RedlockAbortSignal) => Promise<T>): Promise<T>;
155}
156export {};