@upstash/lock
Version:
A distributed lock implementation using Upstash Redis
1 lines • 6.66 kB
Source Map (JSON)
{"version":3,"sources":["../src/lock.ts"],"sourcesContent":["import type { LockAcquireConfig, LockConfig, LockCreateConfig, LockStatus } from \"./types\";\n\nexport class Lock {\n private readonly config: LockConfig;\n private readonly DEFAULT_LEASE_MS = 10000;\n private readonly DEFAULT_RETRY_ATTEMPTS = 3;\n private readonly DEFAULT_RETRY_DELAY_MS = 100;\n\n constructor(config: LockCreateConfig) {\n this.config = {\n redis: config.redis,\n id: config.id,\n lease: config.lease ?? this.DEFAULT_LEASE_MS,\n UUID: null, // set when lock is acquired\n retry: {\n attempts: config.retry?.attempts ?? this.DEFAULT_RETRY_ATTEMPTS,\n delay: config.retry?.delay ?? this.DEFAULT_RETRY_DELAY_MS,\n },\n };\n }\n\n /**\n * Tries to acquire a lock with the given configuration.\n * If initially unsuccessful, the method will retry based on the provided retry configuration.\n *\n * @param config - Optional configuration for the lock acquisition to override the constructor config.\n * @returns {Promise<boolean>} True if the lock was acquired, otherwise false.\n */\n public async acquire(acquireConfig?: LockAcquireConfig): Promise<boolean> {\n // Allow for overriding the constructor lease and retry config\n const lease = acquireConfig?.lease ?? this.config.lease;\n this.config.lease = lease;\n const retryAttempts = acquireConfig?.retry?.attempts ?? this.config.retry?.attempts;\n const retryDelay = acquireConfig?.retry?.delay ?? this.config.retry?.delay;\n\n let attempts = 0;\n\n let UUID: string;\n if (acquireConfig?.uuid) {\n UUID = acquireConfig.uuid;\n } else {\n try {\n UUID = crypto.randomUUID();\n } catch (error) {\n throw new Error('No UUID provided and crypto module is not available in this environment.');\n }\n }\n \n while (attempts < retryAttempts) {\n const upstashResult = await this.config.redis.set(this.config.id, UUID, {\n nx: true,\n px: lease,\n });\n\n if (upstashResult === \"OK\") {\n this.config.UUID = UUID;\n return true;\n }\n\n attempts += 1;\n\n // Wait for the specified delay before retrying\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n }\n\n // Lock acquisition failed\n this.config.UUID = null;\n return false;\n }\n\n /**\n * Safely releases the lock ensuring the UUID matches.\n * This operation utilizes a Lua script to interact with Redis and\n * guarantees atomicity of the unlock operation.\n * @returns {Promise<boolean>} True if the lock was released, otherwise false.\n */\n public async release(): Promise<boolean> {\n const script = `\n -- Check if the current UUID still holds the lock\n if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n return redis.call(\"del\", KEYS[1])\n else\n return 0\n end\n `;\n\n const numReleased = await this.config.redis.eval(script, [this.config.id], [this.config.UUID]);\n return numReleased === 1;\n }\n\n /**\n * Extends the duration for which the lock is held by a given amount of milliseconds.\n * @param amt - The number of milliseconds by which the lock duration should be extended.\n * @returns {Promise<boolean>} True if the lock duration was extended, otherwise false.\n */\n public async extend(amt: number): Promise<boolean> {\n const script = `\n -- Check if the current UUID still holds the lock\n if redis.call(\"get\", KEYS[1]) ~= ARGV[1] then\n return 0\n end\n\n -- Get the current TTL and extend it by the specified amount\n local ttl = redis.call(\"ttl\", KEYS[1])\n if ttl > 0 then\n return redis.call(\"expire\", KEYS[1], ttl + ARGV[2])\n else\n return 0\n end\n `;\n\n const extendBy = amt / 1000; // convert to seconds\n const extended = await this.config.redis.eval(\n script,\n [this.config.id],\n [this.config.UUID, extendBy],\n );\n\n if (extended === 1) {\n this.config.lease += amt;\n }\n return extended === 1;\n }\n\n get id(): string {\n return this.config.id;\n }\n\n /**\n * Gets the status of the lock, ie: ACQUIRED or FREE.\n * @returns {Promise<LockStatus>} The status of the lock.\n */\n async getStatus(): Promise<LockStatus> {\n if (this.config.UUID === null) {\n return \"FREE\";\n }\n\n const UUID = await this.config.redis.get(this.config.id);\n if (UUID === this.config.UUID) {\n return \"ACQUIRED\";\n }\n\n return \"FREE\";\n }\n}\n"],"mappings":"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","names":[]}