commandkit/semaphore
A semaphore controls the number of operations that can access a resource concurrently. It maintains a set number of permits, allowing only that many operations to proceed simultaneously while others wait in queue.
Basic usage
The easiest way to use a semaphore is with the withPermit function,
which automatically handles getting and releasing permits:
import { withPermit } from 'commandkit/semaphore';
// This ensures only a limited number of operations can run at once
const result = await withPermit('database-connection', async () => {
  // This code runs with a permit from the semaphore
  return await executeDatabaseQuery();
});
Custom configuration
You can create a semaphore with specific limits and timeout settings:
import { createSemaphore } from 'commandkit/semaphore';
const semaphore = createSemaphore({
  permits: 5, // Allow 5 concurrent operations
  timeout: 60000, // 60 second timeout
});
const result = await semaphore.withPermit('api-endpoint', async () => {
  return await apiCall();
});
Advanced usage
Manual permit management
Sometimes you need more control over when permits are acquired and released:
import {
  acquirePermit,
  releasePermit,
  getAvailablePermits,
} from 'commandkit/semaphore';
// Acquire permit manually
const acquired = await acquirePermit('resource', 30000);
if (acquired) {
  try {
    // Perform operation with limited concurrency
    await limitedOperation();
  } finally {
    // Always release the permit, even if an error occurs
    await releasePermit('resource');
  }
}
// Check how many permits are available
const available = await getAvailablePermits('resource');
console.log(`${available} permits available`);
Cancelling operations
You can cancel a semaphore operation using an AbortSignal:
import { withPermit } from 'commandkit/semaphore';
// Create a timeout that cancels after 5 seconds
const signal = AbortSignal.timeout(5000);
try {
  const result = await withPermit(
    'resource',
    async () => {
      return await longRunningOperation();
    },
    30000,
    signal,
  );
} catch (error) {
  if (error.message.includes('aborted')) {
    console.log('Permit acquisition was cancelled');
  }
}
Monitoring semaphore state
You can check how many permits are being used and how many are available:
import { getAvailablePermits, getAcquiredPermits } from 'commandkit/semaphore';
const available = await getAvailablePermits('database');
const acquired = await getAcquiredPermits('database');
console.log(`Database connections: ${acquired} active, ${available} available`);
Using external storage
Semaphores store permit information in memory by default. For multi-server deployments, use external storage like Redis:
import { Semaphore, SemaphoreStorage } from 'commandkit/semaphore';
import { RedisSemaphoreStorage } from '@commandkit/redis';
import { Redis } from 'ioredis';
// Create Redis client
const redis = new Redis();
// Use Redis-based semaphore storage
const semaphore = new Semaphore({
  permits: 10,
  timeout: 30000,
  storage: new RedisSemaphoreStorage(redis),
});
You can also use the convenience function:
import { createSemaphore } from 'commandkit/semaphore';
import { RedisSemaphoreStorage } from '@commandkit/redis';
const semaphore = createSemaphore({
  permits: 5,
  timeout: 60000,
  storage: new RedisSemaphoreStorage(redis),
});
Default settings
- Permits: 1 (sequential access)
- Timeout: 30 seconds (30000ms)
- Storage: In-memory (works for single-server applications)