@getlarge/nestjs-tools-lock
Version:
A distributed lock manager for NestJS
102 lines • 3.65 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LockService = void 0;
const tslib_1 = require("tslib");
const common_1 = require("@nestjs/common");
const ioredis_1 = require("ioredis");
const redlock_1 = require("redlock");
const constants_1 = require("./constants");
let LockService = class LockService {
constructor(options) {
this.options = options;
this.defaultLockOptions = {
retryCount: 1,
retryDelay: 200,
// automaticExtensionThreshold: 500,
};
}
async onModuleInit() {
this.createConnection();
await this.waitUntilInitialized();
}
async onModuleDestroy() {
await this.close();
}
async waitUntilInitialized(timeout = 5000) {
return new Promise((resolve, reject) => {
const controller = new AbortController();
const signal = controller.signal;
const interval = setInterval(() => {
if (this.isInitialized) {
controller.abort();
resolve();
}
}, 150);
const timer = setTimeout(() => {
if (!this.isInitialized) {
controller.abort();
reject(new Error('Initialization timed out'));
}
}, timeout);
signal.addEventListener('abort', () => {
clearInterval(interval);
clearTimeout(timer);
});
});
}
get isInitialized() {
const validRedisStatus = ['connected', 'ready'];
return !!this.redis && validRedisStatus.includes(this.redis.status) && !!this.redlock;
}
checkInitialization() {
if (!this.isInitialized) {
throw new Error('Redis was not yet initialized');
}
}
errorHandler(error) {
// Ignore cases where a resource is explicitly marked as locked on a client.
if (error instanceof redlock_1.ResourceLockedError) {
return;
}
// Log all other errors.
console.error(error);
}
createConnection() {
this.redis = new ioredis_1.default(this.options.redis);
this.redlock = new redlock_1.default([this.redis], { ...this.defaultLockOptions, ...(this.options.lock || {}) });
// this.redlock.on('error', this.errorHandler);
}
optimistic(key, ttl, cb) {
this.checkInitialization();
return this.redlock.using([key], ttl, cb);
}
lock(key, ttl) {
this.checkInitialization();
return this.redlock.acquire([key], ttl);
}
async get(key, lockId) {
this.checkInitialization();
const value = await this.redis.get(key);
// provide fake data for attempts and expiration as this lock would be used for release only
return value !== lockId ? null : new redlock_1.Lock(this.redlock, [key], lockId, [], 100);
}
async unlock(key, lockId) {
this.checkInitialization();
const lock = await this.get(key, lockId);
if (!lock) {
throw new Error(`Lock ${key} - ${lockId} not found.`);
}
return this.redlock.release(lock);
}
close() {
this.redlock?.removeAllListeners('error');
return this.redlock?.quit();
}
};
exports.LockService = LockService;
exports.LockService = LockService = tslib_1.__decorate([
(0, common_1.Injectable)(),
tslib_1.__param(0, (0, common_1.Inject)(constants_1.LOCK_SERVICE_OPTIONS)),
tslib_1.__metadata("design:paramtypes", [Object])
], LockService);
//# sourceMappingURL=lock.service.js.map