SDKs & CLI / JavaScript

JavaScript/TypeScript SDK

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.

Installation

Install the package using your preferred package manager:

Install SDK
npm install @edgelock/client
# or
yarn add @edgelock/client
# or
bun add @edgelock/client

You can find the package on npm: @edgelock/client

Configuration

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.

Configure Client
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 once
EdgeLock.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.

Usage

Acquiring a Lock

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.

Acquire Lock Example
import { EdgeLock } from "@edgelock/client";
// Assuming EdgeLock is configured as shown previously
async function processPayment(orderId: string) {
const lockKey = `order-payment-${orderId}`;
let lock = null; // Initialize lock variable
try {
// Attempt to acquire a lock with a 60-second TTL
lock = await EdgeLock.acquire(lockKey, { ttl: 60 });
// If the lock couldn't be acquired (returns null), exit or handle
if (!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 processing
console.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 acquired
if (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 time
console.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:

  • A 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).

Releasing a Lock

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.

Release Lock Example
let lock = await EdgeLock.acquire("my-critical-task", { ttl: 30 });
if (!lock) {
// Handle inability to acquire lock
return;
}
try {
// --- Critical section ---
await doWork();
// --- End critical section ---
} finally {
// Ensure release happens
await 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.

Extending a Lock

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

Extend Lock Example
let lock = await EdgeLock.acquire("long-running-job", { ttl: 60 }); // Initial 60s TTL
if (!lock) return;
try {
await doPartOfWork();
// Need more time, extend lock by another 120 seconds
console.log("Extending lock...");
await lock.extend(120); // Lock now expires 120s from this call
console.log(`Lock extended. New expiry (ms): ${lock.expiresAt}`); // Note: expiresAt property is updated
await 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.

Checking Lock Status

You can check the current status of any lock key without attempting to acquire it using the exported status function.

Check Lock Status Example
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 milliseconds
const 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.

Error Handling

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.

Error Handling Example
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 errors
console.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.status
if (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.

Full API Reference

For a complete reference of all methods, options, and types, please refer to the API Reference section on npm.