147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
import type { WorkerQueueClass } from "@trigger.dev/core/v3/workers";
|
|
import { FEATURE_FLAG, FeatureFlagCatalog } from "~/v3/featureFlags";
|
|
|
|
/**
|
|
* Suffix appended to a region's worker queue name to route scheduled-lineage
|
|
* runs onto their own Redis list (e.g. `us-nyc-3` -> `us-nyc-3:scheduled`). A
|
|
* dedicated consumer fleet dequeues the suffixed list so the top-of-hour
|
|
* scheduled-cron herd can't starve standard/agent run startup. The worker queue
|
|
* name is opaque everywhere downstream (it's only ever `:`-joined into a Redis
|
|
* key and persisted on the run), so encoding the class in the suffix needs no
|
|
* Lua, envelope, or resolver changes.
|
|
*/
|
|
export const SCHEDULED_WORKER_QUEUE_SUFFIX = ":scheduled";
|
|
|
|
/**
|
|
* Recover the base region a worker queue belongs to by stripping any split
|
|
* suffix (e.g. `us-nyc-3:scheduled` -> `us-nyc-3`). Region/masterQueue names are
|
|
* either `<name>` or `<projectId>-<name>` and never contain a colon, so the
|
|
* region is everything before the first `:`. Use this wherever a worker queue is
|
|
* read as a region — for display, filtering, or as a region override — so
|
|
* scheduled-split runs group under their real region instead of a phantom one.
|
|
* Idempotent; returns the input unchanged when there's no suffix. A nullish
|
|
* worker queue (e.g. from a synthetic run snapshot) passes straight through.
|
|
*/
|
|
export function baseWorkerQueue(workerQueue: string): string;
|
|
export function baseWorkerQueue(workerQueue: string | null | undefined): string | null | undefined;
|
|
export function baseWorkerQueue(workerQueue: string | null | undefined): string | null | undefined {
|
|
if (workerQueue == null) {
|
|
return workerQueue;
|
|
}
|
|
|
|
const colon = workerQueue.indexOf(":");
|
|
return colon === -1 ? workerQueue : workerQueue.slice(0, colon);
|
|
}
|
|
|
|
/**
|
|
* User-facing region for read surfaces: the explicit geo region if set, else the
|
|
* region derived from the worker queue, else undefined. Use everywhere a run's
|
|
* region is displayed so an empty queue never surfaces as `""` and all surfaces
|
|
* agree. Not for query keys — those want the raw worker queue, not this fallback.
|
|
*/
|
|
export function regionForDisplay(
|
|
region: string | null | undefined,
|
|
workerQueue: string | null | undefined
|
|
): string | undefined {
|
|
return region || (workerQueue ? baseWorkerQueue(workerQueue) : undefined);
|
|
}
|
|
|
|
/** `TriggerSource` value used for runs originating from a schedule. */
|
|
const SCHEDULE_TRIGGER_SOURCE = "schedule";
|
|
|
|
/**
|
|
* Resolve whether the scheduled worker-queue split is enabled for a run, reading
|
|
* only the in-memory org feature-flags JSON (already loaded on the authenticated
|
|
* environment) — never a DB query, so it is safe on the trigger hot path.
|
|
*
|
|
* Precedence: a per-org override wins in BOTH directions; the global default is
|
|
* used only when the org has not set the flag.
|
|
*/
|
|
export function resolveScheduledQueueSplitEnabled({
|
|
orgFeatureFlags,
|
|
globalDefault,
|
|
}: {
|
|
orgFeatureFlags: Record<string, unknown> | null | undefined;
|
|
globalDefault: boolean;
|
|
}): boolean {
|
|
const override = orgFeatureFlags?.[FEATURE_FLAG.workerQueueScheduledSplitEnabled];
|
|
|
|
if (override !== undefined) {
|
|
const parsed =
|
|
FeatureFlagCatalog[FEATURE_FLAG.workerQueueScheduledSplitEnabled].safeParse(override);
|
|
|
|
if (parsed.success) {
|
|
return parsed.data;
|
|
}
|
|
}
|
|
|
|
return globalDefault;
|
|
}
|
|
|
|
/**
|
|
* Pick the worker queue a run should be enqueued onto. Runs in a scheduled
|
|
* lineage (`rootTriggerSource === "schedule"`, which propagates from a scheduled
|
|
* root down to every descendant) route to the suffixed list when the split is
|
|
* enabled; everything else is unchanged. Idempotent — never double-suffixes.
|
|
*/
|
|
export function workerQueueForRun({
|
|
workerQueue,
|
|
rootTriggerSource,
|
|
splitEnabled,
|
|
}: {
|
|
workerQueue: string;
|
|
rootTriggerSource: string | undefined;
|
|
splitEnabled: boolean;
|
|
}): string {
|
|
if (
|
|
!splitEnabled ||
|
|
rootTriggerSource !== SCHEDULE_TRIGGER_SOURCE ||
|
|
workerQueue.endsWith(SCHEDULED_WORKER_QUEUE_SUFFIX)
|
|
) {
|
|
return workerQueue;
|
|
}
|
|
|
|
return `${workerQueue}${SCHEDULED_WORKER_QUEUE_SUFFIX}`;
|
|
}
|
|
|
|
/**
|
|
* Consumer-side counterpart to {@link workerQueueForRun}: given a worker's base
|
|
* (region) queue and the requested queue class, return the worker queue to
|
|
* dequeue from. `"scheduled"` targets the suffixed list; anything else is the
|
|
* base queue. The server always derives this from the authenticated worker's
|
|
* own `masterQueue`, so a token can only ever reach its own region's queues.
|
|
* Idempotent — never double-suffixes.
|
|
*/
|
|
export function workerQueueForClass(
|
|
masterQueue: string,
|
|
queueClass: WorkerQueueClass | undefined
|
|
): string {
|
|
if (queueClass === "scheduled" && !masterQueue.endsWith(SCHEDULED_WORKER_QUEUE_SUFFIX)) {
|
|
return `${masterQueue}${SCHEDULED_WORKER_QUEUE_SUFFIX}`;
|
|
}
|
|
|
|
return masterQueue;
|
|
}
|
|
|
|
export function parseDisabledWorkerQueues(raw: string | undefined): Set<string> {
|
|
return new Set(
|
|
(raw ?? "")
|
|
.split(",")
|
|
.map((entry) => entry.trim())
|
|
.filter(Boolean)
|
|
);
|
|
}
|
|
|
|
export function matchesDisabledWorkerQueue(
|
|
workerQueue: string,
|
|
disabledWorkerQueues: ReadonlySet<string>
|
|
): boolean {
|
|
if (disabledWorkerQueues.size === 0) {
|
|
return false;
|
|
}
|
|
|
|
return (
|
|
disabledWorkerQueues.has(workerQueue) || disabledWorkerQueues.has(baseWorkerQueue(workerQueue))
|
|
);
|
|
}
|