Integrate EdgeLock into your JavaScript/TypeScript applications running on Node.js, Bun, Deno, Cloudflare Workers, Vercel, Netlify, Fly.io, or browsers using the @edgelock/client
package. This SDK provides a simple asynchronous API for acquiring, releasing, and managing locks.
Install the package using your preferred package manager:
npm install @edgelock/client# oryarn add @edgelock/client# orbun add @edgelock/client
You can find the package on npm: @edgelock/client
Import the EdgeLock
client and configure it with your API key using the static configure
method. It's recommended to load your API key from environment variables.
import { EdgeLock } from "@edgelock/client";// Load API key from environment variable (recommended)const apiKey = process.env.EDGELOCK_KEY;if (!apiKey) {throw new Error("EDGELOCK_KEY environment variable not set.");}// Configure the client onceEdgeLock.configure({ apiKey });// You can also optionally provide a custom base URL// EdgeLock.configure({// apiKey,// baseUrl: 'https://your-custom-proxy.com/edgelock'// });
The configure
method accepts an object with:
apiKey
: (Required) Your EdgeLock API key (string).baseUrl
: (Optional) Custom API URL (string). Defaults to https://api.edgelock.dev
.Use the static EdgeLock.acquire
method to attempt to obtain a lock. You must provide a unique lock key (string). You can also specify options like Time-To-Live (ttl
, in seconds), a timeout
(in milliseconds) to wait for the lock, or an AbortSignal
to cancel the request.
import { EdgeLock } from "@edgelock/client";// Assuming EdgeLock is configured as shown previouslyasync function processPayment(orderId: string) {const lockKey = `order-payment-${orderId}`;let lock = null; // Initialize lock variabletry {// Attempt to acquire a lock with a 60-second TTLlock = await EdgeLock.acquire(lockKey, { ttl: 60 });// If the lock couldn't be acquired (returns null), exit or handleif (!lock) {console.warn(`Could not acquire lock for key: ${lockKey}. Process might be running elsewhere.`);// Optional: throw new Error("Could not obtain lock");return; // Exit the function or implement retry logic}console.log(`Lock acquired (ID: ${lock.lockId}). Processing payment for order: ${orderId}`);// --- Critical section starts ---// Ensure this operation completes within the lock's TTL (60s)await performSensitivePaymentProcessing(orderId);console.log(`Payment processing completed for order: ${orderId}`);// --- Critical section ends ---} catch (error) {// Handle potential errors during acquisition or processingconsole.error(`Error during payment processing for order ${orderId}:`, error);// Consider more specific error handling (see Error Handling section)} finally {// Always attempt to release the lock if it was acquiredif (lock) {try {await lock.release();console.log(`Lock released for key: ${lockKey}`);} catch (releaseError) {console.error(`Failed to release lock for key: ${lockKey}`, releaseError);// Log this error, as it might indicate an issue, but the lock will expire anyway}}}}async function performSensitivePaymentProcessing(orderId: string) {// Simulate work that should only run one instance at a timeconsole.log(`Simulating payment processing for order ${orderId}...`);await new Promise(resolve => setTimeout(resolve, 5000)); // Simulate 5 seconds of work}// Example usage:processPayment("order-12345");
The acquire
method returns:
Lock
object if the lock was successfully acquired.null
if the lock could not be acquired (e.g., it was already held and the optional timeout
was exceeded or not provided).The Lock
object has the following properties:
key
: The lock key (string).lockId
: A unique identifier for this specific lock instance (string).expiresAt
: Unix timestamp (in milliseconds) when the lock will automatically expire if not released or extended (number).It's crucial to release the lock once the critical section is complete using the release()
method on the acquired Lock
object. This should typically be done within a finally
block to ensure release even if errors occur during the critical section.
let lock = await EdgeLock.acquire("my-critical-task", { ttl: 30 });if (!lock) {// Handle inability to acquire lockreturn;}try {// --- Critical section ---await doWork();// --- End critical section ---} finally {// Ensure release happensawait lock.release();}
The release()
method returns a Promise resolving to true
if the lock was successfully released by the caller, or false
otherwise (e.g., if the lock had already expired or was released by another process).
If the lock holder crashes or fails to release the lock, the TTL ensures it automatically becomes available again after the specified duration.
If a critical section might take longer than the initial TTL, you can extend the lock's duration using the extend()
method on the Lock
object. This resets the expiration time based on the new TTL provided (in seconds).
let lock = await EdgeLock.acquire("long-running-job", { ttl: 60 }); // Initial 60s TTLif (!lock) return;try {await doPartOfWork();// Need more time, extend lock by another 120 secondsconsole.log("Extending lock...");await lock.extend(120); // Lock now expires 120s from this callconsole.log(`Lock extended. New expiry (ms): ${lock.expiresAt}`); // Note: expiresAt property is updatedawait doRemainingWork();} finally {await lock.release();}
The extend()
method takes the new TTL (in seconds) as an argument and returns a Promise that resolves when the extension is confirmed or rejects if the lock no longer exists or couldn't be extended. It also updates the lock.expiresAt
property on the Lock object.
You can check the current status of any lock key without attempting to acquire it using the exported status
function.
import { status } from "@edgelock/client";async function checkPaymentLock(orderId: string) {const lockKey = \`order-payment-\${orderId}\`;const result = await status(lockKey);if (result.locked) {console.log(\`Lock \'\${lockKey}\\' is currently HELD.\`,\`Lock ID: \${result.lockId},\`,\`Expires At (ms): \${result.expiresAt}\`);// expiresAt is a Unix timestamp in millisecondsconst expiresInSeconds = result.expiresAt ? Math.round((result.expiresAt - Date.now()) / 1000) : \'N/A\';console.log(\`Expires in approximately: \${expiresInSeconds} seconds\`);} else {console.log(\`Lock \'\${lockKey}\\' is currently AVAILABLE.\`);}}checkPaymentLock("order-12345");
The status
function returns a Promise resolving to an object with:
locked
: Boolean indicating if the key is currently locked.lockId
: The ID of the current lock instance if locked, otherwise null
.expiresAt
: Unix timestamp (ms) when the current lock expires if locked, otherwise null
.Operations can throw an EdgeLockError
for specific API-related issues (like invalid keys, network problems, etc.). It's good practice to wrap API calls in try/catch blocks and check the error type.
import { EdgeLock, EdgeLockError } from "@edgelock/client";async function safeAcquire(key: string) {try {const lock = await EdgeLock.acquire(key, { ttl: 10 });if (lock) {console.log("Lock acquired:", lock.lockId);// Remember to release it later// await lock.release();return lock;} else {console.log("Could not acquire lock (already held).");return null;}} catch (error) {if (error instanceof EdgeLockError) {// Handle specific EdgeLock errorsconsole.error(\`EdgeLock API Error: \${error.message}\`,\`Code: \${error.code}\`, // e.g., \'INVALID_TTL\', \'UNAUTHORIZED\'\`Status: \${error.status}\` // HTTP status code, e.g., 400, 401, 500);// Implement specific logic based on error.code or error.statusif (error.code === \'UNAUTHORIZED\') {console.error("Check your EDGELOCK_KEY environment variable!");}} else {// Handle other unexpected errors (network issues, etc.)console.error("An unexpected error occurred:", error);}return null; // Indicate failure}}
The EdgeLockError
object has additional properties:
code
: A string error code (e.g., 'LOCKED'
, 'INVALID_TTL'
, 'UNAUTHORIZED'
, 'INTERNAL_ERROR'
, etc.).status
: The HTTP status code associated with the error (number).Refer to the specific error code and status for detailed debugging. Common codes include: LOCKED
, INVALID_TTL
, INVALID_BODY
, NOT_FOUND
, UNKNOWN_KEY
, UNAUTHORIZED
, INTERNAL_ERROR
.
For a complete reference of all methods, options, and types, please refer to the API Reference section on npm.