@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.
179 lines (178 loc) • 7.67 kB
TypeScript
/**
* @module Semaphore
*/
import { type IReadableContext } from "../../execution-context/contracts/_module.js";
import { type InvokableFn } from "../../utilities/_module.js";
/**
* Expiration configuration for a single semaphore slot.
* Tracks when a slot becomes available again (expires).
* Null expiration means the slot never expires automatically.
*
* IMPORT_PATH: `"@daiso-tech/core/semaphore/contracts"`
* @group Contracts
*/
export type ISemaphoreSlotExpirationData = {
/**
* The expiration date and time of the slot.
* After this time, the slot is automatically released and becomes available for reuse.
* Null indicates the slot does not expire (persists until explicitly removed).
*/
expiration: Date | null;
};
/**
* Single semaphore slot data persisted in the database.
* Represents one unit/position in a limited resource pool.
*
* Each slot has a unique `id` and tracks expiration time.
* When a request acquires a slot, the slot is marked with expiration.
* When expired or explicitly removed, the slot becomes available again.
*
* IMPORT_PATH: `"@daiso-tech/core/semaphore/contracts"`
* @group Contracts
*/
export type ISemaphoreSlotData = ISemaphoreSlotExpirationData & {
/**
* Unique identifier for this slot within the semaphore.
* Generated when a request acquires a slot.
* Used to release or update the specific slot later.
*/
id: string;
};
/**
* Semaphore configuration persisted in the database.
* Defines the constraint for the resource pool.
*
* A semaphore with limit=5 allows 5 concurrent requests.
* Additional requests must wait for slots to become available (expire or be released).
*
* IMPORT_PATH: `"@daiso-tech/core/semaphore/contracts"`
* @group Contracts
*/
export type ISemaphoreData = {
/**
* Maximum number of concurrent units/slots available.
* Number of concurrent requests that can hold slots simultaneously.
*/
limit: number;
};
/**
* Transaction context for atomic semaphore operations.
* Provides methods to read and modify semaphore state within a database transaction.
*
* All transaction methods must be executed together atomically.
* The database guarantees isolation (race-condition free) for all operations.
*
* IMPORT_PATH: `"@daiso-tech/core/semaphore/contracts"`
* @group Contracts
*/
export type IDatabaseSemaphoreTransaction = {
/**
* Retrieves semaphore configuration by key.
* Returns the limit (max concurrent slots) or null if semaphore doesn't exist.
*
* @param context - Readable execution context for the operation
* @param key - The semaphore identifier (e.g., "api-rate-limit", "db-connection-pool")
* @returns Semaphore config with limit, or null if not found
*/
findSemaphore(context: IReadableContext, key: string): Promise<ISemaphoreData | null>;
/**
* Retrieves all active slots (acquired requests) for a semaphore.
* Returns empty array if semaphore has no slots or doesn't exist.
* Includes expired slots (not auto-cleaned until explicitly removed).
*
* @param context - Readable execution context for the operation
* @param key - The semaphore identifier
* @returns Array of slot data with ids and expirations
*/
findSlots(context: IReadableContext, key: string): Promise<Array<ISemaphoreSlotData>>;
/**
* Creates new semaphore or updates existing semaphore limit.
* Idempotent: calling multiple times with same limit is safe.
*
* @param context - Readable execution context for the operation
* @param key - The semaphore identifier
* @param limit - Maximum number of concurrent slots
* @returns Void (always succeeds)
*/
upsertSemaphore(context: IReadableContext, key: string, limit: number): Promise<void>;
/**
* Creates new slot or updates existing slot expiration.
* Called when a request acquires a slot (with expiration time).
* Idempotent: updating same slot's expiration is safe.
*
* @param context - Readable execution context for the operation
* @param key - The semaphore identifier
* @param slotId - Unique slot identifier (generated by consumer)
* @param expiration - When slot expires and becomes available again, or null for no expiration
* @returns Void (always succeeds)
*/
upsertSlot(context: IReadableContext, key: string, slotId: string, expiration: Date | null): Promise<void>;
};
/**
* Database adapter contract for managing semaphores in SQL/document databases.
* Simplifies semaphore implementation using transactional CRUD patterns.
*
* Designed for persistent semaphores stored in databases like:
* - SQL databases (PostgreSQL, MySQL) with TypeORM or MikroORM
* - Document databases with transaction support
* - Any backend with atomic transaction capability
*
* IMPORT_PATH: `"@daiso-tech/core/semaphore/contracts"`
* @group Contracts
*/
export type IDatabaseSemaphoreAdapter = {
/**
* Executes a function within a database transaction.
* Ensures all operations in the function are atomic and isolated.
*
* Implementations must use the strictest transaction isolation level available
* (SERIALIZABLE for SQL, highest for document databases) to prevent race conditions.
*
* @param context - Readable execution context for the operation
* @param fn - Async function receiving transaction object, returns value of any type
* @returns The value returned by the function
* @throws Error if transaction fails or is rolled back
*
* @template TValue - Return type of the transaction function
*/
transaction<TValue>(context: IReadableContext, fn: InvokableFn<[
transaction: IDatabaseSemaphoreTransaction
], Promise<TValue>>): Promise<TValue>;
/**
* Removes a specific slot, making that resource unit available again.
* Returns the slot's expiration info.
* Returns null if slot doesn't exist (idempotent - safe to call multiple times).
*
* @param context - Readable execution context for the operation
* @param key - The semaphore identifier
* @param slotId - The slot to remove
* @returns Expiration info of the removed slot, or null if not found
*/
removeSlot(context: IReadableContext, key: string, slotId: string): Promise<ISemaphoreSlotExpirationData | null>;
/**
* Removes all slots from a semaphore.
* Used for cleanup or reset operations.
* Returns expiration info for all removed slots.
*
* @param context - Readable execution context for the operation
* @param key - The semaphore identifier
* @returns Array of expiration info for all removed slots
*/
removeAllSlots(context: IReadableContext, key: string): Promise<Array<ISemaphoreSlotExpirationData>>;
/**
* Updates a slot's expiration time if it's currently valid (not expired).
* Useful for extending a held slot's lifetime (e.g., heartbeat/keep-alive).
*
* Conditions for success:
* - Slot must exist
* - Slot must not be expired (expiration time >= current time or null)
* - New expiration must be in the future
*
* @param context - Readable execution context for the operation
* @param key - The semaphore identifier
* @param slotId - The slot to update
* @param expiration - New expiration time (or null for persistent)
* @returns Number of rows affected (1 if updated, 0 if slot not found or expired)
*/
updateExpiration(context: IReadableContext, key: string, slotId: string, expiration: Date): Promise<number>;
};