Skip to main content
Version: Next

Useful Utilities

CommandKit provides a collection of essential utilities that help you manage common programming challenges like controlling how many requests can be made, ensuring only one operation accesses a resource at a time, and managing multiple tasks efficiently.

Available Utilities

Rate Limiter

Think of rate limiting like a traffic light for your application. It controls how often something can happen - like how many times a user can click a button or make an API request. This prevents your system from being overwhelmed.

import { ratelimit } from 'commandkit/ratelimit';

const allowed = await ratelimit('user:123');
if (allowed) {
// Process the request
}

Mutex

A mutex is like a "do not disturb" sign for your data. It ensures that only one operation can access a shared resource at a time. Imagine multiple people trying to edit the same document - a mutex makes sure only one person can edit it at once.

import { withMutex } from 'commandkit/mutex';

const result = await withMutex('shared-resource', async () => {
return await updateSharedResource();
});

Semaphore

A semaphore is like a parking lot with a limited number of spaces. It allows a specific number of operations to happen at the same time, but no more. Perfect for controlling access to limited resources like database connections.

import { withPermit } from 'commandkit/semaphore';

const result = await withPermit('database-connection', async () => {
return await executeDatabaseQuery();
});

Async Queue

An async queue is like a line at a bank. Tasks wait in line and get processed one by one (or a few at a time). This helps you control how many things happen simultaneously and ensures everything gets done in an orderly way.

import { createAsyncQueue } from 'commandkit/async-queue';

const queue = createAsyncQueue({ concurrency: 3 });
const result = await queue.add(async () => await processTask());

Common Patterns

Cancelling Operations

All utilities support cancellation, which is like having an emergency stop button. You can cancel operations if they take too long or if you need to stop them for any reason.

// Create a timeout that cancels after 5 seconds
const signal = AbortSignal.timeout(5000);

// Rate limiting with timeout
const allowed = await ratelimit('user:123');

// Mutex with timeout
const result = await withMutex(
'resource',
async () => {
return await criticalOperation();
},
30000,
signal,
);

// Semaphore with timeout
const result = await withPermit(
'resource',
async () => {
return await limitedOperation();
},
30000,
signal,
);

// Queue with timeout
const queue = createAsyncQueue({ signal });
const result = await queue.add(async () => await task());

Using External Storage

All utilities support custom storage implementations for distributed environments:

// Redis storage implementations are available from @commandkit/redis
import {
RedisRateLimitStorage,
RedisMutexStorage,
RedisSemaphoreStorage,
} from '@commandkit/redis';
import { Redis } from 'ioredis';

const redis = new Redis();

// Rate limiting with Redis
const rateLimiter = createRateLimiter({
storage: new RedisRateLimitStorage(redis),
});

// Mutex with Redis
const mutex = createMutex({
storage: new RedisMutexStorage(redis),
});

// Semaphore with Redis
const semaphore = createSemaphore({
storage: new RedisSemaphoreStorage(redis),
});

Handling Errors Gracefully

When things go wrong, it's important to handle errors properly so your application doesn't crash and can recover gracefully.

try {
const result = await withMutex('resource', async () => {
return await criticalOperation();
});
} catch (error) {
if (error.message.includes('aborted')) {
console.log('Operation was cancelled');
} else if (error.message.includes('timeout')) {
console.log('Operation took too long');
}
}

When to Use Each Utility

UtilityWhat it doesWhen to use it
Rate LimiterControls how often something can happenAPI endpoints, user actions, preventing spam
MutexEnsures only one thing can access a resourceDatabase transactions, file operations, config updates
SemaphoreLimits how many things can happen at onceDatabase connections, API concurrency, resource pools
Async QueueProcesses tasks in an orderly wayBatch operations, file processing, email sending

Best Practices

  1. Choose the right tool: Think about what you're trying to achieve:

    • Need to limit how often something happens? Use a rate limiter
    • Need to ensure only one thing accesses a resource? Use a mutex
    • Need to limit how many things happen at once? Use a semaphore
    • Need to process tasks in order? Use a queue
  2. Set reasonable timeouts: Always set timeouts to prevent your application from hanging forever if something goes wrong.

  3. Use descriptive names: Give your resources meaningful names so you can easily debug issues later.

  4. Handle errors: Always handle errors properly so your application can recover from problems.

  5. Consider your setup: If you're running multiple instances of your app, use external storage like Redis.

  6. Monitor usage: Keep an eye on how these utilities are being used to optimize performance.

Next Steps

Ready to learn more? Check out the detailed guides for each utility: