Files
triggerdotdev--trigger.dev/apps/webapp/app/runEngine/concerns/batchLimits.server.ts
Chris Arderne c7861be520 chore: activate no-unused-vars and import linters (#4096)
Once this is merged, oxlint is at a pretty sensible baseline.

**Enable `no-unused-vars`, `typescript/consistent-type-imports`, and
`import/no-duplicates` lint rules**

Turns on three previously-disabled oxlint rules across the monorepo and
fixes all violations:

- **`no-unused-vars`** – enabled as an error with standard ignore
patterns: unused function arguments are ignored by default (`args:
"none"`), variables/caught errors/destructured array elements prefixed
with `_` are allowed, and rest siblings are permitted.
- **`typescript/consistent-type-imports`** – enforced as an error; all
type-only imports now use the `import type` syntax.
- **`import/no-duplicates`** – enforced as an error; duplicate import
statements from the same module have been merged.

The remaining commits clean up the violations found across the codebase:
removing unused variables/imports/type aliases, adding `_` prefixes to
intentionally unused bindings, fixing duplicate imports, and converting
value imports to `import type` where appropriate.
2026-07-02 11:37:05 +01:00

124 lines
3.9 KiB
TypeScript

import { z } from "zod";
import { env } from "~/env.server";
import {
createLimiterFromConfig,
RateLimiterConfig,
} from "~/services/authorizationRateLimitMiddleware.server";
import type { Duration } from "~/services/rateLimiter.server";
import { createRedisRateLimitClient, RateLimiter } from "~/services/rateLimiter.server";
import { singleton } from "~/utils/singleton";
const BatchLimitsConfig = z.object({
processingConcurrency: z.number().int().default(env.BATCH_CONCURRENCY_LIMIT_DEFAULT),
});
/**
* Batch limits configuration for a plan type
*/
export type BatchLimitsConfig = z.infer<typeof BatchLimitsConfig>;
const batchLimitsRedisClient = singleton("batchLimitsRedisClient", createBatchLimitsRedisClient);
function createBatchLimitsRedisClient() {
const redisClient = createRedisRateLimitClient({
port: env.RATE_LIMIT_REDIS_PORT,
host: env.RATE_LIMIT_REDIS_HOST,
username: env.RATE_LIMIT_REDIS_USERNAME,
password: env.RATE_LIMIT_REDIS_PASSWORD,
tlsDisabled: env.RATE_LIMIT_REDIS_TLS_DISABLED === "true",
clusterMode: env.RATE_LIMIT_REDIS_CLUSTER_MODE_ENABLED === "1",
});
return redisClient;
}
// Just the org fields this module reads. Compatible with both the full
// Prisma `Organization` payload and the slim `AuthenticatedEnvironment`
// `["organization"]` shape (when passed `batchRateLimitConfig` /
// `batchQueueConcurrencyConfig` as `unknown`).
type OrganizationForBatchLimits = {
batchRateLimitConfig?: unknown;
batchQueueConcurrencyConfig?: unknown;
};
function createOrganizationRateLimiter(organization: OrganizationForBatchLimits): RateLimiter {
const limiterConfig = resolveBatchRateLimitConfig(organization.batchRateLimitConfig);
const limiter = createLimiterFromConfig(limiterConfig);
return new RateLimiter({
redisClient: batchLimitsRedisClient,
keyPrefix: "ratelimit:batch",
limiter,
logSuccess: false,
logFailure: true,
});
}
function resolveBatchRateLimitConfig(batchRateLimitConfig?: unknown): RateLimiterConfig {
const defaultRateLimiterConfig: RateLimiterConfig = {
type: "tokenBucket",
refillRate: env.BATCH_RATE_LIMIT_REFILL_RATE,
interval: env.BATCH_RATE_LIMIT_REFILL_INTERVAL as Duration,
maxTokens: env.BATCH_RATE_LIMIT_MAX,
};
if (!batchRateLimitConfig) {
return defaultRateLimiterConfig;
}
const parsedBatchRateLimitConfig = RateLimiterConfig.safeParse(batchRateLimitConfig);
if (!parsedBatchRateLimitConfig.success) {
return defaultRateLimiterConfig;
}
return parsedBatchRateLimitConfig.data;
}
/**
* Get the rate limiter and limits for an organization.
* Internally looks up the plan type, but doesn't expose it to callers.
*/
export async function getBatchLimits(
organization: OrganizationForBatchLimits
): Promise<{ rateLimiter: RateLimiter; config: BatchLimitsConfig }> {
const rateLimiter = createOrganizationRateLimiter(organization);
const config = resolveBatchLimitsConfig(organization.batchQueueConcurrencyConfig);
return { rateLimiter, config };
}
function resolveBatchLimitsConfig(batchLimitsConfig?: unknown): BatchLimitsConfig {
const defaultLimitsConfig: BatchLimitsConfig = {
processingConcurrency: env.BATCH_CONCURRENCY_LIMIT_DEFAULT,
};
if (!batchLimitsConfig) {
return defaultLimitsConfig;
}
const parsedBatchLimitsConfig = BatchLimitsConfig.safeParse(batchLimitsConfig);
if (!parsedBatchLimitsConfig.success) {
return defaultLimitsConfig;
}
return parsedBatchLimitsConfig.data;
}
/**
* Error thrown when batch rate limit is exceeded.
* Contains information for constructing a proper 429 response.
*/
export class BatchRateLimitExceededError extends Error {
constructor(
public readonly limit: number,
public readonly remaining: number,
public readonly resetAt: Date,
public readonly itemCount: number
) {
super(`Batch rate limit exceeded. Limit resets at ${resetAt.toISOString()}`);
this.name = "BatchRateLimitExceededError";
}
}