@daiso-tech/core
Version:
The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.
347 lines • 12.7 kB
JavaScript
/**
* @module Lock
*/
import { resolveLazyable, } from "../../../../utilities/_module-exports.js";
import { resolveOneOrMoreStr } from "../../../../utilities/_module-exports.js";
import { KeyAlreadyAcquiredLockError, LOCK_EVENTS, UnableToAquireLockError, UnableToReleaseLockError, UnownedRefreshLockError, UnownedReleaseLockError, } from "../../../../lock/contracts/_module-exports.js";
import {} from "../../../../lock/contracts/_module-exports.js";
import { LazyPromise } from "../../../../async/_module-exports.js";
/**
* IMPORTANT: This class is not intended to be instantiated directly, instead it should be created by the `LockProvider` class instance.
* @group Derivables
*/
export class Lock {
/**
* @internal
*/
static serialize(deserializedValue) {
return {
key: deserializedValue.key.resolved,
owner: deserializedValue.owner,
ttlInMs: deserializedValue.ttl?.toMilliseconds() ?? null,
expirationInMs: deserializedValue.lockState.get()?.getTime() ?? null,
};
}
createLazyPromise;
adapter;
lockState;
eventDispatcher;
key;
owner;
ttl;
defaultBlockingInterval;
defaultBlockingTime;
defaultRefreshTime;
/**
* @internal
*/
constructor(settings) {
const { createLazyPromise, adapter, lockState, eventDispatcher, key, owner, ttl, expirationInMs, defaultBlockingInterval, defaultBlockingTime, defaultRefreshTime, } = settings;
this.createLazyPromise = createLazyPromise;
this.adapter = adapter;
this.lockState = lockState;
this.lockState.set(expirationInMs);
this.eventDispatcher = eventDispatcher;
this.key = key;
this.owner = resolveOneOrMoreStr(owner);
this.ttl = ttl;
this.defaultBlockingInterval = defaultBlockingInterval;
this.defaultBlockingTime = defaultBlockingTime;
this.defaultRefreshTime = defaultRefreshTime;
}
run(asyncFn) {
return this.createLazyPromise(async () => {
try {
const hasAquired = await this.acquire();
if (!hasAquired) {
return [
null,
new KeyAlreadyAcquiredLockError(`Key "${this.key.resolved}" already acquired`),
];
}
return [await resolveLazyable(asyncFn), null];
}
finally {
await this.release();
}
});
}
runOrFail(asyncFn) {
return this.createLazyPromise(async () => {
try {
await this.acquireOrFail();
return await resolveLazyable(asyncFn);
}
finally {
await this.release();
}
});
}
runBlocking(asyncFn, settings) {
return this.createLazyPromise(async () => {
try {
const hasAquired = await this.acquireBlocking(settings);
if (!hasAquired) {
return [
null,
new KeyAlreadyAcquiredLockError(`Key "${this.key.resolved}" already acquired`),
];
}
return [await resolveLazyable(asyncFn), null];
}
finally {
await this.release();
}
});
}
runBlockingOrFail(asyncFn, settings) {
return new LazyPromise(async () => {
try {
await this.acquireBlockingOrFail(settings);
return await resolveLazyable(asyncFn);
}
finally {
await this.release();
}
});
}
acquire() {
return this.createLazyPromise(async () => {
const prevState = this.lockState.get()?.getTime() ?? null;
try {
this.lockState.remove();
const hasAquired = await this.adapter.acquire(this.key.prefixed, this.owner, this.ttl);
if (hasAquired) {
this.lockState.set(this.ttl);
const event = {
key: this.key.resolved,
owner: this.owner,
ttl: this.ttl,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.ACQUIRED, event)
.defer();
}
else {
const event = {
key: this.key.resolved,
owner: this.owner,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.NOT_AVAILABLE, event)
.defer();
}
return hasAquired;
}
catch (error) {
this.lockState.set(prevState);
const event = {
key: this.key.resolved,
owner: this.owner,
ttl: this.ttl,
error,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event)
.defer();
throw new UnableToAquireLockError(`A Lock with name of "${this.key.resolved}" could not be acquired.`, error);
}
});
}
acquireOrFail() {
return this.createLazyPromise(async () => {
const hasAquired = await this.acquire();
if (!hasAquired) {
throw new KeyAlreadyAcquiredLockError(`Key "${this.key.resolved}" already acquired`);
}
});
}
acquireBlocking(settings = {}) {
return new LazyPromise(async () => {
const { time = this.defaultBlockingTime, interval = this.defaultBlockingInterval, } = settings;
const endDate = time.toEndDate();
while (endDate.getTime() > new Date().getTime()) {
const hasAquired = await this.acquire();
if (hasAquired) {
return true;
}
await LazyPromise.delay(interval);
}
return false;
});
}
acquireBlockingOrFail(settings) {
return new LazyPromise(async () => {
const hasAquired = await this.acquireBlocking(settings);
if (!hasAquired) {
throw new KeyAlreadyAcquiredLockError(`Key "${this.key.resolved}" already acquired`);
}
});
}
release() {
return this.createLazyPromise(async () => {
const prevState = this.lockState.get()?.getTime() ?? null;
try {
const hasReleased = await this.adapter.release(this.key.prefixed, this.owner);
if (hasReleased) {
this.lockState.remove();
const event = {
key: this.key.resolved,
owner: this.owner,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.RELEASED, event)
.defer();
}
else {
const event = {
key: this.key.resolved,
owner: this.owner,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.UNOWNED_RELEASE_TRY, event)
.defer();
}
return hasReleased;
}
catch (error) {
this.lockState.set(prevState);
const event = {
key: this.key.resolved,
owner: this.owner,
ttl: this.ttl,
error,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event)
.defer();
throw new UnableToReleaseLockError(`A Lock with name of "${this.key.resolved}" could not be released.`, error);
}
});
}
releaseOrFail() {
return this.createLazyPromise(async () => {
const hasRelased = await this.release();
if (!hasRelased) {
throw new UnownedReleaseLockError(`Unonwed release on key "${this.key.resolved}" by owner "${this.owner}"`);
}
});
}
forceRelease() {
return this.createLazyPromise(async () => {
const prevState = this.lockState.get()?.getTime() ?? null;
try {
await this.adapter.forceRelease(this.key.prefixed);
this.lockState.remove();
const event = {
key: this.key.resolved,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.FORCE_RELEASED, event)
.defer();
}
catch (error) {
this.lockState.set(prevState);
const event = {
key: this.key.resolved,
owner: this.owner,
ttl: this.ttl,
error,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event)
.defer();
throw new UnableToReleaseLockError(`A Lock with name of "${this.key.resolved}" could not be released.`, error);
}
});
}
isExpired() {
// eslint-disable-next-line @typescript-eslint/require-await
return this.createLazyPromise(async () => {
try {
return this.lockState.isExpired();
}
catch (error) {
const event = {
key: this.key.resolved,
owner: this.owner,
ttl: this.ttl,
error,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event)
.defer();
throw error;
}
});
}
isLocked() {
return this.createLazyPromise(async () => {
const isExpired = await this.isExpired();
return !isExpired;
});
}
refresh(ttl = this.defaultRefreshTime) {
return this.createLazyPromise(async () => {
const prevState = this.lockState.get()?.getTime() ?? null;
try {
const hasRefreshed = await this.adapter.refresh(this.key.prefixed, this.owner, ttl);
if (hasRefreshed) {
const event = {
key: this.key.resolved,
owner: this.owner,
ttl,
};
this.lockState.set(ttl);
this.eventDispatcher
.dispatch(LOCK_EVENTS.REFRESHED, event)
.defer();
}
else {
const event = {
key: this.key.resolved,
owner: this.owner,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.UNOWNED_REFRESH_TRY, event)
.defer();
}
return hasRefreshed;
}
catch (error) {
this.lockState.set(prevState);
const event = {
key: this.key.resolved,
owner: this.owner,
ttl: this.ttl,
error,
};
this.eventDispatcher
.dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event)
.defer();
throw error;
}
});
}
refreshOrFail(ttl) {
return this.createLazyPromise(async () => {
const hasRefreshed = await this.refresh(ttl);
if (!hasRefreshed) {
throw new UnownedRefreshLockError(`Unonwed refresh on key "${this.key.resolved}" by owner "${this.owner}"`);
}
});
}
getRemainingTime() {
// eslint-disable-next-line @typescript-eslint/require-await
return this.createLazyPromise(async () => {
return this.lockState.getRemainingTime();
});
}
getOwner() {
// eslint-disable-next-line @typescript-eslint/require-await
return this.createLazyPromise(async () => {
return this.owner;
});
}
}
//# sourceMappingURL=lock.js.map