Per-tenant budgets
Create one valve per customer credential on demand, and share a single store between them.
In a multi-tenant integration, each customer connects their own account, and the provider meters each account separately. Give every credential its own valve id, created the first time it is needed.
import { createValve, type Valve } from 'valvejs';
import { redisStore } from 'valvejs/redis';
const store = redisStore({ client: redis }); // One store for every tenant.
const valves = new Map<string, Valve>();
export function shopifyValve(shop: string): Valve {
let valve = valves.get(shop);
if (!valve) {
valve = createValve({
id: `shopify:${shop}`,
rate: { limit: 2, interval: '1s', burst: 40 },
store,
maxWait: '1m',
});
valves.set(shop, valve);
}
return valve;
}
export async function closeAll() {
await Promise.all([...valves.values()].map((valve) => valve.close()));
valves.clear();
}Use it wherever you call the provider:
const res = await shopifyValve(shop).run(
({ signal }) =>
fetch(`https://${shop}/admin/api/2025-01/orders.json`, {
headers: { 'X-Shopify-Access-Token': tokens.get(shop)! },
signal,
}),
{ observe: (r) => r },
);The rule here models a bucket of 40 requests that refills at 2 per second. Check the provider's documentation for your plan.
Why one store
A store is cheap, but each Redis store with a subscriber adds pub/sub
subscriptions. Reuse one store for every valve in the process. Valves still
keep separate budgets, because their ids differ.
For a memory store, sharing the instance is required: two valves with the same id only share a budget when they use the same store object.
Drop tenants you no longer need
Each valve holds a small local queue and its listeners. If tenants come and go, close and remove a valve when its tenant disconnects:
export async function removeTenant(shop: string) {
const valve = valves.get(shop);
valves.delete(shop);
await valve?.close();
}The budget state in Redis expires on its own once it is no longer used.
Ids must be safe
Ids are 1 to 200 characters and must not contain { or }. If a tenant key
comes from user input, validate or hash it before building the id.