UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

386 lines 14 kB
import { AlephaError, AsyncFn, Middleware, Primitive, Static } from "alepha"; import { TopicProvider } from "alepha/topic"; import { DateTime, DateTimeProvider, DurationLike, Timeout } from "alepha/datetime"; //#region ../../src/lock/core/errors/LockAcquireError.d.ts declare class LockAcquireError extends AlephaError { constructor(name: string); } //#endregion //#region ../../src/lock/core/providers/LockProvider.d.ts /** * Store Provider Interface */ declare abstract class LockProvider { /** * Set the string value of a key. * * @param key The key of the value to set. * @param value The value to set. * @param nx If set to true, the key will only be set if it does not already exist. * @param px Set the specified expire time, in milliseconds. */ abstract set(key: string, value: string, nx?: boolean, px?: number): Promise<string>; /** * Remove the specified keys. * * @param keys The keys to delete. */ abstract del(...keys: string[]): Promise<void>; } //#endregion //#region ../../src/lock/core/primitives/$lock.d.ts /** * Distributed lock middleware for `use` arrays in `$action`, `$job`, `$page`, `$pipeline`. * * Acquires a distributed lock before the handler runs and releases it after completion. * Throws `LockAcquireError` if the lock cannot be acquired (unless `wait: true`). * * ```ts * processOrder = $action({ * use: [$lock({ name: "process-order" })], * handler: async ({ body }) => { ... }, * }); * ``` */ declare const $lock: (options: LockMiddlewareOptions) => Middleware; /** * Options for $lock in middleware mode (no handler). */ interface LockMiddlewareOptions { /** * Lock key name. Required in middleware mode (no class context available). * Can be a static string or a function that derives the key from handler args. */ name: string | ((...args: any[]) => string); /** * Whether to wait for the lock to become available. * * @default false */ wait?: boolean; /** * Maximum duration the lock can be held before automatic expiration. * * @default [5, "minutes"] */ maxDuration?: DurationLike; } interface LockPrimitiveOptions<TFunc extends AsyncFn> { /** * The function to execute when the lock is successfully acquired. * * This function: * - Only executes on the instance that successfully acquires the lock * - Has exclusive access to the protected resource during execution * - Should contain the critical section logic that must not run concurrently * - Can be async and perform any operations needed * - Will automatically release the lock upon completion or error * - Has access to the full Alepha dependency injection container * * **Handler Design Guidelines**: * - Keep critical sections as short as possible to minimize lock contention * - Include proper error handling to ensure locks are released * - Use timeouts for external operations to prevent deadlocks * - Log important operations for debugging and monitoring * - Consider idempotency for handlers that might be retried * * @param ...args - The arguments passed to the lock execution * @returns Promise that resolves when the protected operation is complete * * @example * ```ts * handler: async (batchId: string) => { * console.log(`Processing batch ${batchId} - only one instance will run this`); * * const batch = await this.getBatchData(batchId); * const results = await this.processBatchItems(batch.items); * await this.saveBatchResults(batchId, results); * * console.log(`Batch ${batchId} completed successfully`); * } * ``` */ handler: TFunc; /** * Whether the lock should wait for other instances to complete before giving up. * * **wait = false (default)**: * - Non-blocking behavior - if lock is held, immediately return without executing * - Perfect for scheduled tasks where you only want one execution per trigger * - Use when multiple triggers are acceptable but concurrent execution is not * - Examples: periodic cleanup, cron jobs, background maintenance * * **wait = true**: * - Blocking behavior - wait for the current lock holder to finish * - All instances will eventually execute (one after another) * - Perfect for initialization tasks where all instances need the work completed * - Examples: database migrations, cache warming, resource initialization * * **Trade-offs**: * - Non-waiting: Better performance, may miss executions if timing is off * - Waiting: Guaranteed execution order, slower overall throughput * * @default false * * @example * ```ts * // Scheduled task - don't wait, just skip if already running * scheduledCleanup = $lock({ * wait: false, // Skip if cleanup already running * handler: async () => { } // perform cleanup * }); * * // Migration - wait for completion before proceeding * migration = $lock({ * wait: true, // All instances wait for migration to complete * handler: async () => { } // perform migration * }); * ``` */ wait?: boolean; /** * The unique identifier for the lock. * * Can be either: * - **Static string**: A fixed identifier for the lock * - **Dynamic function**: A function that generates the lock key based on arguments * * **Dynamic Lock Keys**: * - Enable per-resource locking (e.g., per-user, per-file, per-product) * - Allow fine-grained concurrency control * - Prevent unnecessary blocking between unrelated operations * * **Key Design Guidelines**: * - Use descriptive names that indicate the protected resource * - Include relevant identifiers for dynamic keys * - Keep keys reasonably short but unique * - Consider using hierarchical naming (e.g., "service:operation:resource") * * If not provided, defaults to `{serviceName}:{propertyKey}`. * * @example "user-migration" * @example "daily-report-generation" * @example (userId: string) => `user-profile-update:${userId}` * @example (fileId: string, operation: string) => `file-${operation}:${fileId}` * * @example * ```ts * // Static lock key - all instances compete for the same lock * globalCleanup = $lock({ * name: "system-cleanup", * handler: async () => { } // perform cleanup * }); * * // Dynamic lock key - per-user locks, users don't block each other * updateUserProfile = $lock({ * name: (userId: string) => `user-update:${userId}`, * handler: async (userId: string, data: UserData) => { * // Only one update per user at a time, but different users can update concurrently * } * }); * ``` */ name?: string | ((...args: Parameters<TFunc>) => string); /** * Maximum duration the lock can be held before it expires automatically. * * This prevents deadlocks when a process dies while holding a lock or when * operations take longer than expected. The lock will be automatically released * after this duration, allowing other instances to proceed. * * **Duration Guidelines**: * - Set based on expected operation duration plus safety margin * - Too short: Operations may be interrupted by early expiration * - Too long: Failed processes block others for extended periods * - Consider worst-case scenarios and external dependency timeouts * * **Typical Values**: * - Quick operations: 30 seconds - 2 minutes * - Database operations: 5 - 15 minutes * - File processing: 10 - 30 minutes * - Large migrations: 30 minutes - 2 hours * * @default [5, "minutes"] * * @example [30, "seconds"] // Quick operations * @example [10, "minutes"] // Database migrations * @example [1, "hour"] // Long-running batch jobs * * @example * ```ts * quickTask = $lock({ * maxDuration: [2, "minutes"], // Quick timeout for fast operations * handler: async () => { } // perform quick task * }); * * heavyProcessing = $lock({ * maxDuration: [30, "minutes"], // Longer timeout for heavy work * handler: async () => { } // perform heavy processing * }); * ``` */ maxDuration?: DurationLike; /** * Additional time to keep the lock active after the handler completes successfully. * * This provides a "cooling off" period that can be useful for: * - Preventing immediate re-execution of the same operation * - Giving time for related systems to process the results * - Avoiding race conditions with dependent operations * - Providing a buffer for cleanup operations * * Can be either: * - **Static duration**: Fixed grace period for all executions * - **Dynamic function**: Grace period determined by execution arguments * - **undefined**: No grace period, lock released immediately after completion * * **Grace Period Use Cases**: * - File processing: Prevent immediate reprocessing of uploaded files * - Cache updates: Allow time for cache propagation * - Batch operations: Prevent overlapping batch processing * - External API calls: Respect rate limiting requirements * * @default undefined (no grace period) * * @example [5, "minutes"] // Fixed 5-minute grace period * @example [30, "seconds"] // Short grace for quick operations * @example (userId: string) => userId.startsWith("premium") ? [10, "minutes"] : [2, "minutes"] * * @example * ```ts * fileProcessor = $lock({ * gracePeriod: [10, "minutes"], // Prevent reprocessing same file immediately * handler: async (filePath: string) => { * await this.processFile(filePath); * } * }); * * userOperation = $lock({ * gracePeriod: (userId: string, operation: string) => { * // Dynamic grace based on operation type * return operation === 'migration' ? [30, "minutes"] : [5, "minutes"]; * }, * handler: async (userId: string, operation: string) => { * await this.performUserOperation(userId, operation); * } * }); * ``` */ gracePeriod?: ((...args: Parameters<TFunc>) => DurationLike | undefined) | DurationLike; } /** * Lock configuration atom. */ declare const lockOptions: import("alepha").Atom<import("zod").ZodObject<{ prefixKey: import("zod").ZodString; }, import("zod/v4/core").$strip>, "alepha.lock.options">; type LockAtomOptions = Static<typeof lockOptions.schema>; declare module "alepha" { interface State { [lockOptions.key]: LockAtomOptions; } } declare class LockPrimitive<TFunc extends AsyncFn> extends Primitive<LockPrimitiveOptions<TFunc>> { protected readonly log: import("alepha/logger").Logger; protected readonly provider: LockProvider; protected readonly settings: Readonly<{ prefixKey: string; }>; protected readonly dateTimeProvider: DateTimeProvider; /** * Lazy-initialized UUID to avoid calling crypto.randomUUID() in global scope. * Cloudflare Workers doesn't allow random value generation during initialization. */ protected _id?: string; protected get id(): string; readonly maxDuration: import("alepha/datetime").Duration; protected readonly topicLockEnd: import("alepha/topic").TopicPrimitive<{ payload: import("zod").ZodObject<{ name: import("zod").ZodString; }, import("zod/v4/core").$strip>; }>; run(...args: Parameters<TFunc>): Promise<void>; /** * Set the lock for the given key. */ protected lock(key: string): Promise<LockResult>; protected setGracePeriod(key: string, lock: LockResult, ...args: Parameters<TFunc>): Promise<void>; protected wait(key: string, maxDuration: DurationLike): Promise<void>; protected key(...args: Parameters<TFunc>): string; protected parse(value: string): LockResult; } interface LockResult { id: string; createdAt: DateTime; endedAt?: DateTime; response?: string; } //#endregion //#region ../../src/lock/core/providers/LockTopicProvider.d.ts declare abstract class LockTopicProvider extends TopicProvider {} //#endregion //#region ../../src/lock/core/providers/MemoryLockProvider.d.ts /** * A simple in-memory store provider. */ declare class MemoryLockProvider implements LockProvider { protected readonly dateTimeProvider: DateTimeProvider; protected readonly log: import("alepha/logger").Logger; /** * The in-memory store. */ protected store: Record<string, string>; /** * Timeouts used to expire keys. */ protected storeTimeout: Record<string, Timeout>; set(key: string, value: string, nx?: boolean, px?: number): Promise<string>; del(...keys: string[]): Promise<void>; protected ttl(key: string, ms: number): void; } //#endregion //#region ../../src/lock/core/index.d.ts declare module "alepha" { interface Hooks { /** * Fires when a lock is successfully acquired. */ "lock:acquired": { name: string; id: string; maxDurationMs: number; }; /** * Fires when a lock is released (handler completed or threw). */ "lock:released": { name: string; id: string; heldMs: number; }; /** * Fires when a lock acquisition contends with another holder. * Emitted whether the caller eventually acquires (`wait: true`) or fails. */ "lock:contended": { name: string; id: string; }; } } /** * Resource locking for distributed systems. * * **Features:** * - Distributed locks with timeout * - Time-based lock expiration * - Automatic release on scope exit * - Distributed coordination via Redis * - Providers: Memory (dev), Redis (production) * * @module alepha.lock */ declare const AlephaLock: import("alepha").Service<import("alepha").Module>; //#endregion export { $lock, AlephaLock, LockAcquireError, LockAtomOptions, LockMiddlewareOptions, LockPrimitive, LockPrimitiveOptions, LockProvider, LockResult, LockTopicProvider, MemoryLockProvider, lockOptions }; //# sourceMappingURL=index.d.ts.map