@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.
415 lines • 15.6 kB
JavaScript
/**
* @module SharedLock
*/
import {} from "../../../../event-bus/contracts/_module.js";
import {} from "../../../../execution-context/contracts/_module.js";
import {} from "../../../../middleware/contracts/_module.js";
import {} from "../../../../namespace/contracts/_module.js";
import { FailedAcquireWriterLockError, FailedRefreshReaderSemaphoreError, FailedRefreshWriterLockError, FailedReleaseReaderSemaphoreError, FailedReleaseWriterLockError, LimitReachedReaderSemaphoreError, SHARED_LOCK_EVENTS, SHARED_LOCK_STATE, } from "../../../../shared-lock/contracts/_module.js";
import { handleDispatch, handleUnexpectedError, } from "../../../../shared-lock/implementations/derivables/shared-lock-factory/event-helpers.js";
import {} from "../../../../time-span/contracts/_module.js";
import { TimeSpan } from "../../../../time-span/implementations/_module.js";
import { callInvokable, OPTION, optionNone, optionSome, resolveLazyable, UnexpectedError, } from "../../../../utilities/_module.js";
/**
* @internal
*/
export class SharedLock {
/**
* @internal
*/
static _serialize(deserializedValue) {
return {
version: "1",
key: deserializedValue._key.get(),
limit: deserializedValue.limit,
lockId: deserializedValue.lockId,
ttlInMs: deserializedValue._ttl?.toMilliseconds() ?? null,
};
}
namespace;
adapter;
originalAdapter;
eventDispatcher;
_key;
lockId;
_ttl;
defaultRefreshTime;
serdeTransformerName;
limit;
waitUntil;
executionContext;
use;
constructor(settings) {
const { namespace, adapter, originalAdapter, eventDispatcher, key, lockId, ttl, serdeTransformerName, defaultRefreshTime, limit, waitUntil, executionContext, use, } = settings;
this.use = use;
this.executionContext = executionContext;
this.waitUntil = waitUntil;
this.limit = limit;
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.defaultRefreshTime = defaultRefreshTime;
}
_getNamespace() {
return this.namespace;
}
_getSerdeTransformerName() {
return this.serdeTransformerName;
}
_getAdapter() {
return this.originalAdapter;
}
async runReaderOrFail(asyncFn) {
await this.acquireReaderOrFail();
try {
return await resolveLazyable(asyncFn);
}
finally {
await this.releaseReader();
}
}
acquireReader() {
return this.use(async () => {
return await this.adapter.acquireReader({
context: this.executionContext,
key: this._key.toString(),
lockId: this.lockId,
limit: this.limit,
ttl: this._ttl,
});
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.READER_ACQUIRED,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.UNAVAILABLE,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
])();
}
async acquireReaderOrFail() {
const hasAquired = await this.acquireReader();
if (!hasAquired) {
throw LimitReachedReaderSemaphoreError.create(this._key);
}
}
releaseReader() {
return this.use(async () => {
return await this.adapter.releaseReader(this.executionContext, this._key.toString(), this.lockId);
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.READER_RELEASED,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.READER_FAILED_RELEASE,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
])();
}
async releaseReaderOrFail() {
const hasReleased = await this.releaseReader();
if (!hasReleased) {
throw FailedReleaseReaderSemaphoreError.create(this._key, this.lockId);
}
}
forceReleaseAllReaders() {
return this.use(async () => {
return await this.adapter.forceReleaseAllReaders(this.executionContext, this._key.toString());
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
async ({ next }) => {
const hasReleased = await next();
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(SHARED_LOCK_EVENTS.READER_ALL_FORCE_RELEASED, {
sharedLock: this,
hasReleased,
}));
return hasReleased;
},
])();
}
refreshReader(ttl = this.defaultRefreshTime) {
return this.use(async () => {
return await this.adapter.refreshReader(this.executionContext, this._key.toString(), this.lockId, TimeSpan.fromTimeSpan(ttl));
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.READER_REFRESHED,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.READER_FAILED_REFRESH,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
async ({ next }) => {
const hasRefreshed = await next();
if (hasRefreshed) {
this._ttl = TimeSpan.fromTimeSpan(ttl);
}
return hasRefreshed;
},
])();
}
async refreshReaderOrFail(ttl) {
const hasRefreshed = await this.refreshReader(ttl);
if (!hasRefreshed) {
throw FailedRefreshReaderSemaphoreError.create(this._key, this.lockId);
}
}
async runWriterOrFail(asyncFn) {
await this.acquireWriterOrFail();
try {
return await resolveLazyable(asyncFn);
}
finally {
await this.releaseWriter();
}
}
acquireWriter() {
return this.use(async () => {
return await this.adapter.acquireWriter(this.executionContext, this._key.toString(), this.lockId, this._ttl);
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.WRITER_ACQUIRED,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.UNAVAILABLE,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
])();
}
async acquireWriterOrFail() {
const hasAquired = await this.acquireWriter();
if (!hasAquired) {
throw FailedAcquireWriterLockError.create(this._key);
}
}
releaseWriter() {
return this.use(async () => {
return await this.adapter.releaseWriter(this.executionContext, this._key.toString(), this.lockId);
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.WRITER_RELEASED,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.WRITER_FAILED_RELEASE,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
])();
}
async releaseWriterOrFail() {
const hasRelased = await this.releaseWriter();
if (!hasRelased) {
throw FailedReleaseWriterLockError.create(this._key, this.lockId);
}
}
forceReleaseWriter() {
return this.use(async () => {
return await this.adapter.forceReleaseWriter(this.executionContext, this._key.toString());
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
async ({ next }) => {
const hasReleased = await next();
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(SHARED_LOCK_EVENTS.WRITER_FORCE_RELEASED, {
sharedLock: this,
hasReleased,
}));
return hasReleased;
},
])();
}
refreshWriter(ttl = this.defaultRefreshTime) {
return this.use(async () => {
return await this.adapter.refreshWriter(this.executionContext, this._key.toString(), this.lockId, TimeSpan.fromTimeSpan(ttl));
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.WRITER_REFRESHED,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.WRITER_FAILED_REFRESH,
eventData: {
sharedLock: this,
},
waitUntil: this.waitUntil,
eventDispatcher: this.eventDispatcher,
}),
async ({ next }) => {
const hasRefreshed = await next();
if (hasRefreshed) {
this._ttl = TimeSpan.fromTimeSpan(ttl);
}
return hasRefreshed;
},
])();
}
async refreshWriterOrFail(ttl) {
const hasRefreshed = await this.refreshWriter(ttl);
if (!hasRefreshed) {
throw FailedRefreshWriterLockError.create(this._key, this.lockId);
}
}
get key() {
return this._key;
}
get id() {
return this.lockId;
}
get ttl() {
return this._ttl;
}
forceRelease() {
return this.use(async () => {
return await this.adapter.forceRelease(this.executionContext, this._key.toString());
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
])();
}
extractWriterState(state) {
if (state.writer && state.writer.owner === this.lockId) {
return optionSome({
type: SHARED_LOCK_STATE.WRITER_ACQUIRED,
remainingTime: state.writer.expiration === null
? null
: TimeSpan.fromDateRange({
start: new Date(),
end: state.writer.expiration,
}),
});
}
if (state.writer && state.writer.owner !== this.lockId) {
return optionSome({
type: SHARED_LOCK_STATE.WRITER_UNAVAILABLE,
owner: state.writer.owner,
});
}
return optionNone();
}
extractReaderState(state) {
if (state.reader !== null &&
state.reader.acquiredSlots.size >= state.reader.limit) {
return optionSome({
type: SHARED_LOCK_STATE.READER_LIMIT_REACHED,
limit: state.reader.limit,
acquiredSlots: [...state.reader.acquiredSlots.keys()],
});
}
const slotExpiration = state.reader?.acquiredSlots.get(this.lockId);
if (state.reader !== null && slotExpiration === undefined) {
return optionSome({
type: SHARED_LOCK_STATE.READER_UNACQUIRED,
limit: state.reader.limit,
freeSlotsCount: state.reader.limit - state.reader.acquiredSlots.size,
acquiredSlotsCount: state.reader.acquiredSlots.size,
acquiredSlots: [...state.reader.acquiredSlots.keys()],
});
}
if (state.reader !== null && slotExpiration !== undefined) {
return optionSome({
type: SHARED_LOCK_STATE.READER_ACQUIRED,
acquiredSlots: [...state.reader.acquiredSlots.keys()],
acquiredSlotsCount: state.reader.acquiredSlots.size,
freeSlotsCount: state.reader.limit - state.reader.acquiredSlots.size,
limit: state.reader.limit,
remainingTime: slotExpiration === null
? null
: TimeSpan.fromDateRange({
start: new Date(),
end: slotExpiration,
}),
});
}
return optionNone();
}
getState() {
return this.use(async () => {
const state = await this.adapter.getState(this.executionContext, this._key.toString());
if (state === null) {
return {
type: SHARED_LOCK_STATE.EXPIRED,
};
}
const writerState = this.extractWriterState(state);
if (writerState.type === OPTION.SOME) {
return writerState.value;
}
const readerState = this.extractReaderState(state);
if (readerState.type === OPTION.SOME) {
return readerState.value;
}
throw new UnexpectedError("Invalid ISharedLockAdapterState, expected either the reader field must be defined or the writer field must be defined, but not both.");
}, [
handleUnexpectedError(this.waitUntil, this.eventDispatcher, this),
])();
}
}
//# sourceMappingURL=shared-lock.js.map