Andi Skill Builder API Documentation

Class: LockPowers

Powers to provide synchronization primitives for Andi skills across concurrent async processes, replicas, and beyond. These primitives are distributed and can be shared across skill versions or across all skills in an organization.

Mutexes help exclusive access to a shared resource.

Semaphores help with allowing shared access to a shared resource, up to a defined limit.

Methods

createMutex()

static createMutex(key, options?): Promise<DistributedLock>;

Parameters

Parameter Type Description
key string Unique key for lock shared across options.scope
options? LockOptions -

Returns

Promise<DistributedLock>

Throws

When key is nil

Throws

When key is empty or longer than 256 characters

Example

Safely using a mutex by always releasing

// Ensure you import LockPowers
import { LockPowers } from "@andi/powers";

const mutex = await LockPowers.createMutex("my-lock");
try {
    // may throw `LockAcquireTimeoutError`. To avoid errors, can also use `.tryAcquire()`
    await mutex.acquire();
    // critical code
} finally {
    await mutex.release();
}

createSemaphore()

static createSemaphore(
   key, 
   maxCount, 
options?): Promise<DistributedSemaphore>;

No guaranteed fairness, but measures to minimize fairness issues in place.

Parameters

Parameter Type Description
key string Unique key for lock shared across options.scope.
maxCount number Number of concurrent access
options? LockOptions -

Returns

Promise<DistributedSemaphore>

Throws

When key is nil

Throws

When key is empty or longer than 256 characters

Throws

When maxCount is not in range [1, 1024]

Throws

Various errors when options are invalid. See LockOptions for more information

Example

Safely using a semaphore by always releasing

// Ensure you import LockPowers
import { LockPowers } from "@andi/powers";

// acquire one of the 5 leases to my-shared-lock
const semaphore = await LockPowers.createSemaphore("my-shared-lock", 5);
try {
    // may throw `LockAcquireTimeoutError`. To avoid errors, can also use `.tryAcquire()`
    await semaphore.acquire();
    // critical code with at most 5 concurrent processes
} finally {
    await semaphore.release();
}

runExclusive()

static runExclusive<T>(
   key, 
   callback, 
options?): Promise<T>;

Fits most use cases. Acquires the lock, runs the provided callback, and then releases the lock.

For workflows that span multiple skills, be sure to pass LockScope.Organization as the scope argument for LockOptions.

Type Parameters

Type Parameter
T

Parameters

Parameter Type Description
key string Unique key for lock shared across options.scope
callback () => T | Promise<T> A function containing the callback to run exclusively
options? LockOptions -

Returns

Promise<T>

A promise that resolves with the result of the callback, or rejects if an error occurs

Example

In this example, the skill audits a business workflow managed by the skill. The workflow has been crafted where all updates and deletions to audit data are done through the "audit-update" lock.

Reads of audit data do not require a lock.

Additions of new audit do not require a lock if they are keyed uniquely with PowerHelpers.uuid() or another unique identifier.

In failure scenarios, the "audit-update" lock is naturally released after the expiration of the lockTimeout. Advanced handling of errors and results can help further refine a critical business workflow.

// Ensure you import LockPowers
import { LockPowers, DataPowers } from "@andi/powers";

const exclusiveResult = await LockPowers.runExclusive("audit-update", async () => {
    try {
        const audits = await DataPowers.getOrCreateCollection<SkillAudit>(CollectionType.Dictionary, "skill-audits", { expiry: { expireMode: CollectionExpirationMode.Never } });
        const auditToUpdate = await audits.get(auditId);
        if (!auditToUpdate) return false;
        auditToUpdate.status = false;
        return true;
    }
    catch (err) {
        return false;
    }
});