Skip to main content

Semaphore plugin

The Semaphore prefix plugin intercepts calls to a semaphore adapter and transparently prefixes all semaphore keys with a configurable string. This enables logical key namespacing without modifying the adapter implementation.

Use cases

  • Multi-tenant semaphores — Prefix semaphore keys with a tenant identifier to isolate concurrency limits between tenants
  • Resource scoping — Organize semaphores by resource type to avoid key collisions
  • Environment isolation — Separate development, staging, and production semaphore state
  • Pool differentiation — Prefix keys with a pool name to manage independent semaphore pools

How it works

The withSemaphorePrefix function returns a PluginFn that calls enhance on each adapter method that accepts a semaphore key. When an enhanced method is invoked, the plugin intercepts the call, prepends the configured prefix to the key argument, and forwards the modified arguments to the original method.

The plugin prefixes keys for the following methods:

MethodKey argumentPattern
acquirekey within settings objectprefix + key
forceReleaseAllSecond argument (key)prefix + key
getStateSecond argument (key)prefix + key
refreshSecond argument (key)prefix + key
releaseSecond argument (key)prefix + key

Special handling for acquire

The acquire method accepts a single settings object rather than positional arguments. The plugin destructures the settings object, prefixes the key field, and reconstructs the object with all other fields preserved:

// Before:
adapter.acquire({ context, key: "my-key", slotId: "s1", limit: 5, ttl });

// After plugin transforms:
adapter.acquire({ context, key: "prefix:my-key", slotId: "s1", limit: 5, ttl });

Usage

import { withPlugin } from "@daiso-tech/core/middleware";
import { MemorySemaphoreAdapter } from "@daiso-tech/core/semaphore/memory-semaphore-adapter";
import { withSemaphorePrefix } from "@daiso-tech/core/semaphore/plugins";

const adapter = new MemorySemaphoreAdapter();

// Apply the prefix plugin
const prefixedAdapter = withPlugin(adapter, withSemaphorePrefix("pool-1:"));

// The key "db-connections" is prefixed to "pool-1:db-connections"
const acquired = await prefixedAdapter.acquire({
context,
key: "db-connections",
slotId: "slot-1",
limit: 10,
ttl: TimeSpan.fromMinutes(5),
});

Using with SemaphoreFactory

The plugin can be applied directly to the adapter passed to the SemaphoreFactory constructor:

import { SemaphoreFactory } from "@daiso-tech/core/semaphore";
import { MemorySemaphoreAdapter } from "@daiso-tech/core/semaphore/memory-semaphore-adapter";
import { withPlugin } from "@daiso-tech/core/middleware";
import { withSemaphorePrefix } from "@daiso-tech/core/semaphore/plugins";

const adapter = new MemorySemaphoreAdapter();
const prefixedAdapter = withPlugin(adapter, withSemaphorePrefix("worker:"));

const factory = new SemaphoreFactory({
adapter: prefixedAdapter,
});

Before/after behavior

Before — Semaphore keys are used as-is:

adapter.acquire({ context, key: "connections", ... })
→ acquires slot on "connections"

After — Semaphore keys are automatically prefixed:

adapter.acquire({ context, key: "connections", ... })
→ acquires slot on "pool-1:connections"
info

For more information about the withPlugin function and applying plugins to adapters, see the Middleware plugin documentation.