UNPKG

@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.

542 lines 19.8 kB
/** * @module SharedLock */ import { Task } from "../../../../task/_module-exports.js"; import { FailedAcquireWriterLockError, FailedRefreshReaderSemaphoreError, FailedRefreshWriterLockError, FailedReleaseReaderSemaphoreError, FailedReleaseWriterLockError, isSharedLockError, LimitReachedReaderSemaphoreError, SHARED_LOCK_EVENTS, SHARED_LOCK_STATE, } from "../../../../shared-lock/contracts/_module-exports.js"; import { TimeSpan } from "../../../../time-span/implementations/_module-exports.js"; import { resolveLazyable, resultFailure, resultSuccess, UnexpectedError, } from "../../../../utilities/_module-exports.js"; /** * @internal */ export class SharedLock { /** * @internal */ static _internal_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; defaultBlockingInterval; defaultBlockingTime; defaultRefreshTime; serdeTransformerName; limit; constructor(settings) { const { namespace, adapter, originalAdapter, eventDispatcher, key, lockId, ttl, serdeTransformerName, defaultBlockingInterval, defaultBlockingTime, defaultRefreshTime, limit, } = settings; 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.defaultBlockingInterval = defaultBlockingInterval; this.defaultBlockingTime = defaultBlockingTime; this.defaultRefreshTime = defaultRefreshTime; } _internal_getNamespace() { return this.namespace; } _internal_getSerdeTransformerName() { return this.serdeTransformerName; } _internal_getAdapter() { return this.originalAdapter; } runReader(asyncFn) { return new Task(async () => { try { const hasAquired = await this.acquireReader(); if (!hasAquired) { return resultFailure(new LimitReachedReaderSemaphoreError(`Key "${this._key.toString()}" already acquired`)); } return resultSuccess(await resolveLazyable(asyncFn)); } finally { await this.releaseReader(); } }); } runReaderOrFail(asyncFn) { return new Task(async () => { try { await this.acquireReaderOrFail(); return await resolveLazyable(asyncFn); } finally { await this.releaseReader(); } }); } runReaderBlocking(asyncFn, settings) { return new Task(async () => { try { const hasAquired = await this.acquireReaderBlocking(settings); if (!hasAquired) { return resultFailure(new LimitReachedReaderSemaphoreError(`Key "${this._key.toString()}" has reached the limit ${String(this.limit)}`)); } return resultSuccess(await resolveLazyable(asyncFn)); } finally { await this.releaseReader(); } }); } runReaderBlockingOrFail(asyncFn, settings) { return new Task(async () => { try { await this.acquireReaderBlockingOrFail(settings); return await resolveLazyable(asyncFn); } finally { await this.releaseReader(); } }); } handleUnexpectedError = () => { return async (args, next) => { try { return await next(...args); } catch (error) { if (isSharedLockError(error)) { throw error; } this.eventDispatcher .dispatch(SHARED_LOCK_EVENTS.UNEXPECTED_ERROR, { error, sharedLock: 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; }; }; acquireReader() { return new Task(async () => { return await this.adapter.acquireReader({ key: this._key.get(), lockId: this.lockId, limit: this.limit, ttl: this._ttl, }); }).pipe([ this.handleUnexpectedError(), this.handleDispatch({ on: "true", eventName: SHARED_LOCK_EVENTS.READER_ACQUIRED, eventData: { sharedLock: this, }, }), this.handleDispatch({ on: "false", eventName: SHARED_LOCK_EVENTS.UNAVAILABLE, eventData: { sharedLock: this, }, }), ]); } acquireReaderOrFail() { return new Task(async () => { const hasAquired = await this.acquireReader(); if (!hasAquired) { throw new LimitReachedReaderSemaphoreError(`Key "${this._key.toString()}" has reached the limit`); } }); } acquireReaderBlocking(settings = {}) { return new Task(async () => { const { time = this.defaultBlockingTime, interval = this.defaultBlockingInterval, } = settings; const timeAsTimeSpan = TimeSpan.fromTimeSpan(time); const endDate = timeAsTimeSpan.toEndDate(); while (endDate.getTime() > new Date().getTime()) { const hasAquired = await this.acquireReader(); if (hasAquired) { return true; } await Task.delay(interval); } return false; }); } acquireReaderBlockingOrFail(settings) { return new Task(async () => { const hasAquired = await this.acquireReaderBlocking(settings); if (!hasAquired) { throw new LimitReachedReaderSemaphoreError(`Key "${this._key.toString()}" has reached the limit`); } }); } releaseReader() { return new Task(async () => { return await this.adapter.releaseReader(this._key.get(), this.lockId); }).pipe([ this.handleUnexpectedError(), this.handleDispatch({ on: "true", eventName: SHARED_LOCK_EVENTS.READER_RELEASED, eventData: { sharedLock: this, }, }), this.handleDispatch({ on: "false", eventName: SHARED_LOCK_EVENTS.READER_FAILED_RELEASE, eventData: { sharedLock: this, }, }), ]); } releaseReaderOrFail() { return new Task(async () => { const hasReleased = await this.releaseReader(); if (!hasReleased) { throw new FailedReleaseReaderSemaphoreError(`Failed to release lock "${this.lockId}" of key "${this._key.toString()}"`); } }); } forceReleaseAllReaders() { return new Task(async () => { return await this.adapter.forceReleaseAllReaders(this._key.get()); }).pipe([ this.handleUnexpectedError(), async (args, next) => { const hasReleased = await next(...args); this.eventDispatcher .dispatch(SHARED_LOCK_EVENTS.READER_ALL_FORCE_RELEASED, { sharedLock: this, hasReleased, }) .detach(); return hasReleased; }, ]); } refreshReader(ttl = this.defaultRefreshTime) { return new Task(async () => { return await this.adapter.refreshReader(this._key.get(), this.lockId, TimeSpan.fromTimeSpan(ttl)); }).pipe([ this.handleUnexpectedError(), this.handleDispatch({ on: "true", eventName: SHARED_LOCK_EVENTS.READER_REFRESHED, eventData: { sharedLock: this, }, }), this.handleDispatch({ on: "false", eventName: SHARED_LOCK_EVENTS.READER_FAILED_REFRESH, eventData: { sharedLock: this, }, }), async (args, next) => { const hasRefreshed = await next(...args); this._ttl = TimeSpan.fromTimeSpan(ttl); return hasRefreshed; }, ]); } refreshReaderOrFail(ttl) { return new Task(async () => { const hasRefreshed = await this.refreshReader(ttl); if (!hasRefreshed) { throw new FailedRefreshReaderSemaphoreError(`Failed to refresh lock "${this.lockId}" of key "${this._key.toString()}"`); } }); } runWriter(asyncFn) { return new Task(async () => { try { const hasAquired = await this.acquireWriter(); if (!hasAquired) { return resultFailure(new FailedAcquireWriterLockError(`Key "${this._key.toString()}" already acquired`)); } return resultSuccess(await resolveLazyable(asyncFn)); } finally { await this.releaseWriter(); } }); } runWriterOrFail(asyncFn) { return new Task(async () => { try { await this.acquireWriterOrFail(); return await resolveLazyable(asyncFn); } finally { await this.releaseWriter(); } }); } runWriterBlocking(asyncFn, settings) { return new Task(async () => { try { const hasAquired = await this.acquireWriterBlocking(settings); if (!hasAquired) { return resultFailure(new FailedAcquireWriterLockError(`Key "${this._key.toString()}" already acquired`)); } return resultSuccess(await resolveLazyable(asyncFn)); } finally { await this.releaseWriter(); } }); } runWriterBlockingOrFail(asyncFn, settings) { return new Task(async () => { try { await this.acquireWriterBlockingOrFail(settings); return await resolveLazyable(asyncFn); } finally { await this.releaseWriter(); } }); } acquireWriter() { return new Task(async () => { return await this.adapter.acquireWriter(this._key.get(), this.lockId, this._ttl); }).pipe([ this.handleUnexpectedError(), this.handleDispatch({ on: "true", eventName: SHARED_LOCK_EVENTS.WRITER_ACQUIRED, eventData: { sharedLock: this, }, }), this.handleDispatch({ on: "false", eventName: SHARED_LOCK_EVENTS.UNAVAILABLE, eventData: { sharedLock: this, }, }), ]); } acquireWriterOrFail() { return new Task(async () => { const hasAquired = await this.acquireWriter(); if (!hasAquired) { throw new FailedAcquireWriterLockError(`Key "${this._key.toString()}" already acquired`); } }); } acquireWriterBlocking(settings = {}) { return new Task(async () => { const { time = this.defaultBlockingTime, interval = this.defaultBlockingInterval, } = settings; const timeAsTimeSpan = TimeSpan.fromTimeSpan(time); const endDate = timeAsTimeSpan.toEndDate(); while (endDate > new Date()) { const hasAquired = await this.acquireWriter(); if (hasAquired) { return true; } await Task.delay(interval); } return false; }); } acquireWriterBlockingOrFail(settings) { return new Task(async () => { const hasAquired = await this.acquireWriterBlocking(settings); if (!hasAquired) { throw new FailedAcquireWriterLockError(`Key "${this._key.toString()}" already acquired`); } }); } releaseWriter() { return new Task(async () => { return await this.adapter.releaseWriter(this._key.get(), this.lockId); }).pipe([ this.handleUnexpectedError(), this.handleDispatch({ on: "true", eventName: SHARED_LOCK_EVENTS.WRITER_RELEASED, eventData: { sharedLock: this, }, }), this.handleDispatch({ on: "false", eventName: SHARED_LOCK_EVENTS.WRITER_FAILED_RELEASE, eventData: { sharedLock: this, }, }), ]); } releaseWriterOrFail() { return new Task(async () => { const hasRelased = await this.releaseWriter(); if (!hasRelased) { throw new FailedReleaseWriterLockError(`Unonwed release on key "${this._key.toString()}" by owner "${this.lockId}"`); } }); } forceReleaseWriter() { return new Task(async () => { return await this.adapter.forceReleaseWriter(this._key.get()); }).pipe([ this.handleUnexpectedError(), async (args, next) => { const hasReleased = await next(...args); this.eventDispatcher .dispatch(SHARED_LOCK_EVENTS.WRITER_FORCE_RELEASED, { sharedLock: this, hasReleased, }) .detach(); return hasReleased; }, ]); } refreshWriter(ttl = this.defaultRefreshTime) { return new Task(async () => { return await this.adapter.refreshWriter(this._key.get(), this.lockId, TimeSpan.fromTimeSpan(ttl)); }).pipe([ this.handleUnexpectedError(), this.handleDispatch({ on: "true", eventName: SHARED_LOCK_EVENTS.WRITER_REFRESHED, eventData: { sharedLock: this, }, }), this.handleDispatch({ on: "false", eventName: SHARED_LOCK_EVENTS.WRITER_FAILED_REFRESH, eventData: { sharedLock: this, }, }), async (args, next) => { const hasRefreshed = await next(...args); if (hasRefreshed) { this._ttl = TimeSpan.fromTimeSpan(ttl); } return hasRefreshed; }, ]); } refreshWriterOrFail(ttl) { return new Task(async () => { const hasRefreshed = await this.refreshWriter(ttl); if (!hasRefreshed) { throw new FailedRefreshWriterLockError(`Unonwed refresh on key "${this._key.toString()}" by owner "${this.lockId}"`); } }); } get key() { return this._key.toString(); } get id() { return this.lockId; } get ttl() { return this._ttl; } forceRelease() { return new Task(async () => { return await this.adapter.forceRelease(this._key.get()); }).pipe([this.handleUnexpectedError()]); } getState() { return new Task(async () => { const state = await this.adapter.getState(this._key.get()); if (state === null) { return { type: SHARED_LOCK_STATE.EXPIRED, }; } if (state.writer && state.writer.owner === this.lockId) { return { 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 { type: SHARED_LOCK_STATE.WRITER_UNAVAILABLE, owner: state.writer.owner, }; } if (state.reader !== null && state.reader.acquiredSlots.size >= state.reader.limit) { return { type: SHARED_LOCK_EVENTS.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 { 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 { 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, }), }; } throw new UnexpectedError("Invalid ISharedLockAdapterState, expected either the reader field must be defined or the writer field must be defined, but not both."); }).pipe(this.handleUnexpectedError()); } } //# sourceMappingURL=shared-lock.js.map