@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.
289 lines • 9.44 kB
JavaScript
/**
* @module Lock
*/
import {} from "../../../../event-bus/contracts/_module.js";
import {} from "../../../../hooks/_module.js";
import { FailedAcquireLockError, LOCK_EVENTS, FailedReleaseLockError, FailedRefreshLockError, LOCK_STATE, isLockError, } from "../../../../lock/contracts/_module.js";
import {} from "../../../../namespace/_module.js";
import {} from "../../../../task/contracts/_module.js";
import { Task } from "../../../../task/implementations/_module.js";
import {} from "../../../../time-span/contracts/_module.js";
import { TimeSpan } from "../../../../time-span/implementations/_module.js";
import { resolveLazyable } from "../../../../utilities/_module.js";
/**
* @internal
*/
export class Lock {
/**
* @internal
*/
static _internal_serialize(deserializedValue) {
return {
version: "1",
key: deserializedValue._key.get(),
lockId: deserializedValue.lockId,
ttlInMs: deserializedValue._ttl?.toMilliseconds() ?? null,
};
}
namespace;
adapter;
originalAdapter;
eventDispatcher;
_key;
lockId;
_ttl;
defaultBlockingInterval;
defaultBlockingTime;
defaultRefreshTime;
serdeTransformerName;
constructor(settings) {
const { namespace, adapter, originalAdapter, eventDispatcher, key, lockId, ttl, serdeTransformerName, defaultBlockingInterval, defaultBlockingTime, defaultRefreshTime, } = settings;
this.namespace = namespace;
this.originalAdapter = originalAdapter;
this.serdeTransformerName = serdeTransformerName;
this.adapter = adapter;
this.eventDispatcher = eventDispatcher;
this._key = key;
this.lockId = lockId;
this._ttl = ttl;
this.defaultBlockingInterval = defaultBlockingInterval;
this.defaultBlockingTime = defaultBlockingTime;
this.defaultRefreshTime = defaultRefreshTime;
}
_internal_getNamespace() {
return this.namespace;
}
_internal_getSerdeTransformerName() {
return this.serdeTransformerName;
}
_internal_getAdapter() {
return this.originalAdapter;
}
runOrFail(asyncFn) {
return new Task(async () => {
try {
await this.acquireOrFail();
return await resolveLazyable(asyncFn);
}
finally {
await this.release();
}
});
}
runBlockingOrFail(asyncFn, settings) {
return new Task(async () => {
try {
await this.acquireBlockingOrFail(settings);
return await resolveLazyable(asyncFn);
}
finally {
await this.release();
}
});
}
handleUnexpectedError = () => {
return async (args, next) => {
try {
return await next(...args);
}
catch (error) {
if (isLockError(error)) {
throw error;
}
this.eventDispatcher
.dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, {
error,
lock: this,
})
.detach();
throw error;
}
};
};
handleDispatch = (settings) => {
return async (args, next) => {
const result = await next(...args);
if (result && settings.on === "true") {
this.eventDispatcher
.dispatch(settings.eventName, settings.eventData)
.detach();
}
if (!result && settings.on === "false") {
this.eventDispatcher
.dispatch(settings.eventName, settings.eventData)
.detach();
}
return result;
};
};
acquire() {
return new Task(async () => {
return await this.adapter.acquire(this._key.toString(), this.lockId, this._ttl);
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: LOCK_EVENTS.ACQUIRED,
eventData: {
lock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: LOCK_EVENTS.UNAVAILABLE,
eventData: {
lock: this,
},
}),
]);
}
acquireOrFail() {
return new Task(async () => {
const hasAquired = await this.acquire();
if (!hasAquired) {
throw FailedAcquireLockError.create(this._key);
}
});
}
acquireBlocking(settings = {}) {
return new Task(async () => {
const { time = this.defaultBlockingTime, interval = this.defaultBlockingInterval, } = settings;
const timeAsTimeSpan = TimeSpan.fromTimeSpan(time);
const intervalAsTimeSpan = TimeSpan.fromTimeSpan(interval);
const endDate = timeAsTimeSpan.toEndDate();
while (endDate > new Date()) {
const hasAquired = await this.acquire();
if (hasAquired) {
return true;
}
await Task.delay(intervalAsTimeSpan);
}
return false;
});
}
acquireBlockingOrFail(settings) {
return new Task(async () => {
const hasAquired = await this.acquireBlocking(settings);
if (!hasAquired) {
throw FailedAcquireLockError.create(this._key);
}
});
}
release() {
return new Task(async () => {
return await this.adapter.release(this._key.toString(), this.lockId);
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: LOCK_EVENTS.RELEASED,
eventData: {
lock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: LOCK_EVENTS.FAILED_RELEASE,
eventData: {
lock: this,
},
}),
]);
}
releaseOrFail() {
return new Task(async () => {
const hasRelased = await this.release();
if (!hasRelased) {
throw FailedReleaseLockError.create(this._key, this.lockId);
}
});
}
forceRelease() {
return new Task(async () => {
return await this.adapter.forceRelease(this._key.toString());
}).pipe([
this.handleUnexpectedError(),
async (args, next) => {
const hasReleased = await next(...args);
this.eventDispatcher
.dispatch(LOCK_EVENTS.FORCE_RELEASED, {
lock: this,
hasReleased,
})
.detach();
return hasReleased;
},
]);
}
refresh(ttl = this.defaultRefreshTime) {
return new Task(async () => {
return await this.adapter.refresh(this._key.toString(), this.lockId, TimeSpan.fromTimeSpan(ttl));
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: LOCK_EVENTS.REFRESHED,
eventData: {
lock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: LOCK_EVENTS.FAILED_REFRESH,
eventData: {
lock: this,
},
}),
async (args, next) => {
const hasRefreshed = await next(...args);
if (hasRefreshed) {
this._ttl = TimeSpan.fromTimeSpan(ttl);
}
return hasRefreshed;
},
]);
}
refreshOrFail(ttl) {
return new Task(async () => {
const hasRefreshed = await this.refresh(ttl);
if (!hasRefreshed) {
throw FailedRefreshLockError.create(this._key, this.lockId);
}
});
}
get key() {
return this._key.get();
}
get id() {
return this.lockId;
}
get ttl() {
return this._ttl;
}
getState() {
return new Task(async () => {
const state = await this.adapter.getState(this._key.toString());
if (state === null) {
return {
type: LOCK_STATE.EXPIRED,
};
}
if (state.owner === this.lockId) {
return {
type: LOCK_STATE.ACQUIRED,
remainingTime: state.expiration === null
? null
: TimeSpan.fromDateRange({
start: new Date(),
end: state.expiration,
}),
};
}
return {
type: LOCK_STATE.UNAVAILABLE,
owner: state.owner,
};
}).pipe(this.handleUnexpectedError());
}
}
//# sourceMappingURL=lock.js.map