From b9be9ffc76390cbe26eb0a1fbf42921dbb00c21a Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 12 Jul 2026 22:31:28 +0100 Subject: [PATCH 01/12] refactor(webapp): reject retired v3 (engine V1) runs and read queues from the v2 engine v3 (engine V1) execution is retired. Reject V1 triggers unconditionally with the upgrade message, finalize a cancelled historical V1 run directly in the DB (no coordinator/devPubSub, never throws so the cancel route cannot 500), reject V1 reschedules, and default a fresh dev environment to V2. Move all live queue reads (concurrency limits, env queue length/size guard, project metrics, admin queue debug) off the MarQS singleton onto the v2 run engine so MarQS can be removed. --- apps/webapp/app/components/admin/debugRun.tsx | 262 +----------------- .../v3/EnvironmentQueuePresenter.server.ts | 15 +- .../registerProjectMetrics.server.ts | 22 +- .../resources.taskruns.$runParam.debug.ts | 29 +- .../app/services/deleteProject.server.ts | 6 - apps/webapp/app/v3/engineVersion.server.ts | 5 +- apps/webapp/app/v3/queueSizeLimits.server.ts | 9 +- apps/webapp/app/v3/runQueue.server.ts | 24 +- .../app/v3/services/batchTriggerV3.server.ts | 3 +- .../app/v3/services/cancelTaskRun.server.ts | 32 ++- .../v3/services/rescheduleTaskRun.server.ts | 17 +- .../app/v3/services/triggerTask.server.ts | 26 +- 12 files changed, 73 insertions(+), 377 deletions(-) diff --git a/apps/webapp/app/components/admin/debugRun.tsx b/apps/webapp/app/components/admin/debugRun.tsx index 4c5d40d8bf8..7a93402b064 100644 --- a/apps/webapp/app/components/admin/debugRun.tsx +++ b/apps/webapp/app/components/admin/debugRun.tsx @@ -10,7 +10,6 @@ import { useEffect } from "react"; import { Spinner } from "../primitives/Spinner"; import * as Property from "~/components/primitives/PropertyTable"; import { ClipboardField } from "../primitives/ClipboardField"; -import { MarQSShortKeyProducer } from "~/v3/marqs/marqsKeyProducer"; export function AdminDebugRun({ friendlyId }: { friendlyId: string }) { const hasAdminAccess = useHasAdminAccess(); @@ -69,7 +68,7 @@ function DebugRunContent({ friendlyId }: { friendlyId: string }) { function DebugRunData(props: UseDataFunctionReturn) { if (props.engine === "V1") { - return ; + return ; } return ; @@ -77,18 +76,9 @@ function DebugRunData(props: UseDataFunctionReturn) { function DebugRunDataEngineV1({ run, - environment, - queueConcurrencyLimit, - queueCurrentConcurrency, - envConcurrencyLimit, - envCurrentConcurrency, - queueReserveConcurrency, - envReserveConcurrency, -}: UseDataFunctionReturn) { - const keys = new MarQSShortKeyProducer("marqs:"); - - const withPrefix = (key: string) => `marqs:${key}`; - +}: { + run: UseDataFunctionReturn["run"]; +}) { return ( @@ -98,247 +88,9 @@ function DebugRunDataEngineV1({ - Message key - - - - - - GET message - - - - - - Queue key - - - - - - Get queue set - - - - - - Queue current concurrency key - - - - - - - Get queue current concurrency - - - - - - Queue current concurrency - - {queueCurrentConcurrency ?? "0"} - - - - Queue reserve concurrency key - - - - - - - Get queue reserve concurrency - - - - - - Queue reserve concurrency - - {queueReserveConcurrency ?? "0"} - - - - Queue concurrency limit key - - - - - - GET queue concurrency limit - - - - - - Queue concurrency limit - - {queueConcurrencyLimit ?? "Not set"} - - - - Env current concurrency key - - - - - - Get env current concurrency - - - - - - Env current concurrency - - {envCurrentConcurrency ?? "0"} - - - - Env reserve concurrency key - - - - - - Get env reserve concurrency - - - - - - Env reserve concurrency - - {envReserveConcurrency ?? "0"} - - - - Env concurrency limit key - - - - - - GET env concurrency limit - - - - - - Env concurrency limit - - {envConcurrencyLimit ?? "Not set"} - - - - Shared queue key - - - - - - Get shared queue set - - + Engine + + Engine V1 (v3) is retired. Queue debug data is no longer available for V1 runs. diff --git a/apps/webapp/app/presenters/v3/EnvironmentQueuePresenter.server.ts b/apps/webapp/app/presenters/v3/EnvironmentQueuePresenter.server.ts index 5bcdee6b0a9..bdb593f1ae7 100644 --- a/apps/webapp/app/presenters/v3/EnvironmentQueuePresenter.server.ts +++ b/apps/webapp/app/presenters/v3/EnvironmentQueuePresenter.server.ts @@ -1,5 +1,4 @@ import { type AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { marqs } from "~/v3/marqs/index.server"; import { engine } from "~/v3/runEngine.server"; import { getQueueSizeLimit } from "~/v3/utils/queueLimits.server"; import { BasePresenter } from "./basePresenter.server"; @@ -15,16 +14,10 @@ export type Environment = { export class EnvironmentQueuePresenter extends BasePresenter { async call(environment: AuthenticatedEnvironment): Promise { - const [engineV1Executing, engineV2Executing, engineV1Queued, engineV2Queued] = - await Promise.all([ - marqs.currentConcurrencyOfEnvironment(environment), - engine.concurrencyOfEnvQueue(environment), - marqs.lengthOfEnvQueue(environment), - engine.lengthOfEnvQueue(environment), - ]); - - const running = (engineV1Executing ?? 0) + (engineV2Executing ?? 0); - const queued = (engineV1Queued ?? 0) + (engineV2Queued ?? 0); + const [running, queued] = await Promise.all([ + engine.concurrencyOfEnvQueue(environment), + engine.lengthOfEnvQueue(environment), + ]); const organization = await this._replica.organization.findFirst({ where: { diff --git a/apps/webapp/app/routes/projects.v3.$projectRef.metrics/registerProjectMetrics.server.ts b/apps/webapp/app/routes/projects.v3.$projectRef.metrics/registerProjectMetrics.server.ts index ad5d7cfc917..5aae795baa8 100644 --- a/apps/webapp/app/routes/projects.v3.$projectRef.metrics/registerProjectMetrics.server.ts +++ b/apps/webapp/app/routes/projects.v3.$projectRef.metrics/registerProjectMetrics.server.ts @@ -3,7 +3,7 @@ import type { Registry } from "prom-client"; import { Gauge } from "prom-client"; import { prisma } from "~/db.server"; import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { marqs } from "~/v3/marqs/index.server"; +import { engine } from "~/v3/runEngine.server"; export async function registerProjectMetrics( registry: Registry, @@ -44,7 +44,7 @@ async function registerEnvironmentMetrics( help: `The number of tasks currently being executed in the dev environment queue`, registers: [registry], async collect() { - const length = await marqs?.currentConcurrencyOfEnvironment(env); + const length = await engine.runQueue.currentConcurrencyOfEnvironment(env); if (length) { this.set(length); @@ -57,7 +57,7 @@ async function registerEnvironmentMetrics( help: `The concurrency limit for the dev environment queue`, registers: [registry], async collect() { - const length = await marqs?.getEnvConcurrencyLimit(env); + const length = await engine.runQueue.getEnvConcurrencyLimit(env); if (length) { this.set(length); @@ -70,8 +70,8 @@ async function registerEnvironmentMetrics( help: `The capacity of the dev environment queue`, registers: [registry], async collect() { - const concurrencyLimit = await marqs?.getEnvConcurrencyLimit(env); - const currentConcurrency = await marqs?.currentConcurrencyOfEnvironment(env); + const concurrencyLimit = await engine.runQueue.getEnvConcurrencyLimit(env); + const currentConcurrency = await engine.runQueue.currentConcurrencyOfEnvironment(env); if (typeof concurrencyLimit === "number" && typeof currentConcurrency === "number") { this.set(concurrencyLimit - currentConcurrency); @@ -94,7 +94,7 @@ function registerTaskQueueMetrics( help: `The number of tasks in the ${queue.name} queue`, registers: [registry], async collect() { - const length = await marqs?.lengthOfQueue(env, queue.name); + const length = await engine.runQueue.lengthOfQueue(env, queue.name); if (length) { this.set(length); @@ -107,7 +107,7 @@ function registerTaskQueueMetrics( help: `The number of tasks currently being executed in the ${queue.name} queue`, registers: [registry], async collect() { - const length = await marqs?.currentConcurrencyOfQueue(env, queue.name); + const length = await engine.runQueue.currentConcurrencyOfQueue(env, queue.name); if (length) { this.set(length); @@ -120,7 +120,7 @@ function registerTaskQueueMetrics( help: `The concurrency limit for the ${queue.name} queue`, registers: [registry], async collect() { - const length = await marqs?.getQueueConcurrencyLimit(env, queue.name); + const length = await engine.runQueue.getQueueConcurrencyLimit(env, queue.name); if (length) { this.set(length); @@ -133,8 +133,8 @@ function registerTaskQueueMetrics( help: `The capacity of the ${queue.name} queue`, registers: [registry], async collect() { - const concurrencyLimit = await marqs?.getQueueConcurrencyLimit(env, queue.name); - const currentConcurrency = await marqs?.currentConcurrencyOfQueue(env, queue.name); + const concurrencyLimit = await engine.runQueue.getQueueConcurrencyLimit(env, queue.name); + const currentConcurrency = await engine.runQueue.currentConcurrencyOfQueue(env, queue.name); if (typeof concurrencyLimit === "number" && typeof currentConcurrency === "number") { this.set(concurrencyLimit - currentConcurrency); @@ -147,7 +147,7 @@ function registerTaskQueueMetrics( help: `The age of the oldest message in the ${queue.name} queue`, registers: [registry], async collect() { - const oldestMessage = await marqs?.oldestMessageInQueue(env, queue.name); + const oldestMessage = await engine.runQueue.oldestMessageInQueue(env, queue.name); if (oldestMessage) { this.set(oldestMessage); diff --git a/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts b/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts index 99188cb711b..201942c7c85 100644 --- a/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts +++ b/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts @@ -3,7 +3,6 @@ import { typedjson } from "remix-typedjson"; import { z } from "zod"; import { prisma } from "~/db.server"; import { requireUserId } from "~/services/session.server"; -import { marqs } from "~/v3/marqs/index.server"; import { engine } from "~/v3/runEngine.server"; import { runStore } from "~/v3/runStore.server"; import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server"; @@ -58,34 +57,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) { } if (run.engine === "V1") { - const queueConcurrencyLimit = await marqs.getQueueConcurrencyLimit(environment, run.queue); - const envConcurrencyLimit = await marqs.getEnvConcurrencyLimit(environment); - const queueCurrentConcurrency = await marqs.currentConcurrencyOfQueue( - environment, - run.queue, - run.concurrencyKey ?? undefined - ); - const envCurrentConcurrency = await marqs.currentConcurrencyOfEnvironment(environment); - - const queueReserveConcurrency = await marqs.reserveConcurrencyOfQueue( - environment, - run.queue, - run.concurrencyKey ?? undefined - ); - - const envReserveConcurrency = await marqs.reserveConcurrencyOfEnvironment(environment); - + // v3 (engine V1) is retired: there are no marqs queues left to introspect for a + // historical V1 run, so return a minimal payload instead of querying marqs. return typedjson({ - engine: "V1", + engine: "V1" as const, run, environment, - queueConcurrencyLimit, - envConcurrencyLimit, - queueCurrentConcurrency, - envCurrentConcurrency, - queueReserveConcurrency, - envReserveConcurrency, - keys: [], }); } else { const queueConcurrencyLimit = await engine.runQueue.getQueueConcurrencyLimit( diff --git a/apps/webapp/app/services/deleteProject.server.ts b/apps/webapp/app/services/deleteProject.server.ts index 9689e36199a..e02f2604914 100644 --- a/apps/webapp/app/services/deleteProject.server.ts +++ b/apps/webapp/app/services/deleteProject.server.ts @@ -1,6 +1,5 @@ import type { PrismaClient } from "@trigger.dev/database"; import { prisma } from "~/db.server"; -import { marqs } from "~/v3/marqs/index.server"; import { engine } from "~/v3/runEngine.server"; import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server"; @@ -36,11 +35,6 @@ export class DeleteProjectService { return; } - // Remove queues from MARQS - for (const environment of project.environments) { - await marqs?.removeEnvironmentQueuesFromMasterQueue(project.organization.id, environment.id); - } - // Delete all queues from the RunEngine 2 prod master queues for (const environment of project.environments) { await engine.removeEnvironmentQueuesFromMasterQueue({ diff --git a/apps/webapp/app/v3/engineVersion.server.ts b/apps/webapp/app/v3/engineVersion.server.ts index 32eca6fb882..2de71177493 100644 --- a/apps/webapp/app/v3/engineVersion.server.ts +++ b/apps/webapp/app/v3/engineVersion.server.ts @@ -63,10 +63,11 @@ export async function determineEngineVersion({ return worker.engine; } - // Dev: use the latest BackgroundWorker + // Dev: use the latest BackgroundWorker. Default to V2 when there is no current + // worker: v3 (engine V1) is retired, so a fresh/idle dev env must resolve to V2. if (environment.type === "DEVELOPMENT") { const backgroundWorker = await findCurrentWorkerFromEnvironment(environment); - return backgroundWorker?.engine ?? "V1"; + return backgroundWorker?.engine ?? "V2"; } // Deployed: use the latest deployed BackgroundWorker diff --git a/apps/webapp/app/v3/queueSizeLimits.server.ts b/apps/webapp/app/v3/queueSizeLimits.server.ts index 61058d48b31..20688933cc0 100644 --- a/apps/webapp/app/v3/queueSizeLimits.server.ts +++ b/apps/webapp/app/v3/queueSizeLimits.server.ts @@ -1,6 +1,6 @@ import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { env } from "~/env.server"; -import type { MarQS } from "./marqs/index.server"; +import { engine } from "./runEngine.server"; export type QueueSizeGuardResult = { isWithinLimits: boolean; @@ -10,7 +10,6 @@ export type QueueSizeGuardResult = { export async function guardQueueSizeLimitsForEnv( environment: AuthenticatedEnvironment, - marqs?: MarQS, itemsToAdd: number = 1 ): Promise { const maximumSize = getMaximumSizeForEnvironment(environment); @@ -19,11 +18,7 @@ export async function guardQueueSizeLimitsForEnv( return { isWithinLimits: true }; } - if (!marqs) { - return { isWithinLimits: true, maximumSize }; - } - - const queueSize = await marqs.lengthOfEnvQueue(environment); + const queueSize = await engine.lengthOfEnvQueue(environment); const projectedSize = queueSize + itemsToAdd; return { diff --git a/apps/webapp/app/v3/runQueue.server.ts b/apps/webapp/app/v3/runQueue.server.ts index e7aa13c5c54..0ff28fa0889 100644 --- a/apps/webapp/app/v3/runQueue.server.ts +++ b/apps/webapp/app/v3/runQueue.server.ts @@ -1,10 +1,7 @@ import { type AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { marqs } from "./marqs/index.server"; import { engine } from "./runEngine.server"; -//This allows us to update MARQS and the RunQueue - -/** Updates MARQS and the RunQueue limits */ +/** Updates the RunQueue env concurrency limits */ export async function updateEnvConcurrencyLimits( environment: AuthenticatedEnvironment, maximumConcurrencyLimit?: number @@ -14,31 +11,22 @@ export async function updateEnvConcurrencyLimits( updatedEnvironment.maximumConcurrencyLimit = maximumConcurrencyLimit; } - await Promise.allSettled([ - marqs?.updateEnvConcurrencyLimits(updatedEnvironment), - engine.runQueue.updateEnvConcurrencyLimits(updatedEnvironment), - ]); + await engine.runQueue.updateEnvConcurrencyLimits(updatedEnvironment); } -/** Updates MARQS and the RunQueue limits for a queue */ +/** Updates the RunQueue limits for a queue */ export async function updateQueueConcurrencyLimits( environment: AuthenticatedEnvironment, queueName: string, concurrency: number ) { - await Promise.allSettled([ - marqs?.updateQueueConcurrencyLimits(environment, queueName, concurrency), - engine.runQueue.updateQueueConcurrencyLimits(environment, queueName, concurrency), - ]); + await engine.runQueue.updateQueueConcurrencyLimits(environment, queueName, concurrency); } -/** Removes MARQS and the RunQueue limits for a queue */ +/** Removes the RunQueue limits for a queue */ export async function removeQueueConcurrencyLimits( environment: AuthenticatedEnvironment, queueName: string ) { - await Promise.allSettled([ - marqs?.removeQueueConcurrencyLimits(environment, queueName), - engine.runQueue.removeQueueConcurrencyLimits(environment, queueName), - ]); + await engine.runQueue.removeQueueConcurrencyLimits(environment, queueName); } diff --git a/apps/webapp/app/v3/services/batchTriggerV3.server.ts b/apps/webapp/app/v3/services/batchTriggerV3.server.ts index 66bcca84d30..5eb3e5e5a9f 100644 --- a/apps/webapp/app/v3/services/batchTriggerV3.server.ts +++ b/apps/webapp/app/v3/services/batchTriggerV3.server.ts @@ -30,7 +30,6 @@ import { mintFriendlyIdForKind } from "~/v3/runOpsMigration/mintAnchoredRunFrien import { mintBatchFriendlyId } from "~/v3/runOpsMigration/mintBatchFriendlyId.server"; import { batchTriggerWorker } from "../batchTriggerWorker.server"; import { legacyRunEngineWorker } from "../legacyRunEngineWorker.server"; -import { marqs } from "../marqs/index.server"; import { guardQueueSizeLimitsForEnv } from "../queueSizeLimits.server"; import { downloadPacketFromObjectStore, uploadPacketToObjectStore } from "../objectStore.server"; import { isFinalAttemptStatus, isFinalRunStatus } from "../taskStatus"; @@ -260,7 +259,7 @@ export class BatchTriggerV3Service extends BaseService { }; } - const queueSizeGuard = await guardQueueSizeLimitsForEnv(environment, marqs, newRunCount); + const queueSizeGuard = await guardQueueSizeLimitsForEnv(environment, newRunCount); logger.debug("Queue size guard result", { newRunCount, diff --git a/apps/webapp/app/v3/services/cancelTaskRun.server.ts b/apps/webapp/app/v3/services/cancelTaskRun.server.ts index 76db56535ae..29937366784 100644 --- a/apps/webapp/app/v3/services/cancelTaskRun.server.ts +++ b/apps/webapp/app/v3/services/cancelTaskRun.server.ts @@ -1,7 +1,7 @@ import { RunEngineVersion, type TaskRun } from "@trigger.dev/database"; import { engine } from "../runEngine.server"; +import { isCancellableRunStatus } from "../taskStatus"; import { BaseService } from "./baseService.server"; -import { CancelTaskRunServiceV1 } from "./cancelTaskRunV1.server"; export type CancelTaskRunServiceOptions = { reason?: string; @@ -38,17 +38,29 @@ export class CancelTaskRunService extends BaseService { taskRun: CancelableTaskRun, options?: CancelTaskRunServiceOptions ): Promise { - const service = new CancelTaskRunServiceV1(this._prisma); - const result = await service.call(taskRun, options); - - if (!result) { - return; + // v3 (engine V1) execution is retired: there are no V1 workers or coordinator + // left to signal. A historical V1 run can still be cancelled by finalizing its + // DB row directly. Never throw here: the cancel route returns 500 on any throw. + if (!isCancellableRunStatus(taskRun.status)) { + if (options?.bulkActionId) { + await this._prisma.taskRun.update({ + where: { id: taskRun.id }, + data: { bulkActionGroupIds: { push: options.bulkActionId } }, + }); + } + return { id: taskRun.id, alreadyFinished: true }; } - return { - id: result.id, - alreadyFinished: false, - }; + await this._prisma.taskRun.update({ + where: { id: taskRun.id }, + data: { + status: "CANCELED", + completedAt: options?.cancelledAt ?? new Date(), + bulkActionGroupIds: options?.bulkActionId ? { push: options.bulkActionId } : undefined, + }, + }); + + return { id: taskRun.id, alreadyFinished: false }; } private async callV2( diff --git a/apps/webapp/app/v3/services/rescheduleTaskRun.server.ts b/apps/webapp/app/v3/services/rescheduleTaskRun.server.ts index 6f8e6d895fa..f79a9980c24 100644 --- a/apps/webapp/app/v3/services/rescheduleTaskRun.server.ts +++ b/apps/webapp/app/v3/services/rescheduleTaskRun.server.ts @@ -1,12 +1,18 @@ import type { RescheduleRunRequestBody } from "@trigger.dev/core/v3"; import type { TaskRun } from "@trigger.dev/database"; import { parseDelay } from "~/utils/delays"; +import { V3_TRIGGER_DEPRECATION_MESSAGE } from "../engineDeprecation.server"; import { BaseService, ServiceValidationError } from "./baseService.server"; -import { EnqueueDelayedRunService } from "./enqueueDelayedRun.server"; import { engine } from "../runEngine.server"; export class RescheduleTaskRunService extends BaseService { public async call(taskRun: TaskRun, body: RescheduleRunRequestBody) { + // v3 (engine V1) is retired: reject rescheduling a legacy V1 delayed run + // gracefully instead of enqueuing into the removed V1 worker. + if (taskRun.engine === "V1") { + throw new ServiceValidationError(V3_TRIGGER_DEPRECATION_MESSAGE); + } + if (taskRun.status !== "DELAYED") { throw new ServiceValidationError("Cannot reschedule a run that is not delayed"); } @@ -17,7 +23,7 @@ export class RescheduleTaskRunService extends BaseService { throw new ServiceValidationError(`Invalid delay: ${body.delay}`); } - const updatedRun = await this.runStore.rescheduleRun( + await this.runStore.rescheduleRun( taskRun.id, { delayUntil: delay, @@ -26,11 +32,6 @@ export class RescheduleTaskRunService extends BaseService { this._prisma ); - if (updatedRun.engine === "V1") { - await EnqueueDelayedRunService.reschedule(taskRun.id, delay); - return updatedRun; - } else { - return engine.rescheduleDelayedRun({ runId: taskRun.id, delayUntil: delay }); - } + return engine.rescheduleDelayedRun({ runId: taskRun.id, delayUntil: delay }); } } diff --git a/apps/webapp/app/v3/services/triggerTask.server.ts b/apps/webapp/app/v3/services/triggerTask.server.ts index 354745d40b8..a4290b94490 100644 --- a/apps/webapp/app/v3/services/triggerTask.server.ts +++ b/apps/webapp/app/v3/services/triggerTask.server.ts @@ -10,9 +10,8 @@ import { DefaultTriggerTaskValidator } from "~/runEngine/validators/triggerTaskV import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { determineEngineVersion } from "../engineVersion.server"; import { tracer } from "../tracer.server"; -import { isV3Disabled, V3_TRIGGER_DEPRECATION_MESSAGE } from "../engineDeprecation.server"; +import { V3_TRIGGER_DEPRECATION_MESSAGE } from "../engineDeprecation.server"; import { ServiceValidationError, WithRunEngine } from "./baseService.server"; -import { TriggerTaskServiceV1 } from "./triggerTaskV1.server"; export type TriggerTaskServiceOptions = { idempotencyKey?: string; @@ -74,15 +73,10 @@ export class TriggerTaskService extends WithRunEngine { switch (v) { case "V1": { - // v3 (engine V1) is being sunset. When the shutdown is on, reject the - // trigger with a graceful, actionable error instead of creating a V1 - // run. Covers single, batch, schedule, replay, and triggerAndWait, - // which all route through here. - if (isV3Disabled()) { - throw new ServiceValidationError(V3_TRIGGER_DEPRECATION_MESSAGE); - } - - return await this.callV1(taskId, environment, body, options); + // v3 (engine V1) is retired. Reject the trigger with a graceful, + // actionable error instead of executing. Covers single, batch, + // schedule, replay, and triggerAndWait, which all route through here. + throw new ServiceValidationError(V3_TRIGGER_DEPRECATION_MESSAGE); } case "V2": { return await this.callV2(taskId, environment, body, options); @@ -91,16 +85,6 @@ export class TriggerTaskService extends WithRunEngine { }); } - private async callV1( - taskId: string, - environment: AuthenticatedEnvironment, - body: TriggerTaskRequestBody, - options: TriggerTaskServiceOptions = {} - ): Promise { - const service = new TriggerTaskServiceV1(this._prisma); - return await service.call(taskId, environment, body, options); - } - private async callV2( taskId: string, environment: AuthenticatedEnvironment, From 2a22b01674f1636bbaab1193abc319120c1eb630 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 12 Jul 2026 23:11:00 +0100 Subject: [PATCH 02/12] chore(webapp,core): remove v3 (engine V1) execution code v3 (engine V1) is end-of-life. This removes the V1 execution stack while keeping every still-on-v3 request on a graceful 4xx path (never a 5xx): - the MarQS queue and its shared/dev queue consumers - the V1 socket.io namespaces (coordinator, provider, shared-queue) and the V1 attempt, checkpoint, and batch-resume lifecycle services - the graphile-worker / zod-worker background job stack - the DEPRECATE_V3_ENABLED flag (V1 is now rejected unconditionally) and the @trigger.dev/core v3/zodNamespace export Live queue reads (concurrency limits, env queue length, project metrics, admin queue debug) now read from the v2 run engine instead of MarQS. The batchTriggerV3 service and batch-completion worker stay live for current clients. A fresh dev environment now defaults to V2. --- .changeset/retire-v3-zod-namespace.md | 5 + .claude/rules/legacy-v3-code.md | 35 +- .server-changes/remove-v3-engine.md | 6 + apps/webapp/app/components/admin/debugRun.tsx | 2 +- apps/webapp/app/entry.server.tsx | 9 - apps/webapp/app/env.server.ts | 16 - apps/webapp/app/routes/admin.concurrency.tsx | 39 - .../routes/api.v1.runs.$runParam.attempts.ts | 47 - ...urces.batches.$batchId.check-completion.ts | 34 +- .../resources.taskruns.$runParam.debug.ts | 2 +- .../db/graphileMigrationHelper.server.ts | 96 - .../webapp/app/services/db/pgNotify.server.ts | 28 - apps/webapp/app/services/db/types.ts | 11 - apps/webapp/app/services/worker.server.ts | 366 --- apps/webapp/app/v3/commonWorker.server.ts | 118 - .../webapp/app/v3/engineDeprecation.server.ts | 29 +- apps/webapp/app/v3/failedTaskRun.server.ts | 304 -- apps/webapp/app/v3/handleSocketIo.server.ts | 372 --- .../app/v3/legacyRunEngineWorker.server.ts | 28 - .../webapp/app/v3/marqs/asyncWorker.server.ts | 37 - .../app/v3/marqs/concurrencyMonitor.server.ts | 204 -- apps/webapp/app/v3/marqs/constants.server.ts | 4 - apps/webapp/app/v3/marqs/devPubSub.server.ts | 45 - .../app/v3/marqs/devQueueConsumer.server.ts | 623 ---- .../v3/marqs/fairDequeuingStrategy.server.ts | 598 ---- apps/webapp/app/v3/marqs/index.server.ts | 2691 ----------------- apps/webapp/app/v3/marqs/marqsKeyProducer.ts | 287 -- .../v3/marqs/sharedQueueConsumer.server.ts | 2215 -------------- apps/webapp/app/v3/marqs/types.ts | 111 - .../v3/marqs/v3VisibilityTimeout.server.ts | 39 - apps/webapp/app/v3/scheduleEngine.server.ts | 26 +- .../app/v3/services/batchTriggerV3.server.ts | 5 +- .../services/bulk/createBulkAction.server.ts | 60 - .../services/bulk/performBulkAction.server.ts | 127 - .../app/v3/services/cancelAttempt.server.ts | 100 - .../services/cancelDevSessionRuns.server.ts | 158 - .../cancelTaskAttemptDependencies.server.ts | 108 - .../app/v3/services/cancelTaskRunV1.server.ts | 210 -- .../changeCurrentDeployment.server.ts | 12 - .../app/v3/services/completeAttempt.server.ts | 728 ----- .../app/v3/services/crashTaskRun.server.ts | 200 -- .../v3/services/createCheckpoint.server.ts | 444 --- ...eateDeploymentBackgroundWorkerV3.server.ts | 219 -- .../services/createTaskRunAttempt.server.ts | 278 -- .../services/deploymentIndexFailed.server.ts | 107 - .../v3/services/enqueueDelayedRun.server.ts | 121 - .../app/v3/services/enqueueRun.server.ts | 67 - .../services/executeTasksWaitingForDeploy.ts | 142 - .../v3/services/expireEnqueuedRun.server.ts | 132 - .../v3/services/finalizeDeployment.server.ts | 15 - .../app/v3/services/finalizeTaskRun.server.ts | 361 --- .../v3/services/restoreCheckpoint.server.ts | 137 - .../app/v3/services/resumeAttempt.server.ts | 290 -- .../app/v3/services/resumeBatchRun.server.ts | 399 --- .../services/resumeDependentParents.server.ts | 296 -- .../services/resumeTaskDependency.server.ts | 175 -- .../app/v3/services/retryAttempt.server.ts | 38 - .../taskRunConcurrencyTracker.server.ts | 338 --- .../v3/services/timeoutDeployment.server.ts | 3 - .../app/v3/services/triggerTaskV1.server.ts | 762 ----- .../v3/services/updateFatalRunError.server.ts | 54 - apps/webapp/app/v3/sharedSocketConnection.ts | 141 - .../app/v3/taskRunHeartbeatFailed.server.ts | 186 -- apps/webapp/package.json | 2 - .../webapp/test/fairDequeuingStrategy.test.ts | 1478 --------- apps/webapp/test/marqsKeyProducer.test.ts | 226 -- apps/webapp/test/utils/marqs.ts | 116 - internal-packages/zod-worker/package.json | 22 - internal-packages/zod-worker/src/index.ts | 869 ------ .../zod-worker/src/pgListen.server.ts | 77 - internal-packages/zod-worker/src/types.ts | 11 - internal-packages/zod-worker/tsconfig.json | 25 - package.json | 1 - packages/core/package.json | 12 - packages/core/src/v3/zodNamespace.ts | 203 -- patches/graphile-worker@0.16.6.patch | 27 - pnpm-lock.yaml | 192 +- 77 files changed, 43 insertions(+), 18058 deletions(-) create mode 100644 .changeset/retire-v3-zod-namespace.md create mode 100644 .server-changes/remove-v3-engine.md delete mode 100644 apps/webapp/app/routes/admin.concurrency.tsx delete mode 100644 apps/webapp/app/routes/api.v1.runs.$runParam.attempts.ts delete mode 100644 apps/webapp/app/services/db/graphileMigrationHelper.server.ts delete mode 100644 apps/webapp/app/services/db/pgNotify.server.ts delete mode 100644 apps/webapp/app/services/db/types.ts delete mode 100644 apps/webapp/app/services/worker.server.ts delete mode 100644 apps/webapp/app/v3/failedTaskRun.server.ts delete mode 100644 apps/webapp/app/v3/marqs/asyncWorker.server.ts delete mode 100644 apps/webapp/app/v3/marqs/concurrencyMonitor.server.ts delete mode 100644 apps/webapp/app/v3/marqs/constants.server.ts delete mode 100644 apps/webapp/app/v3/marqs/devPubSub.server.ts delete mode 100644 apps/webapp/app/v3/marqs/devQueueConsumer.server.ts delete mode 100644 apps/webapp/app/v3/marqs/fairDequeuingStrategy.server.ts delete mode 100644 apps/webapp/app/v3/marqs/index.server.ts delete mode 100644 apps/webapp/app/v3/marqs/marqsKeyProducer.ts delete mode 100644 apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts delete mode 100644 apps/webapp/app/v3/marqs/types.ts delete mode 100644 apps/webapp/app/v3/marqs/v3VisibilityTimeout.server.ts delete mode 100644 apps/webapp/app/v3/services/bulk/createBulkAction.server.ts delete mode 100644 apps/webapp/app/v3/services/bulk/performBulkAction.server.ts delete mode 100644 apps/webapp/app/v3/services/cancelAttempt.server.ts delete mode 100644 apps/webapp/app/v3/services/cancelDevSessionRuns.server.ts delete mode 100644 apps/webapp/app/v3/services/cancelTaskAttemptDependencies.server.ts delete mode 100644 apps/webapp/app/v3/services/cancelTaskRunV1.server.ts delete mode 100644 apps/webapp/app/v3/services/completeAttempt.server.ts delete mode 100644 apps/webapp/app/v3/services/crashTaskRun.server.ts delete mode 100644 apps/webapp/app/v3/services/createCheckpoint.server.ts delete mode 100644 apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV3.server.ts delete mode 100644 apps/webapp/app/v3/services/createTaskRunAttempt.server.ts delete mode 100644 apps/webapp/app/v3/services/deploymentIndexFailed.server.ts delete mode 100644 apps/webapp/app/v3/services/enqueueDelayedRun.server.ts delete mode 100644 apps/webapp/app/v3/services/enqueueRun.server.ts delete mode 100644 apps/webapp/app/v3/services/executeTasksWaitingForDeploy.ts delete mode 100644 apps/webapp/app/v3/services/expireEnqueuedRun.server.ts delete mode 100644 apps/webapp/app/v3/services/finalizeTaskRun.server.ts delete mode 100644 apps/webapp/app/v3/services/restoreCheckpoint.server.ts delete mode 100644 apps/webapp/app/v3/services/resumeAttempt.server.ts delete mode 100644 apps/webapp/app/v3/services/resumeBatchRun.server.ts delete mode 100644 apps/webapp/app/v3/services/resumeDependentParents.server.ts delete mode 100644 apps/webapp/app/v3/services/resumeTaskDependency.server.ts delete mode 100644 apps/webapp/app/v3/services/retryAttempt.server.ts delete mode 100644 apps/webapp/app/v3/services/taskRunConcurrencyTracker.server.ts delete mode 100644 apps/webapp/app/v3/services/triggerTaskV1.server.ts delete mode 100644 apps/webapp/app/v3/services/updateFatalRunError.server.ts delete mode 100644 apps/webapp/app/v3/sharedSocketConnection.ts delete mode 100644 apps/webapp/app/v3/taskRunHeartbeatFailed.server.ts delete mode 100644 apps/webapp/test/fairDequeuingStrategy.test.ts delete mode 100644 apps/webapp/test/marqsKeyProducer.test.ts delete mode 100644 apps/webapp/test/utils/marqs.ts delete mode 100644 internal-packages/zod-worker/package.json delete mode 100644 internal-packages/zod-worker/src/index.ts delete mode 100644 internal-packages/zod-worker/src/pgListen.server.ts delete mode 100644 internal-packages/zod-worker/src/types.ts delete mode 100644 internal-packages/zod-worker/tsconfig.json delete mode 100644 packages/core/src/v3/zodNamespace.ts delete mode 100644 patches/graphile-worker@0.16.6.patch diff --git a/.changeset/retire-v3-zod-namespace.md b/.changeset/retire-v3-zod-namespace.md new file mode 100644 index 00000000000..c17ddcb72a2 --- /dev/null +++ b/.changeset/retire-v3-zod-namespace.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Removed the unused `@trigger.dev/core/v3/zodNamespace` export, which was only used by the retired v3 engine. diff --git a/.claude/rules/legacy-v3-code.md b/.claude/rules/legacy-v3-code.md index 6fd8d9402c2..f34d768525f 100644 --- a/.claude/rules/legacy-v3-code.md +++ b/.claude/rules/legacy-v3-code.md @@ -3,31 +3,24 @@ paths: - "apps/webapp/app/v3/**" --- -# Legacy V1 Engine Code in `app/v3/` +# v3 (engine V1) has been removed -The `v3/` directory name is misleading - most code here is actively used by the current V2 engine. Only the specific files below are legacy V1-only code. +The v3 engine (RunEngineVersion `V1`: MarQS queue + Graphile worker) is end-of-life and its execution code has been removed from the webapp. The `app/v3/` directory name is historical: everything under it now serves the current V2 engine (`@internal/run-engine` + `@trigger.dev/redis-worker`). -## V1-Only Files - Never Modify +There is no `V1` execution path anymore. If you find a `RunEngineVersion` branch, the `V1` arm should only ever produce a graceful rejection, never run work. Do not reintroduce MarQS, the graphile worker, or the v3 socket.io namespaces. -- `marqs/` directory (entire MarQS queue system: sharedQueueConsumer, devQueueConsumer, fairDequeuingStrategy, devPubSub) -- `legacyRunEngineWorker.server.ts` (V1 background job worker) -- `services/triggerTaskV1.server.ts` (deprecated V1 task triggering) -- `services/cancelTaskRunV1.server.ts` (deprecated V1 cancellation) -- `authenticatedSocketConnection.server.ts` (V1 dev WebSocket using DevQueueConsumer) -- `sharedSocketConnection.ts` (V1 shared queue socket using SharedQueueConsumer) +## The deprecation boundary (keep this) -## V1/V2 Branching Pattern +Requests from clients still on v3 (old SDK/CLI) or historical V1 runs must return a clean 4xx, never a 5xx. The boundary lives in: -Some services act as routers that branch on `RunEngineVersion`: -- `services/cancelTaskRun.server.ts` - calls V1 service or `engine.cancelRun()` for V2 -- `services/batchTriggerV3.server.ts` - uses marqs for V1 path, run-engine for V2 +- `engineDeprecation.server.ts` - the `V3_TRIGGER_DEPRECATION_MESSAGE` / `V3_DEV_DEPRECATION_MESSAGE` / `V3_MIGRATION_URL` upgrade messages. +- `engineVersion.server.ts` - `determineEngineVersion()` still detects a V1 project/run so callers can reject it. +- `services/triggerTask.server.ts`, `services/cancelTaskRun.server.ts`, `services/rescheduleTaskRun.server.ts` - the `V1` arm rejects or finalizes gracefully instead of executing. +- `services/initializeDeployment.server.ts` - the `DEPRECATE_V3_CLI_DEPLOYS_ENABLED`-gated v3 CLI deploy rejection. +- `handleWebsockets.server.ts` - the legacy `trigger dev` websocket closes with the upgrade message. -When editing these shared services, only modify V2 code paths. +## V2 modern stack -## V2 Modern Stack - -- **Run lifecycle**: `@internal/run-engine` (internal-packages/run-engine) -- **Background jobs**: `@trigger.dev/redis-worker` (not graphile-worker/zodworker) -- **Queue operations**: RunQueue inside run-engine (not MarQS) -- **V2 engine singleton**: `runEngine.server.ts`, `runEngineHandlers.server.ts` -- **V2 workers**: `commonWorker.server.ts`, `alertsWorker.server.ts`, `batchTriggerWorker.server.ts` +- **Run lifecycle**: `@internal/run-engine` (`runEngine.server.ts`, `runEngineHandlers.server.ts`) +- **Background jobs**: `@trigger.dev/redis-worker` (`commonWorker.server.ts`, `alertsWorker.server.ts`, `batchTriggerWorker.server.ts`; `legacyRunEngineWorker.server.ts` still hosts the live batch-completion jobs) +- **Queue operations**: RunQueue inside run-engine (`runQueue.server.ts`), not MarQS diff --git a/.server-changes/remove-v3-engine.md b/.server-changes/remove-v3-engine.md new file mode 100644 index 00000000000..5ffe42e6e4a --- /dev/null +++ b/.server-changes/remove-v3-engine.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: breaking +--- + +Removed support for the end-of-life v3 engine. Instances or projects still on v3 must stay on the 4.5.x release line or upgrade to v4; v3 triggers, batch triggers, reschedules, and deploys now return a clear upgrade message instead of running. diff --git a/apps/webapp/app/components/admin/debugRun.tsx b/apps/webapp/app/components/admin/debugRun.tsx index 7a93402b064..9ab630addc5 100644 --- a/apps/webapp/app/components/admin/debugRun.tsx +++ b/apps/webapp/app/components/admin/debugRun.tsx @@ -104,7 +104,7 @@ function DebugRunDataEngineV2({ envConcurrencyLimit, envCurrentConcurrency, keys, -}: UseDataFunctionReturn) { +}: Extract, { engine: "V2" }>) { return ( diff --git a/apps/webapp/app/entry.server.tsx b/apps/webapp/app/entry.server.tsx index 091f2f28ccf..37156e6db0a 100644 --- a/apps/webapp/app/entry.server.tsx +++ b/apps/webapp/app/entry.server.tsx @@ -7,7 +7,6 @@ import { parseAcceptLanguage } from "intl-parse-accept-language"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; import { PassThrough } from "stream"; -import * as Worker from "~/services/worker.server"; import { initMollifierDrainerWorker } from "~/v3/mollifierDrainerWorker.server"; import { initMollifierStaleSweepWorker } from "~/v3/mollifierStaleSweepWorker.server"; import { initBillingLimitWorker } from "~/v3/billingLimitWorker.server"; @@ -227,10 +226,6 @@ export const handleError = wrapHandleErrorWithSentry((error, { request }) => { } }); -Worker.init().catch((error) => { - logError(error); -}); - initMollifierDrainerWorker(); initMollifierStaleSweepWorker(); initBillingLimitWorker(); @@ -241,10 +236,6 @@ bootstrap().catch((error) => { function logError(error: unknown, request?: Request) { console.error(error); - - if (error instanceof Error && error.message.startsWith("There are locked jobs present")) { - console.log("⚠️ graphile-worker migration issue detected!"); - } } process.on("uncaughtException", (error, origin) => { diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index eeee67b0a85..91253cb63b1 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -224,9 +224,6 @@ const EnvironmentSchema = z PLAIN_CUSTOMER_CARDS_SECRET: z.string().optional(), PLAIN_CUSTOMER_CARDS_KEY: z.string().optional(), PLAIN_CUSTOMER_CARDS_HEADERS: z.string().optional(), - WORKER_SCHEMA: z.string().default("graphile_worker"), - WORKER_CONCURRENCY: z.coerce.number().int().default(10), - WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), // How often each replica reloads the global flags snapshot from the DB. // Sets kill/ramp propagation latency. GLOBAL_FLAGS_RELOAD_INTERVAL_MS: z.coerce.number().int().min(1000).default(5000), @@ -528,9 +525,6 @@ const EnvironmentSchema = z API_RATE_LIMIT_JWT_WINDOW: z.string().default("1m"), API_RATE_LIMIT_JWT_TOKENS: z.coerce.number().int().default(60), - //v3 - PROVIDER_SECRET: z.string().default("provider-secret"), - COORDINATOR_SECRET: z.string().default("coordinator-secret"), DEPOT_TOKEN: z.string().optional(), DEPOT_ORG_ID: z.string().optional(), DEPOT_REGION: z.string().default("us-east-1"), @@ -618,15 +612,6 @@ const EnvironmentSchema = z // log-only mode before enforcement. DEPRECATE_V3_CLI_DEPLOYS_ENABLED: z.string().default("0"), - // Master switch for the v3 engine (RunEngineVersion.V1) shutdown. When - // enabled it: rejects triggers that resolve to V1 (single, batch, schedule, - // replay, triggerAndWait) with a graceful error pointing at the v4 migration - // guide; closes the legacy `trigger dev` websocket used by v3 CLIs; and turns - // the V1 run-lifecycle background jobs (heartbeat timeout, TTL expiry, retry, - // resume, scheduled fires) into no-ops so abandoned V1 runs stop generating - // database load. v4 (V2) is never affected (every gate also checks the run is - // V1). Defaults to off so self-hosted instances still on V1 keep working. - DEPRECATE_V3_ENABLED: z.string().default("0"), // Verify the deploy image exists before promoting. Disable for out-of-band/air-gapped push. ECR only. DEPLOY_IMAGE_VERIFICATION_ENABLED: BoolEnv.default(true), @@ -815,7 +800,6 @@ const EnvironmentSchema = z PROD_TASK_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(), - VERBOSE_GRAPHILE_LOGGING: z.string().default("false"), V2_MARQS_ENABLED: z.string().default("0"), V2_MARQS_CONSUMER_POOL_ENABLED: z.string().default("0"), V2_MARQS_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10), diff --git a/apps/webapp/app/routes/admin.concurrency.tsx b/apps/webapp/app/routes/admin.concurrency.tsx deleted file mode 100644 index f91f02bd617..00000000000 --- a/apps/webapp/app/routes/admin.concurrency.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { InformationCircleIcon } from "@heroicons/react/20/solid"; -import { typedjson, useTypedLoaderData } from "remix-typedjson"; -import { Header1 } from "~/components/primitives/Headers"; -import { InfoPanel } from "~/components/primitives/InfoPanel"; -import { Paragraph } from "~/components/primitives/Paragraph"; -import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder"; -import { concurrencyTracker } from "~/v3/services/taskRunConcurrencyTracker.server"; - -export const loader = dashboardLoader({ authorization: { requireSuper: true } }, async () => { - const deployedConcurrency = await concurrencyTracker.globalConcurrentRunCount(true); - const devConcurrency = await concurrencyTracker.globalConcurrentRunCount(false); - return typedjson({ deployedConcurrency, devConcurrency }); -}); - -export default function AdminDashboardRoute() { - const { deployedConcurrency, devConcurrency } = useTypedLoaderData(); - - return ( -
-
-
- Dev - {devConcurrency} -
-
- Deployed - {deployedConcurrency} -
-
- - This refers to the number of 'Dequeued' runs, which are either currently executing or about - to begin execution. - -
- ); -} diff --git a/apps/webapp/app/routes/api.v1.runs.$runParam.attempts.ts b/apps/webapp/app/routes/api.v1.runs.$runParam.attempts.ts deleted file mode 100644 index 790e52bee4e..00000000000 --- a/apps/webapp/app/routes/api.v1.runs.$runParam.attempts.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { ActionFunctionArgs } from "@remix-run/server-runtime"; -import { json } from "@remix-run/server-runtime"; -import { z } from "zod"; -import { authenticateApiRequest } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { ServiceValidationError } from "~/v3/services/baseService.server"; -import { CreateTaskRunAttemptService } from "~/v3/services/createTaskRunAttempt.server"; - -const ParamsSchema = z.object({ - /* This is the run friendly ID */ - runParam: z.string(), -}); - -export async function action({ request, params }: ActionFunctionArgs) { - // Authenticate the request - const authenticationResult = await authenticateApiRequest(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API Key" }, { status: 401 }); - } - - const parsed = ParamsSchema.safeParse(params); - - if (!parsed.success) { - return json({ error: "Invalid or missing run ID" }, { status: 400 }); - } - - const { runParam } = parsed.data; - - const service = new CreateTaskRunAttemptService(); - - try { - const { execution } = await service.call({ - runId: runParam, - authenticatedEnv: authenticationResult.environment, - }); - - return json(execution, { status: 200 }); - } catch (error) { - if (error instanceof ServiceValidationError) { - return json({ error: error.message }, { status: error.status ?? 422 }); - } - - logger.error("Failed to create run attempt", { error }); - return json({ error: "Something went wrong, please try again." }, { status: 500 }); - } -} diff --git a/apps/webapp/app/routes/resources.batches.$batchId.check-completion.ts b/apps/webapp/app/routes/resources.batches.$batchId.check-completion.ts index 74b274ef436..fffa0d09989 100644 --- a/apps/webapp/app/routes/resources.batches.$batchId.check-completion.ts +++ b/apps/webapp/app/routes/resources.batches.$batchId.check-completion.ts @@ -1,7 +1,6 @@ import { parseWithZod } from "@conform-to/zod"; import type { ActionFunction } from "@remix-run/node"; import { json } from "@remix-run/node"; -import { assertExhaustive } from "@trigger.dev/core/utils"; import { z } from "zod"; import { prisma } from "~/db.server"; import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; @@ -10,7 +9,7 @@ import { requireUserId } from "~/services/session.server"; import { sanitizeRedirectPath } from "~/utils"; import { runStore } from "~/v3/runStore.server"; import { findBatchRunIdForUser } from "~/v3/services/batchRunAccess.server"; -import { ResumeBatchRunService } from "~/v3/services/resumeBatchRun.server"; +import { tryCompleteBatchV3 } from "~/v3/services/batchTriggerV3.server"; export const checkCompletionSchema = z.object({ redirectUrl: z.string(), @@ -44,35 +43,10 @@ export const action: ActionFunction = async ({ request, params }) => { } try { - const resumeBatchRunService = new ResumeBatchRunService(); - // Resume by the resolved internal id: the service looks up strictly by - // `{ id }`, so passing a friendlyId param would resolve to nothing. - const resumeResult = await resumeBatchRunService.call(ownedBatchRunId); + // v3 (engine V1) is retired; finalize the batch through the v2 completion path (no-op if not ready). + await tryCompleteBatchV3(ownedBatchRunId, prisma, true); - let message: string | undefined; - - switch (resumeResult) { - case "ERROR": { - throw "Unknown error during batch completion check"; - } - case "ALREADY_COMPLETED": { - message = "Batch already completed."; - break; - } - case "COMPLETED": { - message = "Batch completed and parent tasks resumed."; - break; - } - case "PENDING": { - message = "Child runs still in progress. Please try again later."; - break; - } - default: { - assertExhaustive(resumeResult); - } - } - - return redirectWithSuccessMessage(safeRedirectUrl, request, message); + return redirectWithSuccessMessage(safeRedirectUrl, request, "Batch completion checked."); } catch (error) { if (error instanceof Error) { logger.error("Failed to check batch completion", { diff --git a/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts b/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts index 201942c7c85..7275e6d73aa 100644 --- a/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts +++ b/apps/webapp/app/routes/resources.taskruns.$runParam.debug.ts @@ -118,7 +118,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { ]; return typedjson({ - engine: "V2", + engine: "V2" as const, run, environment, queueConcurrencyLimit, diff --git a/apps/webapp/app/services/db/graphileMigrationHelper.server.ts b/apps/webapp/app/services/db/graphileMigrationHelper.server.ts deleted file mode 100644 index 817ac38915c..00000000000 --- a/apps/webapp/app/services/db/graphileMigrationHelper.server.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { runMigrations } from "graphile-worker"; -import type { PrismaClient } from "~/db.server"; -import { prisma } from "~/db.server"; -import { env } from "~/env.server"; -import { logger } from "~/services/logger.server"; -import { PgNotifyService } from "./pgNotify.server"; -import { z } from "zod"; - -export class GraphileMigrationHelperService { - #prismaClient: PrismaClient; - - constructor(prismaClient: PrismaClient = prisma) { - this.#prismaClient = prismaClient; - } - - public async call() { - this.#logDebug("GraphileMigrationHelperService.call"); - - await this.#detectAndPrepareForMigrations(); - - await runMigrations({ - connectionString: env.DATABASE_URL, - schema: env.WORKER_SCHEMA, - }); - } - - #logDebug(message: string, args?: any) { - logger.debug(`[migrationHelper] ${message}`, args); - } - - async #getLatestMigration() { - const migrationQueryResult = await this.#prismaClient.$queryRawUnsafe(` - SELECT id FROM ${env.WORKER_SCHEMA}.migrations - ORDER BY id DESC LIMIT 1 - `); - - const MigrationQueryResultSchema = z.array(z.object({ id: z.number() })); - - const migrationResults = MigrationQueryResultSchema.parse(migrationQueryResult); - - if (!migrationResults.length) { - // no migrations applied yet - return -1; - } - - return migrationResults[0].id; - } - - async #graphileSchemaExists() { - const schemaCount = await this.#prismaClient.$executeRaw` - SELECT schema_name FROM information_schema.schemata - WHERE schema_name = ${env.WORKER_SCHEMA} - `; - - return schemaCount === 1; - } - - /** Helper for graphile-worker v0.14.0 migration. No-op if already migrated. */ - async #detectAndPrepareForMigrations() { - if (!(await this.#graphileSchemaExists())) { - // no schema yet, likely first start - return; - } - - const latestMigration = await this.#getLatestMigration(); - - if (latestMigration < 0) { - // no migrations found - return; - } - - // the first v0.14.0 migration has ID 11 - if (latestMigration > 10) { - // already migrated - return; - } - - // add 15s to graceful shutdown timeout, just to be safe - const migrationDelayInMs = env.GRACEFUL_SHUTDOWN_TIMEOUT + 15000; - - this.#logDebug("Delaying worker startup due to pending migration", { - latestMigration, - migrationDelayInMs, - }); - - console.log(`⚠️ detected pending graphile migration`); - console.log(`⚠️ notifying running workers`); - - const pgNotify = new PgNotifyService(); - await pgNotify.call("trigger:graphile:migrate", { latestMigration }); - - console.log(`⚠️ delaying worker startup by ${migrationDelayInMs}ms`); - - await new Promise((resolve) => setTimeout(resolve, migrationDelayInMs)); - } -} diff --git a/apps/webapp/app/services/db/pgNotify.server.ts b/apps/webapp/app/services/db/pgNotify.server.ts deleted file mode 100644 index fe155dbbd99..00000000000 --- a/apps/webapp/app/services/db/pgNotify.server.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { z } from "zod"; -import type { PrismaClient } from "~/db.server"; -import { prisma } from "~/db.server"; -import { logger } from "~/services/logger.server"; -import type { NotificationCatalog, NotificationChannel } from "./types"; - -export class PgNotifyService { - #prismaClient: PrismaClient; - - constructor(prismaClient: PrismaClient = prisma) { - this.#prismaClient = prismaClient; - } - - public async call( - channelName: TChannel, - payload: z.infer - ) { - this.#logDebug("Sending notification", { channelName, notifyPayload: payload }); - - await this.#prismaClient.$executeRaw` - SELECT pg_notify(${channelName}, ${JSON.stringify(payload)}) - `; - } - - #logDebug(message: string, args?: any) { - logger.debug(`[pgNotify] ${message}`, args); - } -} diff --git a/apps/webapp/app/services/db/types.ts b/apps/webapp/app/services/db/types.ts deleted file mode 100644 index 2e5b6d222fd..00000000000 --- a/apps/webapp/app/services/db/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from "zod"; - -export const notificationCatalog = { - "trigger:graphile:migrate": z.object({ - latestMigration: z.number(), - }), -}; - -export type NotificationCatalog = typeof notificationCatalog; - -export type NotificationChannel = keyof NotificationCatalog; diff --git a/apps/webapp/app/services/worker.server.ts b/apps/webapp/app/services/worker.server.ts deleted file mode 100644 index 7de2c7cb2e7..00000000000 --- a/apps/webapp/app/services/worker.server.ts +++ /dev/null @@ -1,366 +0,0 @@ -/** - * ⚠️ LEGACY — Graphile-worker / ZodWorker setup. Do not touch. - * - * This file wires the original background-job system the webapp was - * built on (`@internal/zod-worker` → graphile-worker → Postgres). It is - * now in deprecation mode: every task in `workerCatalog` below is - * annotated with `@deprecated, moved to ` and the live jobs - * for new features all run on `@trigger.dev/redis-worker` instead. - * - * Where to put new things: - * - Background jobs / queues → use redis-worker, alongside - * `~/v3/commonWorker.server.ts`, `~/v3/alertsWorker.server.ts`, or - * `~/v3/batchTriggerWorker.server.ts`. - * - Run lifecycle → `@internal/run-engine` via `~/v3/runEngine.server`. - * - Custom polling loops with their own Redis connection → keep them - * in their own lifecycle module (e.g. `~/v3/mollifierDrainerWorker.server.ts`) - * and wire the bootstrap from `entry.server.tsx`. Don't reach into - * `init()` below. - * - * Edit only when removing legacy paths. - */ -import { ZodWorker } from "@internal/zod-worker"; -import { DeliverEmailSchema } from "emails"; -import { z } from "zod"; -import { $replica, prisma } from "~/db.server"; -import { env } from "~/env.server"; -import { - BatchProcessingOptions as RunEngineBatchProcessingOptions, - RunEngineBatchTriggerService, -} from "~/runEngine/services/batchTrigger.server"; -import { MarqsConcurrencyMonitor } from "~/v3/marqs/concurrencyMonitor.server"; -import { scheduleEngine } from "~/v3/scheduleEngine.server"; -import { DeliverAlertService } from "~/v3/services/alerts/deliverAlert.server"; -import { PerformDeploymentAlertsService } from "~/v3/services/alerts/performDeploymentAlerts.server"; -import { PerformTaskRunAlertsService } from "~/v3/services/alerts/performTaskRunAlerts.server"; -import { BatchProcessingOptions, BatchTriggerV3Service } from "~/v3/services/batchTriggerV3.server"; -import { PerformBulkActionService } from "~/v3/services/bulk/performBulkAction.server"; -import { - CancelDevSessionRunsService, - CancelDevSessionRunsServiceOptions, -} from "~/v3/services/cancelDevSessionRuns.server"; -import { CancelTaskAttemptDependenciesService } from "~/v3/services/cancelTaskAttemptDependencies.server"; -import { EnqueueDelayedRunService } from "~/v3/services/enqueueDelayedRun.server"; -import { ExecuteTasksWaitingForDeployService } from "~/v3/services/executeTasksWaitingForDeploy"; -import { ExpireEnqueuedRunService } from "~/v3/services/expireEnqueuedRun.server"; -import { ResumeBatchRunService } from "~/v3/services/resumeBatchRun.server"; -import { ResumeTaskDependencyService } from "~/v3/services/resumeTaskDependency.server"; -import { RetryAttemptService } from "~/v3/services/retryAttempt.server"; -import { TimeoutDeploymentService } from "~/v3/services/timeoutDeployment.server"; -import { GraphileMigrationHelperService } from "./db/graphileMigrationHelper.server"; -import { sendEmail } from "./email.server"; -import { logger } from "./logger.server"; - -const workerCatalog = { - // @deprecated, moved to commonWorker.server.ts - scheduleEmail: DeliverEmailSchema, - // @deprecated, but still used when resuming batch runs in a transaction - "v3.resumeBatchRun": z.object({ - batchRunId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.resumeTaskDependency": z.object({ - dependencyId: z.string(), - sourceTaskAttemptId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.timeoutDeployment": z.object({ - deploymentId: z.string(), - fromStatus: z.string(), - errorMessage: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.executeTasksWaitingForDeploy": z.object({ - backgroundWorkerId: z.string(), - }), - // @deprecated, moved to ScheduleEngine - "v3.triggerScheduledTask": z.object({ - instanceId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.performTaskRunAlerts": z.object({ - runId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.deliverAlert": z.object({ - alertId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.performDeploymentAlerts": z.object({ - deploymentId: z.string(), - }), - "v3.performBulkAction": z.object({ - bulkActionGroupId: z.string(), - }), - "v3.performBulkActionItem": z.object({ - bulkActionItemId: z.string(), - }), - // @deprecated, moved to legacyRunEngineWorker.server.ts - "v3.requeueTaskRun": z.object({ - runId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.retryAttempt": z.object({ - runId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.enqueueDelayedRun": z.object({ - runId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.expireRun": z.object({ - runId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.cancelTaskAttemptDependencies": z.object({ - attemptId: z.string(), - }), - // @deprecated, moved to commonWorker.server.ts - "v3.cancelDevSessionRuns": CancelDevSessionRunsServiceOptions, - // @deprecated, moved to commonWorker.server.ts - "v3.processBatchTaskRun": BatchProcessingOptions, - // @deprecated, moved to commonWorker.server.ts - "runengine.processBatchTaskRun": RunEngineBatchProcessingOptions, -}; - -let workerQueue: ZodWorker; - -declare global { - var __worker__: ZodWorker; -} - -// this is needed because in development we don't want to restart -// the server with every change, but we want to make sure we don't -// create a new connection to the DB with every change either. -// in production we'll have a single connection to the DB. -if (env.NODE_ENV === "production") { - workerQueue = getWorkerQueue(); -} else { - if (!global.__worker__) { - global.__worker__ = getWorkerQueue(); - } - workerQueue = global.__worker__; -} - -export async function init() { - const migrationHelper = new GraphileMigrationHelperService(); - await migrationHelper.call(); - - if (env.WORKER_ENABLED === "true") { - await workerQueue.initialize(); - } -} - -function getWorkerQueue() { - return new ZodWorker({ - name: "workerQueue", - prisma, - replica: $replica, - runnerOptions: { - connectionString: env.DATABASE_URL, - concurrency: env.WORKER_CONCURRENCY, - pollInterval: env.WORKER_POLL_INTERVAL, - noPreparedStatements: env.DATABASE_URL !== env.DIRECT_URL, - schema: env.WORKER_SCHEMA, - maxPoolSize: env.WORKER_CONCURRENCY + 1, - }, - logger: logger, - shutdownTimeoutInMs: env.GRACEFUL_SHUTDOWN_TIMEOUT, - schema: workerCatalog, - recurringTasks: { - "marqs.v3.queueConcurrencyMonitor": { - // run every 5 minutes - match: "*/5 * * * *", - handler: async (payload, job, helpers) => { - await MarqsConcurrencyMonitor.initiateV3Monitoring(helpers.abortSignal); - }, - }, - }, - tasks: { - // @deprecated, moved to commonWorker.server.ts - scheduleEmail: { - priority: 0, - maxAttempts: 3, - handler: async (payload, job) => { - await sendEmail(payload); - }, - }, - // @deprecated, moved to commonWorker.server.ts but still used when resuming batch runs in a transaction - "v3.resumeBatchRun": { - priority: 0, - maxAttempts: 5, - handler: async (payload, job) => { - const service = new ResumeBatchRunService(); - - await service.call(payload.batchRunId); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "v3.resumeTaskDependency": { - priority: 0, - maxAttempts: 5, - handler: async (payload, job) => { - const service = new ResumeTaskDependencyService(); - - return await service.call(payload.dependencyId, payload.sourceTaskAttemptId); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "v3.timeoutDeployment": { - priority: 0, - maxAttempts: 5, - handler: async (payload, job) => { - const service = new TimeoutDeploymentService(); - - return await service.call(payload.deploymentId, payload.fromStatus, payload.errorMessage); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "v3.executeTasksWaitingForDeploy": { - priority: 0, - maxAttempts: 5, - handler: async (payload, job) => { - const service = new ExecuteTasksWaitingForDeployService(); - - return await service.call(payload.backgroundWorkerId); - }, - }, - // @deprecated, moved to ScheduleEngine - "v3.triggerScheduledTask": { - priority: 0, - maxAttempts: 3, // total delay of 30 seconds - handler: async (payload, job) => { - await scheduleEngine.triggerScheduledTask({ - instanceId: payload.instanceId, - finalAttempt: job.attempts === job.max_attempts, - }); - }, - }, - // @deprecated, moved to alertsWorker.server.ts - "v3.performTaskRunAlerts": { - priority: 0, - maxAttempts: 3, - handler: async (payload, job) => { - const service = new PerformTaskRunAlertsService(); - return await service.call(payload.runId); - }, - }, - // @deprecated, moved to alertsWorker.server.ts - "v3.deliverAlert": { - priority: 0, - maxAttempts: 8, - handler: async (payload, job) => { - const service = new DeliverAlertService(); - - return await service.call(payload.alertId); - }, - }, - // @deprecated, moved to alertsWorker.server.ts - "v3.performDeploymentAlerts": { - priority: 0, - maxAttempts: 3, - handler: async (payload, job) => { - const service = new PerformDeploymentAlertsService(); - - return await service.call(payload.deploymentId); - }, - }, - // @deprecated, new bulk actions use the new bulk actions worker - "v3.performBulkAction": { - priority: 0, - maxAttempts: 3, - handler: async (payload, job) => { - const service = new PerformBulkActionService(); - - return await service.call(payload.bulkActionGroupId); - }, - }, - // @deprecated, new bulk actions use the new bulk actions worker - "v3.performBulkActionItem": { - priority: 0, - maxAttempts: 3, - handler: async (payload, job) => { - const service = new PerformBulkActionService(); - - await service.performBulkActionItem(payload.bulkActionItemId); - }, - }, - "v3.requeueTaskRun": { - priority: 0, - maxAttempts: 3, - handler: async (payload, job) => {}, // This is now handled by redisWorker - }, - // @deprecated, moved to commonWorker.server.ts - "v3.retryAttempt": { - priority: 0, - maxAttempts: 3, - handler: async (payload, job) => { - const service = new RetryAttemptService(); - - return await service.call(payload.runId); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "v3.enqueueDelayedRun": { - priority: 0, - maxAttempts: 8, - handler: async (payload, job) => { - const service = new EnqueueDelayedRunService(); - - return await service.call(payload.runId); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "v3.expireRun": { - priority: 0, - maxAttempts: 8, - handler: async (payload, job) => { - const service = new ExpireEnqueuedRunService(); - - return await service.call(payload.runId); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "v3.cancelTaskAttemptDependencies": { - priority: 0, - maxAttempts: 8, - handler: async (payload, job) => { - const service = new CancelTaskAttemptDependenciesService(); - - return await service.call(payload.attemptId); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "v3.cancelDevSessionRuns": { - priority: 0, - maxAttempts: 5, - handler: async (payload, job) => { - const service = new CancelDevSessionRunsService(); - - return await service.call(payload); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "v3.processBatchTaskRun": { - priority: 0, - maxAttempts: 5, - handler: async (payload, job) => { - const service = new BatchTriggerV3Service(payload.strategy); - - await service.processBatchTaskRun(payload); - }, - }, - // @deprecated, moved to commonWorker.server.ts - "runengine.processBatchTaskRun": { - priority: 0, - maxAttempts: 5, - handler: async (payload, job) => { - const service = new RunEngineBatchTriggerService(payload.strategy); - - await service.processBatchTaskRun(payload); - }, - }, - }, - }); -} -export { workerQueue }; diff --git a/apps/webapp/app/v3/commonWorker.server.ts b/apps/webapp/app/v3/commonWorker.server.ts index 16dc388a6d0..38f9ea94265 100644 --- a/apps/webapp/app/v3/commonWorker.server.ts +++ b/apps/webapp/app/v3/commonWorker.server.ts @@ -17,14 +17,6 @@ import { DeliverAlertService } from "./services/alerts/deliverAlert.server"; import { PerformDeploymentAlertsService } from "./services/alerts/performDeploymentAlerts.server"; import { PerformTaskRunAlertsService } from "./services/alerts/performTaskRunAlerts.server"; import { BatchTriggerV3Service } from "./services/batchTriggerV3.server"; -import { CancelDevSessionRunsService } from "./services/cancelDevSessionRuns.server"; -import { CancelTaskAttemptDependenciesService } from "./services/cancelTaskAttemptDependencies.server"; -import { EnqueueDelayedRunService } from "./services/enqueueDelayedRun.server"; -import { ExecuteTasksWaitingForDeployService } from "./services/executeTasksWaitingForDeploy"; -import { ExpireEnqueuedRunService } from "./services/expireEnqueuedRun.server"; -import { ResumeBatchRunService } from "./services/resumeBatchRun.server"; -import { ResumeTaskDependencyService } from "./services/resumeTaskDependency.server"; -import { RetryAttemptService } from "./services/retryAttempt.server"; import { TimeoutDeploymentService } from "./services/timeoutDeployment.server"; import { BulkActionService } from "./services/bulk/BulkActionV2.server"; @@ -66,25 +58,6 @@ function initializeWorker() { maxAttempts: 3, }, }, - "v3.resumeBatchRun": { - schema: z.object({ - batchRunId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 5, - }, - }, - "v3.resumeTaskDependency": { - schema: z.object({ - dependencyId: z.string(), - sourceTaskAttemptId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 5, - }, - }, "v3.timeoutDeployment": { schema: z.object({ deploymentId: z.string(), @@ -96,45 +69,6 @@ function initializeWorker() { maxAttempts: 5, }, }, - "v3.executeTasksWaitingForDeploy": { - schema: z.object({ - backgroundWorkerId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 5, - }, - }, - "v3.retryAttempt": { - schema: z.object({ - runId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 3, - }, - }, - "v3.cancelTaskAttemptDependencies": { - schema: z.object({ - attemptId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 8, - }, - }, - "v3.cancelDevSessionRuns": { - schema: z.object({ - runIds: z.array(z.string()), - cancelledAt: z.coerce.date(), - reason: z.string(), - cancelledSessionId: z.string().optional(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 5, - }, - }, // @deprecated, moved to batchTriggerWorker.server.ts "v3.processBatchTaskRun": { schema: z.object({ @@ -192,24 +126,6 @@ function initializeWorker() { maxAttempts: 3, }, }, - "v3.expireRun": { - schema: z.object({ - runId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 6, - }, - }, - "v3.enqueueDelayedRun": { - schema: z.object({ - runId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 6, - }, - }, processBulkAction: { schema: z.object({ bulkActionId: z.string(), @@ -239,34 +155,10 @@ function initializeWorker() { "attio.syncUser": async ({ payload }) => { await runAttioUserSync(payload); }, - "v3.resumeBatchRun": async ({ payload }) => { - const service = new ResumeBatchRunService(); - await service.call(payload.batchRunId); - }, - "v3.resumeTaskDependency": async ({ payload }) => { - const service = new ResumeTaskDependencyService(); - await service.call(payload.dependencyId, payload.sourceTaskAttemptId); - }, "v3.timeoutDeployment": async ({ payload }) => { const service = new TimeoutDeploymentService(); await service.call(payload.deploymentId, payload.fromStatus, payload.errorMessage); }, - "v3.executeTasksWaitingForDeploy": async ({ payload }) => { - const service = new ExecuteTasksWaitingForDeployService(); - await service.call(payload.backgroundWorkerId); - }, - "v3.retryAttempt": async ({ payload }) => { - const service = new RetryAttemptService(); - await service.call(payload.runId); - }, - "v3.cancelTaskAttemptDependencies": async ({ payload }) => { - const service = new CancelTaskAttemptDependenciesService(); - await service.call(payload.attemptId); - }, - "v3.cancelDevSessionRuns": async ({ payload }) => { - const service = new CancelDevSessionRunsService(); - await service.call(payload); - }, // @deprecated, moved to batchTriggerWorker.server.ts "v3.processBatchTaskRun": async ({ payload }) => { const service = new BatchTriggerV3Service(payload.strategy); @@ -294,16 +186,6 @@ function initializeWorker() { const service = new PerformTaskRunAlertsService(); await service.call(payload.runId); }, - "v3.expireRun": async ({ payload }) => { - const service = new ExpireEnqueuedRunService(); - - await service.call(payload.runId); - }, - "v3.enqueueDelayedRun": async ({ payload }) => { - const service = new EnqueueDelayedRunService(); - - await service.call(payload.runId); - }, processBulkAction: async ({ payload }) => { const service = new BulkActionService(); await service.process(payload.bulkActionId); diff --git a/apps/webapp/app/v3/engineDeprecation.server.ts b/apps/webapp/app/v3/engineDeprecation.server.ts index 7e7dad96542..a7a9ea35f7b 100644 --- a/apps/webapp/app/v3/engineDeprecation.server.ts +++ b/apps/webapp/app/v3/engineDeprecation.server.ts @@ -1,22 +1,5 @@ -import { env } from "~/env.server"; - -/** - * Graceful sunset of the v3 engine (RunEngineVersion.V1). - * - * v3 maps to engine V1 (MarQS + Graphile); v4 is engine V2 (run-engine). A - * single master flag (DEPRECATE_V3_ENABLED, default off) gates every shutdown - * behaviour so the cloud can flip the switch while self-hosted instances still - * on V1 keep working until they migrate. This mirrors - * DEPRECATE_V3_CLI_DEPLOYS_ENABLED, which already gates deploys. - * - * The flag controls three surfaces: - * 1. Triggers that resolve to V1 are rejected with a graceful error. - * 2. The legacy `trigger dev` websocket (v3 CLIs only) is closed. - * 3. V1 run-lifecycle background jobs become no-ops to shed database load. - * - * Every call site also checks the run/project is actually V1, so v4 (V2) is - * never affected. - */ +// User-facing deprecation messages returned when a retired v3 (engine V1) SDK/CLI +// still triggers, reschedules, or opens the legacy dev websocket. export const V3_MIGRATION_URL = "https://trigger.dev/docs/migrating-from-v3"; @@ -24,11 +7,3 @@ export const V3_TRIGGER_DEPRECATION_MESSAGE = `Trigger.dev v3 is no longer suppo // Sent as a websocket close reason, which is capped at 123 bytes, so keep it short. export const V3_DEV_DEPRECATION_MESSAGE = `Trigger.dev v3 is no longer supported. Upgrade to v4: ${V3_MIGRATION_URL}`; - -/** - * Whether the v3 (engine V1) shutdown is being enforced. Guard every V1-only - * code path with `isV3Disabled() && ` so v4 is untouched. - */ -export function isV3Disabled(): boolean { - return env.DEPRECATE_V3_ENABLED === "1"; -} diff --git a/apps/webapp/app/v3/failedTaskRun.server.ts b/apps/webapp/app/v3/failedTaskRun.server.ts deleted file mode 100644 index aac6aff7ca5..00000000000 --- a/apps/webapp/app/v3/failedTaskRun.server.ts +++ /dev/null @@ -1,304 +0,0 @@ -import type { - TaskRunExecution, - TaskRunExecutionRetry, - TaskRunFailedExecutionResult, - V3TaskRunExecution, -} from "@trigger.dev/core/v3"; -import { calculateNextRetryDelay, RetryOptions } from "@trigger.dev/core/v3"; -import type { Prisma, TaskRun } from "@trigger.dev/database"; -import * as semver from "semver"; -import { logger } from "~/services/logger.server"; -import { sharedQueueTasks } from "./marqs/sharedQueueConsumer.server"; -import { BaseService } from "./services/baseService.server"; -import { CompleteAttemptService } from "./services/completeAttempt.server"; -import { CreateTaskRunAttemptService } from "./services/createTaskRunAttempt.server"; -import { isFailableRunStatus, isFinalAttemptStatus } from "./taskStatus"; - -const FailedTaskRunRetryGetPayload = { - select: { - id: true, - attempts: { - orderBy: { - createdAt: "desc", - }, - take: 1, - }, - lockedById: true, // task - lockedToVersionId: true, // worker - }, -} as const; - -type TaskRunWithAttempts = Prisma.TaskRunGetPayload; - -export class FailedTaskRunService extends BaseService { - public async call(anyRunId: string, completion: TaskRunFailedExecutionResult) { - logger.debug("[FailedTaskRunService] Handling failed task run", { anyRunId, completion }); - - const isFriendlyId = anyRunId.startsWith("run_"); - - const taskRun = await this.runStore.findRun( - { - friendlyId: isFriendlyId ? anyRunId : undefined, - id: !isFriendlyId ? anyRunId : undefined, - }, - this._prisma - ); - - if (!taskRun) { - logger.error("[FailedTaskRunService] Task run not found", { - anyRunId, - completion, - }); - - return; - } - - if (!isFailableRunStatus(taskRun.status)) { - logger.error("[FailedTaskRunService] Task run is not in a failable state", { - taskRun, - completion, - }); - - return; - } - - const retryHelper = new FailedTaskRunRetryHelper(this._prisma); - const retryResult = await retryHelper.call({ - runId: taskRun.id, - completion, - }); - - logger.debug("[FailedTaskRunService] Completion result", { - runId: taskRun.id, - result: retryResult, - }); - } -} - -interface TaskRunWithWorker extends TaskRun { - lockedBy: { retryConfig: Prisma.JsonValue } | null; - lockedToVersion: { sdkVersion: string } | null; -} - -export class FailedTaskRunRetryHelper extends BaseService { - async call({ - runId, - completion, - isCrash, - }: { - runId: string; - completion: TaskRunFailedExecutionResult; - isCrash?: boolean; - }) { - const taskRun = await this.runStore.findRun( - { - id: runId, - }, - FailedTaskRunRetryGetPayload, - this._prisma - ); - - if (!taskRun) { - logger.error("[FailedTaskRunRetryHelper] Task run not found", { - runId, - completion, - }); - - return "NO_TASK_RUN"; - } - - const retriableExecution = await this.#getRetriableAttemptExecution(taskRun, completion); - - if (!retriableExecution) { - return "NO_EXECUTION"; - } - - logger.debug("[FailedTaskRunRetryHelper] Completing attempt", { taskRun, completion }); - - const completeAttempt = new CompleteAttemptService({ - prisma: this._prisma, - isSystemFailure: !isCrash, - isCrash, - }); - const completeResult = await completeAttempt.call({ - completion, - execution: retriableExecution, - }); - - return completeResult; - } - - async #getRetriableAttemptExecution( - run: TaskRunWithAttempts, - completion: TaskRunFailedExecutionResult - ): Promise { - let attempt = run.attempts[0]; - - // We need to create an attempt if: - // - None exists yet - // - The last attempt has a final status, e.g. we failed between attempts - if (!attempt || isFinalAttemptStatus(attempt.status)) { - logger.debug("[FailedTaskRunRetryHelper] No attempts found", { - run, - completion, - }); - - const createAttempt = new CreateTaskRunAttemptService(this._prisma); - - try { - const { execution } = await createAttempt.call({ - runId: run.id, - // This ensures we correctly respect `maxAttempts = 1` when failing before the first attempt was created - startAtZero: true, - }); - return execution; - } catch (error) { - logger.error("[FailedTaskRunRetryHelper] Failed to create attempt", { - run, - completion, - error, - }); - - return; - } - } - - // We already have an attempt with non-final status, let's use it - try { - const executionPayload = await sharedQueueTasks.getExecutionPayloadFromAttempt({ - id: attempt.id, - skipStatusChecks: true, - }); - - return executionPayload?.execution; - } catch (error) { - logger.error("[FailedTaskRunRetryHelper] Failed to get execution payload", { - run, - completion, - error, - }); - - return; - } - } - - static getExecutionRetry({ - run, - execution, - }: { - run: TaskRunWithWorker; - execution: TaskRunExecution; - }): TaskRunExecutionRetry | undefined { - try { - const retryConfig = FailedTaskRunRetryHelper.getRetryConfig({ run, execution }); - if (!retryConfig) { - return; - } - - const delay = calculateNextRetryDelay(retryConfig, execution.attempt.number); - - if (!delay) { - logger.debug("[FailedTaskRunRetryHelper] No more retries", { - run, - execution, - }); - - return; - } - - return { - timestamp: Date.now() + delay, - delay, - }; - } catch (error) { - logger.error("[FailedTaskRunRetryHelper] Failed to get execution retry", { - run, - execution, - error, - }); - - return; - } - } - - static getRetryConfig({ - run, - execution, - }: { - run: TaskRunWithWorker; - execution: TaskRunExecution; - }): RetryOptions | undefined { - try { - const retryConfig = run.lockedBy?.retryConfig; - - if (!retryConfig) { - if (!run.lockedToVersion) { - logger.error("[FailedTaskRunRetryHelper] Run not locked to version", { - run, - execution, - }); - - return; - } - - const sdkVersion = run.lockedToVersion.sdkVersion ?? "0.0.0"; - const isValid = semver.valid(sdkVersion); - - if (!isValid) { - logger.error("[FailedTaskRunRetryHelper] Invalid SDK version", { - run, - execution, - }); - - return; - } - - // With older SDK versions, tasks only have a retry config stored in the DB if it's explicitly defined on the task itself - // It won't get populated with retry.default in trigger.config.ts - if (semver.lt(sdkVersion, FailedTaskRunRetryHelper.DEFAULT_RETRY_CONFIG_SINCE_VERSION)) { - logger.warn( - "[FailedTaskRunRetryHelper] SDK version not recent enough to determine retry config", - { - run, - execution, - } - ); - - return; - } - } - - const parsedRetryConfig = RetryOptions.nullable().safeParse(retryConfig); - - if (!parsedRetryConfig.success) { - logger.error("[FailedTaskRunRetryHelper] Invalid retry config", { - run, - execution, - }); - - return; - } - - if (!parsedRetryConfig.data) { - logger.debug("[FailedTaskRunRetryHelper] No retry config", { - run, - execution, - }); - - return; - } - - return parsedRetryConfig.data; - } catch (error) { - logger.error("[FailedTaskRunRetryHelper] Failed to get execution retry", { - run, - execution, - error, - }); - - return; - } - } - - static DEFAULT_RETRY_CONFIG_SINCE_VERSION = "3.1.0"; -} diff --git a/apps/webapp/app/v3/handleSocketIo.server.ts b/apps/webapp/app/v3/handleSocketIo.server.ts index d34e4c0f1bb..1ca0ab9e25d 100644 --- a/apps/webapp/app/v3/handleSocketIo.server.ts +++ b/apps/webapp/app/v3/handleSocketIo.server.ts @@ -1,43 +1,21 @@ import type { EventBusEventArgs } from "@internal/run-engine"; import { createAdapter } from "@socket.io/redis-adapter"; -import { - ClientToSharedQueueMessages, - CoordinatorSocketData, - CoordinatorToPlatformMessages, - PlatformToCoordinatorMessages, - PlatformToProviderMessages, - ProviderToPlatformMessages, - SharedQueueToClientMessages, -} from "@trigger.dev/core/v3"; import { RunId } from "@trigger.dev/core/v3/isomorphic"; import type { WorkerClientToServerEvents, WorkerServerToClientEvents, } from "@trigger.dev/core/v3/workers"; -import { ZodNamespace } from "@trigger.dev/core/v3/zodNamespace"; import { defaultReconnectOnError } from "@internal/redis"; import { Redis } from "ioredis"; import type { Namespace, Socket } from "socket.io"; import { Server } from "socket.io"; import { env } from "~/env.server"; -import { findEnvironmentById } from "~/models/runtimeEnvironment.server"; import { authenticateApiRequestWithFailure } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; import { singleton } from "~/utils/singleton"; import { recordRunDebugLog } from "./eventRepository/index.server"; -import { sharedQueueTasks } from "./marqs/sharedQueueConsumer.server"; import { engine } from "./runEngine.server"; -import { CompleteAttemptService } from "./services/completeAttempt.server"; -import { CrashTaskRunService } from "./services/crashTaskRun.server"; -import { CreateCheckpointService } from "./services/createCheckpoint.server"; -import { CreateDeploymentBackgroundWorkerServiceV3 } from "./services/createDeploymentBackgroundWorkerV3.server"; -import { CreateTaskRunAttemptService } from "./services/createTaskRunAttempt.server"; -import { DeploymentIndexFailed } from "./services/deploymentIndexFailed.server"; -import { ResumeAttemptService } from "./services/resumeAttempt.server"; -import { UpdateFatalRunErrorService } from "./services/updateFatalRunError.server"; import { WorkerGroupTokenService } from "./services/worker/workerGroupTokenService.server"; -import { SharedSocketConnection } from "./sharedSocketConnection"; -import { isV3Disabled } from "./engineDeprecation.server"; export const socketIo = singleton("socketIo", initalizeIoServer); @@ -48,9 +26,6 @@ function initalizeIoServer() { logger.log(`[socket.io][${socket.id}] connection at url: ${socket.request.url}`); }); - const coordinatorNamespace = createCoordinatorNamespace(io); - const providerNamespace = createProviderNamespace(io); - const sharedQueueConsumerNamespace = createSharedQueueConsumerNamespace(io); const workerNamespace = createWorkerNamespace({ io, namespace: "/worker", @@ -80,9 +55,6 @@ function initalizeIoServer() { return { io, - coordinatorNamespace, - providerNamespace, - sharedQueueConsumerNamespace, workerNamespace, devWorkerNamespace, }; @@ -114,350 +86,6 @@ function initializeSocketIOServerInstance() { return new Server(); } -function createCoordinatorNamespace(io: Server) { - const coordinator = new ZodNamespace({ - // @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking - io, - name: "coordinator", - authToken: env.COORDINATOR_SECRET, - clientMessages: CoordinatorToPlatformMessages, - serverMessages: PlatformToCoordinatorMessages, - socketData: CoordinatorSocketData, - handlers: { - READY_FOR_EXECUTION: async (message) => { - const payload = await sharedQueueTasks.getLatestExecutionPayloadFromRun( - message.runId, - true, - !!message.totalCompletions - ); - - if (!payload) { - logger.error("Failed to retrieve execution payload", message); - return { success: false }; - } else { - return { success: true, payload }; - } - }, - READY_FOR_LAZY_ATTEMPT: async (message) => { - try { - const payload = await sharedQueueTasks.getLazyAttemptPayload( - message.envId, - message.runId - ); - - if (!payload) { - logger.error( - "READY_FOR_LAZY_ATTEMPT: Failed to retrieve lazy attempt payload", - message - ); - return { success: false, reason: "READY_FOR_LAZY_ATTEMPT: Failed to retrieve payload" }; - } - - return { success: true, lazyPayload: payload }; - } catch (error) { - logger.error("READY_FOR_LAZY_ATTEMPT: Error while creating lazy attempt", { - runId: message.runId, - envId: message.envId, - totalCompletions: message.totalCompletions, - error, - }); - return { success: false }; - } - }, - READY_FOR_RESUME: async (message) => { - const resumeAttempt = new ResumeAttemptService(); - await resumeAttempt.call(message); - }, - TASK_RUN_COMPLETED: async (message) => { - const completeAttempt = new CompleteAttemptService({ - supportsRetryCheckpoints: message.version === "v1", - }); - await completeAttempt.call({ - completion: message.completion, - execution: message.execution, - checkpoint: message.checkpoint, - }); - }, - TASK_RUN_COMPLETED_WITH_ACK: async (message) => { - try { - const completeAttempt = new CompleteAttemptService({ - supportsRetryCheckpoints: message.version === "v1", - }); - await completeAttempt.call({ - completion: message.completion, - execution: message.execution, - checkpoint: message.checkpoint, - }); - - return { - success: true, - }; - } catch (error) { - const friendlyError = - error instanceof Error - ? { - name: error.name, - message: error.message, - stack: error.stack, - } - : { - name: "UnknownError", - message: String(error), - }; - - logger.error("Error while completing attempt with ack", { - error: friendlyError, - message, - }); - - return { - success: false, - error: friendlyError, - }; - } - }, - TASK_RUN_FAILED_TO_RUN: async (message) => { - await sharedQueueTasks.taskRunFailed(message.completion); - }, - TASK_HEARTBEAT: async (message) => { - await sharedQueueTasks.taskHeartbeat(message.attemptFriendlyId); - }, - TASK_RUN_HEARTBEAT: async (message) => { - await sharedQueueTasks.taskRunHeartbeat(message.runId); - }, - CHECKPOINT_CREATED: async (message) => { - try { - const createCheckpoint = new CreateCheckpointService(); - const result = await createCheckpoint.call(message); - - return { keepRunAlive: result?.keepRunAlive ?? false }; - } catch (error) { - logger.error("Error while creating checkpoint", { - rawMessage: message, - error: error instanceof Error ? error.message : error, - }); - - return { keepRunAlive: false }; - } - }, - CREATE_WORKER: async (message) => { - try { - const environment = await findEnvironmentById(message.envId); - - if (!environment) { - logger.error("Environment not found", { id: message.envId }); - return { success: false }; - } - - const service = new CreateDeploymentBackgroundWorkerServiceV3(); - const worker = await service.call(message.projectRef, environment, message.deploymentId, { - localOnly: false, - metadata: message.metadata, - supportsLazyAttempts: message.version !== "v1" && message.supportsLazyAttempts, - }); - - return { success: !!worker }; - } catch (error) { - logger.error("Error while creating worker", { - error, - envId: message.envId, - projectRef: message.projectRef, - deploymentId: message.deploymentId, - version: message.version, - }); - return { success: false }; - } - }, - CREATE_TASK_RUN_ATTEMPT: async (message) => { - try { - const environment = await findEnvironmentById(message.envId); - - if (!environment) { - logger.error("CREATE_TASK_RUN_ATTEMPT: Environment not found", message); - return { success: false, reason: "Environment not found" }; - } - - const service = new CreateTaskRunAttemptService(); - const { attempt } = await service.call({ - runId: message.runId, - authenticatedEnv: environment, - setToExecuting: false, - }); - - const payload = await sharedQueueTasks.getExecutionPayloadFromAttempt({ - id: attempt.id, - setToExecuting: true, - skipStatusChecks: true, - }); - - if (!payload) { - logger.error( - "CREATE_TASK_RUN_ATTEMPT: Failed to retrieve payload after attempt creation", - message - ); - return { - success: false, - reason: "CREATE_TASK_RUN_ATTEMPT: Failed to retrieve payload", - }; - } - - return { success: true, executionPayload: payload }; - } catch (error) { - logger.error("CREATE_TASK_RUN_ATTEMPT: Error while creating attempt", { - ...message, - error, - }); - return { success: false }; - } - }, - INDEXING_FAILED: async (message) => { - try { - const service = new DeploymentIndexFailed(); - - await service.call(message.deploymentId, message.error); - } catch (error) { - logger.error("Error while processing index failure", { - deploymentId: message.deploymentId, - error, - }); - } - }, - RUN_CRASHED: async (message) => { - try { - const service = new CrashTaskRunService(); - - await service.call(message.runId, { - reason: `${message.error.name}: ${message.error.message}`, - logs: message.error.stack, - }); - } catch (error) { - logger.error("Error while processing run failure", { - runId: message.runId, - error, - }); - } - }, - }, - onConnection: async (socket, handler, sender, logger) => { - if (socket.data.supportsDynamicConfig) { - socket.emit("DYNAMIC_CONFIG", { - version: "v1", - checkpointThresholdInMs: env.CHECKPOINT_THRESHOLD_IN_MS, - }); - } - }, - postAuth: async (socket, next, logger) => { - function setSocketDataFromHeader( - dataKey: keyof typeof socket.data, - headerName: string, - required: boolean = true - ) { - const value = socket.handshake.headers[headerName]; - - if (value) { - socket.data[dataKey] = Array.isArray(value) ? value[0] : value; - return; - } - - if (required) { - logger.error("missing required header", { headerName }); - throw new Error("missing header"); - } - } - - try { - setSocketDataFromHeader("supportsDynamicConfig", "x-supports-dynamic-config", false); - } catch (error) { - logger.error("setSocketDataFromHeader error", { error }); - socket.disconnect(true); - return; - } - - logger.debug("success", socket.data); - - next(); - }, - }); - - return coordinator.namespace; -} - -function createProviderNamespace(io: Server) { - const provider = new ZodNamespace({ - // @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking - io, - name: "provider", - authToken: env.PROVIDER_SECRET, - clientMessages: ProviderToPlatformMessages, - serverMessages: PlatformToProviderMessages, - handlers: { - WORKER_CRASHED: async (message) => { - try { - if (message.overrideCompletion) { - const updateErrorService = new UpdateFatalRunErrorService(); - await updateErrorService.call(message.runId, { ...message }); - } else { - const crashRunService = new CrashTaskRunService(); - await crashRunService.call(message.runId, { ...message }); - } - } catch (error) { - logger.error("Error while handling crashed worker", { error }); - } - }, - INDEXING_FAILED: async (message) => { - try { - const service = new DeploymentIndexFailed(); - - await service.call(message.deploymentId, message.error, message.overrideCompletion); - } catch (e) { - logger.error("Error while indexing", { error: e }); - } - }, - }, - }); - - return provider.namespace; -} - -function createSharedQueueConsumerNamespace(io: Server) { - const sharedQueue = new ZodNamespace({ - // @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking - io, - name: "shared-queue", - authToken: env.PROVIDER_SECRET, - clientMessages: ClientToSharedQueueMessages, - serverMessages: SharedQueueToClientMessages, - onConnection: async (socket, handler, sender, logger) => { - // v3 (engine V1) shutdown: don't start the MarQS shared-queue consumer, so no - // deployed V1 runs are dequeued. This namespace is V1-only; v4 dequeues through - // the run-engine worker path. This is the code-level equivalent of taking the - // v3 coordinator offline. - if (isV3Disabled()) { - logger.warn("Refusing /shared-queue connection: v3 engine is shut down"); - socket.disconnect(true); - return; - } - - const sharedSocketConnection = new SharedSocketConnection({ - // @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking - namespace: sharedQueue.namespace, - // @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking - socket, - logger, - poolSize: env.SHARED_QUEUE_CONSUMER_POOL_SIZE, - }); - - sharedSocketConnection.onClose.attach((closeEvent) => { - logger.info("Socket closed", { closeEvent }); - }); - - await sharedSocketConnection.initialize(); - }, - }); - - return sharedQueue.namespace; -} - function headersFromHandshake(handshake: Socket["handshake"]) { const headers = new Headers(); diff --git a/apps/webapp/app/v3/legacyRunEngineWorker.server.ts b/apps/webapp/app/v3/legacyRunEngineWorker.server.ts index 7e4458b0175..d6b670e847c 100644 --- a/apps/webapp/app/v3/legacyRunEngineWorker.server.ts +++ b/apps/webapp/app/v3/legacyRunEngineWorker.server.ts @@ -4,10 +4,8 @@ import { z } from "zod"; import { env } from "~/env.server"; import { logger } from "~/services/logger.server"; import { singleton } from "~/utils/singleton"; -import { TaskRunHeartbeatFailedService } from "./taskRunHeartbeatFailed.server"; import { completeBatchTaskRunItemV3, tryCompleteBatchV3 } from "./services/batchTriggerV3.server"; import { prisma } from "~/db.server"; -import { marqs } from "./marqs/index.server"; function initializeWorker() { const redisOptions = { @@ -28,15 +26,6 @@ function initializeWorker() { name: "legacy-run-engine-worker", redisOptions, catalog: { - runHeartbeat: { - schema: z.object({ - runId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 3, - }, - }, completeBatchTaskRunItem: { schema: z.object({ itemId: z.string(), @@ -60,15 +49,6 @@ function initializeWorker() { maxAttempts: 5, }, }, - scheduleRequeueMessage: { - schema: z.object({ - messageId: z.string(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 5, - }, - }, }, concurrency: { workers: env.LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_WORKERS, @@ -80,11 +60,6 @@ function initializeWorker() { shutdownTimeoutMs: env.LEGACY_RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS, logger: new Logger("LegacyRunEngineWorker", env.LEGACY_RUN_ENGINE_WORKER_LOG_LEVEL), jobs: { - runHeartbeat: async ({ payload }) => { - const service = new TaskRunHeartbeatFailedService(); - - await service.call(payload.runId); - }, completeBatchTaskRunItem: async ({ payload, attempt }) => { await completeBatchTaskRunItemV3( payload.itemId, @@ -98,9 +73,6 @@ function initializeWorker() { tryCompleteBatchV3: async ({ payload }) => { await tryCompleteBatchV3(payload.batchId, prisma, payload.scheduleResumeOnComplete); }, - scheduleRequeueMessage: async ({ payload }) => { - await marqs.requeueMessageById(payload.messageId); - }, }, }); diff --git a/apps/webapp/app/v3/marqs/asyncWorker.server.ts b/apps/webapp/app/v3/marqs/asyncWorker.server.ts deleted file mode 100644 index 64171f36654..00000000000 --- a/apps/webapp/app/v3/marqs/asyncWorker.server.ts +++ /dev/null @@ -1,37 +0,0 @@ -export class AsyncWorker { - private running = false; - private timeout?: NodeJS.Timeout; - - constructor( - private readonly fn: () => Promise, - private readonly interval: number - ) {} - - start() { - if (this.running) { - return; - } - - this.running = true; - - this.#run(); - } - - stop() { - this.running = false; - } - - async #run() { - if (!this.running) { - return; - } - - try { - await this.fn(); - } catch (e) { - console.error(e); - } - - this.timeout = setTimeout(this.#run.bind(this), this.interval); - } -} diff --git a/apps/webapp/app/v3/marqs/concurrencyMonitor.server.ts b/apps/webapp/app/v3/marqs/concurrencyMonitor.server.ts deleted file mode 100644 index 3f974b9aeb4..00000000000 --- a/apps/webapp/app/v3/marqs/concurrencyMonitor.server.ts +++ /dev/null @@ -1,204 +0,0 @@ -import type { Logger } from "@trigger.dev/core/logger"; -import type { Redis } from "ioredis"; -import { prisma } from "~/db.server"; -import { logger } from "~/services/logger.server"; -import type { MarQS } from "./index.server"; -import { marqs as marqsv3 } from "./index.server"; -import { env } from "~/env.server"; - -export type MarqsConcurrencyMonitorOptions = { - dryRun?: boolean; - abortSignal?: AbortSignal; -}; - -export interface MarqsConcurrencyResolveCompletedRunsCallback { - (candidateRunIds: string[]): Promise>; -} - -export class MarqsConcurrencyMonitor { - private _logger: Logger; - - constructor( - private marqs: MarQS, - private callback: MarqsConcurrencyResolveCompletedRunsCallback, - private options: MarqsConcurrencyMonitorOptions = {} - ) { - this._logger = logger.child({ - component: "marqs", - operation: "concurrencyMonitor", - dryRun: this.dryRun, - marqs: marqs.name, - }); - } - - get dryRun() { - return typeof this.options.dryRun === "boolean" ? this.options.dryRun : false; - } - - get keys() { - return this.marqs.keys; - } - - get signal() { - return this.options.abortSignal; - } - - public async call() { - this._logger.debug("[MarqsConcurrencyMonitor] Initiating monitoring"); - - const stats = { - streamCallbacks: 0, - processedKeys: 0, - }; - - const { stream, redis } = this.marqs.queueConcurrencyScanStream( - 10, - () => { - this._logger.debug("[MarqsConcurrencyMonitor] stream closed", { - stats, - }); - }, - (error) => { - this._logger.debug("[MarqsConcurrencyMonitor] stream error", { - stats, - error: { - name: error.name, - message: error.message, - stack: error.stack, - }, - }); - } - ); - - stream.on("data", async (keys) => { - stream.pause(); - - if (this.signal?.aborted) { - stream.destroy(); - return; - } - - stats.streamCallbacks++; - - const uniqueKeys = Array.from(new Set(keys)); - - if (uniqueKeys.length === 0) { - stream.resume(); - return; - } - - this._logger.debug("[MarqsConcurrencyMonitor] correcting queues concurrency", { - keys: uniqueKeys, - }); - - stats.processedKeys += uniqueKeys.length; - - await Promise.allSettled(uniqueKeys.map((key) => this.#processKey(key, redis))).finally( - () => { - stream.resume(); - } - ); - }); - } - - async #processKey(key: string, redis: Redis) { - key = this.keys.stripKeyPrefix(key); - const envKey = this.keys.envCurrentConcurrencyKeyFromQueue(key); - - let runIds: string[] = []; - - try { - // Next, we need to get all the items from the key, and any parent keys (org, env, queue) using sunion. - runIds = await redis.sunion(envKey, key); - } catch (e) { - this._logger.error("[MarqsConcurrencyMonitor] error during sunion", { - key, - envKey, - runIds, - error: e, - }); - } - - if (runIds.length === 0) { - return; - } - - const perfNow = performance.now(); - - const completeRuns = await this.callback(runIds); - - const durationMs = performance.now() - perfNow; - - const completedRunIds = completeRuns.map((run) => run.id); - - if (completedRunIds.length === 0) { - this._logger.debug("[MarqsConcurrencyMonitor] no completed runs found", { - key, - envKey, - runIds, - durationMs, - }); - - return; - } - - this._logger.debug("[MarqsConcurrencyMonitor] removing completed runs from queue", { - key, - envKey, - completedRunIds, - durationMs, - }); - - if (this.dryRun) { - return; - } - - const pipeline = redis.pipeline(); - - pipeline.srem(key, ...completedRunIds); - pipeline.srem(envKey, ...completedRunIds); - - try { - await pipeline.exec(); - } catch (e) { - this._logger.error("[MarqsConcurrencyMonitor] error removing completed runs from queue", { - key, - envKey, - completedRunIds, - error: e, - }); - } - } - - static async initiateV3Monitoring(abortSignal?: AbortSignal) { - if (!marqsv3) { - return; - } - - const instance = new MarqsConcurrencyMonitor( - marqsv3, - (runIds) => - prisma.taskRun.findMany({ - select: { id: true }, - where: { - id: { - in: runIds, - }, - status: { - in: [ - "CANCELED", - "COMPLETED_SUCCESSFULLY", - "COMPLETED_WITH_ERRORS", - "CRASHED", - "SYSTEM_FAILURE", - "INTERRUPTED", - ], - }, - }, - }), - { dryRun: env.V3_MARQS_CONCURRENCY_MONITOR_ENABLED === "0", abortSignal } - ); - - await instance.call(); - } -} diff --git a/apps/webapp/app/v3/marqs/constants.server.ts b/apps/webapp/app/v3/marqs/constants.server.ts deleted file mode 100644 index 6ba8dd2a6ae..00000000000 --- a/apps/webapp/app/v3/marqs/constants.server.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET = 31_556_952 * 1000; // 1 year -export const MARQS_RETRY_PRIORITY_TIMESTAMP_OFFSET = 15_778_476 * 1000; // 6 months -export const MARQS_DELAYED_REQUEUE_THRESHOLD_IN_MS = 500; -export const MARQS_SCHEDULED_REQUEUE_AVAILABLE_AT_THRESHOLD_IN_MS = 500; diff --git a/apps/webapp/app/v3/marqs/devPubSub.server.ts b/apps/webapp/app/v3/marqs/devPubSub.server.ts deleted file mode 100644 index 9e33ee95f0d..00000000000 --- a/apps/webapp/app/v3/marqs/devPubSub.server.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { z } from "zod"; -import { singleton } from "~/utils/singleton"; -import type { ZodSubscriber } from "../utils/zodPubSub.server"; -import { ZodPubSub } from "../utils/zodPubSub.server"; -import { env } from "~/env.server"; -import { Gauge } from "prom-client"; -import { metricsRegister } from "~/metrics.server"; - -const messageCatalog = { - CANCEL_ATTEMPT: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - attemptId: z.string(), - taskRunId: z.string(), - }), -}; - -export type DevSubscriber = ZodSubscriber; - -export const devPubSub = singleton("devPubSub", initializeDevPubSub); - -function initializeDevPubSub() { - const pubSub = new ZodPubSub({ - redis: { - port: env.PUBSUB_REDIS_PORT, - host: env.PUBSUB_REDIS_HOST, - username: env.PUBSUB_REDIS_USERNAME, - password: env.PUBSUB_REDIS_PASSWORD, - tlsDisabled: env.PUBSUB_REDIS_TLS_DISABLED === "true", - clusterMode: env.PUBSUB_REDIS_CLUSTER_MODE_ENABLED === "1", - }, - schema: messageCatalog, - }); - - new Gauge({ - name: "dev_pub_sub_subscribers", - help: "Number of dev pub sub subscribers", - collect() { - this.set(pubSub.subscriberCount); - }, - registers: [metricsRegister], - }); - - return pubSub; -} diff --git a/apps/webapp/app/v3/marqs/devQueueConsumer.server.ts b/apps/webapp/app/v3/marqs/devQueueConsumer.server.ts deleted file mode 100644 index 78c5950141d..00000000000 --- a/apps/webapp/app/v3/marqs/devQueueConsumer.server.ts +++ /dev/null @@ -1,623 +0,0 @@ -import type { Context, Span } from "@opentelemetry/api"; -import { ROOT_CONTEXT, SpanKind, context, trace } from "@opentelemetry/api"; -import type { - V3TaskRunExecution, - TaskRunExecutionLazyAttemptPayload, - TaskRunExecutionResult, - TaskRunFailedExecutionResult, - serverWebsocketMessages, -} from "@trigger.dev/core/v3"; -import { getMaxDuration } from "@trigger.dev/core/v3/isomorphic"; -import type { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler"; -import type { BackgroundWorker, BackgroundWorkerTask } from "@trigger.dev/database"; -import { z } from "zod"; -import { prisma } from "~/db.server"; -import { createNewSession, disconnectSession } from "~/models/runtimeEnvironment.server"; -import { findQueueInEnvironment, sanitizeQueueName } from "~/models/taskQueue.server"; -import type { RedisClient } from "~/redis.server"; -import { createRedisClient } from "~/redis.server"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { marqs } from "~/v3/marqs/index.server"; -import { resolveVariablesForEnvironment } from "../environmentVariables/environmentVariablesRepository.server"; -import { FailedTaskRunService } from "../failedTaskRun.server"; -import { CancelDevSessionRunsService } from "../services/cancelDevSessionRuns.server"; -import { CompleteAttemptService } from "../services/completeAttempt.server"; -import { attributesFromAuthenticatedEnv, tracer } from "../tracer.server"; -import type { DevSubscriber } from "./devPubSub.server"; -import { devPubSub } from "./devPubSub.server"; - -const MessageBody = z.discriminatedUnion("type", [ - z.object({ - type: z.literal("EXECUTE"), - taskIdentifier: z.string(), - }), -]); - -type BackgroundWorkerWithTasks = BackgroundWorker & { tasks: BackgroundWorkerTask[] }; - -export type DevQueueConsumerOptions = { - maximumItemsPerTrace?: number; - traceTimeoutSeconds?: number; - ipAddress?: string; -}; - -export class DevQueueConsumer { - private _backgroundWorkers: Map = new Map(); - private _backgroundWorkerSubscriber: Map = new Map(); - private _deprecatedWorkers: Map = new Map(); - private _enabled = false; - private _maximumItemsPerTrace: number; - private _traceTimeoutSeconds: number; - private _perTraceCountdown: number | undefined; - private _lastNewTrace: Date | undefined; - private _currentSpanContext: Context | undefined; - private _taskFailures: number = 0; - private _taskSuccesses: number = 0; - private _currentSpan: Span | undefined; - private _endSpanInNextIteration = false; - private _inProgressRuns: Map = new Map(); // Keys are task run friendly IDs, values are TaskRun internal ids/queue message ids - private _connectionLostAt?: Date; - private _redisClient: RedisClient; - - constructor( - public id: string, - public env: AuthenticatedEnvironment, - private _sender: ZodMessageSender, - private _options: DevQueueConsumerOptions = {} - ) { - this._traceTimeoutSeconds = _options.traceTimeoutSeconds ?? 60; - this._maximumItemsPerTrace = _options.maximumItemsPerTrace ?? 1_000; - this._redisClient = createRedisClient("tr:devQueueConsumer", { - keyPrefix: "tr:devQueueConsumer:", - ...devPubSub.redisOptions, - }); - } - - // This method is called when a background worker is deprecated and will no longer be used unless a run is locked to it - public async deprecateBackgroundWorker(id: string) { - const backgroundWorker = this._backgroundWorkers.get(id); - - if (!backgroundWorker) { - return; - } - - logger.debug("[DevQueueConsumer] Deprecating background worker", { - backgroundWorker: backgroundWorker.id, - env: this.env.id, - }); - - this._deprecatedWorkers.set(id, backgroundWorker); - this._backgroundWorkers.delete(id); - } - - public async registerBackgroundWorker(id: string, inProgressRuns: string[] = []) { - const backgroundWorker = await prisma.backgroundWorker.findFirst({ - where: { friendlyId: id, runtimeEnvironmentId: this.env.id }, - include: { - tasks: true, - }, - }); - - if (!backgroundWorker) { - return; - } - - if (this._backgroundWorkers.has(backgroundWorker.id)) { - return; - } - - this._backgroundWorkers.set(backgroundWorker.id, backgroundWorker); - - logger.debug("[DevQueueConsumer] Registered background worker", { - backgroundWorker: backgroundWorker.id, - inProgressRuns, - env: this.env.id, - }); - - const subscriber = await devPubSub.subscribe(`backgroundWorker:${backgroundWorker.id}:*`); - - subscriber.on("CANCEL_ATTEMPT", async (message) => { - await this._sender.send("BACKGROUND_WORKER_MESSAGE", { - backgroundWorkerId: backgroundWorker.friendlyId, - data: { - type: "CANCEL_ATTEMPT", - taskAttemptId: message.attemptId, - taskRunId: message.taskRunId, - }, - }); - }); - - this._backgroundWorkerSubscriber.set(backgroundWorker.id, subscriber); - - for (const runId of inProgressRuns) { - this._inProgressRuns.set(runId, runId); - } - - // Start reading from the queue if we haven't already - await this.#enable(); - } - - public async taskAttemptCompleted( - workerId: string, - completion: TaskRunExecutionResult, - execution: V3TaskRunExecution - ) { - if (completion.ok) { - this._taskSuccesses++; - } else { - this._taskFailures++; - } - - logger.debug("[DevQueueConsumer] taskAttemptCompleted()", { - taskRunCompletion: completion, - execution, - env: this.env.id, - }); - - const service = new CompleteAttemptService(); - const result = await service.call({ completion, execution, env: this.env }); - - if (result === "COMPLETED") { - this._inProgressRuns.delete(execution.run.id); - } - } - - public async taskRunFailed(workerId: string, completion: TaskRunFailedExecutionResult) { - this._taskFailures++; - - logger.debug("[DevQueueConsumer] taskRunFailed()", { completion, env: this.env.id }); - - this._inProgressRuns.delete(completion.id); - - const service = new FailedTaskRunService(); - - await service.call(completion.id, completion); - } - - /** - * @deprecated Use `taskRunHeartbeat` instead - */ - public async taskHeartbeat(workerId: string, id: string) { - logger.debug("[DevQueueConsumer] taskHeartbeat()", { id }); - - const taskRunAttempt = await prisma.taskRunAttempt.findFirst({ - where: { friendlyId: id }, - }); - - if (!taskRunAttempt) { - return; - } - - await marqs?.heartbeatMessage(taskRunAttempt.taskRunId); - } - - public async taskRunHeartbeat(workerId: string, id: string) { - logger.debug("[DevQueueConsumer] taskRunHeartbeat()", { id }); - - await marqs?.heartbeatMessage(id); - } - - public async stop(reason: string = "CLI disconnected") { - if (!this._enabled) { - return; - } - - logger.debug("[DevQueueConsumer] Stopping dev queue consumer", { env: this.env }); - - this._enabled = false; - - // Create the session - const session = await disconnectSession(this.env.id); - - const runIds = Array.from(this._inProgressRuns.values()); - this._inProgressRuns.clear(); - - if (runIds.length > 0) { - await CancelDevSessionRunsService.enqueue( - { - runIds, - cancelledAt: new Date(), - reason, - cancelledSessionId: session?.id, - }, - new Date(Date.now() + 1000 * 10) // 10 seconds from now - ); - } - - // We need to unsubscribe from the background worker channels - for (const [id, subscriber] of this._backgroundWorkerSubscriber) { - logger.debug("Unsubscribing from background worker channel", { id }); - - await subscriber.stopListening(); - this._backgroundWorkerSubscriber.delete(id); - - logger.debug("Unsubscribed from background worker channel", { id }); - } - - // We need to end the current span - if (this._currentSpan) { - this._currentSpan.end(); - } - } - - async #enable() { - if (this._enabled) { - return; - } - - await this._redisClient.set(`connection:${this.env.id}`, this.id, "EX", 60 * 60 * 24); // 24 hours - - this._enabled = true; - // Create the session - await createNewSession(this.env, this._options.ipAddress ?? "unknown"); - - this._perTraceCountdown = this._options.maximumItemsPerTrace; - this._lastNewTrace = new Date(); - this._taskFailures = 0; - this._taskSuccesses = 0; - - this.#doWork().finally(() => {}); - } - - async #doWork() { - if (!this._enabled) { - return; - } - - const canSendMessage = await this._sender.validateCanSendMessage(); - - if (!canSendMessage) { - this._connectionLostAt ??= new Date(); - - if (Date.now() - this._connectionLostAt.getTime() > 60 * 1000) { - logger.debug("Connection lost for more than 60 seconds, stopping the consumer", { - env: this.env, - }); - - await this.stop("Connection lost for more than 60 seconds"); - return; - } - - setTimeout(() => this.#doWork(), 1000); - return; - } - - this._connectionLostAt = undefined; - - const currentConnection = await this._redisClient.get(`connection:${this.env.id}`); - - if (currentConnection && currentConnection !== this.id) { - logger.debug("Another connection is active, stopping the consumer", { - currentConnection, - env: this.env, - }); - - await this.stop("Another connection is active"); - return; - } - - // Check if the trace has expired - if ( - this._perTraceCountdown === 0 || - Date.now() - this._lastNewTrace!.getTime() > this._traceTimeoutSeconds * 1000 || - this._currentSpanContext === undefined || - this._endSpanInNextIteration - ) { - if (this._currentSpan) { - this._currentSpan.setAttribute("tasks.period.failures", this._taskFailures); - this._currentSpan.setAttribute("tasks.period.successes", this._taskSuccesses); - - logger.debug("Ending DevQueueConsumer.doWork() trace", { - isRecording: this._currentSpan.isRecording(), - }); - - this._currentSpan.end(); - } - - // Create a new trace - this._currentSpan = tracer.startSpan( - "DevQueueConsumer.doWork()", - { - kind: SpanKind.CONSUMER, - attributes: { - ...attributesFromAuthenticatedEnv(this.env), - }, - }, - ROOT_CONTEXT - ); - - // Get the span trace context - this._currentSpanContext = trace.setSpan(ROOT_CONTEXT, this._currentSpan); - - this._perTraceCountdown = this._options.maximumItemsPerTrace; - this._lastNewTrace = new Date(); - this._taskFailures = 0; - this._taskSuccesses = 0; - this._endSpanInNextIteration = false; - } - - return context.with(this._currentSpanContext ?? ROOT_CONTEXT, async () => { - await this.#doWorkInternal(); - this._perTraceCountdown = this._perTraceCountdown! - 1; - }); - } - - async #doWorkInternal() { - // Attempt to dequeue a message from the environment's queue - // If no message is available, reschedule the worker to run again in 1 second - // If a message is available, find the BackgroundWorkerTask that matches the message's taskIdentifier - // If no matching task is found, nack the message and reschedule the worker to run again in 1 second - // If the matching task is found, create the task attempt and lock the task run, then send the task run to the client - // Store the message as a processing message - // If the websocket connection disconnects before the task run is completed, nack the message - // When the task run completes, ack the message - // Using a heartbeat mechanism, if the client keeps responding with a heartbeat, we'll keep the message processing and increase the visibility timeout. - - const message = await marqs?.dequeueMessageInEnv(this.env); - - if (!message) { - setTimeout(() => this.#doWork(), 1000); - return; - } - - const dequeuedStart = Date.now(); - - const messageBody = MessageBody.safeParse(message.data); - - if (!messageBody.success) { - logger.error("Failed to parse message", { - queueMessage: message.data, - error: messageBody.error, - env: this.env, - }); - - await marqs?.acknowledgeMessage( - message.messageId, - "Failed to parse message.data with MessageBody schema in DevQueueConsumer" - ); - - setTimeout(() => this.#doWork(), 100); - return; - } - - const existingTaskRun = await prisma.taskRun.findFirst({ - where: { - id: message.messageId, - }, - }); - - if (!existingTaskRun) { - logger.debug("Failed to find existing task run, acking", { - messageId: message.messageId, - }); - - await marqs?.acknowledgeMessage( - message.messageId, - "Failed to find task run in DevQueueConsumer" - ); - setTimeout(() => this.#doWork(), 100); - return; - } - - const backgroundWorker = existingTaskRun.lockedToVersionId - ? (this._deprecatedWorkers.get(existingTaskRun.lockedToVersionId) ?? - this._backgroundWorkers.get(existingTaskRun.lockedToVersionId)) - : this.#getLatestBackgroundWorker(); - - if (!backgroundWorker) { - logger.debug("Failed to find background worker, acking", { - messageId: message.messageId, - lockedToVersionId: existingTaskRun.lockedToVersionId, - deprecatedWorkers: Array.from(this._deprecatedWorkers.keys()), - backgroundWorkers: Array.from(this._backgroundWorkers.keys()), - latestWorker: this.#getLatestBackgroundWorker(), - }); - - await marqs?.acknowledgeMessage( - message.messageId, - "Failed to find background worker in DevQueueConsumer" - ); - setTimeout(() => this.#doWork(), 100); - return; - } - - const backgroundTask = backgroundWorker.tasks.find( - (task) => task.slug === existingTaskRun.taskIdentifier - ); - - if (!backgroundTask) { - logger.warn("No matching background task found for task run", { - taskRun: existingTaskRun.id, - taskIdentifier: existingTaskRun.taskIdentifier, - backgroundWorker: backgroundWorker.id, - taskSlugs: backgroundWorker.tasks.map((task) => task.slug), - }); - - await marqs?.acknowledgeMessage( - message.messageId, - "No matching background task found in DevQueueConsumer" - ); - - setTimeout(() => this.#doWork(), 100); - return; - } - - const lockedAt = new Date(); - const startedAt = existingTaskRun.startedAt ?? new Date(); - - const lockedTaskRun = await prisma.taskRun.update({ - where: { - id: message.messageId, - }, - data: { - lockedAt, - lockedById: backgroundTask.id, - status: "EXECUTING", - lockedToVersionId: backgroundWorker.id, - taskVersion: backgroundWorker.version, - sdkVersion: backgroundWorker.sdkVersion, - cliVersion: backgroundWorker.cliVersion, - startedAt, - maxDurationInSeconds: getMaxDuration( - existingTaskRun.maxDurationInSeconds, - backgroundTask.maxDurationInSeconds - ), - }, - }); - - if (!lockedTaskRun) { - logger.warn("Failed to lock task run", { - taskRun: existingTaskRun.id, - taskIdentifier: existingTaskRun.taskIdentifier, - backgroundWorker: backgroundWorker.id, - messageId: message.messageId, - }); - - await marqs?.acknowledgeMessage( - message.messageId, - "Failed to lock task run in DevQueueConsumer" - ); - - setTimeout(() => this.#doWork(), 100); - return; - } - - const queue = await findQueueInEnvironment( - lockedTaskRun.queue, - this.env.id, - backgroundTask.id, - backgroundTask - ); - - if (!queue) { - logger.debug("[DevQueueConsumer] Failed to find queue", { - queueName: lockedTaskRun.queue, - sanitizedName: sanitizeQueueName(lockedTaskRun.queue), - taskRun: lockedTaskRun.id, - messageId: message.messageId, - }); - - await marqs?.nackMessage(message.messageId); - setTimeout(() => this.#doWork(), 1000); - return; - } - - if (!this._enabled) { - logger.debug("Dev queue consumer is disabled", { env: this.env, queueMessage: message }); - - await marqs?.nackMessage(message.messageId); - return; - } - - const variables = await resolveVariablesForEnvironment(this.env); - - if (backgroundWorker.supportsLazyAttempts) { - const payload: TaskRunExecutionLazyAttemptPayload = { - traceContext: lockedTaskRun.traceContext as Record, - environment: variables.reduce((acc: Record, curr) => { - acc[curr.key] = curr.value; - return acc; - }, {}), - runId: lockedTaskRun.friendlyId, - messageId: lockedTaskRun.id, - isTest: lockedTaskRun.isTest, - isReplay: !!lockedTaskRun.replayedFromTaskRunFriendlyId, - metrics: [ - { - name: "start", - event: "dequeue", - timestamp: dequeuedStart, - duration: Date.now() - dequeuedStart, - }, - ], - }; - - try { - await this._sender.send("BACKGROUND_WORKER_MESSAGE", { - backgroundWorkerId: backgroundWorker.friendlyId, - data: { - type: "EXECUTE_RUN_LAZY_ATTEMPT", - payload, - }, - }); - - logger.debug("Executing the run", { - messageId: message.messageId, - }); - - this._inProgressRuns.set(lockedTaskRun.friendlyId, message.messageId); - } catch (e) { - if (e instanceof Error) { - this._currentSpan?.recordException(e); - } else { - this._currentSpan?.recordException(new Error(String(e))); - } - - this._endSpanInNextIteration = true; - - // We now need to unlock the task run and delete the task run attempt - await prisma.$transaction([ - prisma.taskRun.update({ - where: { - id: lockedTaskRun.id, - }, - data: { - lockedAt: null, - lockedById: null, - status: "PENDING", - startedAt: existingTaskRun.startedAt, - }, - }), - ]); - - this._inProgressRuns.delete(lockedTaskRun.friendlyId); - - // Finally we need to nack the message so it can be retried - await marqs?.nackMessage(message.messageId); - } finally { - setTimeout(() => this.#doWork(), 100); - } - } else { - logger.debug("We no longer support non-lazy attempts, aborting this run", { - messageId: message.messageId, - backgroundWorker, - }); - await marqs?.acknowledgeMessage( - message.messageId, - "Non-lazy attempts are no longer supported in DevQueueConsumer" - ); - - setTimeout(() => this.#doWork(), 100); - } - } - - // Get the latest background worker based on the version. - // Versions are in the format of 20240101.1 and 20240101.2, or even 20240101.10, 20240101.11, etc. - #getLatestBackgroundWorker() { - const workers = Array.from(this._backgroundWorkers.values()); - - if (workers.length === 0) { - return; - } - - return workers.reduce((acc, curr) => { - const accParts = acc.version.split(".").map(Number); - const currParts = curr.version.split(".").map(Number); - - // Compare the major part - if (accParts[0] < currParts[0]) { - return curr; - } else if (accParts[0] > currParts[0]) { - return acc; - } - - // Compare the minor part (assuming all versions have two parts) - if (accParts[1] < currParts[1]) { - return curr; - } else { - return acc; - } - }); - } -} diff --git a/apps/webapp/app/v3/marqs/fairDequeuingStrategy.server.ts b/apps/webapp/app/v3/marqs/fairDequeuingStrategy.server.ts deleted file mode 100644 index cbc1ececad7..00000000000 --- a/apps/webapp/app/v3/marqs/fairDequeuingStrategy.server.ts +++ /dev/null @@ -1,598 +0,0 @@ -import type { Cache as UnkeyCache } from "@unkey/cache"; -import { createCache, DefaultStatefulContext, Namespace } from "@unkey/cache"; -import { createLRUMemoryStore } from "@internal/cache"; -import { randomUUID } from "crypto"; -import type { Redis } from "ioredis"; -import type { EnvQueues, MarQSFairDequeueStrategy, MarQSKeyProducer } from "./types"; -import seedrandom from "seedrandom"; -import type { Tracer } from "@opentelemetry/api"; -import { startSpan } from "../tracing.server"; - -export type FairDequeuingStrategyBiases = { - /** - * How much to bias towards environments with higher concurrency limits - * 0 = no bias, 1 = full bias based on limit differences - */ - concurrencyLimitBias: number; - - /** - * How much to bias towards environments with more available capacity - * 0 = no bias, 1 = full bias based on available capacity - */ - availableCapacityBias: number; - - /** - * Controls randomization of queue ordering within environments - * 0 = strict age-based ordering (oldest first) - * 1 = completely random ordering - * Values between 0-1 blend between age-based and random ordering - */ - queueAgeRandomization: number; -}; - -export type FairDequeuingStrategyOptions = { - redis: Redis; - keys: MarQSKeyProducer; - defaultEnvConcurrency: number; - parentQueueLimit: number; - tracer: Tracer; - seed?: string; - /** - * Configure biasing for environment shuffling - * If not provided, no biasing will be applied (completely random shuffling) - */ - biases?: FairDequeuingStrategyBiases; - reuseSnapshotCount?: number; - maximumEnvCount?: number; - /** - * Maximum number of queues to process per environment - * If not provided, all queues in an environment will be processed - */ - maximumQueuePerEnvCount?: number; -}; - -type FairQueueConcurrency = { - current: number; - limit: number; - reserve: number; -}; - -type FairQueue = { id: string; age: number; org: string; env: string }; - -type FairQueueSnapshot = { - id: string; - envs: Record; - queues: Array; -}; - -type WeightedEnv = { - envId: string; - weight: number; -}; - -type WeightedQueue = { - queue: FairQueue; - weight: number; -}; - -const emptyFairQueueSnapshot: FairQueueSnapshot = { - id: "empty", - envs: {}, - queues: [], -}; - -const defaultBiases: FairDequeuingStrategyBiases = { - concurrencyLimitBias: 0, - availableCapacityBias: 0, - queueAgeRandomization: 0, // Default to completely age-based ordering -}; - -export class FairDequeuingStrategy implements MarQSFairDequeueStrategy { - private _cache: UnkeyCache<{ - concurrencyLimit: number; - }>; - - private _rng: seedrandom.PRNG; - private _reusedSnapshotForConsumer: Map< - string, - { snapshot: FairQueueSnapshot; reuseCount: number } - > = new Map(); - - constructor(private options: FairDequeuingStrategyOptions) { - const ctx = new DefaultStatefulContext(); - const memory = createLRUMemoryStore(500); - - this._cache = createCache({ - concurrencyLimit: new Namespace(ctx, { - stores: [memory], - fresh: 60_000, // The time in milliseconds that a value is considered fresh. Cache hits within this time will return the cached value. - stale: 180_000, // The time in milliseconds that a value is considered stale. Cache hits within this time will return the cached value and trigger a background refresh. - }), - }); - - this._rng = seedrandom(options.seed); - } - - async distributeFairQueuesFromParentQueue( - parentQueue: string, - consumerId: string - ): Promise> { - return await startSpan( - this.options.tracer, - "distributeFairQueuesFromParentQueue", - async (span) => { - span.setAttribute("consumer_id", consumerId); - span.setAttribute("parent_queue", parentQueue); - - const snapshot = await this.#createQueueSnapshot(parentQueue, consumerId); - - span.setAttributes({ - snapshot_env_count: Object.keys(snapshot.envs).length, - snapshot_queue_count: snapshot.queues.length, - }); - - const queues = snapshot.queues; - - if (queues.length === 0) { - return []; - } - - const envQueues = this.#shuffleQueuesByEnv(snapshot); - - span.setAttribute( - "shuffled_queue_count", - envQueues.reduce((sum, env) => sum + env.queues.length, 0) - ); - - if (envQueues[0]?.queues[0]) { - span.setAttribute("winning_env", envQueues[0].envId); - span.setAttribute( - "winning_org", - this.options.keys.orgIdFromQueue(envQueues[0].queues[0]) - ); - } - - return envQueues; - } - ); - } - - #shuffleQueuesByEnv(snapshot: FairQueueSnapshot): Array { - const envs = Object.keys(snapshot.envs); - const biases = this.options.biases ?? defaultBiases; - - if (biases.concurrencyLimitBias === 0 && biases.availableCapacityBias === 0) { - const shuffledEnvs = this.#shuffle(envs); - return this.#orderQueuesByEnvs(shuffledEnvs, snapshot); - } - - // Find the maximum concurrency limit for normalization - const maxLimit = Math.max(...envs.map((envId) => snapshot.envs[envId].concurrency.limit)); - - // Calculate weights for each environment - const weightedEnvs: WeightedEnv[] = envs.map((envId) => { - const env = snapshot.envs[envId]; - - // Start with base weight of 1 - let weight = 1; - - // Add normalized concurrency limit bias if configured - if (biases.concurrencyLimitBias > 0) { - const normalizedLimit = env.concurrency.limit / maxLimit; - // Square or cube the bias to make it more pronounced at higher values - weight *= 1 + Math.pow(normalizedLimit * biases.concurrencyLimitBias, 2); - } - - // Add available capacity bias if configured - if (biases.availableCapacityBias > 0) { - const usedCapacityPercentage = env.concurrency.current / env.concurrency.limit; - const availableCapacityBonus = 1 - usedCapacityPercentage; - // Square or cube the bias to make it more pronounced at higher values - weight *= 1 + Math.pow(availableCapacityBonus * biases.availableCapacityBias, 2); - } - - return { envId, weight }; - }); - - const shuffledEnvs = this.#weightedShuffle(weightedEnvs); - return this.#orderQueuesByEnvs(shuffledEnvs, snapshot); - } - - #weightedShuffle(weightedItems: WeightedEnv[]): string[] { - const totalWeight = weightedItems.reduce((sum, item) => sum + item.weight, 0); - const result: string[] = []; - const items = [...weightedItems]; - - while (items.length > 0) { - let random = this._rng() * totalWeight; - let index = 0; - - // Find item based on weighted random selection - while (random > 0 && index < items.length) { - random -= items[index].weight; - index++; - } - index = Math.max(0, index - 1); - - // Add selected item to result and remove from items - result.push(items[index].envId); - items.splice(index, 1); - } - - return result; - } - - #orderQueuesByEnvs(envs: string[], snapshot: FairQueueSnapshot): Array { - const queuesByEnv = snapshot.queues.reduce( - (acc, queue) => { - if (!acc[queue.env]) { - acc[queue.env] = []; - } - acc[queue.env].push(queue); - return acc; - }, - {} as Record> - ); - - return envs.reduce((acc, envId) => { - if (queuesByEnv[envId]) { - // Get ordered queues for this env - const orderedQueues = this.#weightedRandomQueueOrder(queuesByEnv[envId]); - - // Apply queue limit if maximumQueuePerEnvCount is set - const limitedQueues = this.options.maximumQueuePerEnvCount - ? orderedQueues.slice(0, this.options.maximumQueuePerEnvCount) - : orderedQueues; - - // Only add the env if it has queues - if (limitedQueues.length > 0) { - acc.push({ - envId, - queues: limitedQueues.map((queue) => queue.id), - }); - } - } - return acc; - }, [] as Array); - } - - #weightedRandomQueueOrder(queues: FairQueue[]): FairQueue[] { - if (queues.length <= 1) return queues; - - const biases = this.options.biases ?? defaultBiases; - - // When queueAgeRandomization is 0, use strict age-based ordering - if (biases.queueAgeRandomization === 0) { - return [...queues].sort((a, b) => b.age - a.age); - } - - // Find the maximum age for normalization - const maxAge = Math.max(...queues.map((q) => q.age)); - - // Calculate weights for each queue - const weightedQueues: WeightedQueue[] = queues.map((queue) => { - // Normalize age to be between 0 and 1 - const normalizedAge = queue.age / maxAge; - - // Calculate weight: combine base weight with configurable age influence - const baseWeight = 1; - const weight = baseWeight + normalizedAge * biases.queueAgeRandomization; - - return { queue, weight }; - }); - - // Perform weighted random selection for ordering - const result: FairQueue[] = []; - let remainingQueues = [...weightedQueues]; - let totalWeight = remainingQueues.reduce((sum, wq) => sum + wq.weight, 0); - - while (remainingQueues.length > 0) { - let random = this._rng() * totalWeight; - let index = 0; - - // Find queue based on weighted random selection - while (random > 0 && index < remainingQueues.length) { - random -= remainingQueues[index].weight; - index++; - } - index = Math.max(0, index - 1); - - // Add selected queue to result and remove from remaining - result.push(remainingQueues[index].queue); - totalWeight -= remainingQueues[index].weight; - remainingQueues.splice(index, 1); - } - - return result; - } - - #shuffle(array: Array): Array { - let currentIndex = array.length; - let temporaryValue; - let randomIndex; - - const newArray = [...array]; - - while (currentIndex !== 0) { - randomIndex = Math.floor(this._rng() * currentIndex); - currentIndex -= 1; - - temporaryValue = newArray[currentIndex]; - newArray[currentIndex] = newArray[randomIndex]; - newArray[randomIndex] = temporaryValue; - } - - return newArray; - } - - async #createQueueSnapshot(parentQueue: string, consumerId: string): Promise { - return await startSpan(this.options.tracer, "createQueueSnapshot", async (span) => { - span.setAttribute("consumer_id", consumerId); - span.setAttribute("parent_queue", parentQueue); - - if ( - typeof this.options.reuseSnapshotCount === "number" && - this.options.reuseSnapshotCount > 0 - ) { - const key = `${parentQueue}:${consumerId}`; - const reusedSnapshot = this._reusedSnapshotForConsumer.get(key); - - if (reusedSnapshot) { - if (reusedSnapshot.reuseCount < this.options.reuseSnapshotCount) { - span.setAttribute("reused_snapshot", true); - - this._reusedSnapshotForConsumer.set(key, { - snapshot: reusedSnapshot.snapshot, - reuseCount: reusedSnapshot.reuseCount + 1, - }); - - return reusedSnapshot.snapshot; - } else { - this._reusedSnapshotForConsumer.delete(key); - } - } - } - - span.setAttribute("reused_snapshot", false); - - const now = Date.now(); - - let queues = await this.#allChildQueuesByScore(parentQueue, consumerId, now); - - span.setAttribute("parent_queue_count", queues.length); - - if (queues.length === 0) { - return emptyFairQueueSnapshot; - } - - // Apply env selection if maximumEnvCount is specified - let selectedEnvIds: Set; - if (this.options.maximumEnvCount && this.options.maximumEnvCount > 0) { - selectedEnvIds = this.#selectTopEnvs(queues, this.options.maximumEnvCount); - // Filter queues to only include selected envs - queues = queues.filter((queue) => selectedEnvIds.has(queue.env)); - - span.setAttribute("selected_env_count", selectedEnvIds.size); - } - - span.setAttribute("selected_queue_count", queues.length); - - const envIds = new Set(); - - for (const queue of queues) { - envIds.add(queue.env); - } - - const envs = await Promise.all( - Array.from(envIds).map(async (envId) => { - return { id: envId, concurrency: await this.#getEnvConcurrency(envId) }; - }) - ); - - const envsAtFullConcurrency = envs.filter( - (env) => env.concurrency.current >= env.concurrency.limit + env.concurrency.reserve - ); - - const envIdsAtFullConcurrency = new Set(envsAtFullConcurrency.map((env) => env.id)); - - const envsSnapshot = envs.reduce( - (acc, env) => { - if (!envIdsAtFullConcurrency.has(env.id)) { - acc[env.id] = env; - } - return acc; - }, - {} as Record - ); - - span.setAttributes({ - env_count: envs.length, - envs_at_full_concurrency_count: envsAtFullConcurrency.length, - }); - - const queuesSnapshot = queues.filter((queue) => !envIdsAtFullConcurrency.has(queue.env)); - - const snapshot = { - id: randomUUID(), - envs: envsSnapshot, - queues: queuesSnapshot, - }; - - if ( - typeof this.options.reuseSnapshotCount === "number" && - this.options.reuseSnapshotCount > 0 - ) { - this._reusedSnapshotForConsumer.set(`${parentQueue}:${consumerId}`, { - snapshot, - reuseCount: 0, - }); - } - - return snapshot; - }); - } - - #selectTopEnvs(queues: FairQueue[], maximumEnvCount: number): Set { - // Group queues by env - const queuesByEnv = queues.reduce( - (acc, queue) => { - if (!acc[queue.env]) { - acc[queue.env] = []; - } - acc[queue.env].push(queue); - return acc; - }, - {} as Record - ); - - // Calculate average age for each env - const envAverageAges = Object.entries(queuesByEnv).map(([envId, envQueues]) => { - const averageAge = envQueues.reduce((sum, q) => sum + q.age, 0) / envQueues.length; - return { envId, averageAge }; - }); - - // Perform weighted shuffle based on average ages - const maxAge = Math.max(...envAverageAges.map((e) => e.averageAge)); - const weightedEnvs = envAverageAges.map((env) => ({ - envId: env.envId, - weight: env.averageAge / maxAge, // Normalize weights - })); - - // Select top N envs using weighted shuffle - const selectedEnvs = new Set(); - let remainingEnvs = [...weightedEnvs]; - let totalWeight = remainingEnvs.reduce((sum, env) => sum + env.weight, 0); - - while (selectedEnvs.size < maximumEnvCount && remainingEnvs.length > 0) { - let random = this._rng() * totalWeight; - let index = 0; - - while (random > 0 && index < remainingEnvs.length) { - random -= remainingEnvs[index].weight; - index++; - } - index = Math.max(0, index - 1); - - selectedEnvs.add(remainingEnvs[index].envId); - totalWeight -= remainingEnvs[index].weight; - remainingEnvs.splice(index, 1); - } - - return selectedEnvs; - } - - async #getEnvConcurrency(envId: string): Promise { - return await startSpan(this.options.tracer, "getEnvConcurrency", async (span) => { - span.setAttribute("env_id", envId); - - const [currentValue, limitValue, reserveValue] = await Promise.all([ - this.#getEnvCurrentConcurrency(envId), - this.#getEnvConcurrencyLimit(envId), - this.#getEnvReserveConcurrency(envId), - ]); - - span.setAttribute("current_value", currentValue); - span.setAttribute("limit_value", limitValue); - span.setAttribute("reserve_value", reserveValue); - - return { current: currentValue, limit: limitValue, reserve: reserveValue }; - }); - } - - async #allChildQueuesByScore( - parentQueue: string, - consumerId: string, - now: number - ): Promise> { - return await startSpan(this.options.tracer, "allChildQueuesByScore", async (span) => { - span.setAttribute("consumer_id", consumerId); - span.setAttribute("parent_queue", parentQueue); - - const valuesWithScores = await this.options.redis.zrangebyscore( - parentQueue, - "-inf", - now, - "WITHSCORES", - "LIMIT", - 0, - this.options.parentQueueLimit - ); - - const result: Array = []; - - for (let i = 0; i < valuesWithScores.length; i += 2) { - result.push({ - id: valuesWithScores[i], - age: now - Number(valuesWithScores[i + 1]), - env: this.options.keys.envIdFromQueue(valuesWithScores[i]), - org: this.options.keys.orgIdFromQueue(valuesWithScores[i]), - }); - } - - span.setAttribute("queue_count", result.length); - - if (result.length === this.options.parentQueueLimit) { - span.setAttribute("parent_queue_limit_reached", true); - } - - return result; - }); - } - - async #getEnvConcurrencyLimit(envId: string) { - return await startSpan(this.options.tracer, "getEnvConcurrencyLimit", async (span) => { - span.setAttribute("env_id", envId); - - const key = this.options.keys.envConcurrencyLimitKey(envId); - - const result = await this._cache.concurrencyLimit.swr(key, async () => { - const value = await this.options.redis.get(key); - - if (!value) { - return this.options.defaultEnvConcurrency; - } - - return Number(value); - }); - - return result.val ?? this.options.defaultEnvConcurrency; - }); - } - - async #getEnvCurrentConcurrency(envId: string) { - return await startSpan(this.options.tracer, "getEnvCurrentConcurrency", async (span) => { - span.setAttribute("env_id", envId); - - const key = this.options.keys.envCurrentConcurrencyKey(envId); - - const result = await this.options.redis.scard(key); - - span.setAttribute("current_value", result); - - return result; - }); - } - - async #getEnvReserveConcurrency(envId: string) { - return await startSpan(this.options.tracer, "getEnvReserveConcurrency", async (span) => { - span.setAttribute("env_id", envId); - - const key = this.options.keys.envReserveConcurrencyKey(envId); - - const result = await this.options.redis.scard(key); - - span.setAttribute("current_value", result); - - return result; - }); - } -} - -export class NoopFairDequeuingStrategy implements MarQSFairDequeueStrategy { - async distributeFairQueuesFromParentQueue( - parentQueue: string, - consumerId: string - ): Promise> { - return []; - } -} diff --git a/apps/webapp/app/v3/marqs/index.server.ts b/apps/webapp/app/v3/marqs/index.server.ts deleted file mode 100644 index 0f4cb2832fe..00000000000 --- a/apps/webapp/app/v3/marqs/index.server.ts +++ /dev/null @@ -1,2691 +0,0 @@ -import { type RedisOptions } from "@internal/redis"; -import type { Span, SpanOptions, Tracer } from "@opentelemetry/api"; -import { context, propagation, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api"; -import { - SEMATTRS_MESSAGE_ID, - SEMATTRS_MESSAGING_OPERATION, - SEMATTRS_MESSAGING_SYSTEM, -} from "@opentelemetry/semantic-conventions"; -import { Logger } from "@trigger.dev/core/logger"; -import { tryCatch } from "@trigger.dev/core/utils"; -import { flattenAttributes } from "@trigger.dev/core/v3"; -import { Worker, type WorkerConcurrencyOptions } from "@trigger.dev/redis-worker"; -import Redis, { type Callback, type Result } from "ioredis"; -import { setInterval as setIntervalAsync } from "node:timers/promises"; -import z from "zod"; -import { env } from "~/env.server"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { signalsEmitter } from "~/services/signals.server"; -import { singleton } from "~/utils/singleton"; -import { legacyRunEngineWorker } from "../legacyRunEngineWorker.server"; -import { concurrencyTracker } from "../services/taskRunConcurrencyTracker.server"; -import { attributesFromAuthenticatedEnv, tracer } from "../tracer.server"; -import { AsyncWorker } from "./asyncWorker.server"; -import { - MARQS_DELAYED_REQUEUE_THRESHOLD_IN_MS, - MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET, - MARQS_RETRY_PRIORITY_TIMESTAMP_OFFSET, - MARQS_SCHEDULED_REQUEUE_AVAILABLE_AT_THRESHOLD_IN_MS, -} from "./constants.server"; -import { FairDequeuingStrategy } from "./fairDequeuingStrategy.server"; -import { MarQSShortKeyProducer } from "./marqsKeyProducer"; -import type { - EnqueueMessageReserveConcurrencyOptions, - MarQSFairDequeueStrategy, - MarQSKeyProducer, - MarQSKeyProducerEnv, - MarQSPriorityLevel, - MessageQueueSubscriber, - VisibilityTimeoutStrategy, -} from "./types"; -import { MessagePayload } from "./types"; -import { V3LegacyRunEngineWorkerVisibilityTimeout } from "./v3VisibilityTimeout.server"; - -const KEY_PREFIX = "marqs:"; - -const SemanticAttributes = { - CONSUMER_ID: "consumer_id", - QUEUE: "queue", - PARENT_QUEUE: "parent_queue", - MESSAGE_ID: "message_id", - CONCURRENCY_KEY: "concurrency_key", -}; - -export type MarQSOptions = { - name: string; - tracer: Tracer; - redis: Redis; - defaultEnvConcurrency: number; - defaultOrgConcurrency: number; - windowSize?: number; - visibilityTimeoutInMs?: number; - workers: number; - keysProducer: MarQSKeyProducer; - queuePriorityStrategy: MarQSFairDequeueStrategy; - envQueuePriorityStrategy: MarQSFairDequeueStrategy; - visibilityTimeoutStrategy: VisibilityTimeoutStrategy; - maximumNackCount: number; - enableRebalancing?: boolean; - verbose?: boolean; - subscriber?: MessageQueueSubscriber; - sharedWorkerQueueConsumerIntervalMs?: number; - sharedWorkerQueueMaxMessageCount?: number; - sharedWorkerQueueCooloffPeriodMs?: number; - sharedWorkerQueueCooloffCountThreshold?: number; - eagerDequeuingEnabled?: boolean; - workerOptions: { - pollIntervalMs?: number; - immediatePollIntervalMs?: number; - shutdownTimeoutMs?: number; - concurrency?: WorkerConcurrencyOptions; - enabled?: boolean; - redisOptions: RedisOptions; - }; -}; - -const workerCatalog = { - processQueueForWorkerQueue: { - schema: z.object({ - queueKey: z.string(), - parentQueueKey: z.string(), - }), - visibilityTimeoutMs: 30_000, - }, -}; - -/** - * MarQS - Multitenant Asynchronous Reliable Queueing System (pronounced "markus") - */ -export class MarQS { - private redis: Redis; - public keys: MarQSKeyProducer; - #rebalanceWorkers: Array = []; - private worker: Worker; - private queueDequeueCooloffPeriod: Map = new Map(); - private queueDequeueCooloffCounts: Map = new Map(); - private clearCooloffPeriodInterval: NodeJS.Timeout; - isShuttingDown: boolean = false; - - constructor(private readonly options: MarQSOptions) { - this.redis = options.redis; - - this.keys = options.keysProducer; - - this.#startRebalanceWorkers(); - this.#registerCommands(); - - // This will prevent these cooloff maps from growing indefinitely - this.clearCooloffPeriodInterval = setInterval(() => { - this.queueDequeueCooloffCounts.clear(); - this.queueDequeueCooloffPeriod.clear(); - }, 60_000 * 10); // 10 minutes - - this.worker = new Worker({ - name: "marqs-worker", - redisOptions: options.workerOptions.redisOptions, - catalog: workerCatalog, - concurrency: options.workerOptions?.concurrency, - pollIntervalMs: options.workerOptions?.pollIntervalMs ?? 1000, - immediatePollIntervalMs: options.workerOptions?.immediatePollIntervalMs ?? 100, - shutdownTimeoutMs: options.workerOptions?.shutdownTimeoutMs ?? 10_000, - logger: new Logger("MarQSWorker", "info"), - jobs: { - processQueueForWorkerQueue: async (job) => { - await this.#processQueueForWorkerQueue(job.payload.queueKey, job.payload.parentQueueKey); - }, - }, - }); - - if (options.workerOptions?.enabled) { - this.worker.start(); - } - - this.#setupShutdownHandlers(); - } - - #setupShutdownHandlers() { - signalsEmitter.on("SIGTERM", () => this.shutdown("SIGTERM")); - signalsEmitter.on("SIGINT", () => this.shutdown("SIGINT")); - } - - async shutdown(signal: NodeJS.Signals) { - if (this.isShuttingDown) return; - this.isShuttingDown = true; - - console.log("👇 Shutting down marqs", this.name, signal); - clearInterval(this.clearCooloffPeriodInterval); - this.#rebalanceWorkers.forEach((worker) => worker.stop()); - } - - get name() { - return this.options.name; - } - - get tracer() { - return this.options.tracer; - } - - public async updateQueueConcurrencyLimits( - env: AuthenticatedEnvironment, - queue: string, - concurrency: number - ) { - return this.redis.set(this.keys.queueConcurrencyLimitKey(env, queue), concurrency); - } - - public async removeQueueConcurrencyLimits(env: AuthenticatedEnvironment, queue: string) { - return this.redis.del(this.keys.queueConcurrencyLimitKey(env, queue)); - } - - public async updateEnvConcurrencyLimits(env: AuthenticatedEnvironment) { - const envConcurrencyLimitKey = this.keys.envConcurrencyLimitKey(env); - - logger.debug("Updating env concurrency limits", { - envConcurrencyLimitKey, - service: this.name, - }); - - await this.#callUpdateGlobalConcurrencyLimits({ - envConcurrencyLimitKey, - envConcurrencyLimit: env.maximumConcurrencyLimit, - }); - } - - public async getQueueConcurrencyLimit(env: MarQSKeyProducerEnv, queue: string) { - const result = await this.redis.get(this.keys.queueConcurrencyLimitKey(env, queue)); - - return result ? Number(result) : undefined; - } - - public async getEnvConcurrencyLimit(env: MarQSKeyProducerEnv) { - const result = await this.redis.get(this.keys.envConcurrencyLimitKey(env)); - - return result ? Number(result) : this.options.defaultEnvConcurrency; - } - - public async lengthOfQueue( - env: AuthenticatedEnvironment, - queue: string, - concurrencyKey?: string - ) { - return this.redis.zcard(this.keys.queueKey(env, queue, concurrencyKey)); - } - - public async lengthOfEnvQueue(env: MarQSKeyProducerEnv) { - return this.redis.zcard(this.keys.envQueueKey(env)); - } - - public async oldestMessageInQueue( - env: AuthenticatedEnvironment, - queue: string, - concurrencyKey?: string - ) { - // Get the "score" of the sorted set to get the oldest message score - const result = await this.redis.zrange( - this.keys.queueKey(env, queue, concurrencyKey), - 0, - 0, - "WITHSCORES" - ); - - if (result.length === 0) { - return; - } - - return Number(result[1]); - } - - public async currentConcurrencyOfQueue( - env: MarQSKeyProducerEnv, - queue: string, - concurrencyKey?: string - ) { - return this.redis.scard(this.keys.queueCurrentConcurrencyKey(env, queue, concurrencyKey)); - } - - public async reserveConcurrencyOfQueue( - env: MarQSKeyProducerEnv, - queue: string, - concurrencyKey?: string - ) { - return this.redis.scard( - this.keys.queueReserveConcurrencyKeyFromQueue(this.keys.queueKey(env, queue, concurrencyKey)) - ); - } - - public async currentConcurrencyOfEnvironment(env: MarQSKeyProducerEnv) { - return this.redis.scard(this.keys.envCurrentConcurrencyKey(env)); - } - - public async reserveConcurrencyOfEnvironment(env: MarQSKeyProducerEnv) { - return this.redis.scard(this.keys.envReserveConcurrencyKey(env.id)); - } - - public async removeEnvironmentQueuesFromMasterQueue(orgId: string, environmentId: string) { - const sharedQueue = this.keys.sharedQueueKey(); - const queuePattern = this.keys.queueKey(orgId, environmentId, "*"); - - // Use scanStream to find all matching members - const stream = this.redis.zscanStream(sharedQueue, { - match: queuePattern, - count: 100, - }); - - return new Promise((resolve, reject) => { - const matchingQueues: string[] = []; - - stream.on("data", (resultKeys) => { - // zscanStream returns [member1, score1, member2, score2, ...] - // We only want the members (even indices) - for (let i = 0; i < resultKeys.length; i += 2) { - matchingQueues.push(resultKeys[i]); - } - }); - - stream.on("end", async () => { - if (matchingQueues.length > 0) { - await this.redis.zrem(sharedQueue, matchingQueues); - } - resolve(); - }); - - stream.on("error", (err) => reject(err)); - }); - } - - public async enqueueMessage( - env: AuthenticatedEnvironment, - queue: string, - messageId: string, - messageData: Record, - concurrencyKey?: string, - timestamp?: number | Date, - reserve?: EnqueueMessageReserveConcurrencyOptions, - priority?: MarQSPriorityLevel - ) { - return await this.#trace( - "enqueueMessage", - async (span) => { - const messageQueue = this.keys.queueKey(env, queue, concurrencyKey); - - const parentQueue = this.keys.envSharedQueueKey(env); - - propagation.inject(context.active(), messageData); - - const $timestamp = - typeof timestamp === "undefined" - ? Date.now() - : typeof timestamp === "number" - ? timestamp - : timestamp.getTime(); - - const messagePayload: MessagePayload = { - version: "1", - data: messageData, - queue: messageQueue, - concurrencyKey, - timestamp: $timestamp, - messageId, - parentQueue, - priority, - availableAt: Date.now(), - enqueueMethod: "enqueue", - }; - - span.setAttributes({ - [SemanticAttributes.QUEUE]: queue, - [SemanticAttributes.MESSAGE_ID]: messageId, - [SemanticAttributes.CONCURRENCY_KEY]: concurrencyKey, - [SemanticAttributes.PARENT_QUEUE]: parentQueue, - }); - - if (reserve) { - span.setAttribute("reserve_message_id", reserve.messageId); - span.setAttribute("reserve_recursive_queue", reserve.recursiveQueue); - } - - if (env.type !== "DEVELOPMENT" && this.options.eagerDequeuingEnabled) { - // This will move the message to the worker queue so it can be dequeued - await this.worker.enqueueOnce({ - id: messageQueue, // dedupe by environment, queue, and concurrency key - job: "processQueueForWorkerQueue", - payload: { - queueKey: messageQueue, - parentQueueKey: parentQueue, - }, - // Add a small delay to dedupe messages so at most one of these will processed, - // every 500ms per queue, concurrency key, and environment - availableAt: new Date(Date.now() + 500), // 500ms from now - }); - } - - const result = await this.#callEnqueueMessage(messagePayload, reserve); - - if (result) { - await this.options.subscriber?.messageEnqueued(messagePayload); - } - - return result; - }, - { - kind: SpanKind.PRODUCER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "publish", - [SEMATTRS_MESSAGE_ID]: messageId, - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - ...attributesFromAuthenticatedEnv(env), - }, - } - ); - } - - public async replaceMessage( - messageId: string, - messageData: Record, - timestamp?: number - ) { - return this.#trace( - "replaceMessage", - async (span) => { - const oldMessage = await this.readMessage(messageId); - - if (!oldMessage) { - return; - } - - span.setAttributes({ - [SemanticAttributes.QUEUE]: oldMessage.queue, - [SemanticAttributes.MESSAGE_ID]: oldMessage.messageId, - [SemanticAttributes.CONCURRENCY_KEY]: oldMessage.concurrencyKey, - [SemanticAttributes.PARENT_QUEUE]: oldMessage.parentQueue, - }); - - const traceContext = { - traceparent: oldMessage.data.traceparent, - tracestate: oldMessage.data.tracestate, - }; - - const newMessage: MessagePayload = { - version: "1", - // preserve original trace context - data: { ...oldMessage.data, ...messageData, ...traceContext, queue: oldMessage.queue }, - queue: oldMessage.queue, - concurrencyKey: oldMessage.concurrencyKey, - timestamp: timestamp ?? Date.now(), - messageId, - parentQueue: oldMessage.parentQueue, - priority: oldMessage.priority, - enqueueMethod: "replace", - }; - - await this.#saveMessageIfExists(newMessage); - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "replace", - [SEMATTRS_MESSAGE_ID]: messageId, - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - public async requeueMessage( - messageId: string, - messageData: Record, - timestamp?: number, - priority?: MarQSPriorityLevel - ) { - return this.#trace( - "requeueMessage", - async (span) => { - const oldMessage = await this.readMessage(messageId); - - if (!oldMessage) { - return; - } - - span.setAttributes({ - [SemanticAttributes.QUEUE]: oldMessage.queue, - [SemanticAttributes.MESSAGE_ID]: oldMessage.messageId, - [SemanticAttributes.CONCURRENCY_KEY]: oldMessage.concurrencyKey, - [SemanticAttributes.PARENT_QUEUE]: oldMessage.parentQueue, - }); - - const traceContext = { - traceparent: oldMessage.data.traceparent, - tracestate: oldMessage.data.tracestate, - }; - - const $timestamp = timestamp ?? Date.now(); - - const newMessage: MessagePayload = { - version: "1", - // preserve original trace context - data: { - ...oldMessage.data, - ...messageData, - ...traceContext, - queue: oldMessage.queue, - }, - queue: oldMessage.queue, - concurrencyKey: oldMessage.concurrencyKey, - timestamp: $timestamp, - messageId, - parentQueue: oldMessage.parentQueue, - priority: priority ?? oldMessage.priority, - availableAt: $timestamp, - enqueueMethod: "requeue", - }; - - await this.options.visibilityTimeoutStrategy.cancelHeartbeat(messageId); - - // If the message timestamp is enough in the future (e.g. more than 500ms from now), - // we will schedule it to be requeued in the future using the legacy run engine redis worker - // If not, we just requeue it immediately - if ($timestamp > Date.now() + MARQS_DELAYED_REQUEUE_THRESHOLD_IN_MS) { - await this.#callDelayedRequeueMessage(newMessage); - } else { - await this.#callRequeueMessage(newMessage); - } - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "requeue", - [SEMATTRS_MESSAGE_ID]: messageId, - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - public async requeueMessageById(messageId: string) { - return this.#trace( - "requeueMessageById", - async (span) => { - const message = await this.readMessage(messageId); - - if (!message) { - return; - } - - span.setAttributes({ - [SemanticAttributes.QUEUE]: message.queue, - [SemanticAttributes.MESSAGE_ID]: message.messageId, - [SemanticAttributes.CONCURRENCY_KEY]: message.concurrencyKey, - [SemanticAttributes.PARENT_QUEUE]: message.parentQueue, - }); - - logger.debug(`Requeueing message by id`, { messageId, message, service: this.name }); - - await this.#callRequeueMessage(message); - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "requeue_by_id", - [SEMATTRS_MESSAGE_ID]: messageId, - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - async #saveMessageIfExists(message: MessagePayload) { - logger.debug(`Saving message if exists`, { message, service: this.name }); - - const messageKey = this.keys.messageKey(message.messageId); - - await this.redis.set(messageKey, JSON.stringify(message), "XX"); // XX means only set if key exists - } - - public async dequeueMessageInEnv(env: AuthenticatedEnvironment) { - return this.#trace( - "dequeueMessageInEnv", - async (span) => { - const parentQueue = this.keys.envSharedQueueKey(env); - - span.setAttribute(SemanticAttributes.PARENT_QUEUE, parentQueue); - span.setAttribute(SemanticAttributes.CONSUMER_ID, env.id); - - // Get prioritized list of queues to try - const environments = - await this.options.envQueuePriorityStrategy.distributeFairQueuesFromParentQueue( - parentQueue, - env.id - ); - - const queues = environments.flatMap((e) => e.queues); - - span.setAttribute("env_count", environments.length); - span.setAttribute("queue_count", queues.length); - - for (const messageQueue of queues) { - const messages = await this.#callDequeueMessages({ - messageQueue, - parentQueue, - maxCount: 1, - }); - - if (!messages || messages.length === 0) { - return; - } - - const messageData = messages[0]; - - const message = await this.readMessage(messageData.messageId); - - if (message) { - span.setAttributes({ - [SEMATTRS_MESSAGE_ID]: message.messageId, - [SemanticAttributes.QUEUE]: message.queue, - [SemanticAttributes.MESSAGE_ID]: message.messageId, - [SemanticAttributes.CONCURRENCY_KEY]: message.concurrencyKey, - [SemanticAttributes.PARENT_QUEUE]: message.parentQueue, - attempted_queues: queues.indexOf(messageQueue) + 1, // How many queues we tried before success - message_timestamp: message.timestamp, - message_age: this.#calculateMessageAge(message), - message_priority: message.priority, - message_enqueue_method: message.enqueueMethod, - message_available_at: message.availableAt, - ...flattenAttributes(message.data, "message.data"), - }); - - await this.options.subscriber?.messageDequeued(message); - } else { - logger.error(`Failed to read message, undoing the dequeueing of the message`, { - messageData, - service: this.name, - }); - - await this.#callAcknowledgeMessage({ - parentQueue, - messageQueue: messageQueue, - messageId: messageData.messageId, - }); - - return; - } - - await this.options.visibilityTimeoutStrategy.startHeartbeat( - messageData.messageId, - this.visibilityTimeoutInMs - ); - - return message; - } - - span.setAttribute("attempted_queues", queues.length); - return; - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - ...attributesFromAuthenticatedEnv(env), - }, - } - ); - } - - /** - * Dequeue a message from the shared worker queue (this should be used in production environments) - */ - public async dequeueMessageFromSharedWorkerQueue(consumerId: string) { - return this.#trace( - "dequeueMessageFromSharedWorkerQueue", - async (span) => { - span.setAttribute(SemanticAttributes.CONSUMER_ID, consumerId); - - const workerQueueKey = this.keys.sharedWorkerQueueKey(); - - span.setAttribute(SemanticAttributes.PARENT_QUEUE, workerQueueKey); - - // Try and pop a message from the worker queue (redis list) - const messageId = await this.#trace("popMessageFromWorkerQueue", async (innerSpan) => { - innerSpan.setAttribute(SemanticAttributes.PARENT_QUEUE, workerQueueKey); - innerSpan.setAttribute(SemanticAttributes.CONSUMER_ID, consumerId); - - const results = await this.redis.popMessageFromWorkerQueue(workerQueueKey); - - if (!results) { - return null; - } - - const [messageId, queueLength] = results; - - innerSpan.setAttribute("queue_length", Number(queueLength)); - - return messageId; - }); - - if (!messageId) { - return; - } - - const message = await this.readMessage(messageId); - - if (!message) { - return; - } - - if (this.options.subscriber) { - await this.#trace( - "postMessageDequeued", - async (subscriberSpan) => { - subscriberSpan.setAttributes({ - [SemanticAttributes.MESSAGE_ID]: message.messageId, - [SemanticAttributes.QUEUE]: message.queue, - [SemanticAttributes.PARENT_QUEUE]: message.parentQueue, - }); - - return await this.options.subscriber?.messageDequeued(message); - }, - { - kind: SpanKind.INTERNAL, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - await this.#trace( - "startHeartbeat", - async (heartbeatSpan) => { - heartbeatSpan.setAttributes({ - [SemanticAttributes.MESSAGE_ID]: message.messageId, - visibility_timeout_ms: this.visibilityTimeoutInMs, - }); - - return await this.options.visibilityTimeoutStrategy.startHeartbeat( - message.messageId, - this.visibilityTimeoutInMs - ); - }, - { - kind: SpanKind.INTERNAL, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - - return message; - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - public startSharedWorkerQueueConsumer(consumerId: string) { - const abortController = new AbortController(); - - this.#startSharedWorkerQueueConsumer(consumerId, abortController).catch((error) => { - logger.error("Failed to start shared worker queue consumer", { - error, - service: this.name, - consumerId, - }); - }); - - return () => { - abortController.abort(); - }; - } - - async #startSharedWorkerQueueConsumer(consumerId: string, abortController: AbortController) { - let lastProcessedAt = Date.now(); - let processedCount = 0; - - try { - for await (const _ of setIntervalAsync( - this.options.sharedWorkerQueueConsumerIntervalMs ?? 500, - null, - { - signal: abortController.signal, - } - )) { - logger.debug(`Processing shared worker queue`, { - processedCount, - lastProcessedAt, - service: this.name, - consumerId, - }); - - const now = performance.now(); - - const [error, results] = await tryCatch(this.#processSharedWorkerQueue(consumerId)); - - if (error) { - logger.error(`Failed to process shared worker queue`, { - error, - service: this.name, - consumerId, - }); - - continue; - } - - const duration = performance.now() - now; - - logger.debug(`Processed shared worker queue`, { - processedCount, - lastProcessedAt, - service: this.name, - duration, - results, - consumerId, - }); - - processedCount++; - lastProcessedAt = Date.now(); - } - } catch (error) { - if (error instanceof Error && error.name !== "AbortError") { - throw error; - } - - logger.debug(`Shared worker queue consumer stopped`, { - service: this.name, - processedCount, - lastProcessedAt, - }); - } - } - - /** - * Dequeue as many messages as possible from queues into the shared worker queue list - */ - async #processSharedWorkerQueue(consumerId: string) { - return this.#trace( - "processSharedWorkerQueue", - async (span) => { - span.setAttribute(SemanticAttributes.CONSUMER_ID, consumerId); - - const parentQueue = this.keys.sharedQueueKey(); - - span.setAttribute(SemanticAttributes.PARENT_QUEUE, parentQueue); - - // Get prioritized list of queues to try - const envQueues = - await this.options.queuePriorityStrategy.distributeFairQueuesFromParentQueue( - parentQueue, - consumerId - ); - - span.setAttribute("environment_count", envQueues.length); - - if (envQueues.length === 0) { - return; - } - - let attemptedEnvs = 0; - let attemptedQueues = 0; - let messageCount = 0; - let coolOffPeriodCount = 0; - - // Try each queue in order, attempt to dequeue a message from each queue, keep going until we've tried all the queues - for (const env of envQueues) { - attemptedEnvs++; - - for (const messageQueue of env.queues) { - attemptedQueues++; - - const cooloffPeriod = this.queueDequeueCooloffPeriod.get(messageQueue); - - // If the queue is in a cooloff period, skip attempting to dequeue from it - if (cooloffPeriod) { - // If the cooloff period is still active, skip attempting to dequeue from it - if (cooloffPeriod > Date.now()) { - coolOffPeriodCount++; - continue; - } else { - // If the cooloff period is over, delete the cooloff period and attempt to dequeue from the queue - this.queueDequeueCooloffPeriod.delete(messageQueue); - } - } - - await this.#trace( - "attemptDequeue", - async (attemptDequeueSpan) => { - try { - attemptDequeueSpan.setAttributes({ - [SemanticAttributes.QUEUE]: messageQueue, - [SemanticAttributes.PARENT_QUEUE]: parentQueue, - }); - - const messages = await this.#trace( - "callDequeueMessages", - async (dequeueSpan) => { - dequeueSpan.setAttributes({ - [SemanticAttributes.QUEUE]: messageQueue, - [SemanticAttributes.PARENT_QUEUE]: parentQueue, - }); - - return await this.#callDequeueMessages({ - messageQueue, - parentQueue, - maxCount: this.options.sharedWorkerQueueMaxMessageCount ?? 10, - }); - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - - if (!messages || messages.length === 0) { - const cooloffCount = this.queueDequeueCooloffCounts.get(messageQueue) ?? 0; - - const cooloffCountThreshold = Math.max( - 10, - this.options.sharedWorkerQueueCooloffCountThreshold ?? 10 - ); // minimum of 10 - - if (cooloffCount >= cooloffCountThreshold) { - // If no messages were dequeued, set a cooloff period for the queue - // This is to prevent the queue from being dequeued too frequently - // and to give other queues a chance to dequeue messages more frequently - this.queueDequeueCooloffPeriod.set( - messageQueue, - Date.now() + (this.options.sharedWorkerQueueCooloffPeriodMs ?? 10_000) // defaults to 10 seconds - ); - this.queueDequeueCooloffCounts.delete(messageQueue); - } else { - this.queueDequeueCooloffCounts.set(messageQueue, cooloffCount + 1); - } - - attemptDequeueSpan.setAttribute("message_count", 0); - return null; // Try next queue if no message was dequeued - } - - this.queueDequeueCooloffCounts.delete(messageQueue); - - messageCount += messages.length; - - attemptDequeueSpan.setAttribute("message_count", messages.length); - - await this.#trace( - "addToWorkerQueue", - async (addToWorkerQueueSpan) => { - const workerQueueKey = this.keys.sharedWorkerQueueKey(); - - addToWorkerQueueSpan.setAttributes({ - message_count: messages.length, - [SemanticAttributes.PARENT_QUEUE]: workerQueueKey, - }); - - await this.redis.rpush( - workerQueueKey, - ...messages.map((message) => message.messageId) - ); - }, - { - kind: SpanKind.INTERNAL, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } catch (error) { - // Log error but continue trying other queues - logger.warn(`[${this.name}] Failed to dequeue from queue ${messageQueue}`, { - error, - }); - return null; - } - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - } - - // If we get here, we tried all queues but couldn't dequeue a message - span.setAttribute("attempted_queues", attemptedQueues); - span.setAttribute("attempted_envs", attemptedEnvs); - span.setAttribute("message_count", messageCount); - span.setAttribute("cooloff_period_count", coolOffPeriodCount); - - return; - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - async #processQueueForWorkerQueue(queueKey: string, parentQueueKey: string) { - return this.#trace("processQueueForWorkerQueue", async (span) => { - span.setAttributes({ - [SemanticAttributes.QUEUE]: queueKey, - [SemanticAttributes.PARENT_QUEUE]: parentQueueKey, - }); - - const maxCount = this.options.sharedWorkerQueueMaxMessageCount ?? 10; - - const dequeuedMessages = await this.#callDequeueMessages({ - messageQueue: queueKey, - parentQueue: parentQueueKey, - maxCount, - }); - - if (!dequeuedMessages || dequeuedMessages.length === 0) { - return; - } - - await this.#trace( - "addToWorkerQueue", - async (addToWorkerQueueSpan) => { - const workerQueueKey = this.keys.sharedWorkerQueueKey(); - - addToWorkerQueueSpan.setAttributes({ - message_count: dequeuedMessages.length, - [SemanticAttributes.PARENT_QUEUE]: workerQueueKey, - }); - - await this.redis.rpush( - workerQueueKey, - ...dequeuedMessages.map((message) => message.messageId) - ); - }, - { - kind: SpanKind.INTERNAL, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - - // If we dequeued the max count, we need to enqueue another job to dequeue the next batch - if (dequeuedMessages.length === maxCount) { - await this.worker.enqueueOnce({ - id: queueKey, - job: "processQueueForWorkerQueue", - payload: { - queueKey, - parentQueueKey, - }, - availableAt: new Date(Date.now() + 500), // 500ms from now - }); - } - }); - } - - public async acknowledgeMessage(messageId: string, reason: string = "unknown") { - return this.#trace( - "acknowledgeMessage", - async (span) => { - const message = await this.readMessage(messageId); - - if (!message) { - logger.log(`[${this.name}].acknowledgeMessage() message not found`, { - messageId, - service: this.name, - reason, - }); - return; - } - - span.setAttributes({ - [SemanticAttributes.QUEUE]: message.queue, - [SemanticAttributes.MESSAGE_ID]: message.messageId, - [SemanticAttributes.CONCURRENCY_KEY]: message.concurrencyKey, - [SemanticAttributes.PARENT_QUEUE]: message.parentQueue, - ["marqs.reason"]: reason, - }); - - await this.options.visibilityTimeoutStrategy.cancelHeartbeat(messageId); - - await this.#callAcknowledgeMessage({ - parentQueue: message.parentQueue, - messageQueue: message.queue, - messageId, - }); - - const sharedQueueKey = this.keys.sharedQueueKey(); - - if (this.options.eagerDequeuingEnabled && message.parentQueue === sharedQueueKey) { - await this.worker.enqueueOnce({ - id: message.queue, - job: "processQueueForWorkerQueue", - payload: { - queueKey: message.queue, - parentQueueKey: message.parentQueue, - }, - availableAt: new Date(Date.now() + 500), // 500ms from now - }); - } - - await this.options.subscriber?.messageAcked(message); - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "ack", - [SEMATTRS_MESSAGE_ID]: messageId, - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - /** - * Negative acknowledge a message, which will requeue the message. - * Returns whether it went back into the queue or not. - */ - public async nackMessage( - messageId: string, - retryAt: number = Date.now(), - updates?: Record - ) { - return this.#trace( - "nackMessage", - async (span) => { - const message = await this.readMessage(messageId); - - if (!message) { - logger.debug(`[${this.name}].nackMessage() message not found`, { - messageId, - retryAt, - updates, - service: this.name, - }); - return false; - } - - const nackCount = await this.#getNackCount(messageId); - - span.setAttribute("nack_count", nackCount); - - if (nackCount >= this.options.maximumNackCount) { - logger.debug(`[${this.name}].nackMessage() maximum nack count reached`, { - messageId, - retryAt, - updates, - service: this.name, - }); - - span.setAttribute("maximum_nack_count_reached", true); - - // If we have reached the maximum nack count, we will ack the message - await this.acknowledgeMessage(messageId, "maximum nack count reached"); - return false; - } - - span.setAttributes({ - [SemanticAttributes.QUEUE]: message.queue, - [SemanticAttributes.MESSAGE_ID]: message.messageId, - [SemanticAttributes.CONCURRENCY_KEY]: message.concurrencyKey, - [SemanticAttributes.PARENT_QUEUE]: message.parentQueue, - }); - - if (updates) { - await this.replaceMessage(messageId, updates, retryAt); - } - - await this.options.visibilityTimeoutStrategy.cancelHeartbeat(messageId); - - await this.#callNackMessage(messageId, message, retryAt); - - await this.options.subscriber?.messageNacked(message); - - return true; - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "nack", - [SEMATTRS_MESSAGE_ID]: messageId, - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - public async cancelHeartbeat(messageId: string) { - return this.#trace( - "cancelHeartbeat", - async (span) => { - span.setAttributes({ - [SemanticAttributes.MESSAGE_ID]: messageId, - }); - - await this.options.visibilityTimeoutStrategy.cancelHeartbeat(messageId); - }, - { - kind: SpanKind.CONSUMER, - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "cancelHeartbeat", - [SEMATTRS_MESSAGE_ID]: messageId, - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - }, - } - ); - } - - async #trace( - name: string, - fn: (span: Span) => Promise, - options?: SpanOptions & { sampleRate?: number } - ): Promise { - return this.tracer.startActiveSpan( - name, - { - ...options, - attributes: { - ...options?.attributes, - }, - }, - async (span) => { - try { - return await fn(span); - } catch (e) { - if (e instanceof Error) { - span.recordException(e); - } else { - span.recordException(new Error(String(e))); - } - - span.setStatus({ - code: SpanStatusCode.ERROR, - message: e instanceof Error ? e.message : "Unknown error", - }); - - throw e; - } finally { - span.end(); - } - } - ); - } - - #nudgeTimestampForPriority(timestamp: number, priority?: MarQSPriorityLevel) { - if (!priority) { - return timestamp; - } - - switch (priority) { - case "resume": { - return timestamp - MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET; - } - case "retry": { - return timestamp - MARQS_RETRY_PRIORITY_TIMESTAMP_OFFSET; - } - } - } - - #calculateMessageAge(message: MessagePayload) { - const $timestamp = message.availableAt ?? message.timestamp; - - return Date.now() - $timestamp; - } - - async #getNackCount(messageId: string): Promise { - const result = await this.redis.get(this.keys.nackCounterKey(messageId)); - - return result ? Number(result) : 0; - } - - // This should increment by the number of seconds, but with a max value of Date.now() + visibilityTimeoutInMs - public async heartbeatMessage(messageId: string) { - await this.options.visibilityTimeoutStrategy.heartbeat(messageId, this.visibilityTimeoutInMs); - } - - get visibilityTimeoutInMs() { - return this.options.visibilityTimeoutInMs ?? 300000; // 5 minutes - } - - async readMessage(messageId: string) { - return this.#trace( - "readMessage", - async (span) => { - const rawMessage = await this.redis.get(this.keys.messageKey(messageId)); - - if (!rawMessage) { - return; - } - - const message = MessagePayload.safeParse(JSON.parse(rawMessage)); - - if (!message.success) { - logger.error(`[${this.name}] Failed to parse message`, { - messageId, - error: message.error, - service: this.name, - }); - - return; - } - - return message.data; - }, - { - attributes: { - [SEMATTRS_MESSAGING_OPERATION]: "receive", - [SEMATTRS_MESSAGE_ID]: messageId, - [SEMATTRS_MESSAGING_SYSTEM]: "marqs", - [SemanticAttributes.MESSAGE_ID]: messageId, - }, - } - ); - } - - #startRebalanceWorkers() { - if (!this.options.enableRebalancing) { - return; - } - - // Start a new worker to rebalance parent queues periodically - for (let i = 0; i < this.options.workers; i++) { - const worker = new AsyncWorker(this.#rebalanceParentQueues.bind(this), 60_000); - - this.#rebalanceWorkers.push(worker); - - worker.start(); - } - } - - queueConcurrencyScanStream( - count: number = 100, - onEndCallback?: () => void, - onErrorCallback?: (error: Error) => void - ) { - const pattern = this.keys.queueCurrentConcurrencyScanPattern(); - - logger.debug("Starting queue concurrency scan stream", { - pattern, - component: "marqs", - operation: "queueConcurrencyScanStream", - service: this.name, - count, - }); - - const redis = this.redis.duplicate(); - - const stream = redis.scanStream({ - match: pattern, - type: "set", - count, - }); - - stream.on("end", () => { - onEndCallback?.(); - redis.quit(); - }); - - stream.on("error", (error) => { - onErrorCallback?.(error); - redis.quit(); - }); - - return { stream, redis }; - } - - async #rebalanceParentQueues() { - return await new Promise((resolve, reject) => { - // Scan for sorted sets with the parent queue pattern - const pattern = this.keys.sharedQueueScanPattern(); - const redis = this.redis.duplicate(); - const stream = redis.scanStream({ - match: pattern, - type: "zset", - count: 100, - }); - - logger.debug("Streaming parent queues based on pattern", { - pattern, - component: "marqs", - operation: "rebalanceParentQueues", - service: this.name, - }); - - stream.on("data", async (keys) => { - const uniqueKeys = Array.from(new Set(keys)); - - if (uniqueKeys.length === 0) { - return; - } - - stream.pause(); - - logger.debug("Rebalancing parent queues", { - component: "marqs", - operation: "rebalanceParentQueues", - parentQueues: uniqueKeys, - service: this.name, - }); - - Promise.all( - uniqueKeys.map(async (key) => this.#rebalanceParentQueue(this.keys.stripKeyPrefix(key))) - ).finally(() => { - stream.resume(); - }); - }); - - stream.on("end", () => { - redis.quit().finally(() => { - resolve(); - }); - }); - - stream.on("error", (e) => { - redis.quit().finally(() => { - reject(e); - }); - }); - }); - } - - // Parent queue is a sorted set, the values of which are queue keys and the scores are is the oldest message in the queue - // We need to scan the parent queue and rebalance the queues based on the oldest message in the queue - async #rebalanceParentQueue(parentQueue: string) { - return await new Promise((resolve, reject) => { - const redis = this.redis.duplicate(); - - const stream = redis.zscanStream(parentQueue, { - match: "*", - count: 100, - }); - - stream.on("data", async (childQueues) => { - stream.pause(); - - // childQueues is a flat array but of the form [queue1, score1, queue2, score2, ...], we want to group them into pairs - const childQueuesWithScores: Record = {}; - - for (let i = 0; i < childQueues.length; i += 2) { - childQueuesWithScores[childQueues[i]] = childQueues[i + 1]; - } - - logger.debug("Rebalancing child queues", { - parentQueue, - childQueuesWithScores, - component: "marqs", - operation: "rebalanceParentQueues", - service: this.name, - }); - - await Promise.all( - Object.entries(childQueuesWithScores).map(async ([childQueue, currentScore]) => - this.#callRebalanceParentQueueChild({ parentQueue, childQueue, currentScore }) - ) - ).finally(() => { - stream.resume(); - }); - }); - - stream.on("end", () => { - redis.quit().finally(() => { - resolve(); - }); - }); - - stream.on("error", (e) => { - redis.quit().finally(() => { - reject(e); - }); - }); - }); - } - - async #callEnqueueMessage( - message: MessagePayload, - reserve?: EnqueueMessageReserveConcurrencyOptions - ) { - const queueKey = message.queue; - const parentQueueKey = message.parentQueue; - const messageKey = this.keys.messageKey(message.messageId); - const queueCurrentConcurrencyKey = this.keys.queueCurrentConcurrencyKeyFromQueue(message.queue); - const queueReserveConcurrencyKey = this.keys.queueReserveConcurrencyKeyFromQueue(message.queue); - const envCurrentConcurrencyKey = this.keys.envCurrentConcurrencyKeyFromQueue(message.queue); - const envReserveConcurrencyKey = this.keys.envReserveConcurrencyKeyFromQueue(message.queue); - const envQueueKey = this.keys.envQueueKeyFromQueue(message.queue); - - const queueName = message.queue; - const messageId = message.messageId; - const messageData = JSON.stringify(message); - const messageScore = String( - this.#nudgeTimestampForPriority(message.timestamp, message.priority) - ); - - if (!reserve) { - logger.debug("Calling enqueueMessage", { - service: this.name, - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - }); - - const result = await this.redis.enqueueMessage( - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore - ); - - logger.debug("enqueueMessage result", { - service: this.name, - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - result, - }); - - return true; - } - - const envConcurrencyLimitKey = this.keys.envConcurrencyLimitKeyFromQueue(message.queue); - const reserveMessageId = reserve.messageId; - const defaultEnvConcurrencyLimit = String(this.options.defaultEnvConcurrency); - - if (!reserve.recursiveQueue) { - logger.debug("Calling enqueueMessageWithReservingConcurrency", { - service: this.name, - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envConcurrencyLimitKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - reserveMessageId, - defaultEnvConcurrencyLimit, - }); - - const result = await this.redis.enqueueMessageWithReservingConcurrency( - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envConcurrencyLimitKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - reserveMessageId, - defaultEnvConcurrencyLimit - ); - - logger.debug("enqueueMessageWithReservingConcurrency result", { - service: this.name, - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envConcurrencyLimitKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - reserveMessageId, - defaultEnvConcurrencyLimit, - result, - }); - - return true; - } else { - const queueConcurrencyLimitKey = this.keys.queueConcurrencyLimitKeyFromQueue(message.queue); - - logger.debug("Calling enqueueMessageWithReservingConcurrencyForRecursiveQueue", { - service: this.name, - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - queueConcurrencyLimitKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envConcurrencyLimitKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - reserveMessageId, - defaultEnvConcurrencyLimit, - }); - - const result = await this.redis.enqueueMessageWithReservingConcurrencyOnRecursiveQueue( - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - queueConcurrencyLimitKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envConcurrencyLimitKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - reserveMessageId, - defaultEnvConcurrencyLimit - ); - - logger.debug("enqueueMessageWithReservingConcurrencyOnRecursiveQueue result", { - service: this.name, - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - queueConcurrencyLimitKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envConcurrencyLimitKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - reserveMessageId, - defaultEnvConcurrencyLimit, - result, - }); - - return !!result; - } - } - - async #callDequeueMessages({ - messageQueue, - parentQueue, - maxCount, - }: { - messageQueue: string; - parentQueue: string; - maxCount: number; - }) { - const queueConcurrencyLimitKey = this.keys.queueConcurrencyLimitKeyFromQueue(messageQueue); - const queueCurrentConcurrencyKey = this.keys.queueCurrentConcurrencyKeyFromQueue(messageQueue); - const envConcurrencyLimitKey = this.keys.envConcurrencyLimitKeyFromQueue(messageQueue); - const envCurrentConcurrencyKey = this.keys.envCurrentConcurrencyKeyFromQueue(messageQueue); - const envReserveConcurrencyKey = this.keys.envReserveConcurrencyKeyFromQueue(messageQueue); - const queueReserveConcurrencyKey = this.keys.queueReserveConcurrencyKeyFromQueue(messageQueue); - const envQueueKey = this.keys.envQueueKeyFromQueue(messageQueue); - - logger.debug("Calling dequeueMessages", { - messageQueue, - parentQueue, - queueConcurrencyLimitKey, - envConcurrencyLimitKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - service: this.name, - }); - - const result = await this.redis.dequeueMessages( - messageQueue, - parentQueue, - queueConcurrencyLimitKey, - envConcurrencyLimitKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - messageQueue, - String(Date.now()), - String(this.options.defaultEnvConcurrency), - String(maxCount) - ); - - if (!result) { - return; - } - - logger.debug("Dequeue message result", { - result, - service: this.name, - }); - - const messages = []; - for (let i = 0; i < result.length; i += 2) { - const messageId = result[i]; - const messageScore = result[i + 1]; - - messages.push({ - messageId, - messageScore, - }); - } - - logger.debug("dequeueMessages parsed result", { - messages, - service: this.name, - }); - - return messages.filter(Boolean); - } - - async #callRequeueMessage(message: MessagePayload) { - const queueKey = message.queue; - const parentQueueKey = message.parentQueue; - const messageKey = this.keys.messageKey(message.messageId); - const queueCurrentConcurrencyKey = this.keys.queueCurrentConcurrencyKeyFromQueue(message.queue); - const queueReserveConcurrencyKey = this.keys.queueReserveConcurrencyKeyFromQueue(message.queue); - const envCurrentConcurrencyKey = this.keys.envCurrentConcurrencyKeyFromQueue(message.queue); - const envReserveConcurrencyKey = this.keys.envReserveConcurrencyKeyFromQueue(message.queue); - const envQueueKey = this.keys.envQueueKeyFromQueue(message.queue); - - const queueName = message.queue; - const messageId = message.messageId; - const messageData = JSON.stringify(message); - const messageScore = String( - this.#nudgeTimestampForPriority(message.timestamp, message.priority) - ); - - logger.debug("Calling requeueMessage", { - service: this.name, - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - }); - - const result = await this.redis.requeueMessage( - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore - ); - - logger.debug("requeueMessage result", { - service: this.name, - queueKey, - parentQueueKey, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - queueName, - messageId, - messageData, - messageScore, - result, - }); - - await this.options.subscriber?.messageRequeued(message); - - return true; - } - - async #callDelayedRequeueMessage(message: MessagePayload) { - const messageKey = this.keys.messageKey(message.messageId); - const queueCurrentConcurrencyKey = this.keys.queueCurrentConcurrencyKeyFromQueue(message.queue); - const queueReserveConcurrencyKey = this.keys.queueReserveConcurrencyKeyFromQueue(message.queue); - const envCurrentConcurrencyKey = this.keys.envCurrentConcurrencyKeyFromQueue(message.queue); - const envReserveConcurrencyKey = this.keys.envReserveConcurrencyKeyFromQueue(message.queue); - - const messageId = message.messageId; - const messageData = JSON.stringify(message); - - logger.debug("Calling delayedRequeueMessage", { - service: this.name, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - messageId, - messageData, - }); - - const result = await this.redis.delayedRequeueMessage( - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - messageId, - messageData - ); - - logger.debug("delayedRequeueMessage result", { - service: this.name, - messageKey, - queueCurrentConcurrencyKey, - queueReserveConcurrencyKey, - envCurrentConcurrencyKey, - envReserveConcurrencyKey, - messageId, - messageData, - result, - }); - - logger.debug("Enqueuing scheduleRequeueMessage in LRE worker", { - service: this.name, - message, - }); - - // Schedule the requeue in the future - await legacyRunEngineWorker.enqueue({ - id: `marqs-requeue-${messageId}`, - job: "scheduleRequeueMessage", - payload: { messageId }, - availableAt: new Date( - message.timestamp - MARQS_SCHEDULED_REQUEUE_AVAILABLE_AT_THRESHOLD_IN_MS - ), - }); - - return true; - } - - async #callAcknowledgeMessage({ - parentQueue, - messageQueue, - messageId, - }: { - parentQueue: string; - messageQueue: string; - messageId: string; - }) { - const messageKey = this.keys.messageKey(messageId); - const concurrencyKey = this.keys.queueCurrentConcurrencyKeyFromQueue(messageQueue); - const envConcurrencyKey = this.keys.envCurrentConcurrencyKeyFromQueue(messageQueue); - const envReserveConcurrencyKey = this.keys.envReserveConcurrencyKeyFromQueue(messageQueue); - const queueReserveConcurrencyKey = this.keys.queueReserveConcurrencyKeyFromQueue(messageQueue); - const envQueueKey = this.keys.envQueueKeyFromQueue(messageQueue); - - logger.debug("Calling acknowledgeMessage", { - messageKey, - messageQueue, - concurrencyKey, - envConcurrencyKey, - messageId, - parentQueue, - envQueueKey, - service: this.name, - }); - - return this.redis.acknowledgeMessage( - parentQueue, - messageKey, - messageQueue, - concurrencyKey, - queueReserveConcurrencyKey, - envConcurrencyKey, - envReserveConcurrencyKey, - envQueueKey, - messageId, - messageQueue - ); - } - - async #callNackMessage(messageId: string, message: MessagePayload, messageScore: number) { - const messageKey = this.keys.messageKey(message.messageId); - const queueKey = message.queue; - const parentQueueKey = message.parentQueue; - const queueCurrentConcurrencyKey = this.keys.queueCurrentConcurrencyKeyFromQueue(message.queue); - const envCurrentConcurrencyKey = this.keys.envCurrentConcurrencyKeyFromQueue(message.queue); - const nackCounterKey = this.keys.nackCounterKey(message.messageId); - const envQueueKey = this.keys.envQueueKeyFromQueue(message.queue); - const queueName = message.queue; - - logger.debug("Calling nackMessage", { - messageKey, - queueKey, - parentQueueKey, - queueCurrentConcurrencyKey, - envCurrentConcurrencyKey, - nackCounterKey, - messageId, - messageScore, - envQueueKey, - service: this.name, - }); - - return this.redis.nackMessage( - messageKey, - queueKey, - parentQueueKey, - queueCurrentConcurrencyKey, - envCurrentConcurrencyKey, - envQueueKey, - nackCounterKey, - queueName, - messageId, - String(Date.now()), - String(messageScore) - ); - } - - #callUpdateGlobalConcurrencyLimits({ - envConcurrencyLimitKey, - envConcurrencyLimit, - }: { - envConcurrencyLimitKey: string; - envConcurrencyLimit: number; - }) { - return this.redis.updateGlobalConcurrencyLimits( - envConcurrencyLimitKey, - String(envConcurrencyLimit) - ); - } - - async #callRebalanceParentQueueChild({ - parentQueue, - childQueue, - currentScore, - }: { - parentQueue: string; - childQueue: string; - currentScore: string; - }) { - const rebalanceResult = await this.redis.rebalanceParentQueueChild( - childQueue, - parentQueue, - childQueue, - currentScore - ); - - if (rebalanceResult) { - logger.debug("Rebalanced parent queue child", { - parentQueue, - childQueue, - currentScore, - rebalanceResult, - operation: "rebalanceParentQueueChild", - service: this.name, - }); - } - - return rebalanceResult; - } - - #registerCommands() { - this.redis.defineCommand("enqueueMessage", { - numberOfKeys: 8, - lua: ` -local queueKey = KEYS[1] -local parentQueueKey = KEYS[2] -local messageKey = KEYS[3] -local queueCurrentConcurrencyKey = KEYS[4] -local queueReserveConcurrencyKey = KEYS[5] -local envCurrentConcurrencyKey = KEYS[6] -local envReserveConcurrencyKey = KEYS[7] -local envQueueKey = KEYS[8] - -local queueName = ARGV[1] -local messageId = ARGV[2] -local messageData = ARGV[3] -local messageScore = ARGV[4] - --- Write the message to the message key -redis.call('SET', messageKey, messageData) - --- Add the message to the queue -redis.call('ZADD', queueKey, messageScore, messageId) - --- Add the message to the env queue -redis.call('ZADD', envQueueKey, messageScore, messageId) - --- Rebalance the parent queue -local earliestMessage = redis.call('ZRANGE', queueKey, 0, 0, 'WITHSCORES') -if #earliestMessage == 0 then - redis.call('ZREM', parentQueueKey, queueName) -else - redis.call('ZADD', parentQueueKey, earliestMessage[2], queueName) -end - --- Update the concurrency keys -redis.call('SREM', queueCurrentConcurrencyKey, messageId) -redis.call('SREM', envCurrentConcurrencyKey, messageId) -redis.call('SREM', envReserveConcurrencyKey, messageId) -redis.call('SREM', queueReserveConcurrencyKey, messageId) - -return true - `, - }); - - this.redis.defineCommand("enqueueMessageWithReservingConcurrency", { - numberOfKeys: 9, - lua: ` -local queueKey = KEYS[1] -local parentQueueKey = KEYS[2] -local messageKey = KEYS[3] -local queueCurrentConcurrencyKey = KEYS[4] -local queueReserveConcurrencyKey = KEYS[5] -local envCurrentConcurrencyKey = KEYS[6] -local envReserveConcurrencyKey = KEYS[7] -local envConcurrencyLimitKey = KEYS[8] -local envQueueKey = KEYS[9] - -local queueName = ARGV[1] -local messageId = ARGV[2] -local messageData = ARGV[3] -local messageScore = ARGV[4] -local reserveMessageId = ARGV[5] -local defaultEnvConcurrencyLimit = ARGV[6] - --- Write the message to the message key -redis.call('SET', messageKey, messageData) - --- Add the message to the queue -redis.call('ZADD', queueKey, messageScore, messageId) - --- Add the message to the env queue -redis.call('ZADD', envQueueKey, messageScore, messageId) - --- Rebalance the parent queue -local earliestMessage = redis.call('ZRANGE', queueKey, 0, 0, 'WITHSCORES') -if #earliestMessage == 0 then - redis.call('ZREM', parentQueueKey, queueName) -else - redis.call('ZADD', parentQueueKey, earliestMessage[2], queueName) -end - --- Update the concurrency keys -redis.call('SREM', queueCurrentConcurrencyKey, messageId) -redis.call('SREM', envCurrentConcurrencyKey, messageId) -redis.call('SREM', envReserveConcurrencyKey, messageId) -redis.call('SREM', queueReserveConcurrencyKey, messageId) - --- Reserve the concurrency for the message -local envReserveConcurrencyLimit = tonumber(redis.call('GET', envConcurrencyLimitKey) or defaultEnvConcurrencyLimit) --- Count the number of messages in the reserve concurrency set -local envReserveConcurrency = tonumber(redis.call('SCARD', envReserveConcurrencyKey) or '0') - --- If there is space, add the messaageId to the env reserve concurrency set -if envReserveConcurrency < envReserveConcurrencyLimit then - redis.call('SADD', envReserveConcurrencyKey, reserveMessageId) -end - -return true - `, - }); - - this.redis.defineCommand("enqueueMessageWithReservingConcurrencyOnRecursiveQueue", { - numberOfKeys: 10, - lua: ` -local queueKey = KEYS[1] -local parentQueueKey = KEYS[2] -local messageKey = KEYS[3] -local queueCurrentConcurrencyKey = KEYS[4] -local queueReserveConcurrencyKey = KEYS[5] -local queueConcurrencyLimitKey = KEYS[6] -local envCurrentConcurrencyKey = KEYS[7] -local envReserveConcurrencyKey = KEYS[8] -local envConcurrencyLimitKey = KEYS[9] -local envQueueKey = KEYS[10] - -local queueName = ARGV[1] -local messageId = ARGV[2] -local messageData = ARGV[3] -local messageScore = ARGV[4] -local reserveMessageId = ARGV[5] -local defaultEnvConcurrencyLimit = ARGV[6] - --- Get the env reserve concurrency limit because we need it to calculate the max reserve concurrency --- for the specific queue -local envReserveConcurrencyLimit = tonumber(redis.call('GET', envConcurrencyLimitKey) or defaultEnvConcurrencyLimit) - --- Count the number of messages in the queue reserve concurrency set -local queueReserveConcurrency = tonumber(redis.call('SCARD', queueReserveConcurrencyKey) or '0') -local queueConcurrencyLimit = tonumber(redis.call('GET', queueConcurrencyLimitKey) or '1000000') - -local queueReserveConcurrencyLimit = math.min(queueConcurrencyLimit, envReserveConcurrencyLimit) - --- If we cannot add the reserve concurrency, then we have to return false -if queueReserveConcurrency >= queueReserveConcurrencyLimit then - return false -end - --- Write the message to the message key -redis.call('SET', messageKey, messageData) - --- Add the message to the queue -redis.call('ZADD', queueKey, messageScore, messageId) - --- Add the message to the env queue -redis.call('ZADD', envQueueKey, messageScore, messageId) - --- Rebalance the parent queue -local earliestMessage = redis.call('ZRANGE', queueKey, 0, 0, 'WITHSCORES') -if #earliestMessage == 0 then - redis.call('ZREM', parentQueueKey, queueName) -else - redis.call('ZADD', parentQueueKey, earliestMessage[2], queueName) -end - --- Update the concurrency keys -redis.call('SREM', queueCurrentConcurrencyKey, messageId) -redis.call('SREM', envCurrentConcurrencyKey, messageId) -redis.call('SREM', envReserveConcurrencyKey, messageId) -redis.call('SREM', queueReserveConcurrencyKey, messageId) - --- Count the number of messages in the env reserve concurrency set -local envReserveConcurrency = tonumber(redis.call('SCARD', envReserveConcurrencyKey) or '0') - --- If there is space, add the messaageId to the env reserve concurrency set -if envReserveConcurrency < envReserveConcurrencyLimit then - redis.call('SADD', envReserveConcurrencyKey, reserveMessageId) -end - -redis.call('SADD', queueReserveConcurrencyKey, reserveMessageId) - -return true - `, - }); - - this.redis.defineCommand("dequeueMessages", { - numberOfKeys: 9, - lua: ` -local queueKey = KEYS[1] -local parentQueueKey = KEYS[2] -local queueConcurrencyLimitKey = KEYS[3] -local envConcurrencyLimitKey = KEYS[4] -local queueCurrentConcurrencyKey = KEYS[5] -local queueReserveConcurrencyKey = KEYS[6] -local envCurrentConcurrencyKey = KEYS[7] -local envReserveConcurrencyKey = KEYS[8] -local envQueueKey = KEYS[9] - -local queueName = ARGV[1] -local currentTime = tonumber(ARGV[2]) -local defaultEnvConcurrencyLimit = ARGV[3] -local maxCount = tonumber(ARGV[4] or '1') - --- Check current env concurrency against the limit -local envCurrentConcurrency = tonumber(redis.call('SCARD', envCurrentConcurrencyKey) or '0') -local envConcurrencyLimit = tonumber(redis.call('GET', envConcurrencyLimitKey) or defaultEnvConcurrencyLimit) -local envReserveConcurrency = tonumber(redis.call('SCARD', envReserveConcurrencyKey) or '0') -local totalEnvConcurrencyLimit = envConcurrencyLimit + envReserveConcurrency - -if envCurrentConcurrency >= totalEnvConcurrencyLimit then - return nil -end - --- Check current queue concurrency against the limit -local queueCurrentConcurrency = tonumber(redis.call('SCARD', queueCurrentConcurrencyKey) or '0') -local queueConcurrencyLimit = math.min(tonumber(redis.call('GET', queueConcurrencyLimitKey) or '1000000'), envConcurrencyLimit) -local queueReserveConcurrency = tonumber(redis.call('SCARD', queueReserveConcurrencyKey) or '0') -local totalQueueConcurrencyLimit = queueConcurrencyLimit + queueReserveConcurrency - --- Check condition only if concurrencyLimit exists -if queueCurrentConcurrency >= totalQueueConcurrencyLimit then - return nil -end - --- Calculate how many messages we can actually dequeue based on concurrency limits -local envAvailableCapacity = totalEnvConcurrencyLimit - envCurrentConcurrency -local queueAvailableCapacity = totalQueueConcurrencyLimit - queueCurrentConcurrency -local actualMaxCount = math.min(maxCount, envAvailableCapacity, queueAvailableCapacity) - -if actualMaxCount <= 0 then - return nil -end - --- Attempt to dequeue messages up to actualMaxCount -local messagesWithScores = redis.call('ZRANGEBYSCORE', queueKey, '-inf', currentTime, 'WITHSCORES', 'LIMIT', 0, actualMaxCount) - -if #messagesWithScores == 0 then - return nil -end - -local messageIds = {} -for i = 1, #messagesWithScores, 2 do - table.insert(messageIds, messagesWithScores[i]) -end - --- Remove the messages from the queue and update concurrency -redis.call('ZREM', queueKey, unpack(messageIds)) -redis.call('ZREM', envQueueKey, unpack(messageIds)) -redis.call('SADD', queueCurrentConcurrencyKey, unpack(messageIds)) -redis.call('SADD', envCurrentConcurrencyKey, unpack(messageIds)) - --- Remove the message from the reserve concurrency set -redis.call('SREM', envReserveConcurrencyKey, unpack(messageIds)) - --- Remove the message from the queue reserve concurrency set -redis.call('SREM', queueReserveConcurrencyKey, unpack(messageIds)) - --- Rebalance the parent queue -local earliestMessage = redis.call('ZRANGE', queueKey, 0, 0, 'WITHSCORES') -if #earliestMessage == 0 then - redis.call('ZREM', parentQueueKey, queueName) -else - redis.call('ZADD', parentQueueKey, earliestMessage[2], queueName) -end - -return messagesWithScores - `, - }); - - this.redis.defineCommand("popMessageFromWorkerQueue", { - numberOfKeys: 1, - lua: ` -local workerQueueKey = KEYS[1] - --- lpop the first message from the worker queue -local messageId = redis.call('LPOP', workerQueueKey) - --- if there is no messageId, return nil -if not messageId then - return nil -end - --- get the length of the worker queue -local queueLength = tonumber(redis.call('LLEN', workerQueueKey) or '0') - -return {messageId, queueLength} -- Return message details - `, - }); - - this.redis.defineCommand("acknowledgeMessage", { - numberOfKeys: 8, - lua: ` -local parentQueueKey = KEYS[1] -local messageKey = KEYS[2] -local queueKey = KEYS[3] -local queueConcurrencyKey = KEYS[4] -local queueReserveConcurrencyKey = KEYS[5] -local envCurrentConcurrencyKey = KEYS[6] -local envReserveConcurrencyKey = KEYS[7] -local envQueueKey = KEYS[8] - -local messageId = ARGV[1] -local queueName = ARGV[2] - --- Remove the message from the queue -redis.call('ZREM', queueKey, messageId) - --- Rebalance the parent queue -local earliestMessage = redis.call('ZRANGE', queueKey, 0, 0, 'WITHSCORES') -if #earliestMessage == 0 then - redis.call('ZREM', parentQueueKey, queueName) -else - redis.call('ZADD', parentQueueKey, earliestMessage[2], queueName) -end - --- Update the concurrency keys -redis.call('SREM', queueConcurrencyKey, messageId) -redis.call('SREM', envCurrentConcurrencyKey, messageId) -redis.call('SREM', envReserveConcurrencyKey, messageId) -redis.call('SREM', queueReserveConcurrencyKey, messageId) -redis.call('ZREM', envQueueKey, messageId) -redis.call('DEL', messageKey) -`, - }); - - this.redis.defineCommand("requeueMessage", { - numberOfKeys: 8, - lua: ` -local queueKey = KEYS[1] -local parentQueueKey = KEYS[2] -local messageKey = KEYS[3] -local queueCurrentConcurrencyKey = KEYS[4] -local queueReserveConcurrencyKey = KEYS[5] -local envCurrentConcurrencyKey = KEYS[6] -local envReserveConcurrencyKey = KEYS[7] -local envQueueKey = KEYS[8] - -local queueName = ARGV[1] -local messageId = ARGV[2] -local messageData = ARGV[3] -local messageScore = ARGV[4] - --- Write the new message data -redis.call('SET', messageKey, messageData) - --- Add the message to the queue with a new score -redis.call('ZADD', queueKey, messageScore, messageId) -redis.call('ZADD', envQueueKey, messageScore, messageId) - --- Rebalance the parent queue -local earliestMessage = redis.call('ZRANGE', queueKey, 0, 0, 'WITHSCORES') -if #earliestMessage == 0 then - redis.call('ZREM', parentQueueKey, queueName) -else - redis.call('ZADD', parentQueueKey, earliestMessage[2], queueName) -end - --- Clear all concurrency sets (combined from both scripts) -redis.call('SREM', queueCurrentConcurrencyKey, messageId) -redis.call('SREM', queueReserveConcurrencyKey, messageId) -redis.call('SREM', envCurrentConcurrencyKey, messageId) -redis.call('SREM', envReserveConcurrencyKey, messageId) - -return true -`, - }); - - this.redis.defineCommand("delayedRequeueMessage", { - numberOfKeys: 5, - lua: ` -local messageKey = KEYS[1] -local queueCurrentConcurrencyKey = KEYS[2] -local queueReserveConcurrencyKey = KEYS[3] -local envCurrentConcurrencyKey = KEYS[4] -local envReserveConcurrencyKey = KEYS[5] - -local messageId = ARGV[1] -local messageData = ARGV[2] - --- Write the new message data -redis.call('SET', messageKey, messageData) - --- Clear all concurrency sets -redis.call('SREM', queueCurrentConcurrencyKey, messageId) -redis.call('SREM', queueReserveConcurrencyKey, messageId) -redis.call('SREM', envCurrentConcurrencyKey, messageId) -redis.call('SREM', envReserveConcurrencyKey, messageId) - -return true -`, - }); - - this.redis.defineCommand("nackMessage", { - numberOfKeys: 7, - lua: ` -local messageKey = KEYS[1] -local queueKey = KEYS[2] -local parentQueueKey = KEYS[3] -local queueCurrentConcurrencyKey = KEYS[4] -local envCurrentConcurrencyKey = KEYS[5] -local envQueueKey = KEYS[6] -local nackCounterKey = KEYS[7] - -local queueName = ARGV[1] -local messageId = ARGV[2] -local currentTime = tonumber(ARGV[3]) -local messageScore = tonumber(ARGV[4]) - --- Update the current concurrency keys -redis.call('SREM', queueCurrentConcurrencyKey, messageId) -redis.call('SREM', envCurrentConcurrencyKey, messageId) - --- Enqueue the message into the queue -redis.call('ZADD', queueKey, messageScore, messageId) - --- Enqueue the message into the env queue -redis.call('ZADD', envQueueKey, messageScore, messageId) - --- Increment the nack counter with an expiry of 30 days -redis.call('INCR', nackCounterKey) -redis.call('EXPIRE', nackCounterKey, 2592000) - --- Rebalance the parent queue -local earliestMessage = redis.call('ZRANGE', queueKey, 0, 0, 'WITHSCORES') -if #earliestMessage == 0 then - redis.call('ZREM', parentQueueKey, queueName) -else - redis.call('ZADD', parentQueueKey, earliestMessage[2], queueName) -end -`, - }); - - this.redis.defineCommand("updateGlobalConcurrencyLimits", { - numberOfKeys: 1, - lua: ` -local envConcurrencyLimitKey = KEYS[1] -local envConcurrencyLimit = ARGV[1] - -redis.call('SET', envConcurrencyLimitKey, envConcurrencyLimit) - `, - }); - - this.redis.defineCommand("rebalanceParentQueueChild", { - numberOfKeys: 2, - lua: ` --- Keys: childQueueKey, parentQueueKey -local childQueueKey = KEYS[1] -local parentQueueKey = KEYS[2] - --- Args: childQueueName, currentScore -local childQueueName = ARGV[1] -local currentScore = ARGV[2] - --- Rebalance the parent queue -local earliestMessage = redis.call('ZRANGE', childQueueKey, 0, 0, 'WITHSCORES') -if #earliestMessage == 0 then - redis.call('ZREM', parentQueueKey, childQueueName) - - -- Return true because the parent queue was rebalanced - return true -else - -- If the earliest message is different, update the parent queue and return true, else return false - if earliestMessage[2] == currentScore then - return false - end - - redis.call('ZADD', parentQueueKey, earliestMessage[2], childQueueName) - - return earliestMessage[2] -end -`, - }); - } -} - -declare module "ioredis" { - interface RedisCommander { - enqueueMessage( - queueKey: string, - parentQueueKey: string, - messageKey: string, - queueCurrentConcurrencyKey: string, - queueReserveConcurrencyKey: string, - envCurrentConcurrencyKey: string, - envReserveConcurrencyKey: string, - envQueueKey: string, - queueName: string, - messageId: string, - messageData: string, - messageScore: string, - callback?: Callback - ): Result; - - enqueueMessageWithReservingConcurrency( - queueKey: string, - parentQueueKey: string, - messageKey: string, - queueCurrentConcurrencyKey: string, - queueReserveConcurrencyKey: string, - envCurrentConcurrencyKey: string, - envReserveConcurrencyKey: string, - envConcurrencyLimitKey: string, - envQueueKey: string, - queueName: string, - messageId: string, - messageData: string, - messageScore: string, - reserveMessageId: string, - defaultEnvConcurrencyLimit: string, - callback?: Callback - ): Result; - - enqueueMessageWithReservingConcurrencyOnRecursiveQueue( - queueKey: string, - parentQueueKey: string, - messageKey: string, - queueCurrentConcurrencyKey: string, - queueReserveConcurrencyKey: string, - queueConcurrencyLimitKey: string, - envCurrentConcurrencyKey: string, - envReserveConcurrencyKey: string, - envConcurrencyLimitKey: string, - envQueueKey: string, - queueName: string, - messageId: string, - messageData: string, - messageScore: string, - reserveMessageId: string, - defaultEnvConcurrencyLimit: string, - callback?: Callback - ): Result; - - dequeueMessages( - queueKey: string, - parentQueueKey: string, - queueConcurrencyLimitKey: string, - envConcurrencyLimitKey: string, - queueCurrentConcurrencyKey: string, - queueReserveConcurrencyKey: string, - envCurrentConcurrencyKey: string, - envReserveConcurrencyKey: string, - envQueueKey: string, - queueName: string, - currentTime: string, - defaultEnvConcurrencyLimit: string, - maxCount: string, - callback?: Callback - ): Result; - - popMessageFromWorkerQueue( - workerQueueKey: string, - callback?: Callback<[string, string] | null> - ): Result<[string, string] | null, Context>; - - requeueMessage( - queueKey: string, - parentQueueKey: string, - messageKey: string, - queueCurrentConcurrencyKey: string, - queueReserveConcurrencyKey: string, - envCurrentConcurrencyKey: string, - envReserveConcurrencyKey: string, - envQueueKey: string, - queueName: string, - messageId: string, - messageData: string, - messageScore: string, - callback?: Callback - ): Result; - - delayedRequeueMessage( - messageKey: string, - queueCurrentConcurrencyKey: string, - queueReserveConcurrencyKey: string, - envCurrentConcurrencyKey: string, - envReserveConcurrencyKey: string, - messageId: string, - messageData: string, - callback?: Callback - ): Result; - - acknowledgeMessage( - parentQueue: string, - messageKey: string, - messageQueue: string, - concurrencyKey: string, - queueReserveConcurrencyKey: string, - envConcurrencyKey: string, - envReserveConcurrencyKey: string, - envQueueKey: string, - messageId: string, - messageQueueName: string, - callback?: Callback - ): Result; - - nackMessage( - messageKey: string, - queueKey: string, - parentQueueKey: string, - queueCurrentConcurrencyKey: string, - envCurrentConcurrencyKey: string, - envQueueKey: string, - nackCounterKey: string, - queueName: string, - messageId: string, - currentTime: string, - messageScore: string, - callback?: Callback - ): Result; - - updateGlobalConcurrencyLimits( - envConcurrencyLimitKey: string, - envConcurrencyLimit: string, - callback?: Callback - ): Result; - - rebalanceParentQueueChild( - childQueueKey: string, - parentQueueKey: string, - childQueueName: string, - currentScore: string, - callback?: Callback - ): Result; - } -} - -export const marqs = singleton("marqs", getMarQSClient); - -function getMarQSClient() { - if (!env.REDIS_HOST || !env.REDIS_PORT) { - throw new Error( - "Could not initialize Trigger.dev because process.env.REDIS_HOST and process.env.REDIS_PORT are required to be set." - ); - } - - const redisOptions = { - keyPrefix: KEY_PREFIX, - port: env.REDIS_PORT, - host: env.REDIS_HOST, - username: env.REDIS_USERNAME, - password: env.REDIS_PASSWORD, - enableAutoPipelining: true, - ...(env.REDIS_TLS_DISABLED === "true" ? {} : { tls: {} }), - }; - - const redis = new Redis(redisOptions); - const keysProducer = new MarQSShortKeyProducer(KEY_PREFIX); - - return new MarQS({ - name: "marqs", - tracer: trace.getTracer("marqs"), - keysProducer, - visibilityTimeoutStrategy: new V3LegacyRunEngineWorkerVisibilityTimeout(), - queuePriorityStrategy: new FairDequeuingStrategy({ - tracer: tracer, - redis, - parentQueueLimit: env.MARQS_SHARED_QUEUE_LIMIT, - keys: keysProducer, - defaultEnvConcurrency: env.DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT, - biases: { - concurrencyLimitBias: env.MARQS_CONCURRENCY_LIMIT_BIAS, - availableCapacityBias: env.MARQS_AVAILABLE_CAPACITY_BIAS, - queueAgeRandomization: env.MARQS_QUEUE_AGE_RANDOMIZATION_BIAS, - }, - reuseSnapshotCount: env.MARQS_REUSE_SNAPSHOT_COUNT, - maximumEnvCount: env.MARQS_MAXIMUM_ENV_COUNT, - maximumQueuePerEnvCount: env.MARQS_MAXIMUM_QUEUE_PER_ENV_COUNT, - }), - envQueuePriorityStrategy: new FairDequeuingStrategy({ - tracer: tracer, - redis, - parentQueueLimit: env.MARQS_DEV_QUEUE_LIMIT, - keys: keysProducer, - defaultEnvConcurrency: env.DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT, - biases: { - concurrencyLimitBias: 0.0, - availableCapacityBias: 0.0, - queueAgeRandomization: 0.1, - }, - }), - workers: 1, - redis, - defaultEnvConcurrency: env.DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT, - defaultOrgConcurrency: env.DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT, - visibilityTimeoutInMs: env.MARQS_VISIBILITY_TIMEOUT_MS, - enableRebalancing: !env.MARQS_DISABLE_REBALANCING, - maximumNackCount: env.MARQS_MAXIMUM_NACK_COUNT, - subscriber: concurrencyTracker, - sharedWorkerQueueConsumerIntervalMs: env.MARQS_SHARED_WORKER_QUEUE_CONSUMER_INTERVAL_MS, - sharedWorkerQueueMaxMessageCount: env.MARQS_SHARED_WORKER_QUEUE_MAX_MESSAGE_COUNT, - eagerDequeuingEnabled: env.MARQS_SHARED_WORKER_QUEUE_EAGER_DEQUEUE_ENABLED === "1", - sharedWorkerQueueCooloffCountThreshold: env.MARQS_SHARED_WORKER_QUEUE_COOLOFF_COUNT_THRESHOLD, - sharedWorkerQueueCooloffPeriodMs: env.MARQS_SHARED_WORKER_QUEUE_COOLOFF_PERIOD_MS, - workerOptions: { - enabled: env.MARQS_WORKER_ENABLED === "1", - pollIntervalMs: env.MARQS_WORKER_POLL_INTERVAL_MS, - immediatePollIntervalMs: env.MARQS_WORKER_IMMEDIATE_POLL_INTERVAL_MS, - shutdownTimeoutMs: env.MARQS_WORKER_SHUTDOWN_TIMEOUT_MS, - concurrency: { - workers: env.MARQS_WORKER_COUNT, - tasksPerWorker: env.MARQS_WORKER_CONCURRENCY_TASKS_PER_WORKER, - limit: env.MARQS_WORKER_CONCURRENCY_LIMIT, - }, - redisOptions: { - keyPrefix: KEY_PREFIX, - port: env.REDIS_PORT ?? undefined, - host: env.REDIS_HOST ?? undefined, - username: env.REDIS_USERNAME ?? undefined, - password: env.REDIS_PASSWORD ?? undefined, - enableAutoPipelining: true, - ...(env.REDIS_TLS_DISABLED === "true" ? {} : { tls: {} }), - }, - }, - }); -} diff --git a/apps/webapp/app/v3/marqs/marqsKeyProducer.ts b/apps/webapp/app/v3/marqs/marqsKeyProducer.ts deleted file mode 100644 index 867eebb81d1..00000000000 --- a/apps/webapp/app/v3/marqs/marqsKeyProducer.ts +++ /dev/null @@ -1,287 +0,0 @@ -import type { MarQSKeyProducer, MarQSKeyProducerEnv, QueueDescriptor } from "./types"; - -const constants = { - SHARED_QUEUE: "sharedQueue", - SHARED_WORKER_QUEUE: "sharedWorkerQueue", - CURRENT_CONCURRENCY_PART: "currentConcurrency", - CONCURRENCY_LIMIT_PART: "concurrency", - DISABLED_CONCURRENCY_LIMIT_PART: "disabledConcurrency", - ENV_PART: "env", - ORG_PART: "org", - QUEUE_PART: "queue", - CONCURRENCY_KEY_PART: "ck", - MESSAGE_PART: "message", - RESERVE_CONCURRENCY_PART: "reserveConcurrency", -} as const; - -const ORG_REGEX = /org:([^:]+):/; -const ENV_REGEX = /env:([^:]+):/; -const QUEUE_REGEX = /queue:([^:]+)(?::|$)/; -const CONCURRENCY_KEY_REGEX = /ck:([^:]+)(?::|$)/; - -export class MarQSShortKeyProducer implements MarQSKeyProducer { - constructor(private _prefix: string) {} - - sharedQueueScanPattern() { - return `${this._prefix}*${constants.SHARED_QUEUE}`; - } - - queueCurrentConcurrencyScanPattern() { - return `${this._prefix}${constants.ORG_PART}:*:${constants.ENV_PART}:*:queue:*:${constants.CURRENT_CONCURRENCY_PART}`; - } - - stripKeyPrefix(key: string): string { - if (key.startsWith(this._prefix)) { - return key.slice(this._prefix.length); - } - - return key; - } - - queueConcurrencyLimitKey(env: MarQSKeyProducerEnv, queue: string) { - return [this.queueKey(env, queue), constants.CONCURRENCY_LIMIT_PART].join(":"); - } - - envConcurrencyLimitKey(envId: string): string; - envConcurrencyLimitKey(env: MarQSKeyProducerEnv): string; - envConcurrencyLimitKey(envOrId: MarQSKeyProducerEnv | string): string { - return [ - this.envKeySection(typeof envOrId === "string" ? envOrId : envOrId.id), - constants.CONCURRENCY_LIMIT_PART, - ].join(":"); - } - - queueKey(orgId: string, envId: string, queue: string, concurrencyKey?: string): string; - queueKey(env: MarQSKeyProducerEnv, queue: string, concurrencyKey?: string): string; - queueKey( - envOrOrgId: MarQSKeyProducerEnv | string, - queueOrEnvId: string, - queueOrConcurrencyKey: string, - concurrencyKeyOrPriority?: string | number - ): string { - if (typeof envOrOrgId === "string") { - return [ - this.orgKeySection(envOrOrgId), - this.envKeySection(queueOrEnvId), - this.queueSection(queueOrConcurrencyKey), - ] - .concat( - typeof concurrencyKeyOrPriority === "string" - ? this.concurrencyKeySection(concurrencyKeyOrPriority) - : [] - ) - .join(":"); - } else { - return [ - this.orgKeySection(envOrOrgId.organizationId), - this.envKeySection(envOrOrgId.id), - this.queueSection(queueOrEnvId), - ] - .concat(queueOrConcurrencyKey ? this.concurrencyKeySection(queueOrConcurrencyKey) : []) - .join(":"); - } - } - - queueKeyFromQueue(queue: string): string { - const descriptor = this.queueDescriptorFromQueue(queue); - - return this.queueKey( - descriptor.organization, - descriptor.environment, - descriptor.name, - descriptor.concurrencyKey - ); - } - - envSharedQueueKey(env: MarQSKeyProducerEnv) { - if (env.type === "DEVELOPMENT") { - return [ - this.orgKeySection(env.organizationId), - this.envKeySection(env.id), - constants.SHARED_QUEUE, - ].join(":"); - } - - return this.sharedQueueKey(); - } - - sharedQueueKey(): string { - return constants.SHARED_QUEUE; - } - - sharedWorkerQueueKey(): string { - return constants.SHARED_WORKER_QUEUE; - } - - queueConcurrencyLimitKeyFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - - return this.queueConcurrencyLimitKeyFromDescriptor(descriptor); - } - - queueCurrentConcurrencyKeyFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - return this.currentConcurrencyKeyFromDescriptor(descriptor); - } - - queueReserveConcurrencyKeyFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - - return this.queueReserveConcurrencyKeyFromDescriptor(descriptor); - } - - queueCurrentConcurrencyKey( - env: MarQSKeyProducerEnv, - queue: string, - concurrencyKey?: string - ): string { - return [this.queueKey(env, queue, concurrencyKey), constants.CURRENT_CONCURRENCY_PART].join( - ":" - ); - } - - envConcurrencyLimitKeyFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - - return `${constants.ENV_PART}:${descriptor.environment}:${constants.CONCURRENCY_LIMIT_PART}`; - } - - envCurrentConcurrencyKeyFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - - return `${constants.ENV_PART}:${descriptor.environment}:${constants.CURRENT_CONCURRENCY_PART}`; - } - - envReserveConcurrencyKeyFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - - return this.envReserveConcurrencyKey(descriptor.environment); - } - - envReserveConcurrencyKey(envId: string): string { - return `${constants.ENV_PART}:${this.shortId(envId)}:${constants.RESERVE_CONCURRENCY_PART}`; - } - - envCurrentConcurrencyKey(envId: string): string; - envCurrentConcurrencyKey(env: MarQSKeyProducerEnv): string; - envCurrentConcurrencyKey(envOrId: MarQSKeyProducerEnv | string): string { - return [ - this.envKeySection(typeof envOrId === "string" ? envOrId : envOrId.id), - constants.CURRENT_CONCURRENCY_PART, - ].join(":"); - } - - envQueueKeyFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - - return `${constants.ENV_PART}:${descriptor.environment}:${constants.QUEUE_PART}`; - } - - envQueueKey(env: MarQSKeyProducerEnv): string { - return [constants.ENV_PART, this.shortId(env.id), constants.QUEUE_PART].join(":"); - } - - messageKey(messageId: string) { - return `${constants.MESSAGE_PART}:${messageId}`; - } - - nackCounterKey(messageId: string): string { - return `${constants.MESSAGE_PART}:${messageId}:nacks`; - } - - orgIdFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - - return descriptor.organization; - } - - envIdFromQueue(queue: string) { - const descriptor = this.queueDescriptorFromQueue(queue); - - return descriptor.environment; - } - - queueDescriptorFromQueue(queue: string): QueueDescriptor { - const match = queue.match(QUEUE_REGEX); - - if (!match) { - throw new Error(`Invalid queue: ${queue}, no queue name found`); - } - - const [, queueName] = match; - - const envMatch = queue.match(ENV_REGEX); - - if (!envMatch) { - throw new Error(`Invalid queue: ${queue}, no environment found`); - } - - const [, envId] = envMatch; - - const orgMatch = queue.match(ORG_REGEX); - - if (!orgMatch) { - throw new Error(`Invalid queue: ${queue}, no organization found`); - } - - const [, orgId] = orgMatch; - - const concurrencyKeyMatch = queue.match(CONCURRENCY_KEY_REGEX); - - const concurrencyKey = concurrencyKeyMatch ? concurrencyKeyMatch[1] : undefined; - - return { - name: queueName, - environment: envId, - organization: orgId, - concurrencyKey, - }; - } - - private shortId(id: string) { - // Return the last 12 characters of the id - return id.slice(-12); - } - - private envKeySection(envId: string) { - return `${constants.ENV_PART}:${this.shortId(envId)}`; - } - - private orgKeySection(orgId: string) { - return `${constants.ORG_PART}:${this.shortId(orgId)}`; - } - - private queueSection(queue: string) { - return `${constants.QUEUE_PART}:${queue}`; - } - - private concurrencyKeySection(concurrencyKey: string) { - return `${constants.CONCURRENCY_KEY_PART}:${concurrencyKey}`; - } - - private currentConcurrencyKeyFromDescriptor(descriptor: QueueDescriptor) { - return [ - this.queueKey( - descriptor.organization, - descriptor.environment, - descriptor.name, - descriptor.concurrencyKey - ), - constants.CURRENT_CONCURRENCY_PART, - ].join(":"); - } - - private queueReserveConcurrencyKeyFromDescriptor(descriptor: QueueDescriptor) { - return [ - this.queueKey(descriptor.organization, descriptor.environment, descriptor.name), - constants.RESERVE_CONCURRENCY_PART, - ].join(":"); - } - - private queueConcurrencyLimitKeyFromDescriptor(descriptor: QueueDescriptor) { - return [ - this.queueKey(descriptor.organization, descriptor.environment, descriptor.name), - constants.CONCURRENCY_LIMIT_PART, - ].join(":"); - } -} diff --git a/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts b/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts deleted file mode 100644 index 4ed307126e3..00000000000 --- a/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts +++ /dev/null @@ -1,2215 +0,0 @@ -import type { Context, Span, SpanOptions } from "@opentelemetry/api"; -import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, trace } from "@opentelemetry/api"; -import type { - AckCallbackResult, - MachinePreset, - V3ProdTaskRunExecution, - V3ProdTaskRunExecutionPayload, - TaskRunError, - TaskRunExecution, - TaskRunExecutionLazyAttemptPayload, - TaskRunExecutionResult, - TaskRunFailedExecutionResult, - TaskRunSuccessfulExecutionResult, - serverWebsocketMessages, -} from "@trigger.dev/core/v3"; -import { TaskRunErrorCodes, parsePacket, SemanticInternalAttributes } from "@trigger.dev/core/v3"; -import type { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler"; -import type { - BackgroundWorker, - BackgroundWorkerTask, - Prisma, - TaskRunStatus, -} from "@trigger.dev/database"; -import { z } from "zod"; -import { $replica, prisma } from "~/db.server"; -import { env } from "~/env.server"; -import { findEnvironmentById } from "~/models/runtimeEnvironment.server"; -import { findQueueInEnvironment, sanitizeQueueName } from "~/models/taskQueue.server"; -import { generateJWTTokenForEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { singleton } from "~/utils/singleton"; -import { marqs } from "~/v3/marqs/index.server"; -import type { RuntimeEnvironmentForEnvRepo } from "../environmentVariables/environmentVariablesRepository.server"; -import { - RuntimeEnvironmentForEnvRepoPayload, - resolveVariablesForEnvironment, -} from "../environmentVariables/environmentVariablesRepository.server"; -import type { EnvironmentVariable } from "../environmentVariables/repository"; -import { FailedTaskRunService } from "../failedTaskRun.server"; -import { generateFriendlyId } from "../friendlyIdentifiers"; -import { socketIo } from "../handleSocketIo.server"; -import { machinePresetFromConfig, machinePresetFromRun } from "../machinePresets.server"; -import { - findCurrentWorkerDeployment, - getWorkerDeploymentFromWorker, - getWorkerDeploymentFromWorkerTask, -} from "../models/workerDeployment.server"; -import { CrashTaskRunService } from "../services/crashTaskRun.server"; -import { CreateTaskRunAttemptService } from "../services/createTaskRunAttempt.server"; -import { RestoreCheckpointService } from "../services/restoreCheckpoint.server"; -import { - FINAL_ATTEMPT_STATUSES, - FINAL_RUN_STATUSES, - isFinalAttemptStatus, - isFinalRunStatus, -} from "../taskStatus"; -import { tracer } from "../tracer.server"; -import { getMaxDuration } from "../utils/maxDuration"; -import type { MessagePayload } from "./types"; - -const WithTraceContext = z.object({ - traceparent: z.string().optional(), - tracestate: z.string().optional(), -}); - -export const SharedQueueExecuteMessageBody = WithTraceContext.extend({ - type: z.literal("EXECUTE"), - taskIdentifier: z.string(), - checkpointEventId: z.string().optional(), - retryCheckpointsDisabled: z.boolean().optional(), -}); - -export type SharedQueueExecuteMessageBody = z.infer; - -export const SharedQueueResumeMessageBody = WithTraceContext.extend({ - type: z.literal("RESUME"), - completedAttemptIds: z.string().array(), - resumableAttemptId: z.string(), - checkpointEventId: z.string().optional(), -}); - -export type SharedQueueResumeMessageBody = z.infer; - -export const SharedQueueResumeAfterDurationMessageBody = WithTraceContext.extend({ - type: z.literal("RESUME_AFTER_DURATION"), - resumableAttemptId: z.string(), - checkpointEventId: z.string(), -}); - -export type SharedQueueResumeAfterDurationMessageBody = z.infer< - typeof SharedQueueResumeAfterDurationMessageBody ->; - -export const SharedQueueFailMessageBody = WithTraceContext.extend({ - type: z.literal("FAIL"), - reason: z.string(), -}); - -export type SharedQueueFailMessageBody = z.infer; - -export const SharedQueueMessageBody = z.discriminatedUnion("type", [ - SharedQueueExecuteMessageBody, - SharedQueueResumeMessageBody, - SharedQueueResumeAfterDurationMessageBody, - SharedQueueFailMessageBody, -]); - -export type SharedQueueMessageBody = z.infer; - -type BackgroundWorkerWithTasks = BackgroundWorker & { tasks: BackgroundWorkerTask[] }; - -export type SharedQueueConsumerOptions = { - maximumItemsPerTrace?: number; - traceTimeoutSeconds?: number; - nextTickInterval?: number; - interval?: number; -}; - -type HandleMessageAction = "ack_and_do_more_work" | "nack" | "nack_and_do_more_work" | "noop"; - -type DoWorkInternalResult = { - reason: string; - outcome: "execution" | "retry_with_nack" | "fail_with_ack" | "noop"; - attrs?: Record; - error?: Error | string; - interval?: number; - action?: HandleMessageAction; -}; - -type HandleMessageResult = { - action: HandleMessageAction; - interval?: number; - retryInMs?: number; - reason?: string; - attrs?: Record; - error?: Error | string; -}; - -export class SharedQueueConsumer { - private _backgroundWorkers: Map = new Map(); - private _deprecatedWorkers: Map = new Map(); - private _enabled = false; - private _options: Required; - private _perTraceCountdown: number | undefined; - private _traceStartedAt: Date | undefined; - private _currentSpanContext: Context | undefined; - private _reasonStats: Record = {}; - private _actionStats: Record = {}; - private _outcomeStats: Record = {}; - private _currentSpan: Span | undefined; - private _endSpanInNextIteration = false; - private _tasks = sharedQueueTasks; - private _id: string; - private _connectedAt: Date; - private _iterationsCount = 0; - private _totalIterationsCount = 0; - private _runningDurationInMs = 0; - private _currentMessage: MessagePayload | undefined; - private _currentMessageData: SharedQueueMessageBody | undefined; - private _stopWorkerQueueConsumer?: () => void; - - constructor( - private _providerSender: ZodMessageSender, - options: SharedQueueConsumerOptions = {} - ) { - this._options = { - maximumItemsPerTrace: options.maximumItemsPerTrace ?? 500, - traceTimeoutSeconds: options.traceTimeoutSeconds ?? 1, - nextTickInterval: options.nextTickInterval ?? 1000, // 1 second - interval: options.interval ?? 100, // 100ms - }; - - this._id = generateFriendlyId("shared-queue", 6); - this._connectedAt = new Date(); - } - - // This method is called when a background worker is deprecated and will no longer be used unless a run is locked to it - public async deprecateBackgroundWorker(id: string) { - const backgroundWorker = this._backgroundWorkers.get(id); - - if (!backgroundWorker) { - return; - } - - this._deprecatedWorkers.set(id, backgroundWorker); - this._backgroundWorkers.delete(id); - } - - public async registerBackgroundWorker(id: string, envId?: string) { - if (!envId) { - logger.error("Environment ID is required for background worker registration", { - backgroundWorkerId: id, - }); - return; - } - - const backgroundWorker = await prisma.backgroundWorker.findFirst({ - where: { - friendlyId: id, - runtimeEnvironmentId: envId, - }, - include: { - tasks: true, - }, - }); - - if (!backgroundWorker) { - return; - } - - this._backgroundWorkers.set(backgroundWorker.id, backgroundWorker); - - logger.debug("Registered background worker", { backgroundWorker: backgroundWorker.id }); - - // Start reading from the queue if we haven't already - this.#enable(); - } - - public async start() { - this.#enable(); - } - - public async stop(reason: string = "Provider disconnected") { - if (!this._enabled) { - return; - } - - console.log("❌ Stopping the SharedQueueConsumer"); - - this._stopWorkerQueueConsumer?.(); - - logger.debug("Stopping shared queue consumer"); - this._enabled = false; - - if (this._currentSpan) { - this._currentSpan.end(); - } - } - - #enable() { - if (this._enabled) { - return; - } - - this._enabled = true; - this._perTraceCountdown = this._options.maximumItemsPerTrace; - this._traceStartedAt = new Date(); - this._reasonStats = {}; - this._actionStats = {}; - this._outcomeStats = {}; - this._stopWorkerQueueConsumer = marqs?.startSharedWorkerQueueConsumer(this._id); - - console.log("✅ Started the SharedQueueConsumer"); - - this.#doWork().finally(() => {}); - } - - #endCurrentSpan() { - if (this._currentSpan) { - for (const [reason, count] of Object.entries(this._reasonStats)) { - this._currentSpan.setAttribute(`reasons_${reason}`, count); - } - - for (const [action, count] of Object.entries(this._actionStats)) { - this._currentSpan.setAttribute(`actions_${action}`, count); - } - - for (const [outcome, count] of Object.entries(this._outcomeStats)) { - this._currentSpan.setAttribute(`outcomes_${outcome}`, count); - } - - this._currentSpan.end(); - } - } - - async #doWork() { - if (!this._enabled) { - this.#endCurrentSpan(); - return; - } - - // Check if the trace has expired - if ( - this._perTraceCountdown === 0 || - Date.now() - this._traceStartedAt!.getTime() > this._options.traceTimeoutSeconds * 1000 || - this._currentSpanContext === undefined || - this._endSpanInNextIteration - ) { - this.#endCurrentSpan(); - - const traceDurationInMs = this._traceStartedAt - ? Date.now() - this._traceStartedAt.getTime() - : undefined; - const iterationsPerSecond = traceDurationInMs - ? this._iterationsCount / (traceDurationInMs / 1000) - : undefined; - - // Create a new trace - this._currentSpan = tracer.startSpan( - "SharedQueueConsumer.doWork()", - { - kind: SpanKind.CONSUMER, - attributes: { - id: this._id, - iterations: this._iterationsCount, - total_iterations: this._totalIterationsCount, - options_maximumItemsPerTrace: this._options.maximumItemsPerTrace, - options_nextTickInterval: this._options.nextTickInterval, - options_interval: this._options.interval, - connected_at: this._connectedAt.toISOString(), - consumer_age_in_seconds: (Date.now() - this._connectedAt.getTime()) / 1000, - do_work_internal_per_second: this._iterationsCount / (this._runningDurationInMs / 1000), - running_duration_ms: this._runningDurationInMs, - trace_timeout_in_seconds: this._options.traceTimeoutSeconds, - trace_duration_ms: traceDurationInMs, - iterations_per_second: iterationsPerSecond, - iterations_per_minute: iterationsPerSecond ? iterationsPerSecond * 60 : undefined, - }, - }, - ROOT_CONTEXT - ); - - logger.debug("SharedQueueConsumer starting new trace", { - reasonStats: this._reasonStats, - actionStats: this._actionStats, - outcomeStats: this._outcomeStats, - iterationCount: this._iterationsCount, - consumerId: this._id, - }); - - // Get the span trace context - this._currentSpanContext = trace.setSpan(ROOT_CONTEXT, this._currentSpan); - - this._perTraceCountdown = this._options.maximumItemsPerTrace; - this._traceStartedAt = new Date(); - this._reasonStats = {}; - this._actionStats = {}; - this._outcomeStats = {}; - this._iterationsCount = 0; - this._runningDurationInMs = 0; - this._endSpanInNextIteration = false; - } - - return context.with(this._currentSpanContext ?? ROOT_CONTEXT, async () => { - await tracer.startActiveSpan("doWorkInternal()", async (span) => { - let nextInterval = this._options.interval; - - span.setAttributes({ - id: this._id, - total_iterations: this._totalIterationsCount, - iterations: this._iterationsCount, - }); - - const startAt = performance.now(); - - try { - const result = await this.#doWorkInternal(); - - if (result.reason !== "no_message_dequeued") { - logger.debug("SharedQueueConsumer doWorkInternal result", { result }); - } - - this._reasonStats[result.reason] = (this._reasonStats[result.reason] ?? 0) + 1; - this._outcomeStats[result.outcome] = (this._outcomeStats[result.outcome] ?? 0) + 1; - - if (result.action) { - this._actionStats[result.action] = (this._actionStats[result.action] ?? 0) + 1; - } - - span.setAttribute("reason", result.reason); - - if (result.attrs) { - for (const [key, value] of Object.entries(result.attrs)) { - if (value) { - span.setAttribute(key, value); - } - } - } - - if (result.error) { - span.recordException(result.error); - span.setStatus({ code: SpanStatusCode.ERROR }); - this._currentSpan?.recordException(result.error); - this._currentSpan?.setStatus({ code: SpanStatusCode.ERROR }); - this._endSpanInNextIteration = true; - } - - if (typeof result.interval === "number") { - nextInterval = Math.max(result.interval, 0); // Cannot be negative - } - - span.setAttribute("nextInterval", nextInterval); - } catch (error) { - if (error instanceof Error) { - this._currentSpan?.recordException(error); - } else { - this._currentSpan?.recordException(new Error(String(error))); - } - - this._endSpanInNextIteration = true; - } finally { - this._runningDurationInMs = this._runningDurationInMs + (performance.now() - startAt); - this._iterationsCount++; - this._totalIterationsCount++; - this._perTraceCountdown = this._perTraceCountdown! - 1; - - span.end(); - - setTimeout(() => { - this.#doWork().finally(() => {}); - }, nextInterval); - } - }); - }); - } - - async #doWorkInternal(): Promise { - // Attempt to dequeue a message from the shared queue - // If no message is available, reschedule the worker to run again in 1 second - // If a message is available, find the BackgroundWorkerTask that matches the message's taskIdentifier - // If no matching task is found, nack the message and reschedule the worker to run again in 1 second - // If the matching task is found, create the task attempt and lock the task run, then send the task run to the client - // Store the message as a processing message - // If the websocket connection disconnects before the task run is completed, nack the message - // When the task run completes, ack the message - // Using a heartbeat mechanism, if the client keeps responding with a heartbeat, we'll keep the message processing and increase the visibility timeout. - - this._currentMessage = undefined; - this._currentMessageData = undefined; - - const message = await marqs?.dequeueMessageFromSharedWorkerQueue(this._id); - - if (!message) { - return { - reason: "no_message_dequeued", - outcome: "noop", - interval: this._options.nextTickInterval, - }; - } - - const dequeuedAt = new Date(); - - logger.log("dequeueMessageInSharedQueue()", { queueMessage: message }); - - const messageBody = SharedQueueMessageBody.safeParse(message.data); - - if (!messageBody.success) { - logger.error("Failed to parse message", { - queueMessage: message.data, - error: messageBody.error, - }); - - await this.#ack(message.messageId); - - return { - reason: "failed_to_parse_message", - outcome: "fail_with_ack", - attrs: { message_id: message.messageId, message_version: message.version }, - error: messageBody.error, - }; - } - - const hydrateAttributes = (attrs: Record) => { - return { - ...attrs, - message_id: message.messageId, - message_version: message.version, - run_id: message.messageId, - message_type: messageBody.data.type, - }; - }; - - this._currentMessage = message; - this._currentMessageData = messageBody.data; - - const messageResult = await this.#handleMessage(message, messageBody.data, dequeuedAt); - - switch (messageResult.action) { - case "noop": { - return { - reason: messageResult.reason ?? "none_specified", - outcome: "execution", - attrs: hydrateAttributes(messageResult.attrs ?? {}), - error: messageResult.error, - interval: messageResult.interval, - action: "noop", - }; - } - case "ack_and_do_more_work": { - await this.#ack(message.messageId); - - return { - reason: messageResult.reason ?? "none_specified", - outcome: "fail_with_ack", - attrs: hydrateAttributes(messageResult.attrs ?? {}), - error: messageResult.error, - interval: messageResult.interval, - action: "ack_and_do_more_work", - }; - } - case "nack_and_do_more_work": { - await this.#nack(message.messageId, messageResult.retryInMs); - - return { - reason: messageResult.reason ?? "none_specified", - outcome: "retry_with_nack", - attrs: hydrateAttributes(messageResult.attrs ?? {}), - error: messageResult.error, - interval: messageResult.interval, - action: "nack_and_do_more_work", - }; - } - case "nack": { - await marqs?.nackMessage(message.messageId); - - return { - reason: messageResult.reason ?? "none_specified", - outcome: "retry_with_nack", - attrs: hydrateAttributes(messageResult.attrs ?? {}), - error: messageResult.error, - action: "nack", - }; - } - } - } - - async #handleMessage( - message: MessagePayload, - data: SharedQueueMessageBody, - dequeuedAt: Date - ): Promise { - return await this.#startActiveSpan("handleMessage()", async (span) => { - // TODO: For every ACK, decide what should be done with the existing run and attempts. Make sure to check the current statuses first. - switch (data.type) { - // MARK: EXECUTE - case "EXECUTE": { - return await this.#handleExecuteMessage(message, data, dequeuedAt); - } - // MARK: DEP RESUME - // Resume after dependency completed with no remaining retries - case "RESUME": { - return await this.#handleResumeMessage(message, data, dequeuedAt); - } - // MARK: DURATION RESUME - // Resume after duration-based wait - case "RESUME_AFTER_DURATION": { - return await this.#handleResumeAfterDurationMessage(message, data, dequeuedAt); - } - // MARK: FAIL - // Fail for whatever reason, usually runs that have been resumed but stopped heartbeating - case "FAIL": { - return await this.#handleFailMessage(message, data, dequeuedAt); - } - } - }); - } - - async #handleExecuteMessage( - message: MessagePayload, - data: SharedQueueExecuteMessageBody, - dequeuedAt: Date - ): Promise { - const existingTaskRun = await prisma.taskRun.findFirst({ - where: { - id: message.messageId, - }, - }); - - if (!existingTaskRun) { - logger.error("No existing task run", { - queueMessage: message.data, - messageId: message.messageId, - }); - - // INFO: There used to be a race condition where tasks could be triggered, but execute messages could be dequeued before the run finished being created in the DB - // This should not be happening anymore. In case it does, consider reqeueuing here with a brief delay while limiting total retries. - return { action: "ack_and_do_more_work", reason: "no_existing_task_run" }; - } - - const retryingFromCheckpoint = !!data.checkpointEventId; - - const EXECUTABLE_RUN_STATUSES = { - fromCheckpoint: ["WAITING_TO_RESUME"] satisfies TaskRunStatus[], - withoutCheckpoint: ["PENDING", "RETRYING_AFTER_FAILURE"] satisfies TaskRunStatus[], - }; - - if ( - (retryingFromCheckpoint && - !EXECUTABLE_RUN_STATUSES.fromCheckpoint.includes(existingTaskRun.status)) || - (!retryingFromCheckpoint && - !EXECUTABLE_RUN_STATUSES.withoutCheckpoint.includes(existingTaskRun.status)) - ) { - logger.warn("Task run has invalid status for execution. Going to ack", { - queueMessage: message.data, - messageId: message.messageId, - taskRun: existingTaskRun.id, - status: existingTaskRun.status, - retryingFromCheckpoint, - }); - - return { - action: "ack_and_do_more_work", - reason: "invalid_run_status", - attrs: { status: existingTaskRun.status, retryingFromCheckpoint }, - }; - } - - // Check if the task run is locked to a specific worker, if not, use the current worker deployment - const deployment = await this.#startActiveSpan("findCurrentWorkerDeployment", async (span) => { - return existingTaskRun.lockedById - ? await getWorkerDeploymentFromWorkerTask(existingTaskRun.lockedById) - : existingTaskRun.lockedToVersionId - ? await getWorkerDeploymentFromWorker(existingTaskRun.lockedToVersionId) - : await findCurrentWorkerDeployment({ - environmentId: existingTaskRun.runtimeEnvironmentId, - type: "V1", - }); - }); - - const worker = deployment?.worker; - - if (!deployment || !worker) { - // This happens when a run is "WAITING_FOR_DEPLOY" and is expected - logger.info("No matching deployment found for task run", { - queueMessage: message.data, - messageId: message.messageId, - }); - - await this.#markRunAsWaitingForDeploy(existingTaskRun.id); - - return { - action: "ack_and_do_more_work", - reason: "no_matching_deployment", - attrs: { - run_id: existingTaskRun.id, - locked_by_id: existingTaskRun.lockedById ?? undefined, - locked_to_version_id: existingTaskRun.lockedToVersionId ?? undefined, - environment_id: existingTaskRun.runtimeEnvironmentId, - }, - }; - } - - const imageReference = deployment.imageReference; - - if (!imageReference) { - logger.error("Deployment is missing an image reference", { - queueMessage: message.data, - messageId: message.messageId, - deployment: deployment.id, - }); - - await this.#markRunAsWaitingForDeploy(existingTaskRun.id); - - return { - action: "ack_and_do_more_work", - reason: "missing_image_reference", - attrs: { - deployment_id: deployment.id, - }, - }; - } - - const backgroundTask = worker.tasks.find( - (task) => task.slug === existingTaskRun.taskIdentifier - ); - - if (!backgroundTask) { - const nonCurrentTask = await prisma.backgroundWorkerTask.findFirst({ - where: { - slug: existingTaskRun.taskIdentifier, - projectId: existingTaskRun.projectId, - runtimeEnvironmentId: existingTaskRun.runtimeEnvironmentId, - }, - include: { - worker: { - include: { - deployment: { - include: {}, - }, - }, - }, - }, - }); - - if (nonCurrentTask) { - logger.warn("Task for this run exists but is not part of the current deploy", { - taskRun: existingTaskRun.id, - taskIdentifier: existingTaskRun.taskIdentifier, - }); - } else { - logger.warn("Task for this run has never been deployed", { - taskRun: existingTaskRun.id, - taskIdentifier: existingTaskRun.taskIdentifier, - }); - } - - await this.#markRunAsWaitingForDeploy(existingTaskRun.id); - - // If this task is ever deployed, a new message will be enqueued after successful indexing - return { - action: "ack_and_do_more_work", - reason: "task_not_deployed", - attrs: { - run_id: existingTaskRun.id, - task_identifier: existingTaskRun.taskIdentifier, - }, - }; - } - - const lockedAt = new Date(); - const machinePreset = - existingTaskRun.machinePreset ?? - machinePresetFromConfig(backgroundTask.machineConfig ?? {}).name; - const maxDurationInSeconds = getMaxDuration( - existingTaskRun.maxDurationInSeconds, - backgroundTask.maxDurationInSeconds - ); - const startedAt = existingTaskRun.startedAt ?? dequeuedAt; - const baseCostInCents = env.CENTS_PER_RUN; - - const lockedTaskRun = await prisma.taskRun.update({ - where: { - id: message.messageId, - }, - data: { - lockedAt, - lockedById: backgroundTask.id, - lockedToVersionId: worker.id, - taskVersion: worker.version, - sdkVersion: worker.sdkVersion, - cliVersion: worker.cliVersion, - startedAt: startedAt, - baseCostInCents: baseCostInCents, - machinePreset: machinePreset, - maxDurationInSeconds, - }, - include: { - runtimeEnvironment: true, - attempts: { - take: 1, - orderBy: { number: "desc" }, - }, - checkpoints: { - take: 1, - orderBy: { - createdAt: "desc", - }, - }, - lockedBy: true, - }, - }); - - if (!lockedTaskRun) { - logger.warn("Failed to lock task run", { - taskRun: existingTaskRun.id, - taskIdentifier: existingTaskRun.taskIdentifier, - deployment: deployment.id, - backgroundWorker: worker.id, - messageId: message.messageId, - }); - - return { - action: "ack_and_do_more_work", - reason: "failed_to_lock_task_run", - attrs: { - run_id: existingTaskRun.id, - task_identifier: existingTaskRun.taskIdentifier, - deployment_id: deployment.id, - background_worker_id: worker.id, - message_id: message.messageId, - }, - }; - } - - const queue = await findQueueInEnvironment( - lockedTaskRun.queue, - lockedTaskRun.runtimeEnvironmentId, - lockedTaskRun.lockedById ?? undefined, - backgroundTask - ); - - if (!queue) { - logger.debug("SharedQueueConsumer queue not found, so acking message", { - queueMessage: message, - taskRunQueue: lockedTaskRun.queue, - runtimeEnvironmentId: lockedTaskRun.runtimeEnvironmentId, - }); - - return { - action: "ack_and_do_more_work", - reason: "queue_not_found", - attrs: { - queue_name: sanitizeQueueName(lockedTaskRun.queue), - runtime_environment_id: lockedTaskRun.runtimeEnvironmentId, - }, - interval: this._options.nextTickInterval, - }; - } - - if (!this._enabled) { - logger.debug("SharedQueueConsumer not enabled, so nacking message", { - queueMessage: message, - }); - - return { - action: "nack", - reason: "not_enabled", - attrs: { - message_id: message.messageId, - }, - }; - } - - const nextAttemptNumber = lockedTaskRun.attempts[0] ? lockedTaskRun.attempts[0].number + 1 : 1; - - const isRetry = - nextAttemptNumber > 1 && - (lockedTaskRun.status === "WAITING_TO_RESUME" || - lockedTaskRun.status === "RETRYING_AFTER_FAILURE"); - - try { - if (data.checkpointEventId) { - const restoreService = new RestoreCheckpointService(); - - const checkpoint = await restoreService.call({ - eventId: data.checkpointEventId, - isRetry, - }); - - if (!checkpoint) { - logger.error("Failed to restore checkpoint", { - queueMessage: message.data, - messageId: message.messageId, - runStatus: lockedTaskRun.status, - isRetry, - }); - - return { - action: "ack_and_do_more_work", - reason: "failed_to_restore_checkpoint", - attrs: { - run_status: lockedTaskRun.status, - is_retry: isRetry, - checkpoint_event_id: data.checkpointEventId, - }, - }; - } - - return { - action: "noop", - reason: "restored_checkpoint", - attrs: { - checkpoint_event_id: data.checkpointEventId, - }, - }; - } - - if (!worker.supportsLazyAttempts) { - try { - const service = new CreateTaskRunAttemptService(); - await service.call({ - runId: lockedTaskRun.id, - setToExecuting: false, - }); - } catch (error) { - logger.error("Failed to create task run attempt for outdated worker", { - error, - taskRun: lockedTaskRun.id, - }); - - const service = new CrashTaskRunService(); - await service.call(lockedTaskRun.id, { - errorCode: TaskRunErrorCodes.OUTDATED_SDK_VERSION, - }); - - return { - action: "ack_and_do_more_work", - reason: "failed_to_create_attempt_for_outdated_worker", - attrs: { - message_id: message.messageId, - run_id: lockedTaskRun.id, - }, - error: error instanceof Error ? error : String(error), - }; - } - } - - if (isRetry && !data.retryCheckpointsDisabled) { - socketIo.coordinatorNamespace.emit("READY_FOR_RETRY", { - version: "v1", - runId: lockedTaskRun.id, - }); - - // Retries for workers with disabled retry checkpoints will be handled just like normal attempts - return { - action: "noop", - reason: "retry_checkpoints_disabled", - }; - } - - const machine = - machinePresetFromRun(lockedTaskRun) ?? - machinePresetFromConfig(lockedTaskRun.lockedBy?.machineConfig ?? {}); - - return await this.#startActiveSpan("scheduleAttemptOnProvider", async (span) => { - span.setAttributes({ - run_id: lockedTaskRun.id, - }); - - if (await this._providerSender.validateCanSendMessage()) { - await this._providerSender.send("BACKGROUND_WORKER_MESSAGE", { - backgroundWorkerId: worker.friendlyId, - data: { - type: "SCHEDULE_ATTEMPT", - image: imageReference, - version: deployment.version, - machine, - nextAttemptNumber, - // identifiers - id: "placeholder", // TODO: Remove this completely in a future release - envId: lockedTaskRun.runtimeEnvironment.id, - envType: lockedTaskRun.runtimeEnvironment.type, - orgId: lockedTaskRun.runtimeEnvironment.organizationId, - projectId: lockedTaskRun.runtimeEnvironment.projectId, - runId: lockedTaskRun.id, - dequeuedAt: dequeuedAt.getTime(), - }, - }); - - return { - action: "noop", - reason: "scheduled_attempt", - attrs: { - next_attempt_number: nextAttemptNumber, - }, - }; - } else { - return { - action: "nack_and_do_more_work", - reason: "provider_not_connected", - attrs: { - run_id: lockedTaskRun.id, - }, - interval: this._options.nextTickInterval, - retryInMs: 5_000, - }; - } - }); - } catch (e) { - // We now need to unlock the task run and delete the task run attempt - await prisma.$transaction([ - prisma.taskRun.update({ - where: { - id: lockedTaskRun.id, - }, - data: { - lockedAt: null, - lockedById: null, - status: lockedTaskRun.status, - startedAt: existingTaskRun.startedAt, - }, - }), - ]); - - logger.error("SharedQueueConsumer errored, so nacking message", { - queueMessage: message, - error: e instanceof Error ? { name: e.name, message: e.message, stack: e.stack } : e, - }); - - return { - action: "nack_and_do_more_work", - reason: "failed_to_schedule_attempt", - error: e instanceof Error ? e : String(e), - interval: this._options.nextTickInterval, - retryInMs: 5_000, - }; - } - } - - async #handleResumeMessage( - message: MessagePayload, - data: SharedQueueResumeMessageBody, - dequeuedAt: Date - ): Promise { - if (data.checkpointEventId) { - try { - const restoreService = new RestoreCheckpointService(); - - const checkpoint = await restoreService.call({ - eventId: data.checkpointEventId, - }); - - if (!checkpoint) { - logger.error("Failed to restore checkpoint", { - queueMessage: message.data, - messageId: message.messageId, - }); - - return { - action: "ack_and_do_more_work", - reason: "failed_to_restore_checkpoint", - attrs: { - checkpoint_event_id: data.checkpointEventId, - }, - }; - } - - return { - action: "noop", - reason: "restored_checkpoint", - attrs: { - checkpoint_event_id: data.checkpointEventId, - }, - }; - } catch (e) { - return { - action: "nack_and_do_more_work", - reason: "failed_to_restore_checkpoint", - error: e instanceof Error ? e : String(e), - }; - } - } - - const resumableRun = await prisma.taskRun.findFirst({ - where: { - id: message.messageId, - status: { - notIn: FINAL_RUN_STATUSES, - }, - }, - }); - - if (!resumableRun) { - logger.error("Resumable run not found", { - queueMessage: message.data, - messageId: message.messageId, - }); - - return { - action: "ack_and_do_more_work", - reason: "run_not_found", - }; - } - - if (resumableRun.status !== "EXECUTING") { - logger.warn("Run is not executing, will try to resume anyway", { - queueMessage: message.data, - messageId: message.messageId, - runStatus: resumableRun.status, - }); - } - - const resumableAttempt = await prisma.taskRunAttempt.findFirst({ - where: { - id: data.resumableAttemptId, - }, - include: { - checkpoints: { - take: 1, - orderBy: { - createdAt: "desc", - }, - }, - }, - }); - - if (!resumableAttempt) { - logger.error("Resumable attempt not found", { - queueMessage: message.data, - messageId: message.messageId, - }); - - return { - action: "ack_and_do_more_work", - reason: "resumable_attempt_not_found", - attrs: { - attempt_id: data.resumableAttemptId, - }, - }; - } - - const queue = await findQueueInEnvironment( - resumableRun.queue, - resumableRun.runtimeEnvironmentId, - resumableRun.lockedById ?? undefined - ); - - if (!queue) { - logger.debug("SharedQueueConsumer queue not found, so nacking message", { - queueName: sanitizeQueueName(resumableRun.queue), - attempt: resumableAttempt, - }); - - return { - action: "ack_and_do_more_work", - reason: "queue_not_found", - attrs: { - queue_name: sanitizeQueueName(resumableRun.queue), - }, - interval: this._options.nextTickInterval, - }; - } - - if (!this._enabled) { - return { - action: "nack", - reason: "not_enabled", - attrs: { - message_id: message.messageId, - }, - }; - } - - try { - const { completions, executions } = await this.#resolveCompletedAttemptsForResumeMessage( - data.completedAttemptIds - ); - - const resumeMessage = { - version: "v1" as const, - runId: resumableAttempt.taskRunId, - attemptId: resumableAttempt.id, - attemptFriendlyId: resumableAttempt.friendlyId, - completions, - executions, - }; - - logger.debug("Broadcasting RESUME_AFTER_DEPENDENCY_WITH_ACK", { - resumeMessage, - message, - resumableRun, - }); - - // The attempt should still be running so we can broadcast to all coordinators to resume immediately - const responses = await this.#startActiveSpan( - "emitResumeAfterDependencyWithAck", - async (span) => { - try { - span.setAttribute("attempt_id", resumableAttempt.id); - span.setAttribute( - "timeout_in_ms", - env.SHARED_QUEUE_CONSUMER_EMIT_RESUME_DEPENDENCY_TIMEOUT_MS - ); - - const responses = await socketIo.coordinatorNamespace - .timeout(env.SHARED_QUEUE_CONSUMER_EMIT_RESUME_DEPENDENCY_TIMEOUT_MS) - .emitWithAck("RESUME_AFTER_DEPENDENCY_WITH_ACK", resumeMessage); - - span.setAttribute("response_count", responses.length); - - const hasSuccess = responses.some((response) => response.success); - - span.setAttribute("has_success", hasSuccess); - span.setAttribute("is_timeout", false); - - return responses; - } catch (e) { - if (e instanceof Error && "responses" in e && Array.isArray(e.responses)) { - span.setAttribute("is_timeout", false); - - const responses = e.responses as AckCallbackResult[]; - - span.setAttribute("response_count", responses.length); - - const hasSuccess = responses.some( - (response) => "success" in response && response.success - ); - - span.setAttribute("has_success", hasSuccess); - - return responses; - } - - throw e; - } - } - ); - - logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK received", { - resumeMessage, - responses, - message, - }); - - if (responses.length === 0) { - logger.error("RESUME_AFTER_DEPENDENCY_WITH_ACK no response", { - resumeMessage, - message, - }); - - return { - action: "nack_and_do_more_work", - reason: "resume_after_dependency_with_ack_no_response", - attrs: { - resume_message: "RESUME_AFTER_DEPENDENCY_WITH_ACK", - }, - interval: this._options.nextTickInterval, - retryInMs: 5_000, - }; - } - - const hasSuccess = responses.some((response) => response.success); - - if (hasSuccess) { - return { - action: "noop", - reason: "resume_after_dependency_with_ack_success", - }; - } - - // No coordinator was able to resume the run - logger.warn("RESUME_AFTER_DEPENDENCY_WITH_ACK failed", { - resumeMessage, - responses, - message, - }); - - // Let's check if the run is frozen - if (resumableRun.status === "WAITING_TO_RESUME") { - logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK run is waiting to be restored", { - queueMessage: message.data, - messageId: message.messageId, - }); - - try { - const restoreService = new RestoreCheckpointService(); - - const checkpointEvent = await restoreService.getLastCheckpointEventIfUnrestored( - resumableRun.id - ); - - if (checkpointEvent) { - // The last checkpoint hasn't been restored yet, so restore it - const checkpoint = await restoreService.call({ - eventId: checkpointEvent.id, - }); - - if (!checkpoint) { - logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK failed to restore checkpoint", { - queueMessage: message.data, - messageId: message.messageId, - }); - - return { - action: "ack_and_do_more_work", - reason: "failed_to_restore_checkpoint", - attrs: { - checkpoint_event_id: checkpointEvent.id, - }, - }; - } - - logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK restored checkpoint", { - queueMessage: message.data, - messageId: message.messageId, - checkpoint, - }); - - return { - action: "noop", - reason: "restored_checkpoint", - attrs: { - checkpoint_event_id: data.checkpointEventId, - }, - }; - } else { - logger.debug( - "RESUME_AFTER_DEPENDENCY_WITH_ACK run is frozen without last checkpoint event", - { - queueMessage: message.data, - messageId: message.messageId, - } - ); - - return { - action: "noop", - reason: "resume_after_dependency_with_ack_frozen", - }; - } - } catch (e) { - return { - action: "nack_and_do_more_work", - reason: "waiting_to_resume_threw", - error: e instanceof Error ? e : String(e), - interval: this._options.nextTickInterval, - retryInMs: 5_000, - }; - } - } - - logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK retrying", { - queueMessage: message.data, - messageId: message.messageId, - }); - - return { - action: "nack_and_do_more_work", - reason: "resume_after_dependency_with_ack_retrying", - attrs: { - message_id: message.messageId, - }, - interval: this._options.nextTickInterval, - retryInMs: 5_000, - }; - } catch (e) { - if (e instanceof ResumePayloadAttemptsNotFoundError) { - return { - action: "ack_and_do_more_work", - reason: "failed_to_get_resume_payloads_for_attempts", - attrs: { - attempt_ids: e.attemptIds.join(","), - }, - }; - } else if (e instanceof ResumePayloadExecutionNotFoundError) { - return { - action: "ack_and_do_more_work", - reason: "failed_to_get_resume_payloads_missing_execution", - attrs: { - attempt_id: e.attemptId, - }, - }; - } else if (e instanceof ResumePayloadCompletionNotFoundError) { - return { - action: "ack_and_do_more_work", - reason: "failed_to_get_resume_payloads_missing_completion", - attrs: { - attempt_id: e.attemptId, - }, - }; - } - - logger.error("RESUME_AFTER_DEPENDENCY_WITH_ACK threw, nacking with delay", { - message, - error: e, - }); - - return { - action: "nack_and_do_more_work", - reason: "resume_after_dependency_with_ack_threw", - error: e instanceof Error ? e : String(e), - interval: this._options.nextTickInterval, - retryInMs: 5_000, - }; - } - } - - async #handleResumeAfterDurationMessage( - message: MessagePayload, - data: SharedQueueResumeAfterDurationMessageBody, - dequeuedAt: Date - ): Promise { - try { - const restoreService = new RestoreCheckpointService(); - - const checkpoint = await restoreService.call({ - eventId: data.checkpointEventId, - }); - - if (!checkpoint) { - logger.error("Failed to restore checkpoint", { - queueMessage: message.data, - messageId: message.messageId, - }); - - return { - action: "ack_and_do_more_work", - reason: "failed_to_restore_checkpoint", - attrs: { - checkpoint_event_id: data.checkpointEventId, - }, - }; - } - - return { - action: "noop", - reason: "restored_checkpoint", - attrs: { - checkpoint_event_id: data.checkpointEventId, - }, - }; - } catch (e) { - return { - action: "nack_and_do_more_work", - reason: "restoring_checkpoint_threw", - error: e instanceof Error ? e : String(e), - }; - } - } - - async #handleFailMessage( - message: MessagePayload, - data: SharedQueueFailMessageBody, - dequeuedAt: Date - ): Promise { - const existingTaskRun = await prisma.taskRun.findFirst({ - where: { - id: message.messageId, - }, - }); - - if (!existingTaskRun) { - logger.error("No existing task run to fail", { - queueMessage: data, - messageId: message.messageId, - }); - - return { - action: "ack_and_do_more_work", - reason: "no_existing_task_run", - }; - } - - // TODO: Consider failing the attempt and retrying instead. This may not be a good idea, as dequeued FAIL messages tend to point towards critical, persistent errors. - const service = new CrashTaskRunService(); - await service.call(existingTaskRun.id, { - crashAttempts: true, - reason: data.reason, - }); - - return { - action: "ack_and_do_more_work", - reason: "message_failed", - }; - } - - async #ack(messageId: string) { - await marqs?.acknowledgeMessage(messageId, "Acking and doing more work in SharedQueueConsumer"); - } - - async #nack(messageId: string, nackRetryInMs?: number) { - const retryAt = nackRetryInMs ? Date.now() + nackRetryInMs : undefined; - await marqs?.nackMessage(messageId, retryAt); - } - - async #markRunAsWaitingForDeploy(runId: string) { - logger.debug("Marking run as waiting for deploy", { runId }); - - const _run = await prisma.taskRun.update({ - where: { - id: runId, - }, - data: { - status: "WAITING_FOR_DEPLOY", - }, - }); - } - - async #resolveCompletedAttemptsForResumeMessage( - completedAttemptIds: string[] - ): Promise<{ completions: TaskRunExecutionResult[]; executions: TaskRunExecution[] }> { - return await this.#startActiveSpan("resolveCompletedAttemptsForResumeMessage", async (span) => { - span.setAttribute("completed_attempt_count", completedAttemptIds.length); - - // Chunk the completedAttemptIds into chunks of 10 - const chunkedCompletedAttemptIds = chunk( - completedAttemptIds, - env.SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE - ); - - span.setAttribute("chunk_count", chunkedCompletedAttemptIds.length); - span.setAttribute("chunk_size", env.SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE); - - const allResumePayloads = await this.#startActiveSpan( - "resolveAllResumePayloads", - async (span) => { - span.setAttribute("chunk_count", chunkedCompletedAttemptIds.length); - span.setAttribute("chunk_size", env.SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE); - span.setAttribute("completed_attempt_count", completedAttemptIds.length); - - return await Promise.all( - chunkedCompletedAttemptIds.map(async (attemptIds) => { - const payloads = await this.#startActiveSpan("getResumePayloads", async (span) => { - span.setAttribute("attempt_ids", attemptIds.join(",")); - span.setAttribute("attempt_count", attemptIds.length); - - const payloads = await this._tasks.getResumePayloads(attemptIds); - - span.setAttribute("payload_count", payloads.length); - - return payloads; - }); - - return { - completions: payloads.map((payload) => payload.completion), - executions: payloads.map((payload) => payload.execution), - }; - }) - ); - } - ); - - return { - completions: allResumePayloads.flatMap((payload) => payload.completions), - executions: allResumePayloads.flatMap((payload) => payload.executions), - }; - }); - } - - async #startActiveSpan( - name: string, - fn: (span: Span) => Promise, - options?: SpanOptions - ): Promise { - return await tracer.startActiveSpan(name, options ?? {}, async (span) => { - if (this._currentMessage) { - span.setAttribute("message_id", this._currentMessage.messageId); - span.setAttribute("run_id", this._currentMessage.messageId); - span.setAttribute("message_version", this._currentMessage.version); - } - - if (this._currentMessageData) { - span.setAttribute("message_type", this._currentMessageData.type); - } - - try { - return await fn(span); - } catch (error) { - if (error instanceof Error) { - span.recordException(error); - } else { - span.recordException(String(error)); - } - - span.setStatus({ code: SpanStatusCode.ERROR }); - - throw error; - } finally { - span.end(); - } - }); - } -} - -class ResumePayloadAttemptsNotFoundError extends Error { - constructor(public readonly attemptIds: string[]) { - super(`Resume payload attempts not found for attempts ${attemptIds.join(", ")}`); - } -} - -class ResumePayloadExecutionNotFoundError extends Error { - constructor(public readonly attemptId: string) { - super(`Resume payload execution not found for attempt ${attemptId}`); - } -} - -class ResumePayloadCompletionNotFoundError extends Error { - constructor(public readonly attemptId: string) { - super(`Resume payload completion not found for attempt ${attemptId}`); - } -} - -function chunk(arr: T[], chunkSize: number): T[][] { - return Array.from({ length: Math.ceil(arr.length / chunkSize) }, (_, i) => - arr.slice(i * chunkSize, i * chunkSize + chunkSize) - ); -} - -export const AttemptForCompletionGetPayload = { - select: { - status: true, - output: true, - outputType: true, - error: true, - taskRun: { - select: { - taskIdentifier: true, - friendlyId: true, - }, - }, - }, -} as const; - -type AttemptForCompletion = Prisma.TaskRunAttemptGetPayload; - -export const AttemptForExecutionGetPayload = { - select: { - id: true, - friendlyId: true, - taskRunId: true, - number: true, - startedAt: true, - createdAt: true, - backgroundWorkerId: true, - backgroundWorkerTaskId: true, - backgroundWorker: { - select: { - contentHash: true, - version: true, - }, - }, - backgroundWorkerTask: { - select: { - machineConfig: true, - slug: true, - filePath: true, - exportName: true, - }, - }, - status: true, - runtimeEnvironment: { - select: { - ...RuntimeEnvironmentForEnvRepoPayload.select, - organization: { - select: { - id: true, - slug: true, - title: true, - }, - }, - project: { - select: { - id: true, - externalRef: true, - slug: true, - name: true, - }, - }, - }, - }, - taskRun: { - select: { - id: true, - status: true, - traceContext: true, - machinePreset: true, - friendlyId: true, - payload: true, - payloadType: true, - context: true, - createdAt: true, - startedAt: true, - isTest: true, - replayedFromTaskRunFriendlyId: true, - metadata: true, - metadataType: true, - idempotencyKey: true, - usageDurationMs: true, - costInCents: true, - baseCostInCents: true, - maxDurationInSeconds: true, - runTags: true, - taskEventStore: true, - }, - }, - queue: { - select: { - name: true, - friendlyId: true, - }, - }, - }, -} as const; - -type AttemptForExecution = Prisma.TaskRunAttemptGetPayload; - -class SharedQueueTasks { - private _completionPayloadFromAttempt(attempt: AttemptForCompletion): TaskRunExecutionResult { - const ok = attempt.status === "COMPLETED"; - - if (ok) { - const success: TaskRunSuccessfulExecutionResult = { - ok, - id: attempt.taskRun.friendlyId, - output: attempt.output ?? undefined, - outputType: attempt.outputType, - taskIdentifier: attempt.taskRun.taskIdentifier, - }; - return success; - } else { - const failure: TaskRunFailedExecutionResult = { - ok, - id: attempt.taskRun.friendlyId, - error: attempt.error as TaskRunError, - taskIdentifier: attempt.taskRun.taskIdentifier, - }; - return failure; - } - } - - private async _executionFromAttempt( - attempt: AttemptForExecution, - machinePreset?: MachinePreset - ): Promise { - const { backgroundWorkerTask, taskRun, queue } = attempt; - - if (!machinePreset) { - machinePreset = - machinePresetFromRun(attempt.taskRun) ?? - machinePresetFromConfig(backgroundWorkerTask.machineConfig ?? {}); - } - - const metadata = await parsePacket({ - data: taskRun.metadata ?? undefined, - dataType: taskRun.metadataType, - }); - - const execution: V3ProdTaskRunExecution = { - task: { - id: backgroundWorkerTask.slug, - filePath: backgroundWorkerTask.filePath, - exportName: backgroundWorkerTask.exportName ?? backgroundWorkerTask.slug, - }, - attempt: { - id: attempt.friendlyId, - number: attempt.number, - startedAt: attempt.startedAt ?? attempt.createdAt, - backgroundWorkerId: attempt.backgroundWorkerId, - backgroundWorkerTaskId: attempt.backgroundWorkerTaskId, - status: "EXECUTING" as const, - }, - run: { - id: taskRun.friendlyId, - payload: taskRun.payload, - payloadType: taskRun.payloadType, - context: taskRun.context, - createdAt: taskRun.createdAt, - startedAt: taskRun.startedAt ?? taskRun.createdAt, - tags: taskRun.runTags ?? [], - isTest: taskRun.isTest, - isReplay: !!taskRun.replayedFromTaskRunFriendlyId, - idempotencyKey: taskRun.idempotencyKey ?? undefined, - durationMs: taskRun.usageDurationMs, - costInCents: taskRun.costInCents, - baseCostInCents: taskRun.baseCostInCents, - metadata, - maxDuration: taskRun.maxDurationInSeconds ?? undefined, - }, - queue: { - id: queue.friendlyId, - name: queue.name, - }, - environment: { - id: attempt.runtimeEnvironment.id, - slug: attempt.runtimeEnvironment.slug, - type: attempt.runtimeEnvironment.type, - }, - organization: { - id: attempt.runtimeEnvironment.organization.id, - slug: attempt.runtimeEnvironment.organization.slug, - name: attempt.runtimeEnvironment.organization.title, - }, - project: { - id: attempt.runtimeEnvironment.project.id, - ref: attempt.runtimeEnvironment.project.externalRef, - slug: attempt.runtimeEnvironment.project.slug, - name: attempt.runtimeEnvironment.project.name, - }, - batch: undefined, // TODO: Removing this for now until we can do it more efficiently - worker: { - id: attempt.backgroundWorkerId, - contentHash: attempt.backgroundWorker.contentHash, - version: attempt.backgroundWorker.version, - }, - machine: machinePreset, - }; - - return execution; - } - - async getCompletionPayloadFromAttempt(id: string): Promise { - const attempt = await prisma.taskRunAttempt.findFirst({ - where: { - id, - status: { - in: FINAL_ATTEMPT_STATUSES, - }, - }, - ...AttemptForCompletionGetPayload, - }); - - if (!attempt) { - logger.error("No completed attempt found", { id }); - return; - } - - return this._completionPayloadFromAttempt(attempt); - } - - async getExecutionPayloadFromAttempt({ - id, - setToExecuting, - isRetrying, - skipStatusChecks, - }: { - id: string; - setToExecuting?: boolean; - isRetrying?: boolean; - skipStatusChecks?: boolean; - }): Promise { - const attempt = await prisma.taskRunAttempt.findFirst({ - where: { - id, - }, - ...AttemptForExecutionGetPayload, - }); - - if (!attempt) { - logger.error("getExecutionPayloadFromAttempt: No attempt found", { id }); - return; - } - - if (!skipStatusChecks) { - switch (attempt.status) { - case "CANCELED": - case "EXECUTING": { - logger.error( - "getExecutionPayloadFromAttempt: Invalid attempt status for execution payload retrieval", - { - attemptId: id, - status: attempt.status, - } - ); - return; - } - } - - switch (attempt.taskRun.status) { - case "CANCELED": - case "EXECUTING": - case "INTERRUPTED": { - logger.error( - "getExecutionPayloadFromAttempt: Invalid run status for execution payload retrieval", - { - attemptId: id, - runId: attempt.taskRunId, - status: attempt.taskRun.status, - } - ); - return; - } - } - } - - if (setToExecuting) { - if (isFinalAttemptStatus(attempt.status) || isFinalRunStatus(attempt.taskRun.status)) { - logger.error("getExecutionPayloadFromAttempt: Status already in final state", { - attempt: { - id: attempt.id, - status: attempt.status, - }, - run: { - id: attempt.taskRunId, - status: attempt.taskRun.status, - }, - }); - return; - } - - await prisma.taskRunAttempt.update({ - where: { - id, - }, - data: { - status: "EXECUTING", - taskRun: { - update: { - data: { - status: isRetrying ? "RETRYING_AFTER_FAILURE" : "EXECUTING", - }, - }, - }, - }, - }); - } - - const { backgroundWorkerTask, taskRun } = attempt; - - const machinePreset = - machinePresetFromRun(attempt.taskRun) ?? - machinePresetFromConfig(backgroundWorkerTask.machineConfig ?? {}); - - const execution = await this._executionFromAttempt(attempt, machinePreset); - const variables = await this.#buildEnvironmentVariables( - attempt.runtimeEnvironment, - taskRun.id, - machinePreset, - taskRun.taskEventStore ?? undefined - ); - - const payload: V3ProdTaskRunExecutionPayload = { - execution, - traceContext: taskRun.traceContext as Record, - environment: variables.reduce((acc: Record, curr) => { - acc[curr.key] = curr.value; - return acc; - }, {}), - }; - - return payload; - } - - async getResumePayload(attemptId: string): Promise< - | { - execution: V3ProdTaskRunExecution; - completion: TaskRunExecutionResult; - } - | undefined - > { - const attempt = await prisma.taskRunAttempt.findFirst({ - where: { - id: attemptId, - }, - select: { - ...AttemptForExecutionGetPayload.select, - error: true, - output: true, - outputType: true, - taskRun: { - select: { - ...AttemptForExecutionGetPayload.select.taskRun.select, - taskIdentifier: true, - }, - }, - }, - }); - - if (!attempt) { - logger.error("getResumePayload: No attempt found", { id: attemptId }); - return; - } - - const execution = await this._executionFromAttempt(attempt); - const completion = this._completionPayloadFromAttempt(attempt); - - return { - execution, - completion, - }; - } - - async getResumePayloads(attemptIds: string[]): Promise< - Array<{ - execution: V3ProdTaskRunExecution; - completion: TaskRunExecutionResult; - }> - > { - const attempts = await prisma.taskRunAttempt.findMany({ - where: { - id: { - in: attemptIds, - }, - }, - select: { - ...AttemptForExecutionGetPayload.select, - error: true, - output: true, - outputType: true, - taskRun: { - select: { - ...AttemptForExecutionGetPayload.select.taskRun.select, - taskIdentifier: true, - }, - }, - }, - }); - - if (attempts.length !== attemptIds.length) { - logger.error("getResumePayloads: Not all attempts found", { attemptIds }); - - throw new ResumePayloadAttemptsNotFoundError(attemptIds); - } - - const payloads = await Promise.all( - attempts.map(async (attempt) => { - const execution = await this._executionFromAttempt(attempt); - - if (!execution) { - throw new ResumePayloadExecutionNotFoundError(attempt.id); - } - - const completion = this._completionPayloadFromAttempt(attempt); - - if (!completion) { - throw new ResumePayloadCompletionNotFoundError(attempt.id); - } - - return { - execution, - completion, - }; - }) - ); - - return payloads; - } - - async getLatestExecutionPayloadFromRun( - id: string, - setToExecuting?: boolean, - isRetrying?: boolean - ): Promise { - const run = await prisma.taskRun.findFirst({ - where: { - id, - }, - include: { - attempts: { - take: 1, - orderBy: { - createdAt: "desc", - }, - }, - }, - }); - - const latestAttempt = run?.attempts[0]; - - if (!latestAttempt) { - logger.error("No attempts for run", { id }); - return; - } - - return this.getExecutionPayloadFromAttempt({ - id: latestAttempt.id, - setToExecuting, - isRetrying, - }); - } - - async getLazyAttemptPayload( - envId: string, - runId: string - ): Promise { - const environment = await findEnvironmentById(envId); - - if (!environment) { - logger.error("getLazyAttemptPayload: Environment not found", { runId, envId }); - return; - } - - const run = await prisma.taskRun.findFirst({ - where: { - id: runId, - }, - select: { - id: true, - traceContext: true, - friendlyId: true, - isTest: true, - replayedFromTaskRunFriendlyId: true, - lockedBy: { - select: { - machineConfig: true, - }, - }, - machinePreset: true, - taskEventStore: true, - }, - }); - - if (!run) { - logger.error("getLazyAttemptPayload: Run not found", { runId, envId }); - return; - } - - const attemptCount = await prisma.taskRunAttempt.count({ - where: { - taskRunId: run.id, - }, - }); - - logger.debug("getLazyAttemptPayload: Getting lazy attempt payload for run", { - run, - attemptCount, - }); - - const machinePreset = - machinePresetFromRun(run) ?? machinePresetFromConfig(run.lockedBy?.machineConfig ?? {}); - - const variables = await this.#buildEnvironmentVariables( - environment, - run.id, - machinePreset, - run.taskEventStore ?? undefined - ); - - return { - traceContext: run.traceContext as Record, - environment: variables.reduce((acc: Record, curr) => { - acc[curr.key] = curr.value; - return acc; - }, {}), - runId: run.friendlyId, - messageId: run.id, - isTest: run.isTest, - isReplay: !!run.replayedFromTaskRunFriendlyId, - attemptCount, - metrics: [], - } satisfies TaskRunExecutionLazyAttemptPayload; - } - - async taskHeartbeat(attemptFriendlyId: string) { - logger.debug("[SharedQueueConsumer] taskHeartbeat()", { id: attemptFriendlyId }); - - const taskRunAttempt = await prisma.taskRunAttempt.findFirst({ - where: { friendlyId: attemptFriendlyId }, - }); - - if (!taskRunAttempt) { - return; - } - - await this.#heartbeat(taskRunAttempt.taskRunId); - } - - async taskRunHeartbeat(runId: string) { - logger.debug("[SharedQueueConsumer] taskRunHeartbeat()", { runId }); - - await this.#heartbeat(runId); - } - - public async taskRunFailed(completion: TaskRunFailedExecutionResult) { - logger.debug("[SharedQueueConsumer] taskRunFailed()", { completion }); - - const service = new FailedTaskRunService(); - - await service.call(completion.id, completion); - } - - async #heartbeat(runId: string) { - await marqs?.heartbeatMessage(runId); - - try { - // There can be a lot of calls per minute and the data doesn't have to be accurate, so use the read replica - const taskRun = await $replica.taskRun.findFirst({ - where: { - id: runId, - }, - select: { - id: true, - status: true, - runtimeEnvironment: { - select: { - type: true, - }, - }, - lockedToVersion: { - select: { - supportsLazyAttempts: true, - }, - }, - }, - }); - - if (!taskRun) { - logger.error("SharedQueueTasks.#heartbeat: Task run not found", { - runId, - }); - - return; - } - - if (taskRun.runtimeEnvironment.type === "DEVELOPMENT") { - return; - } - - if (isFinalRunStatus(taskRun.status)) { - logger.debug("SharedQueueTasks.#heartbeat: Task run is in final status", { - runId, - status: taskRun.status, - }); - - // Signal to exit any leftover containers - socketIo.coordinatorNamespace.emit("REQUEST_RUN_CANCELLATION", { - version: "v1", - runId: taskRun.id, - // Give the run a few seconds to exit to complete any flushing etc - delayInMs: taskRun.lockedToVersion?.supportsLazyAttempts ? 5_000 : undefined, - }); - return; - } - } catch (error) { - logger.error("SharedQueueTasks.#heartbeat: Error signaling run cancellation", { - runId, - error: error instanceof Error ? error.message : error, - }); - } - } - - async #buildEnvironmentVariables( - environment: RuntimeEnvironmentForEnvRepo, - runId: string, - machinePreset: MachinePreset, - taskEventStore?: string - ): Promise> { - const variables = await resolveVariablesForEnvironment(environment); - - const jwt = await generateJWTTokenForEnvironment(environment, { - run_id: runId, - machine_preset: machinePreset.name, - }); - - if (taskEventStore) { - const resourceAttributes = JSON.stringify({ - [SemanticInternalAttributes.TASK_EVENT_STORE]: taskEventStore, - }); - - variables.push(...[{ key: "OTEL_RESOURCE_ATTRIBUTES", value: resourceAttributes }]); - } - - return [ - ...variables, - ...[ - { key: "TRIGGER_JWT", value: jwt }, - { key: "TRIGGER_RUN_ID", value: runId }, - { - key: "TRIGGER_MACHINE_PRESET", - value: machinePreset.name, - }, - ], - ]; - } -} - -export const sharedQueueTasks = singleton("sharedQueueTasks", () => new SharedQueueTasks()); diff --git a/apps/webapp/app/v3/marqs/types.ts b/apps/webapp/app/v3/marqs/types.ts deleted file mode 100644 index 69e75ac44a5..00000000000 --- a/apps/webapp/app/v3/marqs/types.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { RuntimeEnvironmentType } from "@trigger.dev/database"; -import { z } from "zod"; - -export type QueueRange = { offset: number; count: number }; - -export type QueueDescriptor = { - organization: string; - environment: string; - name: string; - concurrencyKey?: string; -}; - -export type MarQSKeyProducerEnv = { - id: string; - organizationId: string; - type: RuntimeEnvironmentType; -}; - -export interface MarQSKeyProducer { - queueConcurrencyLimitKey(env: MarQSKeyProducerEnv, queue: string): string; - - envConcurrencyLimitKey(envId: string): string; - envConcurrencyLimitKey(env: MarQSKeyProducerEnv): string; - - envCurrentConcurrencyKey(envId: string): string; - envCurrentConcurrencyKey(env: MarQSKeyProducerEnv): string; - - envReserveConcurrencyKey(envId: string): string; - - queueKey(orgId: string, envId: string, queue: string, concurrencyKey?: string): string; - queueKey(env: MarQSKeyProducerEnv, queue: string, concurrencyKey?: string): string; - - queueKeyFromQueue(queue: string): string; - - envQueueKey(env: MarQSKeyProducerEnv): string; - envSharedQueueKey(env: MarQSKeyProducerEnv): string; - sharedQueueKey(): string; - sharedQueueScanPattern(): string; - sharedWorkerQueueKey(): string; - queueCurrentConcurrencyScanPattern(): string; - queueConcurrencyLimitKeyFromQueue(queue: string): string; - queueCurrentConcurrencyKeyFromQueue(queue: string): string; - queueCurrentConcurrencyKey( - env: MarQSKeyProducerEnv, - queue: string, - concurrencyKey?: string - ): string; - envConcurrencyLimitKeyFromQueue(queue: string): string; - envCurrentConcurrencyKeyFromQueue(queue: string): string; - envReserveConcurrencyKeyFromQueue(queue: string): string; - envQueueKeyFromQueue(queue: string): string; - messageKey(messageId: string): string; - nackCounterKey(messageId: string): string; - stripKeyPrefix(key: string): string; - orgIdFromQueue(queue: string): string; - envIdFromQueue(queue: string): string; - - queueReserveConcurrencyKeyFromQueue(queue: string): string; - queueDescriptorFromQueue(queue: string): QueueDescriptor; -} - -export type EnvQueues = { - envId: string; - queues: string[]; -}; - -const MarQSPriorityLevel = z.enum(["resume", "retry"]); - -export type MarQSPriorityLevel = z.infer; - -export interface MarQSFairDequeueStrategy { - distributeFairQueuesFromParentQueue( - parentQueue: string, - consumerId: string - ): Promise>; -} - -export const MessagePayload = z.object({ - version: z.literal("1"), - data: z.record(z.unknown()), - queue: z.string(), - messageId: z.string(), - timestamp: z.number(), - parentQueue: z.string(), - concurrencyKey: z.string().optional(), - priority: MarQSPriorityLevel.optional(), - availableAt: z.number().optional(), - enqueueMethod: z.enum(["enqueue", "requeue", "replace"]).default("enqueue"), -}); - -export type MessagePayload = z.infer; - -export interface MessageQueueSubscriber { - messageEnqueued(message: MessagePayload): Promise; - messageDequeued(message: MessagePayload): Promise; - messageAcked(message: MessagePayload): Promise; - messageNacked(message: MessagePayload): Promise; - messageReplaced(message: MessagePayload): Promise; - messageRequeued(message: MessagePayload): Promise; -} - -export interface VisibilityTimeoutStrategy { - startHeartbeat(messageId: string, timeoutInMs: number): Promise; - heartbeat(messageId: string, timeoutInMs: number): Promise; - cancelHeartbeat(messageId: string): Promise; -} - -export type EnqueueMessageReserveConcurrencyOptions = { - messageId: string; - recursiveQueue: boolean; -}; diff --git a/apps/webapp/app/v3/marqs/v3VisibilityTimeout.server.ts b/apps/webapp/app/v3/marqs/v3VisibilityTimeout.server.ts deleted file mode 100644 index ed10bf207fb..00000000000 --- a/apps/webapp/app/v3/marqs/v3VisibilityTimeout.server.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { legacyRunEngineWorker } from "../legacyRunEngineWorker.server"; -import { TaskRunHeartbeatFailedService } from "../taskRunHeartbeatFailed.server"; -import type { VisibilityTimeoutStrategy } from "./types"; - -export class V3GraphileVisibilityTimeout implements VisibilityTimeoutStrategy { - async startHeartbeat(messageId: string, timeoutInMs: number): Promise { - await TaskRunHeartbeatFailedService.enqueue(messageId, new Date(Date.now() + timeoutInMs)); - } - - async heartbeat(messageId: string, timeoutInMs: number): Promise { - await TaskRunHeartbeatFailedService.enqueue(messageId, new Date(Date.now() + timeoutInMs)); - } - - async cancelHeartbeat(messageId: string): Promise { - await TaskRunHeartbeatFailedService.dequeue(messageId); - } -} - -export class V3LegacyRunEngineWorkerVisibilityTimeout implements VisibilityTimeoutStrategy { - async startHeartbeat(messageId: string, timeoutInMs: number): Promise { - await legacyRunEngineWorker.enqueue({ - id: `heartbeat:${messageId}`, - job: "runHeartbeat", - payload: { runId: messageId }, - availableAt: new Date(Date.now() + timeoutInMs), - }); - } - - async heartbeat(messageId: string, timeoutInMs: number): Promise { - await legacyRunEngineWorker.reschedule( - `heartbeat:${messageId}`, - new Date(Date.now() + timeoutInMs) - ); - } - - async cancelHeartbeat(messageId: string): Promise { - await legacyRunEngineWorker.ack(`heartbeat:${messageId}`); - } -} diff --git a/apps/webapp/app/v3/scheduleEngine.server.ts b/apps/webapp/app/v3/scheduleEngine.server.ts index 53a48a3f817..68f78af376e 100644 --- a/apps/webapp/app/v3/scheduleEngine.server.ts +++ b/apps/webapp/app/v3/scheduleEngine.server.ts @@ -8,9 +8,7 @@ import { logger } from "~/services/logger.server"; import { singleton } from "~/utils/singleton"; import { OutOfEntitlementError, TriggerTaskService } from "./services/triggerTask.server"; import { meter, tracer } from "./tracer.server"; -import { workerQueue } from "~/services/worker.server"; import { ServiceValidationError } from "./services/common.server"; -import { isV3Disabled } from "./engineDeprecation.server"; export const scheduleEngine = singleton("ScheduleEngine", createScheduleEngine); @@ -85,11 +83,8 @@ function createScheduleEngine() { exactScheduleTime, }) => { try { - // v3 (engine V1) shutdown: skip firing schedules for V1 projects so the - // cron doesn't keep doing trigger work just to be rejected. Return success - // so the schedule engine treats it as handled and doesn't retry. v4 is - // unaffected. - if (isV3Disabled() && environment.project.engine === "V1") { + // v3 (engine V1) is retired: skip firing V1 schedules instead of triggering into a guaranteed rejection every tick. + if (environment.project.engine === "V1") { logger.debug("[ScheduleEngine] Skipping scheduled fire for shut-down v3 project", { taskIdentifier, scheduleId, @@ -151,24 +146,7 @@ function createScheduleEngine() { } }, isDevEnvironmentConnectedHandler: isDevEnvironmentConnectedHandler, - onRegisterScheduleInstance: removeDeprecatedWorkerQueueItem, }); return engine; } - -async function removeDeprecatedWorkerQueueItem(instanceId: string) { - // We need to dequeue the instance from the existing workerQueue - try { - await workerQueue.dequeue(`scheduled-task-instance:${instanceId}`); - - logger.debug("Removed deprecated worker queue item", { - instanceId, - }); - } catch (error) { - logger.error("Error dequeuing scheduled task instance from deprecated queue", { - instanceId, - error: error instanceof Error ? error.message : String(error), - }); - } -} diff --git a/apps/webapp/app/v3/services/batchTriggerV3.server.ts b/apps/webapp/app/v3/services/batchTriggerV3.server.ts index 5eb3e5e5a9f..b3f25efab89 100644 --- a/apps/webapp/app/v3/services/batchTriggerV3.server.ts +++ b/apps/webapp/app/v3/services/batchTriggerV3.server.ts @@ -35,7 +35,6 @@ import { downloadPacketFromObjectStore, uploadPacketToObjectStore } from "../obj import { isFinalAttemptStatus, isFinalRunStatus } from "../taskStatus"; import { startActiveSpan } from "../tracer.server"; import { BaseService, ServiceValidationError } from "./baseService.server"; -import { ResumeBatchRunService } from "./resumeBatchRun.server"; import { OutOfEntitlementError, TriggerTaskService } from "./triggerTask.server"; const PROCESSING_BATCH_SIZE = 50; @@ -1049,9 +1048,7 @@ export async function tryCompleteBatchV3( logger.debug("tryCompleteBatchV3: Batch completed", { batchId, completedCount }); - if (scheduleResumeOnComplete && batch.dependentTaskAttemptId) { - await ResumeBatchRunService.enqueue(batchId, true, tx); - } + // Dependent-attempt batches (batchTriggerAndWait) only exist on the retired V1 engine, so there is no parent to resume here. } export async function completeBatchTaskRunItemV3( diff --git a/apps/webapp/app/v3/services/bulk/createBulkAction.server.ts b/apps/webapp/app/v3/services/bulk/createBulkAction.server.ts deleted file mode 100644 index 9acd936db9b..00000000000 --- a/apps/webapp/app/v3/services/bulk/createBulkAction.server.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { type BulkActionType } from "@trigger.dev/database"; -import { bulkActionVerb } from "~/components/runs/v3/BulkAction"; -import { BULK_ACTION_RUN_LIMIT } from "~/consts"; -import { logger } from "~/services/logger.server"; -import { generateFriendlyId } from "../../friendlyIdentifiers"; -import { BaseService } from "../baseService.server"; -import { PerformBulkActionService } from "./performBulkAction.server"; - -type BulkAction = { - projectId: string; - action: BulkActionType; - runIds: string[]; -}; - -export class CreateBulkActionService extends BaseService { - public async call({ projectId, action, runIds }: BulkAction) { - const group = await this._prisma.bulkActionGroup.create({ - data: { - friendlyId: generateFriendlyId("bulk"), - projectId, - type: action, - }, - }); - - //limit to the first X runs - const passedTooManyRuns = runIds.length > BULK_ACTION_RUN_LIMIT; - runIds = runIds.slice(0, BULK_ACTION_RUN_LIMIT); - - const _items = await this._prisma.bulkActionItem.createMany({ - data: runIds.map((runId) => ({ - friendlyId: generateFriendlyId("bulkitem"), - type: action, - groupId: group.id, - sourceRunId: runId, - })), - }); - - logger.debug("Created bulk action group", { - groupId: group.id, - action, - runIds, - }); - - await PerformBulkActionService.enqueue(group.id, this._prisma); - - let message = bulkActionVerb(action); - if (passedTooManyRuns) { - message += ` the first ${BULK_ACTION_RUN_LIMIT} runs`; - } else { - message += ` ${runIds.length} runs`; - } - - return { - id: group.id, - friendlyId: group.friendlyId, - runCount: runIds.length, - message, - }; - } -} diff --git a/apps/webapp/app/v3/services/bulk/performBulkAction.server.ts b/apps/webapp/app/v3/services/bulk/performBulkAction.server.ts deleted file mode 100644 index b6ea92aace6..00000000000 --- a/apps/webapp/app/v3/services/bulk/performBulkAction.server.ts +++ /dev/null @@ -1,127 +0,0 @@ -import assertNever from "assert-never"; -import type { PrismaClientOrTransaction } from "~/db.server"; -import { workerQueue } from "~/services/worker.server"; -import { BaseService } from "../baseService.server"; -import { CancelTaskRunService } from "../cancelTaskRun.server"; -import { ReplayTaskRunService } from "../replayTaskRun.server"; - -export class PerformBulkActionService extends BaseService { - public async performBulkActionItem(bulkActionItemId: string) { - const item = await this._prisma.bulkActionItem.findFirst({ - where: { id: bulkActionItemId }, - include: { - sourceRun: true, - destinationRun: true, - }, - }); - - if (!item) { - return; - } - - if (item.status !== "PENDING") { - return; - } - - switch (item.type) { - case "REPLAY": { - const service = new ReplayTaskRunService(this._prisma); - const result = await service.call(item.sourceRun, { triggerSource: "dashboard" }); - - await this._prisma.bulkActionItem.update({ - where: { id: item.id }, - data: { - destinationRunId: result?.id, - status: result ? "COMPLETED" : "FAILED", - error: result ? undefined : "Failed to replay task run", - }, - }); - - break; - } - case "CANCEL": { - const service = new CancelTaskRunService(this._prisma); - - const result = await service.call(item.sourceRun); - - await this._prisma.bulkActionItem.update({ - where: { id: item.id }, - data: { - destinationRunId: item.sourceRun.id, - status: result ? "COMPLETED" : "FAILED", - error: result ? undefined : "Task wasn't cancelable", - }, - }); - - break; - } - default: { - assertNever(item.type); - } - } - - const groupItems = await this._prisma.bulkActionItem.findMany({ - where: { groupId: item.groupId }, - select: { - status: true, - }, - }); - - const isGroupCompleted = groupItems.every((item) => item.status !== "PENDING"); - - if (isGroupCompleted) { - await this._prisma.bulkActionItem.update({ - where: { id: item.id }, - data: { - status: "COMPLETED", - }, - }); - } - } - - public async enqueueBulkActionItem(bulkActionItemId: string, groupId: string) { - await workerQueue.enqueue( - "v3.performBulkActionItem", - { - bulkActionItemId, - }, - { - jobKey: `performBulkActionItem:${bulkActionItemId}`, - } - ); - } - - public async call(bulkActionGroupId: string) { - const actionGroup = await this._prisma.bulkActionGroup.findFirst({ - where: { id: bulkActionGroupId }, - select: { id: true }, - }); - - if (!actionGroup) { - return; - } - - const items = await this._prisma.bulkActionItem.findMany({ - where: { groupId: bulkActionGroupId }, - select: { id: true }, - }); - - for (const item of items) { - await this.enqueueBulkActionItem(item.id, bulkActionGroupId); - } - } - - static async enqueue(bulkActionGroupId: string, tx: PrismaClientOrTransaction, runAt?: Date) { - return await workerQueue.enqueue( - "v3.performBulkAction", - { - bulkActionGroupId, - }, - { - tx, - runAt, - jobKey: `performBulkAction:${bulkActionGroupId}`, - } - ); - } -} diff --git a/apps/webapp/app/v3/services/cancelAttempt.server.ts b/apps/webapp/app/v3/services/cancelAttempt.server.ts deleted file mode 100644 index 79b05ede26c..00000000000 --- a/apps/webapp/app/v3/services/cancelAttempt.server.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { $transaction, type PrismaClientOrTransaction, prisma } from "~/db.server"; -import { type AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { isCancellableRunStatus } from "../taskStatus"; -import { BaseService } from "./baseService.server"; -import { FinalizeTaskRunService } from "./finalizeTaskRun.server"; - -export class CancelAttemptService extends BaseService { - public async call( - attemptId: string, - taskRunId: string, - cancelledAt: Date, - reason: string, - env?: AuthenticatedEnvironment - ) { - let environment: AuthenticatedEnvironment | undefined = env; - - if (!environment) { - environment = await getAuthenticatedEnvironmentFromAttempt(attemptId); - - if (!environment) { - return; - } - } - - return await this.traceWithEnv("call()", environment, async (span) => { - span.setAttribute("taskRunId", taskRunId); - span.setAttribute("attemptId", attemptId); - - const taskRunAttempt = await this._prisma.taskRunAttempt.findFirst({ - where: { - friendlyId: attemptId, - }, - include: { - taskRun: true, - }, - }); - - if (!taskRunAttempt) { - return; - } - - if (taskRunAttempt.status === "CANCELED") { - logger.warn("Task run attempt is already cancelled", { - attemptId, - }); - - return; - } - - await $transaction(this._prisma, "cancel attempt", async (tx) => { - await tx.taskRunAttempt.update({ - where: { - friendlyId: attemptId, - }, - data: { - status: "CANCELED", - completedAt: cancelledAt, - }, - }); - - const isCancellable = isCancellableRunStatus(taskRunAttempt.taskRun.status); - - const finalizeService = new FinalizeTaskRunService(tx); - await finalizeService.call({ - id: taskRunId, - status: isCancellable ? "INTERRUPTED" : undefined, - completedAt: isCancellable ? cancelledAt : undefined, - attemptStatus: isCancellable ? "CANCELED" : undefined, - error: isCancellable ? { type: "STRING_ERROR", raw: reason } : undefined, - }); - }); - }); - } -} - -async function getAuthenticatedEnvironmentFromAttempt( - friendlyId: string, - prismaClient?: PrismaClientOrTransaction -) { - const taskRunAttempt = await (prismaClient ?? prisma).taskRunAttempt.findFirst({ - where: { - friendlyId, - }, - include: { - runtimeEnvironment: { - include: { - organization: true, - project: true, - }, - }, - }, - }); - - if (!taskRunAttempt) { - return; - } - - return taskRunAttempt?.runtimeEnvironment; -} diff --git a/apps/webapp/app/v3/services/cancelDevSessionRuns.server.ts b/apps/webapp/app/v3/services/cancelDevSessionRuns.server.ts deleted file mode 100644 index 3575a750521..00000000000 --- a/apps/webapp/app/v3/services/cancelDevSessionRuns.server.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { type RunStore } from "@internal/run-store"; -import { z } from "zod"; -import { type PrismaClientOrTransaction } from "~/db.server"; -import { findLatestSession } from "~/models/runtimeEnvironment.server"; -import { logger } from "~/services/logger.server"; -import { commonWorker } from "../commonWorker.server"; -import { type ReadThroughDeps, readThroughRun } from "../runOpsMigration/readThrough.server"; -import { BaseService } from "./baseService.server"; -import { type CancelableTaskRun, CancelTaskRunService } from "./cancelTaskRun.server"; - -export const CancelDevSessionRunsServiceOptions = z.object({ - runIds: z.array(z.string()), - cancelledAt: z.coerce.date(), - reason: z.string(), - cancelledSessionId: z.string().optional(), -}); - -export type CancelDevSessionRunsServiceOptions = z.infer; - -export class CancelDevSessionRunsService extends BaseService { - // Injectable read-through deps for the run-ops TaskRun read. Undefined in production: - // readThroughRun then uses its ~/db.server singleton handles and the boot split flag, - // so single-DB is unchanged. Tests inject the hetero new/legacy handles + splitEnabled. - readonly #readThroughDeps?: ReadThroughDeps; - - constructor( - opts: { - prisma?: PrismaClientOrTransaction; - replica?: PrismaClientOrTransaction; - runStore?: RunStore; - readThroughDeps?: ReadThroughDeps; - } = {} - ) { - super(opts.prisma, opts.replica, opts.runStore); - this.#readThroughDeps = opts.readThroughDeps; - } - - public async call(options: CancelDevSessionRunsServiceOptions) { - const cancelledSession = options.cancelledSessionId - ? await this._prisma.runtimeEnvironmentSession.findFirst({ - where: { id: options.cancelledSessionId }, - }) - : undefined; - - if (cancelledSession) { - const latestSession = await findLatestSession(cancelledSession.environmentId, this._replica); - - if ( - latestSession && - latestSession.id !== cancelledSession.id && - !latestSession.disconnectedAt - ) { - logger.debug("Not cancelling runs because there is a newer session", { - cancelledSessionId: cancelledSession.id, - latestSessionId: latestSession.id, - }); - - return; - } - } - - logger.debug( - "Cancelling in progress runs for dev session because there isn't a newer connected session", - { - options, - cancelledSession, - } - ); - - const cancelTaskRunService = new CancelTaskRunService(); - - // readThroughRun resolves residency from the run id alone; an env scope is only - // available when a cancelled session was resolved. - const environmentId = cancelledSession?.environmentId ?? ""; - - for (const runId of options.runIds) { - await this.#cancelInProgressRun( - runId, - cancelTaskRunService, - options.cancelledAt, - options.reason, - environmentId - ); - } - } - - async #cancelInProgressRun( - runId: string, - service: CancelTaskRunService, - cancelledAt: Date, - reason: string, - environmentId: string - ) { - logger.debug("Cancelling in progress run", { runId }); - - // Read-through: new store first, legacy read replica for an old - // in-retention run; single plain read in single-DB passthrough. - const where = runId.startsWith("run_") ? { friendlyId: runId } : { id: runId }; - - const result = await readThroughRun({ - runId, - environmentId, - readNew: (client) => - client.taskRun.findFirst({ - where, - select: { - id: true, - engine: true, - status: true, - friendlyId: true, - taskEventStore: true, - createdAt: true, - completedAt: true, - }, - }), - readLegacy: (replica) => - replica.taskRun.findFirst({ - where, - select: { - id: true, - engine: true, - status: true, - friendlyId: true, - taskEventStore: true, - createdAt: true, - completedAt: true, - }, - }), - deps: this.#readThroughDeps, - }); - - if (result.source === "not-found" || result.source === "past-retention") { - return; - } - - const taskRun = result.value; - - try { - await service.call(taskRun, { reason, cancelAttempts: true, cancelledAt }); - } catch (e) { - logger.error("Failed to cancel in progress run", { - runId, - error: e, - }); - } - } - - static async enqueue(options: CancelDevSessionRunsServiceOptions, runAt?: Date) { - return await commonWorker.enqueue({ - id: options.cancelledSessionId - ? `cancelDevSessionRuns:${options.cancelledSessionId}` - : undefined, - job: "v3.cancelDevSessionRuns", - payload: options, - availableAt: runAt, - }); - } -} diff --git a/apps/webapp/app/v3/services/cancelTaskAttemptDependencies.server.ts b/apps/webapp/app/v3/services/cancelTaskAttemptDependencies.server.ts deleted file mode 100644 index 9f76a3281cc..00000000000 --- a/apps/webapp/app/v3/services/cancelTaskAttemptDependencies.server.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { logger } from "~/services/logger.server"; -import { commonWorker } from "../commonWorker.server"; -import { BaseService } from "./baseService.server"; -import { CancelTaskRunService } from "./cancelTaskRun.server"; - -export class CancelTaskAttemptDependenciesService extends BaseService { - public async call(attemptId: string) { - const taskAttempt = await this._prisma.taskRunAttempt.findFirst({ - where: { id: attemptId }, - include: { - dependencies: { - select: { - taskRunId: true, - }, - }, - batchDependencies: { - include: { - runDependencies: { - select: { - taskRunId: true, - }, - }, - }, - }, - }, - }); - - if (!taskAttempt) { - return; - } - - if (taskAttempt.status !== "CANCELED") { - logger.debug("Task attempt is not cancelled, continuing anyway", { - attemptId, - status: taskAttempt.status, - }); - } - - const cancelRunService = new CancelTaskRunService(); - - logger.debug("Cancelling task attempt dependencies", { - taskAttempt, - dependencies: taskAttempt.dependencies, - batchDependencies: taskAttempt.batchDependencies, - }); - - // Hydrate the dependent runs from both relation paths in a single batched read, - // deduping the ids that feed the query while preserving the original iteration order. - const taskRunIds = new Set(); - for (const dependency of taskAttempt.dependencies) { - taskRunIds.add(dependency.taskRunId); - } - for (const batchDependency of taskAttempt.batchDependencies) { - for (const runDependency of batchDependency.runDependencies) { - taskRunIds.add(runDependency.taskRunId); - } - } - - const runs = - taskRunIds.size > 0 - ? await this.runStore.findRuns( - { - where: { id: { in: [...taskRunIds] } }, - select: { - id: true, - engine: true, - status: true, - friendlyId: true, - taskEventStore: true, - createdAt: true, - completedAt: true, - }, - }, - this._prisma - ) - : []; - - const runMap = new Map(runs.map((run) => [run.id, run])); - - // TaskAttempt will either have dependencies or batchDependencies - for (const dependency of taskAttempt.dependencies) { - const run = runMap.get(dependency.taskRunId); - if (run) { - await cancelRunService.call(run); - } - } - - for (const batchDependency of taskAttempt.batchDependencies) { - for (const runDependency of batchDependency.runDependencies) { - const run = runMap.get(runDependency.taskRunId); - if (run) { - await cancelRunService.call(run); - } - } - } - } - - static async enqueue(attemptId: string, runAt?: Date) { - return await commonWorker.enqueue({ - id: `cancelTaskAttemptDependencies:${attemptId}`, - job: "v3.cancelTaskAttemptDependencies", - payload: { - attemptId, - }, - availableAt: runAt, - }); - } -} diff --git a/apps/webapp/app/v3/services/cancelTaskRunV1.server.ts b/apps/webapp/app/v3/services/cancelTaskRunV1.server.ts deleted file mode 100644 index 819b447c90c..00000000000 --- a/apps/webapp/app/v3/services/cancelTaskRunV1.server.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { type Prisma } from "@trigger.dev/database"; -import assertNever from "assert-never"; -import { logger } from "~/services/logger.server"; -import { socketIo } from "../handleSocketIo.server"; -import { devPubSub } from "../marqs/devPubSub.server"; -import { CANCELLABLE_ATTEMPT_STATUSES, isCancellableRunStatus } from "../taskStatus"; -import { BaseService } from "./baseService.server"; -import { CancelAttemptService } from "./cancelAttempt.server"; -import { CancelTaskAttemptDependenciesService } from "./cancelTaskAttemptDependencies.server"; -import type { CancelableTaskRun } from "./cancelTaskRun.server"; -import { FinalizeTaskRunService } from "./finalizeTaskRun.server"; -import { tryCatch } from "@trigger.dev/core/utils"; -import { getEventRepositoryForStore } from "../eventRepository/index.server"; - -type ExtendedTaskRun = Prisma.TaskRunGetPayload<{ - include: { - runtimeEnvironment: true; - lockedToVersion: true; - }; -}>; - -type ExtendedTaskRunAttempt = Prisma.TaskRunAttemptGetPayload<{ - include: { - backgroundWorker: true; - }; -}>; - -export type CancelTaskRunServiceOptions = { - reason?: string; - cancelAttempts?: boolean; - cancelledAt?: Date; - bulkActionId?: string; -}; - -export class CancelTaskRunServiceV1 extends BaseService { - public async call(taskRun: CancelableTaskRun, options?: CancelTaskRunServiceOptions) { - const opts = { - reason: "Task run was cancelled by user", - cancelAttempts: true, - cancelledAt: new Date(), - ...options, - }; - - // Make sure the task run is in a cancellable state - if (!isCancellableRunStatus(taskRun.status)) { - logger.info("Task run is not in a cancellable state", { - runId: taskRun.id, - status: taskRun.status, - }); - - //add the bulk action id to the run - if (opts.bulkActionId) { - await this._prisma.taskRun.update({ - where: { id: taskRun.id }, - data: { - bulkActionGroupIds: { - push: opts.bulkActionId, - }, - }, - }); - } - - return; - } - - const finalizeService = new FinalizeTaskRunService(); - const cancelledTaskRun = await finalizeService.call({ - id: taskRun.id, - status: "CANCELED", - completedAt: opts.cancelledAt, - bulkActionId: opts.bulkActionId, - include: { - attempts: { - where: { - status: { - in: CANCELLABLE_ATTEMPT_STATUSES, - }, - }, - include: { - backgroundWorker: true, - dependencies: { - include: { - taskRun: true, - }, - }, - batchTaskRunItems: { - include: { - taskRun: true, - }, - }, - }, - }, - runtimeEnvironment: true, - lockedToVersion: true, - project: true, - }, - attemptStatus: "CANCELED", - error: { - type: "STRING_ERROR", - raw: opts.reason, - }, - }); - - const eventRepository = await getEventRepositoryForStore( - cancelledTaskRun.taskEventStore, - cancelledTaskRun.runtimeEnvironment.organizationId - ); - - const [cancelRunEventError] = await tryCatch( - eventRepository.cancelRunEvent({ - reason: opts.reason, - run: cancelledTaskRun, - cancelledAt: opts.cancelledAt, - }) - ); - - if (cancelRunEventError) { - logger.error("[CancelTaskRunServiceV1] Failed to cancel run event", { - error: cancelRunEventError, - runId: cancelledTaskRun.id, - }); - } - - // Cancel any in progress attempts - if (opts.cancelAttempts) { - await this.#cancelPotentiallyRunningAttempts(cancelledTaskRun, cancelledTaskRun.attempts); - await this.#cancelRemainingRunWorkers(cancelledTaskRun); - } - - return { - id: cancelledTaskRun.id, - }; - } - - async #cancelPotentiallyRunningAttempts( - run: ExtendedTaskRun, - attempts: ExtendedTaskRunAttempt[] - ) { - for (const attempt of attempts) { - await CancelTaskAttemptDependenciesService.enqueue(attempt.id); - - if (run.runtimeEnvironment.type === "DEVELOPMENT") { - // Signal the task run attempt to stop - await devPubSub.publish( - `backgroundWorker:${attempt.backgroundWorkerId}:${attempt.id}`, - "CANCEL_ATTEMPT", - { - attemptId: attempt.friendlyId, - backgroundWorkerId: attempt.backgroundWorker.friendlyId, - taskRunId: run.friendlyId, - } - ); - } else { - switch (attempt.status) { - case "EXECUTING": { - // We need to send a cancel message to the coordinator - socketIo.coordinatorNamespace.emit("REQUEST_ATTEMPT_CANCELLATION", { - version: "v1", - attemptId: attempt.id, - attemptFriendlyId: attempt.friendlyId, - }); - - break; - } - case "PENDING": - case "PAUSED": { - logger.debug("Cancelling pending or paused attempt", { - attempt, - }); - - const service = new CancelAttemptService(); - - await service.call( - attempt.friendlyId, - run.id, - new Date(), - "Task run was cancelled by user" - ); - - break; - } - case "CANCELED": - case "COMPLETED": - case "FAILED": { - // Do nothing - break; - } - default: { - assertNever(attempt.status); - } - } - } - } - } - - async #cancelRemainingRunWorkers(run: ExtendedTaskRun) { - if (run.runtimeEnvironment.type === "DEVELOPMENT") { - // Nothing to do - return; - } - - // Broadcast cancel message to all coordinators - socketIo.coordinatorNamespace.emit("REQUEST_RUN_CANCELLATION", { - version: "v1", - runId: run.id, - // Give the attempts some time to exit gracefully. If the runs supports lazy attempts, it also supports exit delays. - delayInMs: run.lockedToVersion?.supportsLazyAttempts ? 5_000 : undefined, - }); - } -} diff --git a/apps/webapp/app/v3/services/changeCurrentDeployment.server.ts b/apps/webapp/app/v3/services/changeCurrentDeployment.server.ts index 636a86c450f..067b4be2f33 100644 --- a/apps/webapp/app/v3/services/changeCurrentDeployment.server.ts +++ b/apps/webapp/app/v3/services/changeCurrentDeployment.server.ts @@ -10,7 +10,6 @@ import { import { taskMetadataCacheInstance } from "~/services/taskMetadataCacheInstance.server"; import { BaseService, ServiceValidationError } from "./baseService.server"; import { syncDeclarativeSchedules } from "./createBackgroundWorker.server"; -import { ExecuteTasksWaitingForDeployService } from "./executeTasksWaitingForDeploy"; import { compareDeploymentVersions } from "../utils/deploymentVersions"; export type ChangeCurrentDeploymentDirection = "promote" | "rollback"; @@ -175,17 +174,6 @@ export class ChangeCurrentDeploymentService extends BaseService { }); } - // Only V1 engine workers need the WAITING_FOR_DEPLOY drain — V2 runs sit - // in PENDING_VERSION and are handled out of band, so enqueuing here for V2 - // just produces empty scans of the TaskRun status index. - const worker = await this._prisma.backgroundWorker.findFirst({ - where: { id: deployment.workerId }, - select: { engine: true }, - }); - - if (worker?.engine === "V1") { - await ExecuteTasksWaitingForDeployService.enqueue(deployment.workerId); - } } async #syncSchedulesForDeployment(deployment: WorkerDeployment) { diff --git a/apps/webapp/app/v3/services/completeAttempt.server.ts b/apps/webapp/app/v3/services/completeAttempt.server.ts deleted file mode 100644 index 96b6cabc629..00000000000 --- a/apps/webapp/app/v3/services/completeAttempt.server.ts +++ /dev/null @@ -1,728 +0,0 @@ -import { tryCatch } from "@trigger.dev/core/utils"; -import type { - MachinePresetName, - TaskRunExecution, - TaskRunExecutionResult, - TaskRunExecutionRetry, - TaskRunFailedExecutionResult, - TaskRunSuccessfulExecutionResult, - V3TaskRunExecution, -} from "@trigger.dev/core/v3"; -import { - TaskRunContext, - TaskRunErrorCodes, - flattenAttributes, - isOOMRunError, - sanitizeError, - shouldRetryError, - taskRunErrorEnhancer, -} from "@trigger.dev/core/v3"; -import type { TaskRun } from "@trigger.dev/database"; -import { MAX_TASK_RUN_ATTEMPTS } from "~/consts"; -import type { PrismaClientOrTransaction } from "~/db.server"; -import { env } from "~/env.server"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { marqs } from "~/v3/marqs/index.server"; -import { FailedTaskRunRetryHelper } from "../failedTaskRun.server"; -import { socketIo } from "../handleSocketIo.server"; -import { createExceptionPropertiesFromError } from "../eventRepository/common.server"; -import type { FAILED_RUN_STATUSES } from "../taskStatus"; -import { isFinalAttemptStatus, isFinalRunStatus } from "../taskStatus"; -import { BaseService } from "./baseService.server"; -import { CancelAttemptService } from "./cancelAttempt.server"; -import { CreateCheckpointService } from "./createCheckpoint.server"; -import { FinalizeTaskRunService } from "./finalizeTaskRun.server"; -import { RetryAttemptService } from "./retryAttempt.server"; -import { getEventRepositoryForStore } from "../eventRepository/index.server"; - -type FoundAttempt = Awaited>; - -type CheckpointData = { - docker: boolean; - location: string; -}; - -type CompleteAttemptServiceOptions = { - prisma?: PrismaClientOrTransaction; - supportsRetryCheckpoints?: boolean; - isSystemFailure?: boolean; - isCrash?: boolean; -}; - -export class CompleteAttemptService extends BaseService { - constructor(private opts: CompleteAttemptServiceOptions = {}) { - super(opts.prisma); - } - - public async call({ - completion, - execution, - env, - checkpoint, - }: { - completion: TaskRunExecutionResult; - execution: V3TaskRunExecution; - env?: AuthenticatedEnvironment; - checkpoint?: CheckpointData; - }): Promise<"COMPLETED" | "RETRIED"> { - const taskRunAttempt = await findAttempt(this._prisma, execution.attempt.id); - - if (!taskRunAttempt) { - logger.error("[CompleteAttemptService] Task run attempt not found", { - id: execution.attempt.id, - }); - - const run = await this.runStore.findRun( - { - friendlyId: execution.run.id, - }, - { - select: { - id: true, - }, - }, - this._prisma - ); - - if (!run) { - logger.error("[CompleteAttemptService] Task run not found", { - friendlyId: execution.run.id, - }); - - return "COMPLETED"; - } - - const finalizeService = new FinalizeTaskRunService(); - await finalizeService.call({ - id: run.id, - status: "SYSTEM_FAILURE", - completedAt: new Date(), - attemptStatus: "FAILED", - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_EXECUTION_FAILED, - message: "Tried to complete attempt but it doesn't exist", - }, - metadata: completion.metadata, - env, - }); - - // No attempt, so there's no message to ACK - return "COMPLETED"; - } - - if ( - isFinalAttemptStatus(taskRunAttempt.status) || - isFinalRunStatus(taskRunAttempt.taskRun.status) - ) { - // We don't want to retry a task run that has already been marked as failed, cancelled, or completed - logger.debug("[CompleteAttemptService] Attempt or run is already in a final state", { - taskRunAttempt, - completion, - }); - - return "COMPLETED"; - } - - if (completion.ok) { - return await this.#completeAttemptSuccessfully(completion, taskRunAttempt, env); - } else { - return await this.#completeAttemptFailed({ - completion, - execution, - taskRunAttempt, - env, - checkpoint, - }); - } - } - - async #completeAttemptSuccessfully( - completion: TaskRunSuccessfulExecutionResult, - taskRunAttempt: NonNullable, - env?: AuthenticatedEnvironment - ): Promise<"COMPLETED"> { - await this._prisma.taskRunAttempt.update({ - where: { id: taskRunAttempt.id }, - data: { - status: "COMPLETED", - completedAt: new Date(), - output: completion.output, - outputType: completion.outputType, - usageDurationMs: completion.usage?.durationMs, - taskRun: { - update: { - output: completion.output, - outputType: completion.outputType, - }, - }, - }, - }); - - const finalizeService = new FinalizeTaskRunService(); - await finalizeService.call({ - id: taskRunAttempt.taskRunId, - status: "COMPLETED_SUCCESSFULLY", - completedAt: new Date(), - metadata: completion.metadata, - env, - }); - - const eventRepository = await getEventRepositoryForStore( - taskRunAttempt.taskRun.taskEventStore, - taskRunAttempt.taskRun.organizationId ?? "" - ); - - const [completeSuccessfulRunEventError] = await tryCatch( - eventRepository.completeSuccessfulRunEvent({ - run: taskRunAttempt.taskRun, - endTime: new Date(), - }) - ); - - if (completeSuccessfulRunEventError) { - logger.error("[CompleteAttemptService] Failed to complete successful run event", { - error: completeSuccessfulRunEventError, - runId: taskRunAttempt.taskRunId, - }); - } - - return "COMPLETED"; - } - - async #completeAttemptFailed({ - completion, - execution, - taskRunAttempt, - env, - checkpoint, - }: { - completion: TaskRunFailedExecutionResult; - execution: V3TaskRunExecution; - taskRunAttempt: NonNullable; - env?: AuthenticatedEnvironment; - checkpoint?: CheckpointData; - }): Promise<"COMPLETED" | "RETRIED"> { - if ( - completion.error.type === "INTERNAL_ERROR" && - completion.error.code === "TASK_RUN_CANCELLED" - ) { - // We need to cancel the task run instead of fail it - const cancelService = new CancelAttemptService(); - - // TODO: handle usages - await cancelService.call( - taskRunAttempt.friendlyId, - taskRunAttempt.taskRunId, - new Date(), - "Canceled by user", - env - ); - - return "COMPLETED"; - } - - const failedAt = new Date(); - const sanitizedError = sanitizeError(completion.error); - - await this._prisma.taskRunAttempt.update({ - where: { id: taskRunAttempt.id }, - data: { - status: "FAILED", - completedAt: failedAt, - error: sanitizedError, - usageDurationMs: completion.usage?.durationMs, - }, - }); - - const environment = env ?? (await this.#getEnvironment(execution.environment.id)); - - // This means that tasks won't know they are being retried - let executionRetryInferred = false; - let executionRetry = completion.retry; - - const shouldInfer = this.opts.isCrash || this.opts.isSystemFailure; - - if (!executionRetry && shouldInfer) { - executionRetryInferred = true; - executionRetry = FailedTaskRunRetryHelper.getExecutionRetry({ - run: { - ...taskRunAttempt.taskRun, - lockedBy: taskRunAttempt.backgroundWorkerTask, - lockedToVersion: taskRunAttempt.backgroundWorker, - }, - execution, - }); - } - - let retriableError = shouldRetryError(taskRunErrorEnhancer(completion.error)); - let isOOMRetry = false; - let isOOMAttempt = isOOMRunError(completion.error); - let isOnMaxOOMMachine = false; - let oomMachine: MachinePresetName | undefined; - - //OOM errors should retry (if an OOM machine is specified, and we're not already on it) - if (isOOMAttempt) { - const retryConfig = FailedTaskRunRetryHelper.getRetryConfig({ - run: { - ...taskRunAttempt.taskRun, - lockedBy: taskRunAttempt.backgroundWorkerTask, - lockedToVersion: taskRunAttempt.backgroundWorker, - }, - execution, - }); - - oomMachine = retryConfig?.outOfMemory?.machine; - isOnMaxOOMMachine = oomMachine === taskRunAttempt.taskRun.machinePreset; - - if (oomMachine && !isOnMaxOOMMachine) { - //we will retry - isOOMRetry = true; - retriableError = true; - executionRetry = FailedTaskRunRetryHelper.getExecutionRetry({ - run: { - ...taskRunAttempt.taskRun, - lockedBy: taskRunAttempt.backgroundWorkerTask, - lockedToVersion: taskRunAttempt.backgroundWorker, - }, - execution, - }); - - //update the machine on the run - await this._prisma.taskRun.update({ - where: { - id: taskRunAttempt.taskRunId, - }, - data: { - machinePreset: oomMachine, - }, - }); - } - } - - if ( - retriableError && - executionRetry !== undefined && - taskRunAttempt.number < MAX_TASK_RUN_ATTEMPTS - ) { - return await this.#retryAttempt({ - execution, - executionRetry, - executionRetryInferred, - taskRunAttempt, - environment, - checkpoint, - forceRequeue: isOOMRetry, - oomMachine, - }); - } - - // The attempt has failed and we won't retry - - if (isOOMAttempt && isOnMaxOOMMachine && environment.type !== "DEVELOPMENT") { - // The attempt failed due to an OOM error but we're already on the machine we should retry on - exitRun(taskRunAttempt.taskRunId); - } - - const eventRepository = await getEventRepositoryForStore( - taskRunAttempt.taskRun.taskEventStore, - taskRunAttempt.taskRun.organizationId ?? "" - ); - - const [completeFailedRunEventError] = await tryCatch( - eventRepository.completeFailedRunEvent({ - run: taskRunAttempt.taskRun, - endTime: failedAt, - exception: createExceptionPropertiesFromError(sanitizedError), - }) - ); - - if (completeFailedRunEventError) { - logger.error("[CompleteAttemptService] Failed to complete failed run event", { - error: completeFailedRunEventError, - runId: taskRunAttempt.taskRunId, - }); - } - - await this._prisma.taskRun.update({ - where: { - id: taskRunAttempt.taskRunId, - }, - data: { - error: sanitizedError, - }, - }); - - let status: FAILED_RUN_STATUSES; - - // Set the correct task run status - if (this.opts.isSystemFailure) { - status = "SYSTEM_FAILURE"; - } else if (this.opts.isCrash) { - status = "CRASHED"; - } else if ( - sanitizedError.type === "INTERNAL_ERROR" && - sanitizedError.code === "MAX_DURATION_EXCEEDED" - ) { - status = "TIMED_OUT"; - } else if (sanitizedError.type === "INTERNAL_ERROR") { - status = "CRASHED"; - } else { - status = "COMPLETED_WITH_ERRORS"; - } - - const finalizeService = new FinalizeTaskRunService(); - await finalizeService.call({ - id: taskRunAttempt.taskRunId, - status, - completedAt: failedAt, - metadata: completion.metadata, - env, - }); - - if (status !== "CRASHED" && status !== "SYSTEM_FAILURE") { - return "COMPLETED"; - } - - // Handle in-progress events - switch (status) { - case "CRASHED": { - const [createAttemptFailedEventError] = await tryCatch( - eventRepository.createAttemptFailedRunEvent({ - run: taskRunAttempt.taskRun, - endTime: failedAt, - attemptNumber: taskRunAttempt.number, - exception: createExceptionPropertiesFromError(sanitizedError), - }) - ); - - if (createAttemptFailedEventError) { - logger.error("[CompleteAttemptService] Failed to create attempt failed run event", { - error: createAttemptFailedEventError, - runId: taskRunAttempt.taskRunId, - }); - } - - break; - } - case "SYSTEM_FAILURE": { - const [createAttemptFailedEventError] = await tryCatch( - eventRepository.createAttemptFailedRunEvent({ - run: taskRunAttempt.taskRun, - endTime: failedAt, - attemptNumber: taskRunAttempt.number, - exception: createExceptionPropertiesFromError(sanitizedError), - }) - ); - - if (createAttemptFailedEventError) { - logger.error("[CompleteAttemptService] Failed to create attempt failed run event", { - error: createAttemptFailedEventError, - runId: taskRunAttempt.taskRunId, - }); - } - } - } - - return "COMPLETED"; - } - - async #enqueueReattempt({ - run, - executionRetry, - executionRetryInferred, - checkpointEventId, - supportsLazyAttempts, - forceRequeue = false, - }: { - run: TaskRun; - executionRetry: TaskRunExecutionRetry; - executionRetryInferred: boolean; - checkpointEventId?: string; - supportsLazyAttempts: boolean; - forceRequeue?: boolean; - }) { - const retryViaQueue = () => { - logger.debug("[CompleteAttemptService] Enqueuing retry attempt", { runId: run.id }); - - return marqs.requeueMessage( - run.id, - { - type: "EXECUTE", - taskIdentifier: run.taskIdentifier, - checkpointEventId: this.opts.supportsRetryCheckpoints ? checkpointEventId : undefined, - retryCheckpointsDisabled: !this.opts.supportsRetryCheckpoints, - }, - executionRetry.timestamp, - "retry" - ); - }; - - const retryDirectly = () => { - logger.debug("[CompleteAttemptService] Retrying attempt directly", { runId: run.id }); - return RetryAttemptService.enqueue(run.id, new Date(executionRetry.timestamp)); - }; - - // There's a checkpoint, so we need to go through the queue - if (checkpointEventId) { - if (!this.opts.supportsRetryCheckpoints) { - logger.error( - "[CompleteAttemptService] Worker does not support retry checkpoints, but a checkpoint was created", - { - runId: run.id, - checkpointEventId, - } - ); - } - - logger.debug("[CompleteAttemptService] Enqueuing retry attempt with checkpoint", { - runId: run.id, - }); - await retryViaQueue(); - return; - } - - // Workers without lazy attempt support always need to go through the queue, which is where the attempt is created - if (!supportsLazyAttempts) { - logger.debug("[CompleteAttemptService] Worker does not support lazy attempts", { - runId: run.id, - }); - await retryViaQueue(); - return; - } - - if (forceRequeue) { - logger.debug("[CompleteAttemptService] Forcing retry via queue", { runId: run.id }); - - // The run won't know it should shut down as we make the decision to force requeue here - // This also ensures that this change is backwards compatible with older workers - exitRun(run.id); - - await retryViaQueue(); - return; - } - - // Workers that never checkpoint between attempts will exit after completing their current attempt if the retry delay exceeds the threshold - if ( - !this.opts.supportsRetryCheckpoints && - executionRetry.delay >= env.CHECKPOINT_THRESHOLD_IN_MS - ) { - logger.debug( - "[CompleteAttemptService] Worker does not support retry checkpoints and the delay exceeds the threshold", - { runId: run.id } - ); - await retryViaQueue(); - return; - } - - if (executionRetryInferred) { - logger.debug("[CompleteAttemptService] Execution retry inferred, forcing retry via queue", { - runId: run.id, - }); - await retryViaQueue(); - return; - } - - // The worker is still running and waiting for a retry message - await retryDirectly(); - } - - async #retryAttempt({ - execution, - executionRetry, - executionRetryInferred, - taskRunAttempt, - environment, - checkpoint, - forceRequeue = false, - oomMachine, - }: { - execution: V3TaskRunExecution; - executionRetry: TaskRunExecutionRetry; - executionRetryInferred: boolean; - taskRunAttempt: NonNullable; - environment: AuthenticatedEnvironment; - checkpoint?: CheckpointData; - forceRequeue?: boolean; - /** Setting this will also alter the retry span message */ - oomMachine?: MachinePresetName; - }) { - const retryAt = new Date(executionRetry.timestamp); - - const eventRepository = await getEventRepositoryForStore( - taskRunAttempt.taskRun.taskEventStore, - taskRunAttempt.taskRun.organizationId ?? "" - ); - - // Retry the task run - await eventRepository.recordEvent( - `Retry #${execution.attempt.number} delay${oomMachine ? " after OOM" : ""}`, - { - taskSlug: taskRunAttempt.taskRun.taskIdentifier, - environment, - attributes: { - metadata: this.#generateMetadataAttributesForNextAttempt(execution), - properties: { - retryAt: retryAt.toISOString(), - previousMachine: oomMachine - ? (taskRunAttempt.taskRun.machinePreset ?? undefined) - : undefined, - nextMachine: oomMachine, - }, - runId: taskRunAttempt.taskRun.friendlyId, - style: { - icon: "schedule-attempt", - }, - }, - context: taskRunAttempt.taskRun.traceContext as Record, - spanIdSeed: `retry-${taskRunAttempt.number + 1}`, - endTime: retryAt, - } - ); - - logger.debug("[CompleteAttemptService] Retrying", { - taskRun: taskRunAttempt.taskRun.friendlyId, - retry: executionRetry, - }); - - await this._prisma.taskRun.update({ - where: { - id: taskRunAttempt.taskRunId, - }, - data: { - status: "RETRYING_AFTER_FAILURE", - }, - }); - - if (environment.type === "DEVELOPMENT") { - await marqs.requeueMessage(taskRunAttempt.taskRunId, {}, executionRetry.timestamp, "retry"); - - return "RETRIED"; - } - - if (checkpoint) { - // This is only here for backwards compat - we don't checkpoint between attempts anymore - return await this.#retryAttemptWithCheckpoint({ - execution, - taskRunAttempt, - executionRetry, - executionRetryInferred, - checkpoint, - }); - } - - await this.#enqueueReattempt({ - run: taskRunAttempt.taskRun, - executionRetry, - supportsLazyAttempts: taskRunAttempt.backgroundWorker.supportsLazyAttempts, - executionRetryInferred, - forceRequeue, - }); - - return "RETRIED"; - } - - async #retryAttemptWithCheckpoint({ - execution, - taskRunAttempt, - executionRetry, - executionRetryInferred, - checkpoint, - }: { - execution: V3TaskRunExecution; - taskRunAttempt: NonNullable; - executionRetry: TaskRunExecutionRetry; - executionRetryInferred: boolean; - checkpoint: CheckpointData; - }) { - const createCheckpoint = new CreateCheckpointService(this._prisma); - const checkpointCreateResult = await createCheckpoint.call({ - attemptFriendlyId: execution.attempt.id, - docker: checkpoint.docker, - location: checkpoint.location, - reason: { - type: "RETRYING_AFTER_FAILURE", - attemptNumber: execution.attempt.number, - }, - }); - - if (!checkpointCreateResult.success) { - logger.error("[CompleteAttemptService] Failed to create reattempt checkpoint", { - checkpoint, - runId: execution.run.id, - attemptId: execution.attempt.id, - }); - - const finalizeService = new FinalizeTaskRunService(); - await finalizeService.call({ - id: taskRunAttempt.taskRunId, - status: "SYSTEM_FAILURE", - completedAt: new Date(), - error: { - type: "STRING_ERROR", - raw: "Failed to create reattempt checkpoint", - }, - }); - - return "COMPLETED" as const; - } - - await this.#enqueueReattempt({ - run: taskRunAttempt.taskRun, - executionRetry, - checkpointEventId: checkpointCreateResult.event.id, - supportsLazyAttempts: taskRunAttempt.backgroundWorker.supportsLazyAttempts, - executionRetryInferred, - }); - - return "RETRIED" as const; - } - - #generateMetadataAttributesForNextAttempt(execution: TaskRunExecution) { - const context = TaskRunContext.parse(execution); - - // @ts-ignore - context.attempt = { - number: context.attempt.number + 1, - }; - - return flattenAttributes(context, "ctx"); - } - - async #getEnvironment(id: string) { - return await this._prisma.runtimeEnvironment.findFirstOrThrow({ - where: { - id, - }, - include: { - project: true, - organization: true, - }, - }); - } -} - -async function findAttempt(prismaClient: PrismaClientOrTransaction, friendlyId: string) { - return prismaClient.taskRunAttempt.findFirst({ - where: { friendlyId }, - include: { - taskRun: true, - backgroundWorkerTask: true, - backgroundWorker: { - select: { - id: true, - supportsLazyAttempts: true, - sdkVersion: true, - }, - }, - }, - }); -} - -function exitRun(runId: string) { - socketIo.coordinatorNamespace.emit("REQUEST_RUN_CANCELLATION", { - version: "v1", - runId, - }); -} diff --git a/apps/webapp/app/v3/services/crashTaskRun.server.ts b/apps/webapp/app/v3/services/crashTaskRun.server.ts deleted file mode 100644 index 94f38e534b3..00000000000 --- a/apps/webapp/app/v3/services/crashTaskRun.server.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { tryCatch } from "@trigger.dev/core/utils"; -import type { TaskRunInternalError } from "@trigger.dev/core/v3"; -import { sanitizeError, TaskRunErrorCodes } from "@trigger.dev/core/v3"; -import type { TaskRun, TaskRunAttempt } from "@trigger.dev/database"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { FailedTaskRunRetryHelper } from "../failedTaskRun.server"; -import { CRASHABLE_ATTEMPT_STATUSES, isCrashableRunStatus } from "../taskStatus"; -import { BaseService } from "./baseService.server"; -import { FinalizeTaskRunService } from "./finalizeTaskRun.server"; -import { getEventRepositoryForStore } from "../eventRepository/index.server"; - -export type CrashTaskRunServiceOptions = { - reason?: string; - exitCode?: number; - logs?: string; - crashAttempts?: boolean; - crashedAt?: Date; - overrideCompletion?: boolean; - errorCode?: TaskRunInternalError["code"]; -}; - -export class CrashTaskRunService extends BaseService { - public async call(runId: string, options?: CrashTaskRunServiceOptions) { - const opts = { - reason: "Worker crashed", - crashAttempts: true, - crashedAt: new Date(), - ...options, - }; - - logger.debug("CrashTaskRunService.call", { runId, opts }); - - if (options?.overrideCompletion) { - logger.error("CrashTaskRunService.call: overrideCompletion is deprecated", { runId }); - return; - } - - const taskRun = await this.runStore.findRun({ id: runId }, this._prisma); - - if (!taskRun) { - logger.error("[CrashTaskRunService] Task run not found", { runId }); - return; - } - - // Make sure the task run is in a crashable state - if (!opts.overrideCompletion && !isCrashableRunStatus(taskRun.status)) { - logger.error("[CrashTaskRunService] Task run is not in a crashable state", { - runId, - status: taskRun.status, - }); - return; - } - - logger.debug("[CrashTaskRunService] Completing attempt", { runId, options }); - - const retryHelper = new FailedTaskRunRetryHelper(this._prisma); - const retryResult = await retryHelper.call({ - runId, - completion: { - ok: false, - id: runId, - error: { - type: "INTERNAL_ERROR", - code: opts.errorCode ?? TaskRunErrorCodes.TASK_RUN_CRASHED, - message: opts.reason, - stackTrace: opts.logs, - }, - }, - isCrash: true, - }); - - logger.debug("[CrashTaskRunService] Completion result", { runId, retryResult }); - - if (retryResult === "RETRIED") { - logger.debug("[CrashTaskRunService] Retried task run", { runId }); - return; - } - - if (!opts.overrideCompletion) { - return; - } - - logger.debug("[CrashTaskRunService] Overriding completion", { runId, options }); - - const finalizeService = new FinalizeTaskRunService(); - const crashedTaskRun = await finalizeService.call({ - id: taskRun.id, - status: "CRASHED", - completedAt: new Date(), - include: { - attempts: { - where: { - status: { - in: CRASHABLE_ATTEMPT_STATUSES, - }, - }, - include: { - backgroundWorker: true, - runtimeEnvironment: true, - }, - }, - dependency: true, - runtimeEnvironment: { - include: { - organization: true, - project: true, - }, - }, - }, - attemptStatus: "FAILED", - error: { - type: "INTERNAL_ERROR", - code: opts.errorCode ?? TaskRunErrorCodes.TASK_RUN_CRASHED, - message: opts.reason, - stackTrace: opts.logs, - }, - }); - - const eventRepository = await getEventRepositoryForStore( - crashedTaskRun.taskEventStore, - crashedTaskRun.runtimeEnvironment.organizationId - ); - - const [createAttemptFailedEventError] = await tryCatch( - eventRepository.completeFailedRunEvent({ - run: crashedTaskRun, - endTime: opts.crashedAt, - exception: { - type: opts.errorCode ?? TaskRunErrorCodes.TASK_RUN_CRASHED, - message: opts.reason, - stacktrace: opts.logs, - }, - }) - ); - - if (createAttemptFailedEventError) { - logger.error("[CrashTaskRunService] Failed to complete failed run event", { - error: createAttemptFailedEventError, - runId: crashedTaskRun.id, - }); - } - - if (!opts.crashAttempts) { - return; - } - - // Cancel any in progress attempts - for (const attempt of crashedTaskRun.attempts) { - await this.#failAttempt( - attempt, - crashedTaskRun, - new Date(), - crashedTaskRun.runtimeEnvironment, - { - reason: opts.reason, - logs: opts.logs, - code: opts.errorCode, - } - ); - } - } - - async #failAttempt( - attempt: TaskRunAttempt, - run: TaskRun, - failedAt: Date, - environment: AuthenticatedEnvironment, - error: { - reason: string; - logs?: string; - code?: TaskRunInternalError["code"]; - } - ) { - return await this.traceWithEnv( - "[CrashTaskRunService] failAttempt()", - environment, - async (span) => { - span.setAttribute("taskRunId", run.id); - span.setAttribute("attemptId", attempt.id); - - await this._prisma.taskRunAttempt.update({ - where: { - id: attempt.id, - }, - data: { - status: "FAILED", - completedAt: failedAt, - error: sanitizeError({ - type: "INTERNAL_ERROR", - code: error.code ?? TaskRunErrorCodes.TASK_RUN_CRASHED, - message: error.reason, - stackTrace: error.logs, - }), - }, - }); - } - ); - } -} diff --git a/apps/webapp/app/v3/services/createCheckpoint.server.ts b/apps/webapp/app/v3/services/createCheckpoint.server.ts deleted file mode 100644 index f7359cb1d51..00000000000 --- a/apps/webapp/app/v3/services/createCheckpoint.server.ts +++ /dev/null @@ -1,444 +0,0 @@ -import type { CoordinatorToPlatformMessages, ManualCheckpointMetadata } from "@trigger.dev/core/v3"; -import type { InferSocketMessageSchema } from "@trigger.dev/core/v3/zodSocket"; -import type { Checkpoint, CheckpointRestoreEvent } from "@trigger.dev/database"; -import { logger } from "~/services/logger.server"; -import { marqs } from "~/v3/marqs/index.server"; -import { isFreezableAttemptStatus, isFreezableRunStatus } from "../taskStatus"; -import { BaseService } from "./baseService.server"; -import { CreateCheckpointRestoreEventService } from "./createCheckpointRestoreEvent.server"; -import { ResumeBatchRunService } from "./resumeBatchRun.server"; -import { ResumeDependentParentsService } from "./resumeDependentParents.server"; -import { CheckpointId } from "@trigger.dev/core/v3/isomorphic"; - -export class CreateCheckpointService extends BaseService { - public async call( - params: Omit< - InferSocketMessageSchema, - "version" - > - ): Promise< - | { - success: true; - checkpoint: Checkpoint; - event: CheckpointRestoreEvent; - keepRunAlive: boolean; - } - | { - success: false; - keepRunAlive?: boolean; - } - > { - logger.debug(`Creating checkpoint`, params); - - const attempt = await this._prisma.taskRunAttempt.findFirst({ - where: { - friendlyId: params.attemptFriendlyId, - }, - include: { - taskRun: true, - backgroundWorker: { - select: { - id: true, - deployment: { - select: { - imageReference: true, - }, - }, - }, - }, - }, - }); - - if (!attempt) { - logger.error("Attempt not found", params); - - return { - success: false, - }; - } - - if ( - !isFreezableAttemptStatus(attempt.status) || - !isFreezableRunStatus(attempt.taskRun.status) - ) { - logger.error("Unfreezable state", { - attempt: { - id: attempt.id, - status: attempt.status, - }, - run: { - id: attempt.taskRunId, - status: attempt.taskRun.status, - }, - params, - }); - - return { - success: false, - keepRunAlive: true, - }; - } - - const imageRef = attempt.backgroundWorker.deployment?.imageReference; - - if (!imageRef) { - logger.error("Missing deployment or image ref", { - attemptId: attempt.id, - workerId: attempt.backgroundWorker.id, - params, - }); - - return { - success: false, - }; - } - - const { reason } = params; - - // Check if we should accept this checkpoint - switch (reason.type) { - case "MANUAL": { - // Always accept manual checkpoints - break; - } - case "WAIT_FOR_DURATION": { - // Always accept duration checkpoints - break; - } - case "WAIT_FOR_TASK": { - const childRun = await this._prisma.taskRun.findFirst({ - where: { - friendlyId: reason.friendlyId, - }, - select: { - dependency: { - select: { - resumedAt: true, - }, - }, - }, - }); - - if (!childRun) { - logger.error("CreateCheckpointService: Pre-check - WAIT_FOR_TASK child run not found", { - friendlyId: reason.friendlyId, - params, - }); - - return { - success: false, - keepRunAlive: false, - }; - } - - if (childRun.dependency?.resumedAt) { - logger.info("CreateCheckpointService: Child run already resumed", { - childRun, - params, - }); - - return { - success: false, - keepRunAlive: true, - }; - } - - break; - } - case "WAIT_FOR_BATCH": { - // Routed by friendlyId so a run-ops id (NEW-resident) batch is found on the owning DB; - // env-scoped to the dependent attempt's run (a batch shares its dependent's env). Read the - // primary: a batch that just resumed the parent may lag the replica, and a stale resumedAt - // (null) would checkpoint (suspend) an already-resumed run -> it stalls until a sweep. - const batchRun = await this.runStore.findBatchTaskRunByFriendlyId( - reason.batchFriendlyId, - attempt.taskRun.runtimeEnvironmentId, - undefined, - this._prisma - ); - - if (!batchRun) { - logger.error("CreateCheckpointService: Pre-check - Batch not found", { - batchFriendlyId: reason.batchFriendlyId, - params, - }); - - return { - success: false, - keepRunAlive: false, - }; - } - - if (batchRun.resumedAt) { - logger.info("CreateCheckpointService: Batch already resumed", { - batchRun, - params, - }); - - return { - success: false, - keepRunAlive: true, - }; - } - - break; - } - default: { - break; - } - } - - //sleep to test slow checkpoints - // Sleep a random value between 4 and 30 seconds - // await new Promise((resolve) => { - // const waitSeconds = Math.floor(Math.random() * 26) + 4; - // logger.log(`Sleep for ${waitSeconds} seconds`); - // setTimeout(resolve, waitSeconds * 1000); - // }); - - let metadata: string; - - if (params.reason.type === "MANUAL") { - metadata = JSON.stringify({ - ...params.reason, - attemptId: attempt.id, - previousAttemptStatus: attempt.status, - previousRunStatus: attempt.taskRun.status, - } satisfies ManualCheckpointMetadata); - } else { - metadata = JSON.stringify(params.reason); - } - - const checkpoint = await this._prisma.checkpoint.create({ - data: { - ...CheckpointId.generate(), - runtimeEnvironmentId: attempt.taskRun.runtimeEnvironmentId, - projectId: attempt.taskRun.projectId, - attemptId: attempt.id, - attemptNumber: attempt.number, - runId: attempt.taskRunId, - location: params.location, - type: params.docker ? "DOCKER" : "KUBERNETES", - reason: params.reason.type, - metadata, - imageRef, - }, - }); - - const eventService = new CreateCheckpointRestoreEventService(this._prisma); - - await this._prisma.taskRunAttempt.update({ - where: { - id: attempt.id, - }, - data: { - status: params.reason.type === "RETRYING_AFTER_FAILURE" ? undefined : "PAUSED", - taskRun: { - update: { - status: "WAITING_TO_RESUME", - }, - }, - }, - }); - - let checkpointEvent: CheckpointRestoreEvent | undefined; - - switch (reason.type) { - case "MANUAL": - case "WAIT_FOR_DURATION": { - let restoreAtUnixTimeMs: number; - - if (reason.type === "MANUAL") { - // Restore immediately if not specified, useful for live migration - restoreAtUnixTimeMs = reason.restoreAtUnixTimeMs ?? Date.now(); - } else { - restoreAtUnixTimeMs = reason.now + reason.ms; - } - - checkpointEvent = await eventService.checkpoint({ - checkpointId: checkpoint.id, - }); - - if (checkpointEvent) { - await marqs.requeueMessage( - attempt.taskRunId, - { - type: "RESUME_AFTER_DURATION", - resumableAttemptId: attempt.id, - checkpointEventId: checkpointEvent.id, - }, - restoreAtUnixTimeMs, - "resume" - ); - - return { - success: true, - checkpoint, - event: checkpointEvent, - keepRunAlive: false, - }; - } - - break; - } - case "WAIT_FOR_TASK": { - checkpointEvent = await eventService.checkpoint({ - checkpointId: checkpoint.id, - dependencyFriendlyRunId: reason.friendlyId, - }); - - if (checkpointEvent) { - //heartbeats will start again when the run resumes - logger.log("CreateCheckpointService: Canceling heartbeat", { - attemptId: attempt.id, - taskRunId: attempt.taskRunId, - type: "WAIT_FOR_TASK", - reason, - params, - }); - await marqs?.cancelHeartbeat(attempt.taskRunId); - - const childRun = await this._prisma.taskRun.findFirst({ - where: { - friendlyId: reason.friendlyId, - }, - }); - - if (!childRun) { - logger.error("CreateCheckpointService: WAIT_FOR_TASK child run not found", { - friendlyId: reason.friendlyId, - params, - }); - - return { - success: true, - checkpoint, - event: checkpointEvent, - keepRunAlive: false, - }; - } - - const resumeService = new ResumeDependentParentsService(this._prisma); - const result = await resumeService.call({ id: childRun.id }); - - if (result.success) { - logger.log("CreateCheckpointService: Resumed dependent parents", { - result, - childRun, - attempt, - checkpointEvent, - params, - }); - } else { - logger.error("CreateCheckpointService: Failed to resume dependent parents", { - result, - childRun, - attempt, - checkpointEvent, - params, - }); - } - - return { - success: true, - checkpoint, - event: checkpointEvent, - keepRunAlive: false, - }; - } - - break; - } - case "WAIT_FOR_BATCH": { - checkpointEvent = await eventService.checkpoint({ - checkpointId: checkpoint.id, - batchDependencyFriendlyId: reason.batchFriendlyId, - }); - - if (checkpointEvent) { - //heartbeats will start again when the run resumes - logger.log("CreateCheckpointService: Canceling heartbeat", { - attemptId: attempt.id, - taskRunId: attempt.taskRunId, - type: "WAIT_FOR_BATCH", - params, - }); - await marqs?.cancelHeartbeat(attempt.taskRunId); - - // Routed by friendlyId; read the primary (this._prisma) so a just-resumed batch that still - // lags the replica doesn't leave a stale resumedAt and suspend an already-resumed run. - const batchRun = await this.runStore.findBatchTaskRunByFriendlyId( - reason.batchFriendlyId, - attempt.taskRun.runtimeEnvironmentId, - undefined, - this._prisma - ); - - if (!batchRun) { - logger.error("CreateCheckpointService: Batch not found", { - friendlyId: reason.batchFriendlyId, - params, - }); - - return { - success: true, - checkpoint, - event: checkpointEvent, - keepRunAlive: false, - }; - } - - //if there's a message in the queue, we make sure the checkpoint event is on it - await marqs.replaceMessage(attempt.taskRun.id, { - checkpointEventId: checkpointEvent.id, - }); - - await ResumeBatchRunService.enqueue(batchRun.id, batchRun.batchVersion === "v3"); - - return { - success: true, - checkpoint, - event: checkpointEvent, - keepRunAlive: false, - }; - } - - break; - } - case "RETRYING_AFTER_FAILURE": { - checkpointEvent = await eventService.checkpoint({ - checkpointId: checkpoint.id, - }); - - // ACK is already handled by attempt completion - break; - } - default: { - break; - } - } - - if (!checkpointEvent) { - logger.error("No checkpoint event", { - attemptId: attempt.id, - checkpointId: checkpoint.id, - params, - }); - await marqs?.acknowledgeMessage( - attempt.taskRunId, - "No checkpoint event in CreateCheckpointService" - ); - - return { - success: false, - }; - } - - return { - success: true, - checkpoint, - event: checkpointEvent, - keepRunAlive: false, - }; - } -} diff --git a/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV3.server.ts b/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV3.server.ts deleted file mode 100644 index 17b65efd2f3..00000000000 --- a/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV3.server.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { CreateBackgroundWorkerRequestBody } from "@trigger.dev/core/v3"; -import { tryCatch } from "@trigger.dev/core/v3"; -import type { BackgroundWorker, PrismaClientOrTransaction } from "@trigger.dev/database"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { syncTaskIdentifiers } from "~/services/taskIdentifierRegistry.server"; -import { type TaskMetadataCache } from "~/services/taskMetadataCache.server"; -import { taskMetadataCacheInstance } from "~/services/taskMetadataCacheInstance.server"; -import { socketIo } from "../handleSocketIo.server"; -import { updateEnvConcurrencyLimits } from "../runQueue.server"; -import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server"; -import { BaseService } from "./baseService.server"; -import { - createWorkerResources, - stripBackgroundWorkerMetadataForStorage, - syncDeclarativeSchedules, -} from "./createBackgroundWorker.server"; -import { ExecuteTasksWaitingForDeployService } from "./executeTasksWaitingForDeploy"; -import { projectPubSub } from "./projectPubSub.server"; -import { TimeoutDeploymentService } from "./timeoutDeployment.server"; -import { CURRENT_DEPLOYMENT_LABEL, BackgroundWorkerId } from "@trigger.dev/core/v3/isomorphic"; - -/** - * This service was only used before the new build system was introduced in v3. - * It's now replaced by the CreateDeploymentBackgroundWorkerServiceV4. - * - * @deprecated - */ -export class CreateDeploymentBackgroundWorkerServiceV3 extends BaseService { - private readonly _taskMetaCache: TaskMetadataCache; - - constructor( - prisma?: PrismaClientOrTransaction, - replica?: PrismaClientOrTransaction, - taskMetaCache: TaskMetadataCache = taskMetadataCacheInstance - ) { - super(prisma, replica); - this._taskMetaCache = taskMetaCache; - } - - public async call( - projectRef: string, - environment: AuthenticatedEnvironment, - deploymentId: string, - body: CreateBackgroundWorkerRequestBody - ): Promise { - return this.traceWithEnv("call", environment, async (span) => { - span.setAttribute("projectRef", projectRef); - - const deployment = await this._prisma.workerDeployment.findFirst({ - where: { - friendlyId: deploymentId, - }, - }); - - if (!deployment) { - return; - } - - if (deployment.status !== "DEPLOYING") { - return; - } - - const backgroundWorker = await this._prisma.backgroundWorker.create({ - data: { - ...BackgroundWorkerId.generate(), - version: deployment.version, - runtimeEnvironmentId: environment.id, - projectId: environment.projectId, - metadata: stripBackgroundWorkerMetadataForStorage(body.metadata), - contentHash: body.metadata.contentHash, - cliVersion: body.metadata.cliPackageVersion, - sdkVersion: body.metadata.packageVersion, - supportsLazyAttempts: body.supportsLazyAttempts, - engine: body.engine, - }, - }); - - //upgrade the project to engine "V2" if it's not already - if (environment.project.engine === "V1" && body.engine === "V2") { - await this._prisma.project.update({ - where: { - id: environment.project.id, - }, - data: { - engine: "V2", - }, - }); - } - - let workerTaskEntries: Awaited> = []; - try { - workerTaskEntries = await createWorkerResources( - body.metadata, - backgroundWorker, - environment, - this._prisma - ); - await syncDeclarativeSchedules( - body.metadata.tasks, - backgroundWorker, - environment, - this._prisma - ); - } catch (error) { - const name = error instanceof Error ? error.name : "UnknownError"; - const message = error instanceof Error ? error.message : JSON.stringify(error); - - await this._prisma.workerDeployment.update({ - where: { - id: deployment.id, - }, - data: { - status: "FAILED", - failedAt: new Date(), - errorData: { - name, - message, - }, - }, - }); - - throw error; - } - - // Link the deployment with the background worker - await this._prisma.workerDeployment.update({ - where: { - id: deployment.id, - }, - data: { - status: "DEPLOYED", - workerId: backgroundWorker.id, - deployedAt: new Date(), - type: backgroundWorker.engine === "V2" ? "MANAGED" : "V1", - }, - }); - - //set this deployment as the current deployment for this environment - await this._prisma.workerDeploymentPromotion.upsert({ - where: { - environmentId_label: { - environmentId: environment.id, - label: CURRENT_DEPLOYMENT_LABEL, - }, - }, - create: { - deploymentId: deployment.id, - environmentId: environment.id, - label: CURRENT_DEPLOYMENT_LABEL, - }, - update: { - deploymentId: deployment.id, - }, - }); - - const [syncIdError] = await tryCatch( - syncTaskIdentifiers( - environment.id, - environment.projectId, - backgroundWorker.id, - body.metadata.tasks.map((t) => ({ id: t.id, triggerSource: t.triggerSource })) - ) - ); - - if (syncIdError) { - logger.error("Error syncing task identifiers", { error: syncIdError }); - } - - // V3 promotes the deployment immediately above, so this worker is now - // current for the env — write both keyspaces atomically. Cache calls - // log+swallow internally. Empty `workerTaskEntries` is intentional: the - // populate methods clear stale hashes for zero-task deploys. - await this._taskMetaCache.populateByCurrentWorker( - environment.id, - backgroundWorker.id, - workerTaskEntries - ); - - try { - //send a notification that a new worker has been created - await projectPubSub.publish( - `project:${environment.projectId}:env:${environment.id}`, - "WORKER_CREATED", - { - environmentId: environment.id, - environmentType: environment.type, - createdAt: backgroundWorker.createdAt, - taskCount: body.metadata.tasks.length, - type: "deployed", - } - ); - await updateEnvConcurrencyLimits(environment); - } catch (err) { - logger.error("Failed to publish WORKER_CREATED event", { err }); - } - - if (deployment.imageReference) { - socketIo.providerNamespace.emit("PRE_PULL_DEPLOYMENT", { - version: "v1", - imageRef: deployment.imageReference, - shortCode: deployment.shortCode, - // identifiers - deploymentId: deployment.id, - envId: environment.id, - envType: environment.type, - orgId: environment.organizationId, - projectId: deployment.projectId, - }); - } - - await ExecuteTasksWaitingForDeployService.enqueue(backgroundWorker.id); - await PerformDeploymentAlertsService.enqueue(deployment.id); - await TimeoutDeploymentService.dequeue(deployment.id, this._prisma); - - return backgroundWorker; - }); - } -} diff --git a/apps/webapp/app/v3/services/createTaskRunAttempt.server.ts b/apps/webapp/app/v3/services/createTaskRunAttempt.server.ts deleted file mode 100644 index 094e75c9a11..00000000000 --- a/apps/webapp/app/v3/services/createTaskRunAttempt.server.ts +++ /dev/null @@ -1,278 +0,0 @@ -import type { V3TaskRunExecution } from "@trigger.dev/core/v3"; -import { parsePacket } from "@trigger.dev/core/v3"; -import type { TaskRun, TaskRunAttempt } from "@trigger.dev/database"; -import { MAX_TASK_RUN_ATTEMPTS } from "~/consts"; -import type { PrismaClientOrTransaction } from "~/db.server"; -import { $transaction, prisma } from "~/db.server"; -import { findQueueInEnvironment } from "~/models/taskQueue.server"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { reportInvocationUsage } from "~/services/platform.v3.server"; -import { generateFriendlyId } from "../friendlyIdentifiers"; -import { machinePresetFromConfig, machinePresetFromRun } from "../machinePresets.server"; -import { FINAL_RUN_STATUSES } from "../taskStatus"; -import { BaseService, ServiceValidationError } from "./baseService.server"; -import { CrashTaskRunService } from "./crashTaskRun.server"; -import { ExpireEnqueuedRunService } from "./expireEnqueuedRun.server"; -import { runStore } from "../runStore.server"; -import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server"; - -export class CreateTaskRunAttemptService extends BaseService { - public async call({ - runId, - authenticatedEnv, - setToExecuting = true, - startAtZero = false, - }: { - runId: string; - authenticatedEnv?: AuthenticatedEnvironment; - setToExecuting?: boolean; - startAtZero?: boolean; - }): Promise<{ - execution: V3TaskRunExecution; - run: TaskRun; - attempt: TaskRunAttempt; - }> { - const environment = - authenticatedEnv ?? (await getAuthenticatedEnvironmentFromRun(runId, this._prisma)); - - if (!environment) { - throw new ServiceValidationError("Environment not found", 404); - } - - const isFriendlyId = runId.startsWith("run_"); - - return await this.traceWithEnv("call()", environment, async (span) => { - if (isFriendlyId) { - span.setAttribute("taskRunFriendlyId", runId); - } else { - span.setAttribute("taskRunId", runId); - } - - const taskRun = await this.runStore.findRun( - { - id: !isFriendlyId ? runId : undefined, - friendlyId: isFriendlyId ? runId : undefined, - runtimeEnvironmentId: environment.id, - }, - { - include: { - attempts: { - take: 1, - orderBy: { - number: "desc", - }, - }, - batchItems: { - include: { - batchTaskRun: { - select: { - friendlyId: true, - }, - }, - }, - }, - }, - }, - this._prisma - ); - - logger.debug("Creating a task run attempt", { taskRun }); - - if (!taskRun) { - throw new ServiceValidationError("Task run not found", 404); - } - - span.setAttribute("taskRunId", taskRun.id); - span.setAttribute("taskRunFriendlyId", taskRun.friendlyId); - span.setAttribute("taskRunStatus", taskRun.status); - - if (taskRun.status === "CANCELED") { - throw new ServiceValidationError("Task run is cancelled", 400); - } - - // If the run is finalized, it's pointless to create another attempt - if (FINAL_RUN_STATUSES.includes(taskRun.status)) { - throw new ServiceValidationError("Task run is already finished", 400); - } - - const lockedWorker = await controlPlaneResolver.resolveRunLockedWorker({ - lockedById: taskRun.lockedById, - }); - const lockedBy = lockedWorker?.lockedBy; - - if (!lockedBy) { - throw new ServiceValidationError("Task run is not locked", 400); - } - - const queue = await findQueueInEnvironment(taskRun.queue, environment.id, lockedBy.id); - - if (!queue) { - throw new ServiceValidationError("Queue not found", 404); - } - - const nextAttemptNumber = taskRun.attempts[0] - ? taskRun.attempts[0].number + 1 - : startAtZero - ? 0 - : 1; - - if (nextAttemptNumber > MAX_TASK_RUN_ATTEMPTS) { - const service = new CrashTaskRunService(this._prisma); - await service.call(taskRun.id, { - reason: lockedBy.worker.supportsLazyAttempts - ? "Max attempts reached." - : "Max attempts reached. Please upgrade your CLI and SDK.", - }); - - throw new ServiceValidationError("Max attempts reached", 400); - } - - const taskRunAttempt = await $transaction(this._prisma, "create attempt", async (tx) => { - const taskRunAttempt = await tx.taskRunAttempt.create({ - data: { - number: nextAttemptNumber, - friendlyId: generateFriendlyId("attempt"), - taskRunId: taskRun.id, - startedAt: new Date(), - backgroundWorkerId: lockedBy.worker.id, - backgroundWorkerTaskId: lockedBy.id, - status: setToExecuting ? "EXECUTING" : "PENDING", - queueId: queue.id, - runtimeEnvironmentId: environment.id, - }, - }); - - await tx.taskRun.update({ - where: { - id: taskRun.id, - }, - data: { - status: setToExecuting ? "EXECUTING" : undefined, - executedAt: taskRun.executedAt ?? new Date(), - attemptNumber: nextAttemptNumber, - }, - }); - - if (taskRun.ttl) { - await ExpireEnqueuedRunService.ack(taskRun.id, tx); - } - - return taskRunAttempt; - }); - - if (!taskRunAttempt) { - logger.error("Failed to create task run attempt", { runId: taskRun.id, nextAttemptNumber }); - throw new ServiceValidationError("Failed to create task run attempt", 500); - } - - if (taskRunAttempt.number === 1 && taskRun.baseCostInCents > 0) { - await reportInvocationUsage(environment.organizationId, taskRun.baseCostInCents, { - runId: taskRun.id, - }); - } - - const machinePreset = - machinePresetFromRun(taskRun) ?? machinePresetFromConfig(lockedBy.machineConfig ?? {}); - - const metadata = await parsePacket({ - data: taskRun.metadata ?? undefined, - dataType: taskRun.metadataType, - }); - - const execution: V3TaskRunExecution = { - task: { - id: lockedBy.slug, - filePath: lockedBy.filePath, - exportName: lockedBy.exportName ?? "@deprecated", - }, - attempt: { - id: taskRunAttempt.friendlyId, - number: taskRunAttempt.number, - startedAt: taskRunAttempt.startedAt ?? taskRunAttempt.createdAt, - backgroundWorkerId: lockedBy.worker.id, - backgroundWorkerTaskId: lockedBy.id, - status: "EXECUTING" as const, - }, - run: { - id: taskRun.friendlyId, - payload: taskRun.payload, - payloadType: taskRun.payloadType, - context: taskRun.context, - createdAt: taskRun.createdAt, - tags: taskRun.runTags ?? [], - isTest: taskRun.isTest, - isReplay: !!taskRun.replayedFromTaskRunFriendlyId, - idempotencyKey: taskRun.idempotencyKey ?? undefined, - startedAt: taskRun.startedAt ?? taskRun.createdAt, - durationMs: taskRun.usageDurationMs, - costInCents: taskRun.costInCents, - baseCostInCents: taskRun.baseCostInCents, - maxAttempts: taskRun.maxAttempts ?? undefined, - version: lockedBy.worker.version, - metadata, - maxDuration: taskRun.maxDurationInSeconds ?? undefined, - }, - queue: { - id: queue.friendlyId, - name: queue.name, - }, - environment: { - id: environment.id, - slug: environment.slug, - type: environment.type, - }, - organization: { - id: environment.organization.id, - slug: environment.organization.slug, - name: environment.organization.title, - }, - project: { - id: environment.project.id, - ref: environment.project.externalRef, - slug: environment.project.slug, - name: environment.project.name, - }, - batch: - taskRun.batchItems[0] && taskRun.batchItems[0].batchTaskRun - ? { id: taskRun.batchItems[0].batchTaskRun.friendlyId } - : undefined, - machine: machinePreset, - }; - - return { - execution, - run: taskRun, - attempt: taskRunAttempt, - }; - }); - } -} - -async function getAuthenticatedEnvironmentFromRun( - friendlyId: string, - prismaClient?: PrismaClientOrTransaction -) { - const isFriendlyId = friendlyId.startsWith("run_"); - - const taskRun = await runStore.findRun( - { - id: !isFriendlyId ? friendlyId : undefined, - friendlyId: isFriendlyId ? friendlyId : undefined, - }, - { - select: { - runtimeEnvironmentId: true, - }, - }, - prismaClient ?? prisma - ); - - if (!taskRun) { - return; - } - - return ( - (await controlPlaneResolver.resolveAuthenticatedEnv(taskRun.runtimeEnvironmentId)) ?? undefined - ); -} diff --git a/apps/webapp/app/v3/services/deploymentIndexFailed.server.ts b/apps/webapp/app/v3/services/deploymentIndexFailed.server.ts deleted file mode 100644 index e8c0acdb909..00000000000 --- a/apps/webapp/app/v3/services/deploymentIndexFailed.server.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server"; -import { BaseService } from "./baseService.server"; -import { logger } from "~/services/logger.server"; -import { type WorkerDeploymentStatus } from "@trigger.dev/database"; -import { DeploymentService } from "./deployment.server"; -import { recordDeploymentOutcome } from "./recordDeploymentOutcome.server"; - -const FINAL_DEPLOYMENT_STATUSES: WorkerDeploymentStatus[] = [ - "CANCELED", - "DEPLOYED", - "FAILED", - "TIMED_OUT", -]; - -export class DeploymentIndexFailed extends BaseService { - public async call( - maybeFriendlyId: string, - error: { - name: string; - message: string; - stack?: string; - stderr?: string; - }, - overrideCompletion = false - ) { - const isFriendlyId = maybeFriendlyId.startsWith("deployment_"); - - const deployment = await this._prisma.workerDeployment.findFirst({ - where: isFriendlyId - ? { - friendlyId: maybeFriendlyId, - } - : { - id: maybeFriendlyId, - }, - include: { - environment: { - include: { - project: true, - }, - }, - }, - }); - - if (!deployment) { - logger.error("Worker deployment not found", { maybeFriendlyId }); - return; - } - - if (FINAL_DEPLOYMENT_STATUSES.includes(deployment.status)) { - if (overrideCompletion) { - logger.error("No support for overriding final deployment statuses just yet", { - id: deployment.id, - status: deployment.status, - previousError: deployment.errorData, - incomingError: error, - }); - } - - logger.error("Worker deployment already in final state", { - id: deployment.id, - status: deployment.status, - }); - return; - } - - const failedDeployment = await this._prisma.workerDeployment.update({ - where: { - id: deployment.id, - }, - data: { - status: "FAILED", - failedAt: new Date(), - errorData: error, - }, - }); - - recordDeploymentOutcome({ - status: "FAILED", - deploymentFriendlyId: deployment.friendlyId, - organizationId: deployment.environment.project.organizationId, - projectId: deployment.environment.projectId, - environmentId: deployment.environmentId, - environmentType: deployment.environment.type, - reason: error.message, - }); - - const deploymentService = new DeploymentService(); - await deploymentService - .appendToEventLog(deployment.environment.project, failedDeployment, [ - { - type: "finalized", - data: { - result: "failed", - message: error.message, - }, - }, - ]) - .orTee((error) => { - logger.error("Failed to append failed deployment event to event log", { error }); - }); - - await PerformDeploymentAlertsService.enqueue(failedDeployment.id); - - return failedDeployment; - } -} diff --git a/apps/webapp/app/v3/services/enqueueDelayedRun.server.ts b/apps/webapp/app/v3/services/enqueueDelayedRun.server.ts deleted file mode 100644 index 9b78622a057..00000000000 --- a/apps/webapp/app/v3/services/enqueueDelayedRun.server.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { parseNaturalLanguageDuration } from "@trigger.dev/core/v3/isomorphic"; -import { logger } from "~/services/logger.server"; -import { workerQueue } from "~/services/worker.server"; -import { commonWorker } from "../commonWorker.server"; -import { BaseService } from "./baseService.server"; -import { enqueueRun } from "./enqueueRun.server"; -import { ExpireEnqueuedRunService } from "./expireEnqueuedRun.server"; -import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server"; -import { isV3Disabled } from "../engineDeprecation.server"; - -export class EnqueueDelayedRunService extends BaseService { - public static async enqueue(runId: string, runAt?: Date) { - await commonWorker.enqueue({ - job: "v3.enqueueDelayedRun", - payload: { runId }, - availableAt: runAt, - id: `v3.enqueueDelayed:${runId}`, - }); - } - - public static async reschedule(runId: string, runAt?: Date) { - // We have to do this for now because it's possible that the workerQueue - // was used when the run was first delayed, and EnqueueDelayedRunService.reschedule - // is called from RescheduleTaskRunService, which allows the runAt to be changed - // so if we don't dequeue the old job, we might end up with multiple jobs - await workerQueue.dequeue(`v3.enqueueDelayedRun.${runId}`); - - await commonWorker.enqueue({ - job: "v3.enqueueDelayedRun", - payload: { runId }, - availableAt: runAt, - id: `v3.enqueueDelayed:${runId}`, - }); - } - - public async call(runId: string) { - const run = await this.runStore.findRun( - { - id: runId, - }, - { - include: { - dependency: { - include: { - dependentBatchRun: { - include: { - dependentTaskAttempt: { - include: { - taskRun: true, - }, - }, - }, - }, - dependentAttempt: { - include: { - taskRun: true, - }, - }, - }, - }, - }, - }, - this._prisma - ); - - if (!run) { - logger.debug("Could not find delayed run to enqueue", { - runId, - }); - - return; - } - - // v3 (engine V1) shutdown: don't enqueue delayed V1 runs into MarQS. v4 is unaffected. - if (isV3Disabled() && run.engine === "V1") { - logger.debug("[EnqueueDelayedRunService] Skipping enqueue for shut-down v3 run", { runId }); - return; - } - - const env = await controlPlaneResolver.resolveAuthenticatedEnv(run.runtimeEnvironmentId); - - if (!env) { - logger.debug("EnqueueDelayedRunService: environment not found", { runId }); - return; - } - - if (run.status !== "DELAYED") { - logger.debug("Delayed run cannot be enqueued because it's not in DELAYED status", { - run, - }); - - return; - } - - await this._prisma.taskRun.update({ - where: { - id: run.id, - }, - data: { - status: "PENDING", - queuedAt: new Date(), - }, - }); - - if (run.ttl) { - const expireAt = parseNaturalLanguageDuration(run.ttl); - - if (expireAt) { - await ExpireEnqueuedRunService.enqueue(run.id, expireAt); - } - } - - await enqueueRun({ - env, - run: run, - dependentRun: - run.dependency?.dependentAttempt?.taskRun ?? - run.dependency?.dependentBatchRun?.dependentTaskAttempt?.taskRun, - }); - } -} diff --git a/apps/webapp/app/v3/services/enqueueRun.server.ts b/apps/webapp/app/v3/services/enqueueRun.server.ts deleted file mode 100644 index 0cea6638e12..00000000000 --- a/apps/webapp/app/v3/services/enqueueRun.server.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { TaskRunError } from "@trigger.dev/core/v3/schemas"; -import { TaskRunErrorCodes } from "@trigger.dev/core/v3/schemas"; -import type { TaskRun } from "@trigger.dev/database"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { marqs } from "../marqs/index.server"; - -export type EnqueueRunOptions = { - env: AuthenticatedEnvironment; - run: TaskRun; - dependentRun?: { queue: string; id: string }; -}; - -export type EnqueueRunResult = - | { - ok: true; - } - | { - ok: false; - error: TaskRunError; - }; - -export async function enqueueRun({ - env, - run, - dependentRun, -}: EnqueueRunOptions): Promise { - // If this is a triggerAndWait or batchTriggerAndWait, - // we need to add the parent run to the reserve concurrency set - // to free up concurrency for the children to run - // In the case of a recursive queue, reserving concurrency can fail, which means there is a deadlock and we need to fail the run - - // TODO: reserveConcurrency can fail because of a deadlock, we need to handle that case - const wasEnqueued = await marqs.enqueueMessage( - env, - run.queue, - run.id, - { - type: "EXECUTE", - taskIdentifier: run.taskIdentifier, - projectId: env.projectId, - environmentId: env.id, - environmentType: env.type, - }, - run.concurrencyKey ?? undefined, - run.queueTimestamp ?? undefined, - dependentRun - ? { messageId: dependentRun.id, recursiveQueue: dependentRun.queue === run.queue } - : undefined - ); - - if (!wasEnqueued) { - const error = { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.RECURSIVE_WAIT_DEADLOCK, - message: `This run will never execute because it was triggered recursively and the task has no remaining concurrency available`, - } satisfies TaskRunError; - - return { - ok: false, - error, - }; - } - - return { - ok: true, - }; -} diff --git a/apps/webapp/app/v3/services/executeTasksWaitingForDeploy.ts b/apps/webapp/app/v3/services/executeTasksWaitingForDeploy.ts deleted file mode 100644 index 4a123ff777d..00000000000 --- a/apps/webapp/app/v3/services/executeTasksWaitingForDeploy.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { ownerEngine } from "@trigger.dev/core/v3/isomorphic"; -import { env } from "~/env.server"; -import { logger } from "~/services/logger.server"; -import { marqs } from "~/v3/marqs/index.server"; -import { commonWorker } from "../commonWorker.server"; -import { BaseService } from "./baseService.server"; - -export class ExecuteTasksWaitingForDeployService extends BaseService { - public async call(backgroundWorkerId: string) { - // Kill-switch for the legacy V1 WAITING_FOR_DEPLOY drain. Set to "1" to - // neuter any jobs already enqueued (V2 has its own PENDING_VERSION path). - if (env.LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_DISABLED === "1") { - return; - } - - const backgroundWorker = await this._prisma.backgroundWorker.findFirst({ - where: { - id: backgroundWorkerId, - }, - include: { - runtimeEnvironment: { - include: { - project: true, - organization: true, - }, - }, - tasks: { - select: { - slug: true, - }, - }, - }, - }); - - if (!backgroundWorker) { - logger.error("Background worker not found", { id: backgroundWorkerId }); - return; - } - - const maxCount = env.LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_SIZE; - - const runsWaitingForDeploy = await this.runStore.findRuns( - { - where: { - runtimeEnvironmentId: backgroundWorker.runtimeEnvironmentId, - projectId: backgroundWorker.projectId, - status: "WAITING_FOR_DEPLOY", - taskIdentifier: { - in: backgroundWorker.tasks.map((task) => task.slug), - }, - }, - orderBy: { - createdAt: "asc", - }, - select: { - id: true, - status: true, - taskIdentifier: true, - concurrencyKey: true, - queue: true, - updatedAt: true, - createdAt: true, - }, - take: maxCount + 1, - }, - this._replica - ); - - if (!runsWaitingForDeploy.length) { - return; - } - - // Defense-in-depth: the open-predicate findRuns fan-out can select runs from - // either DB, but the status flip below is a single control-plane updateMany. A - // run-ops id (NEW-resident) run can only reach WAITING_FOR_DEPLOY via a misconfiguration - // (it is a V1/cuid-only status — V2 uses PENDING_VERSION). Surface it loudly rather - // than silently strand the run, and only mutate the LEGACY-resident runs the - // control-plane client can actually reach. - const newResidentRuns = runsWaitingForDeploy.filter((run) => ownerEngine(run.id) === "NEW"); - if (newResidentRuns.length) { - logger.error( - "WAITING_FOR_DEPLOY selected NEW-resident runs; skipping their control-plane status flip", - { runIds: newResidentRuns.map((run) => run.id) } - ); - } - const legacyRuns = runsWaitingForDeploy.filter((run) => !newResidentRuns.includes(run)); - - const pendingRuns = await this._prisma.taskRun.updateMany({ - where: { - id: { - in: legacyRuns.map((run) => run.id), - }, - }, - data: { - status: "PENDING", - }, - }); - - if (pendingRuns.count) { - logger.debug("Task runs waiting for deploy are now ready for execution", { - tasks: legacyRuns.map((run) => run.id), - total: pendingRuns.count, - }); - } - - // Only enqueue the runs whose status was actually flipped (the legacy set) — never - // marqs-enqueue a NEW-resident run we couldn't transition out of WAITING_FOR_DEPLOY. - for (const run of legacyRuns) { - await marqs?.enqueueMessage( - backgroundWorker.runtimeEnvironment, - run.queue, - run.id, - { - type: "EXECUTE", - taskIdentifier: run.taskIdentifier, - projectId: backgroundWorker.runtimeEnvironment.projectId, - environmentId: backgroundWorker.runtimeEnvironment.id, - environmentType: backgroundWorker.runtimeEnvironment.type, - }, - run.concurrencyKey ?? undefined - ); - } - - if (runsWaitingForDeploy.length > maxCount) { - await ExecuteTasksWaitingForDeployService.enqueue( - backgroundWorkerId, - new Date(Date.now() + env.LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_STAGGER_MS) - ); - } - } - - static async enqueue(backgroundWorkerId: string, runAt?: Date) { - return await commonWorker.enqueue({ - id: `v3.executeTasksWaitingForDeploy:${backgroundWorkerId}`, - job: "v3.executeTasksWaitingForDeploy", - payload: { - backgroundWorkerId, - }, - availableAt: runAt, - }); - } -} diff --git a/apps/webapp/app/v3/services/expireEnqueuedRun.server.ts b/apps/webapp/app/v3/services/expireEnqueuedRun.server.ts deleted file mode 100644 index 06b4db57ec4..00000000000 --- a/apps/webapp/app/v3/services/expireEnqueuedRun.server.ts +++ /dev/null @@ -1,132 +0,0 @@ -import type { PrismaClientOrTransaction } from "~/db.server"; -import { logger } from "~/services/logger.server"; -import { commonWorker } from "../commonWorker.server"; -import { BaseService } from "./baseService.server"; -import { FinalizeTaskRunService } from "./finalizeTaskRun.server"; -import { tryCatch } from "@trigger.dev/core/utils"; -import { getEventRepositoryForStore } from "../eventRepository/index.server"; -import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server"; -import { isV3Disabled } from "../engineDeprecation.server"; - -export class ExpireEnqueuedRunService extends BaseService { - public static async ack(runId: string, tx?: PrismaClientOrTransaction) { - // We don't "dequeue" from the workerQueue here because it would be redundant and if this service - // is called for a run that has already started, nothing happens - await commonWorker.ack(`v3.expireRun:${runId}`); - } - - public static async enqueue(runId: string, runAt?: Date) { - return await commonWorker.enqueue({ - job: "v3.expireRun", - payload: { runId }, - availableAt: runAt, - id: `v3.expireRun:${runId}`, - }); - } - - public async call(runId: string) { - const run = await this.runStore.findRun( - { - id: runId, - }, - { - select: { - id: true, - status: true, - engine: true, - lockedAt: true, - ttl: true, - taskEventStore: true, - runtimeEnvironmentId: true, - friendlyId: true, - traceId: true, - spanId: true, - parentSpanId: true, - createdAt: true, - completedAt: true, - taskIdentifier: true, - projectId: true, - organizationId: true, - isTest: true, - }, - }, - this._prisma - ); - - if (!run) { - logger.debug("Could not find enqueued run to expire", { - runId, - }); - - return; - } - - // v3 (engine V1) shutdown: skip expiring abandoned V1 runs. v4 is unaffected. - if (isV3Disabled() && run.engine === "V1") { - logger.debug("[ExpireEnqueuedRunService] Skipping expiry for shut-down v3 run", { runId }); - return; - } - - const env = await controlPlaneResolver.resolveEnv(run.runtimeEnvironmentId); - - if (!env) { - logger.debug("ExpireEnqueuedRunService: environment not found", { runId }); - return; - } - - if (run.status !== "PENDING") { - logger.debug("Run cannot be expired because it's not in PENDING status", { - run, - }); - - return; - } - - if (run.lockedAt) { - logger.debug("Run cannot be expired because it's locked", { - run, - }); - - return; - } - - logger.debug("Expiring enqueued run", { - run, - }); - - const finalizeService = new FinalizeTaskRunService(); - await finalizeService.call({ - id: run.id, - status: "EXPIRED", - expiredAt: new Date(), - completedAt: new Date(), - attemptStatus: "FAILED", - error: { - type: "STRING_ERROR", - raw: `Run expired because the TTL (${run.ttl}) was reached`, - }, - }); - - const eventRepository = await getEventRepositoryForStore( - run.taskEventStore, - env.organizationId - ); - - if (run.ttl) { - const [completeExpiredRunEventError] = await tryCatch( - eventRepository.completeExpiredRunEvent({ - run, - endTime: new Date(), - ttl: run.ttl, - }) - ); - - if (completeExpiredRunEventError) { - logger.error("[ExpireEnqueuedRunService] Failed to complete expired run event", { - error: completeExpiredRunEventError, - runId: run.id, - }); - } - } - } -} diff --git a/apps/webapp/app/v3/services/finalizeDeployment.server.ts b/apps/webapp/app/v3/services/finalizeDeployment.server.ts index 4cd01cb3db2..98ef7a73bd5 100644 --- a/apps/webapp/app/v3/services/finalizeDeployment.server.ts +++ b/apps/webapp/app/v3/services/finalizeDeployment.server.ts @@ -1,7 +1,6 @@ import type { FinalizeDeploymentRequestBody } from "@trigger.dev/core/v3/schemas"; import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; -import { socketIo } from "../handleSocketIo.server"; import { updateEnvConcurrencyLimits } from "../runQueue.server"; import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server"; import { BaseService, ServiceValidationError } from "./baseService.server"; @@ -129,20 +128,6 @@ export class FinalizeDeploymentService extends BaseService { logger.error("Failed to publish WORKER_CREATED event", { err }); } - if (finalizedDeployment.imageReference) { - socketIo.providerNamespace.emit("PRE_PULL_DEPLOYMENT", { - version: "v1", - imageRef: finalizedDeployment.imageReference, - shortCode: finalizedDeployment.shortCode, - // identifiers - deploymentId: finalizedDeployment.id, - envId: authenticatedEnv.id, - envType: authenticatedEnv.type, - orgId: authenticatedEnv.organizationId, - projectId: finalizedDeployment.projectId, - }); - } - if (deployment.worker.engine === "V2") { const [schedulePendingVersionsError] = await tryCatch( engine.scheduleEnqueueRunsForBackgroundWorker(deployment.worker.id) diff --git a/apps/webapp/app/v3/services/finalizeTaskRun.server.ts b/apps/webapp/app/v3/services/finalizeTaskRun.server.ts deleted file mode 100644 index 1443a6b7a0d..00000000000 --- a/apps/webapp/app/v3/services/finalizeTaskRun.server.ts +++ /dev/null @@ -1,361 +0,0 @@ -import { type FlushedRunMetadata, type TaskRunError, sanitizeError } from "@trigger.dev/core/v3"; -import { type Prisma, type TaskRun } from "@trigger.dev/database"; -import { findQueueInEnvironment } from "~/models/taskQueue.server"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; -import { updateMetadataService } from "~/services/metadata/updateMetadataInstance.server"; -import { marqs } from "~/v3/marqs/index.server"; -import { generateFriendlyId } from "../friendlyIdentifiers"; -import { socketIo } from "../handleSocketIo.server"; -import { - type FINAL_ATTEMPT_STATUSES, - isFailedRunStatus, - isFatalRunStatus, - type FINAL_RUN_STATUSES, -} from "../taskStatus"; -import { PerformTaskRunAlertsService } from "./alerts/performTaskRunAlerts.server"; -import { BaseService } from "./baseService.server"; -import { completeBatchTaskRunItemV3 } from "./batchTriggerV3.server"; -import { ExpireEnqueuedRunService } from "./expireEnqueuedRun.server"; -import { ResumeBatchRunService } from "./resumeBatchRun.server"; -import { ResumeDependentParentsService } from "./resumeDependentParents.server"; -import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server"; - -type BaseInput = { - id: string; - status?: FINAL_RUN_STATUSES; - expiredAt?: Date; - completedAt?: Date; - attemptStatus?: FINAL_ATTEMPT_STATUSES; - error?: TaskRunError; - metadata?: FlushedRunMetadata; - env?: AuthenticatedEnvironment; - bulkActionId?: string; -}; - -type InputWithInclude = BaseInput & { - include: T; -}; - -type InputWithoutInclude = BaseInput & { - include?: undefined; -}; - -type Output = T extends Prisma.TaskRunInclude - ? Prisma.TaskRunGetPayload<{ include: T }> - : TaskRun; - -export class FinalizeTaskRunService extends BaseService { - public async call({ - id, - status, - expiredAt, - completedAt, - bulkActionId, - include, - attemptStatus, - error, - metadata, - env, - }: T extends Prisma.TaskRunInclude ? InputWithInclude : InputWithoutInclude): Promise< - Output - > { - logger.debug("Finalizing run marqs ack", { - id, - status, - expiredAt, - completedAt, - }); - await marqs?.acknowledgeMessage(id, "FinalTaskRunService call"); - - logger.debug("Finalizing run updating run status", { - id, - status, - expiredAt, - completedAt, - }); - - if (metadata) { - try { - await updateMetadataService.call(id, metadata, env); - } catch (e) { - logger.error("[FinalizeTaskRunService] Failed to update metadata", { - taskRun: id, - error: - e instanceof Error - ? { - name: e.name, - message: e.message, - stack: e.stack, - } - : e, - }); - } - } - - // Error is written in the same update as the status: a separate later write races realtime, - // which shuts the stream down on the final status before the error lands, losing it. - const taskRunError = error ? sanitizeError(error) : undefined; - - const run = await this._prisma.taskRun.update({ - where: { id }, - data: { - status, - expiredAt, - completedAt, - error: taskRunError, - bulkActionGroupIds: bulkActionId - ? { - push: bulkActionId, - } - : undefined, - }, - ...(include ? { include } : {}), - }); - - if (run.ttl) { - await ExpireEnqueuedRunService.ack(run.id); - } - - if (attemptStatus || error) { - await this.finalizeAttempt({ attemptStatus, error, run }); - } - - try { - await this.#finalizeBatch(run); - } catch (finalizeBatchError) { - logger.error("FinalizeTaskRunService: Failed to finalize batch", { - runId: run.id, - error: finalizeBatchError, - }); - } - - const resumeService = new ResumeDependentParentsService(this._prisma); - const result = await resumeService.call({ id: run.id }); - - if (result.success) { - logger.log("FinalizeTaskRunService: Resumed dependent parents", { result, run: run.id }); - } else { - logger.error("FinalizeTaskRunService: Failed to resume dependent parents", { - result, - run: run.id, - }); - } - - if (isFailedRunStatus(run.status)) { - await PerformTaskRunAlertsService.enqueue(run.id); - } - - if (isFatalRunStatus(run.status)) { - logger.warn("FinalizeTaskRunService: Fatal status", { runId: run.id, status: run.status }); - - const extendedRun = await this.runStore.findRun( - { id: run.id }, - { - select: { - id: true, - runtimeEnvironmentId: true, - lockedToVersionId: true, - }, - }, - this._prisma - ); - - const extendedEnv = extendedRun - ? await controlPlaneResolver.resolveEnv(extendedRun.runtimeEnvironmentId) - : null; - const extendedLockedWorker = extendedRun - ? await controlPlaneResolver.resolveRunLockedWorker({ - lockedToVersionId: extendedRun.lockedToVersionId, - }) - : null; - - if (extendedRun && extendedEnv && extendedEnv.type !== "DEVELOPMENT") { - logger.warn("FinalizeTaskRunService: Fatal status, requesting worker exit", { - runId: run.id, - status: run.status, - }); - - // Signal to exit any leftover containers - socketIo.coordinatorNamespace.emit("REQUEST_RUN_CANCELLATION", { - version: "v1", - runId: run.id, - // Give the run a few seconds to exit to complete any flushing etc - delayInMs: extendedLockedWorker?.lockedToVersion?.supportsLazyAttempts - ? 5_000 - : undefined, - }); - } - } - - return run as Output; - } - - async #finalizeBatch(run: TaskRun) { - if (!run.batchId) { - return; - } - - logger.debug("FinalizeTaskRunService: Finalizing batch", { runId: run.id }); - - const environment = await this._prisma.runtimeEnvironment.findFirst({ - where: { - id: run.runtimeEnvironmentId, - }, - }); - - if (!environment) { - return; - } - - const batchItems = await this._prisma.batchTaskRunItem.findMany({ - where: { - taskRunId: run.id, - }, - include: { - batchTaskRun: { - select: { - id: true, - dependentTaskAttemptId: true, - batchVersion: true, - }, - }, - }, - }); - - if (batchItems.length === 0) { - return; - } - - if (batchItems.length > 10) { - logger.error("FinalizeTaskRunService: More than 10 batch items", { - runId: run.id, - batchItems: batchItems.length, - }); - - return; - } - - for (const item of batchItems) { - // Don't do anything if this is a batchTriggerAndWait in a deployed task - // As that is being handled in resumeDependentParents and resumeTaskRunDependencies - if (environment.type !== "DEVELOPMENT" && item.batchTaskRun.dependentTaskAttemptId) { - continue; - } - - if (item.batchTaskRun.batchVersion === "v3") { - await completeBatchTaskRunItemV3(item.id, item.batchTaskRunId, this._prisma); - } else { - // THIS IS DEPRECATED and only happens with batchVersion != v3 - await this._prisma.batchTaskRunItem.update({ - where: { - id: item.id, - }, - data: { - status: "COMPLETED", - }, - }); - - // This won't resume because this batch does not have a dependent task attempt ID - // or is in development, but this service will mark the batch as completed - await ResumeBatchRunService.enqueue(item.batchTaskRunId, false); - } - } - } - - async finalizeAttempt({ - attemptStatus, - error, - run, - }: { - attemptStatus?: FINAL_ATTEMPT_STATUSES; - error?: TaskRunError; - run: TaskRun; - }) { - if (!attemptStatus && !error) { - logger.error("FinalizeTaskRunService: No attemptStatus or error provided", { runId: run.id }); - return; - } - - const latestAttempt = await this._prisma.taskRunAttempt.findFirst({ - where: { taskRunId: run.id }, - orderBy: { id: "desc" }, - take: 1, - }); - - if (latestAttempt) { - logger.debug("Finalizing run attempt", { - id: latestAttempt.id, - status: attemptStatus, - error, - }); - - await this._prisma.taskRunAttempt.update({ - where: { id: latestAttempt.id }, - data: { status: attemptStatus, error: error ? sanitizeError(error) : undefined }, - }); - - return; - } - - // There's no attempt, so create one - - logger.debug("Finalizing run no attempt found", { - runId: run.id, - attemptStatus, - error, - }); - - if (!run.lockedById) { - // This happens when a run is expired or was cancelled before an attempt, it's not a problem - logger.info( - "FinalizeTaskRunService: No lockedById, so can't get the BackgroundWorkerTask. Not creating an attempt.", - { runId: run.id, status: run.status } - ); - return; - } - - const workerTask = await this._prisma.backgroundWorkerTask.findFirst({ - select: { - id: true, - workerId: true, - runtimeEnvironmentId: true, - queueConfig: true, - }, - where: { - id: run.lockedById, - }, - }); - - if (!workerTask) { - logger.error("FinalizeTaskRunService: No worker task found", { runId: run.id }); - return; - } - - const queue = await findQueueInEnvironment( - run.queue, - workerTask.runtimeEnvironmentId, - workerTask.id, - workerTask - ); - - if (!queue) { - logger.error("FinalizeTaskRunService: No queue found", { runId: run.id }); - return; - } - - await this._prisma.taskRunAttempt.create({ - data: { - number: 1, - friendlyId: generateFriendlyId("attempt"), - taskRunId: run.id, - backgroundWorkerId: workerTask?.workerId, - backgroundWorkerTaskId: workerTask?.id, - queueId: queue.id, - runtimeEnvironmentId: workerTask.runtimeEnvironmentId, - status: attemptStatus, - error: error ? sanitizeError(error) : undefined, - }, - }); - } -} diff --git a/apps/webapp/app/v3/services/restoreCheckpoint.server.ts b/apps/webapp/app/v3/services/restoreCheckpoint.server.ts deleted file mode 100644 index 81e53e7dc03..00000000000 --- a/apps/webapp/app/v3/services/restoreCheckpoint.server.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { type Checkpoint } from "@trigger.dev/database"; -import { logger } from "~/services/logger.server"; -import { socketIo } from "../handleSocketIo.server"; -import { machinePresetFromConfig, machinePresetFromRun } from "../machinePresets.server"; -import { BaseService } from "./baseService.server"; -import { CreateCheckpointRestoreEventService } from "./createCheckpointRestoreEvent.server"; -import { isRestorableAttemptStatus, isRestorableRunStatus } from "../taskStatus"; - -export class RestoreCheckpointService extends BaseService { - public async call(params: { - eventId: string; - isRetry?: boolean; - }): Promise { - logger.debug(`Restoring checkpoint`, params); - - const checkpointEvent = await this._prisma.checkpointRestoreEvent.findFirst({ - where: { - id: params.eventId, - type: "CHECKPOINT", - }, - include: { - checkpoint: { - include: { - run: { - select: { - status: true, - machinePreset: true, - }, - }, - attempt: { - select: { - status: true, - backgroundWorkerTask: { - select: { - machineConfig: true, - }, - }, - }, - }, - runtimeEnvironment: true, - }, - }, - }, - }); - - if (!checkpointEvent) { - logger.error("Checkpoint event not found", { eventId: params.eventId }); - return; - } - - const checkpoint = checkpointEvent.checkpoint; - - if (!isRestorableRunStatus(checkpoint.run.status)) { - logger.error("Run is unrestorable", { - eventId: params.eventId, - runId: checkpoint.runId, - runStatus: checkpoint.run.status, - attemptId: checkpoint.attemptId, - }); - return; - } - - if (!isRestorableAttemptStatus(checkpoint.attempt.status) && !params.isRetry) { - logger.error("Attempt is unrestorable", { - eventId: params.eventId, - runId: checkpoint.runId, - attemptId: checkpoint.attemptId, - attemptStatus: checkpoint.attempt.status, - }); - return; - } - - const machine = - machinePresetFromRun(checkpoint.run) ?? - machinePresetFromConfig(checkpoint.attempt.backgroundWorkerTask.machineConfig ?? {}); - - const restoreEvent = await this._prisma.checkpointRestoreEvent.findFirst({ - where: { - checkpointId: checkpoint.id, - type: "RESTORE", - }, - }); - - if (restoreEvent) { - logger.warn("Restore event already exists", { - runId: checkpoint.runId, - attemptId: checkpoint.attemptId, - checkpointId: checkpoint.id, - restoreEventId: restoreEvent.id, - }); - - return; - } - - const eventService = new CreateCheckpointRestoreEventService(this._prisma); - await eventService.restore({ checkpointId: checkpoint.id }); - - socketIo.providerNamespace.emit("RESTORE", { - version: "v1", - type: checkpoint.type, - location: checkpoint.location, - reason: checkpoint.reason ?? undefined, - imageRef: checkpoint.imageRef, - machine, - attemptNumber: checkpoint.attemptNumber ?? undefined, - // identifiers - checkpointId: checkpoint.id, - envId: checkpoint.runtimeEnvironment.id, - envType: checkpoint.runtimeEnvironment.type, - orgId: checkpoint.runtimeEnvironment.organizationId, - projectId: checkpoint.runtimeEnvironment.projectId, - runId: checkpoint.runId, - }); - - return checkpoint; - } - - async getLastCheckpointEventIfUnrestored(runId: string) { - const event = await this._prisma.checkpointRestoreEvent.findFirst({ - where: { - runId, - }, - take: 1, - orderBy: { - createdAt: "desc", - }, - }); - - if (!event) { - return; - } - - if (event.type === "CHECKPOINT") { - return event; - } - } -} diff --git a/apps/webapp/app/v3/services/resumeAttempt.server.ts b/apps/webapp/app/v3/services/resumeAttempt.server.ts deleted file mode 100644 index 0390839986b..00000000000 --- a/apps/webapp/app/v3/services/resumeAttempt.server.ts +++ /dev/null @@ -1,290 +0,0 @@ -import type { - CoordinatorToPlatformMessages, - TaskRunExecution, - TaskRunExecutionResult, -} from "@trigger.dev/core/v3"; -import type { InferSocketMessageSchema } from "@trigger.dev/core/v3/zodSocket"; -import type { Prisma, TaskRunAttempt } from "@trigger.dev/database"; -import { logger } from "~/services/logger.server"; -import { marqs } from "~/v3/marqs/index.server"; -import { socketIo } from "../handleSocketIo.server"; -import { sharedQueueTasks } from "../marqs/sharedQueueConsumer.server"; -import { FINAL_ATTEMPT_STATUSES, isFinalRunStatus } from "../taskStatus"; -import { BaseService } from "./baseService.server"; - -export class ResumeAttemptService extends BaseService { - private _logger = logger; - - public async call( - params: InferSocketMessageSchema - ): Promise { - this._logger.debug(`ResumeAttemptService.call()`, params); - - const latestAttemptSelect = { - orderBy: { - number: "desc", - }, - take: 1, - select: { - id: true, - number: true, - status: true, - }, - } satisfies Prisma.TaskRunInclude["attempts"]; - - const attempt = await this._prisma.taskRunAttempt.findFirst({ - where: { - friendlyId: params.attemptFriendlyId, - }, - include: { - taskRun: true, - dependencies: { - select: { - taskRun: { - select: { - attempts: latestAttemptSelect, - }, - }, - }, - orderBy: { - createdAt: "desc", - }, - take: 1, - }, - batchDependencies: { - select: { - items: { - select: { - taskRun: { - select: { - attempts: latestAttemptSelect, - }, - }, - }, - }, - }, - orderBy: { - createdAt: "desc", - }, - take: 1, - }, - }, - }); - - if (!attempt) { - this._logger.error("Could not find attempt", params); - return; - } - - this._logger = logger.child({ - attemptId: attempt.id, - attemptFriendlyId: attempt.friendlyId, - taskRun: attempt.taskRun, - }); - - if (isFinalRunStatus(attempt.taskRun.status)) { - this._logger.error("Run is not resumable"); - return; - } - - let completedAttemptIds: string[] = []; - - switch (params.type) { - case "WAIT_FOR_DURATION": { - this._logger.debug("Sending duration wait resume message"); - - await this.#setPostResumeStatuses(attempt); - - socketIo.coordinatorNamespace.emit("RESUME_AFTER_DURATION", { - version: "v1", - attemptId: attempt.id, - attemptFriendlyId: attempt.friendlyId, - }); - break; - } - case "WAIT_FOR_TASK": { - if (attempt.dependencies.length) { - // We only care about the latest dependency - const dependentAttempt = attempt.dependencies[0].taskRun.attempts[0]; - - if (!dependentAttempt) { - this._logger.error("No dependent attempt"); - return; - } - - completedAttemptIds = [dependentAttempt.id]; - } else { - this._logger.error("No task dependency"); - return; - } - - await this.#handleDependencyResume(attempt, completedAttemptIds); - - break; - } - case "WAIT_FOR_BATCH": { - if (attempt.batchDependencies) { - // We only care about the latest batch dependency - const dependentBatchItems = attempt.batchDependencies[0].items; - - if (!dependentBatchItems) { - this._logger.error("No dependent batch items"); - return; - } - - //find the best attempt for each batch item - //it should be the most recent one in a final state - const finalAttempts = dependentBatchItems - .map((item) => { - return item.taskRun.attempts - .filter((a) => FINAL_ATTEMPT_STATUSES.includes(a.status)) - .sort((a, b) => b.number - a.number) - .at(0); - }) - .filter(Boolean); - - completedAttemptIds = finalAttempts.map((a) => a.id); - - if (completedAttemptIds.length !== dependentBatchItems.length) { - this._logger.error("[ResumeAttemptService] not all batch items have attempts", { - runId: attempt.taskRunId, - completedAttemptIds, - finalAttempts, - dependentBatchItems, - }); - - return; - } - } else { - this._logger.error("No batch dependency"); - return; - } - - await this.#handleDependencyResume(attempt, completedAttemptIds); - - break; - } - default: { - break; - } - } - } - - async #handleDependencyResume(attempt: TaskRunAttempt, completedAttemptIds: string[]) { - if (completedAttemptIds.length === 0) { - this._logger.error("No completed attempt IDs"); - return; - } - - const completions: TaskRunExecutionResult[] = []; - const executions: TaskRunExecution[] = []; - - for (const completedAttemptId of completedAttemptIds) { - const completedAttempt = await this._prisma.taskRunAttempt.findFirst({ - where: { - id: completedAttemptId, - taskRun: { - lockedAt: { - not: null, - }, - lockedById: { - not: null, - }, - }, - }, - }); - - if (!completedAttempt) { - this._logger.error("Completed attempt not found", { completedAttemptId }); - await marqs?.acknowledgeMessage( - attempt.taskRunId, - "Cannot find completed attempt in ResumeAttemptService" - ); - return; - } - - const logger = this._logger.child({ - completedAttemptId: completedAttempt.id, - completedAttemptFriendlyId: completedAttempt.friendlyId, - completedRunId: completedAttempt.taskRunId, - }); - - const resumePayload = await sharedQueueTasks.getResumePayload(completedAttempt.id); - - if (!resumePayload) { - logger.error("Failed to get resume payload"); - await marqs?.acknowledgeMessage( - attempt.taskRunId, - "Failed to get resume payload in ResumeAttemptService" - ); - return; - } - - completions.push(resumePayload.completion); - executions.push(resumePayload.execution); - } - - await this.#setPostResumeStatuses(attempt); - - socketIo.coordinatorNamespace.emit("RESUME_AFTER_DEPENDENCY", { - version: "v1", - runId: attempt.taskRunId, - attemptId: attempt.id, - attemptFriendlyId: attempt.friendlyId, - completions, - executions, - }); - } - - async #setPostResumeStatuses(attempt: TaskRunAttempt) { - try { - const updatedAttempt = await this._prisma.taskRunAttempt.update({ - where: { - id: attempt.id, - }, - data: { - status: "EXECUTING", - taskRun: { - update: { - data: { - status: attempt.number > 1 ? "RETRYING_AFTER_FAILURE" : "EXECUTING", - }, - }, - }, - }, - select: { - id: true, - status: true, - taskRun: { - select: { - id: true, - status: true, - }, - }, - }, - }); - - this._logger.debug("Set post resume statuses", { - run: { - id: updatedAttempt.taskRun.id, - status: updatedAttempt.taskRun.status, - }, - attempt: { - id: updatedAttempt.id, - status: updatedAttempt.status, - }, - }); - } catch (error) { - this._logger.error("Failed to set post resume statuses", { - error: - error instanceof Error - ? { - name: error.name, - message: error.message, - stack: error.stack, - } - : error, - }); - } - } -} diff --git a/apps/webapp/app/v3/services/resumeBatchRun.server.ts b/apps/webapp/app/v3/services/resumeBatchRun.server.ts deleted file mode 100644 index a7e42407d34..00000000000 --- a/apps/webapp/app/v3/services/resumeBatchRun.server.ts +++ /dev/null @@ -1,399 +0,0 @@ -import type { PrismaClientOrTransaction } from "~/db.server"; -import { commonWorker } from "../commonWorker.server"; -import { marqs } from "~/v3/marqs/index.server"; -import { BaseService } from "./baseService.server"; -import { logger } from "~/services/logger.server"; -import type { BatchTaskRun, Prisma } from "@trigger.dev/database"; -import { findEnvironmentById } from "~/models/runtimeEnvironment.server"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { workerQueue } from "~/services/worker.server"; -import { isV3Disabled } from "../engineDeprecation.server"; - -const finishedBatchRunStatuses = ["COMPLETED", "FAILED", "CANCELED"]; - -const BATCH_RUN_INCLUDE = { - items: { - select: { - status: true, - taskRunAttemptId: true, - }, - }, -} satisfies Prisma.BatchTaskRunInclude; - -type RetrieveBatchRunResult = Prisma.BatchTaskRunGetPayload<{ - include: typeof BATCH_RUN_INCLUDE; -}>; - -export class ResumeBatchRunService extends BaseService { - public async call(batchRunId: string) { - const batchRun = await this.runStore.findBatchTaskRunById(batchRunId, { - include: BATCH_RUN_INCLUDE, - }); - - if (!batchRun) { - logger.error( - "ResumeBatchRunService: Batch run doesn't exist or doesn't have a dependent attempt", - { - batchRunId, - } - ); - - return "ERROR"; - } - - // BatchTaskRun -> RuntimeEnvironment FK is dropped; resolve the env from the scalar id. - const environment = await findEnvironmentById(batchRun.runtimeEnvironmentId); - if (!environment) { - logger.error("ResumeBatchRunService: Environment not found", { - batchRunId, - runtimeEnvironmentId: batchRun.runtimeEnvironmentId, - }); - - return "ERROR"; - } - - // v3 (engine V1) shutdown: don't resume batches for abandoned V1 projects. v4 is unaffected. - // The BatchTaskRun -> RuntimeEnvironment relation is dropped, so read the engine from the - // resolved environment's project rather than the unloaded batchRun.runtimeEnvironment relation. - if (isV3Disabled() && environment.project.engine === "V1") { - logger.debug("[ResumeBatchRunService] Skipping resume for shut-down v3 batch", { - batchRunId, - }); - return "ERROR"; - } - - if (batchRun.batchVersion === "v3") { - return await this.#handleV3BatchRun(batchRun, environment); - } else { - return await this.#handleLegacyBatchRun(batchRun, environment); - } - } - - async #handleV3BatchRun(batchRun: RetrieveBatchRunResult, environment: AuthenticatedEnvironment) { - // V3 batch runs should already be complete by the time this is called - if (batchRun.status !== "COMPLETED") { - logger.debug("ResumeBatchRunService: Batch run is already completed", { - batchRunId: batchRun.id, - batchRun: { - id: batchRun.id, - status: batchRun.status, - }, - }); - - return "ERROR"; - } - - // Even though we are in v3, we still need to check if the batch run has a dependent attempt - if (!batchRun.dependentTaskAttemptId) { - logger.debug("ResumeBatchRunService: Batch run doesn't have a dependent attempt", { - batchRunId: batchRun.id, - }); - - return "ERROR"; - } - - return await this.#handleDependentTaskAttempt( - batchRun, - batchRun.dependentTaskAttemptId, - environment - ); - } - - async #handleLegacyBatchRun( - batchRun: RetrieveBatchRunResult, - environment: AuthenticatedEnvironment - ) { - if (batchRun.status === "COMPLETED") { - logger.debug("ResumeBatchRunService: Batch run is already completed", { - batchRunId: batchRun.id, - batchRun: { - id: batchRun.id, - status: batchRun.status, - }, - }); - - return "ERROR"; - } - - if (batchRun.batchVersion === "v2") { - if (batchRun.items.length < batchRun.runCount) { - logger.debug("ResumeBatchRunService: All items aren't yet completed [v2]", { - batchRunId: batchRun.id, - batchRun: { - id: batchRun.id, - status: batchRun.status, - itemsLength: batchRun.items.length, - runCount: batchRun.runCount, - }, - }); - - return "PENDING"; - } - } - - if (batchRun.items.some((item) => !finishedBatchRunStatuses.includes(item.status))) { - logger.debug("ResumeBatchRunService: All items aren't yet completed [v1]", { - batchRunId: batchRun.id, - batchRun: { - id: batchRun.id, - status: batchRun.status, - }, - }); - - return "PENDING"; - } - - // If we are in development, or there is no dependent attempt, we can just mark the batch as completed and return - if (environment.type === "DEVELOPMENT" || !batchRun.dependentTaskAttemptId) { - // We need to update the batchRun status so we don't resume it again - await this.runStore.updateBatchTaskRun({ - where: { - id: batchRun.id, - }, - data: { - status: "COMPLETED", - }, - select: { id: true }, - }); - - return "COMPLETED"; - } - - return await this.#handleDependentTaskAttempt( - batchRun, - batchRun.dependentTaskAttemptId, - environment - ); - } - - async #handleDependentTaskAttempt( - batchRun: RetrieveBatchRunResult, - dependentTaskAttemptId: string, - environment: AuthenticatedEnvironment - ) { - const dependentTaskAttempt = await this._prisma.taskRunAttempt.findFirst({ - where: { - id: dependentTaskAttemptId, - }, - select: { - status: true, - id: true, - taskRun: { - select: { - id: true, - queue: true, - taskIdentifier: true, - concurrencyKey: true, - createdAt: true, - queueTimestamp: true, - }, - }, - }, - }); - - if (!dependentTaskAttempt) { - logger.error("ResumeBatchRunService: Dependent attempt not found", { - batchRunId: batchRun.id, - dependentTaskAttemptId: batchRun.dependentTaskAttemptId, - }); - - return "ERROR"; - } - - // This batch has a dependent attempt and just finalized, we should resume that attempt - const dependentRun = dependentTaskAttempt.taskRun; - - if (dependentTaskAttempt.status === "PAUSED" && batchRun.checkpointEventId) { - logger.debug("ResumeBatchRunService: Attempt is paused and has a checkpoint event", { - batchRunId: batchRun.id, - dependentTaskAttempt: dependentTaskAttempt, - checkpointEventId: batchRun.checkpointEventId, - }); - - // We need to update the batchRun status so we don't resume it again - const wasUpdated = await this.#setBatchToResumedOnce(batchRun); - - if (wasUpdated) { - logger.debug("ResumeBatchRunService: Resuming dependent run with checkpoint", { - batchRunId: batchRun.id, - dependentTaskAttemptId: dependentTaskAttempt.id, - }); - - await marqs.enqueueMessage( - environment, - dependentRun.queue, - dependentRun.id, - { - type: "RESUME", - completedAttemptIds: [], - resumableAttemptId: dependentTaskAttempt.id, - checkpointEventId: batchRun.checkpointEventId, - taskIdentifier: dependentTaskAttempt.taskRun.taskIdentifier, - projectId: environment.projectId, - environmentId: environment.id, - environmentType: environment.type, - }, - dependentRun.concurrencyKey ?? undefined, - dependentRun.queueTimestamp ?? dependentRun.createdAt, - undefined, - "resume" - ); - - return "COMPLETED"; - } else { - logger.debug("ResumeBatchRunService: with checkpoint was already completed", { - batchRunId: batchRun.id, - dependentTaskAttempt: dependentTaskAttempt, - checkpointEventId: batchRun.checkpointEventId, - hasCheckpointEvent: !!batchRun.checkpointEventId, - }); - - return "ALREADY_COMPLETED"; - } - } else { - logger.debug("ResumeBatchRunService: attempt is not paused or there's no checkpoint event", { - batchRunId: batchRun.id, - dependentTaskAttempt: dependentTaskAttempt, - checkpointEventId: batchRun.checkpointEventId, - hasCheckpointEvent: !!batchRun.checkpointEventId, - }); - - if (dependentTaskAttempt.status === "PAUSED" && !batchRun.checkpointEventId) { - // In case of race conditions the status can be PAUSED without a checkpoint event - // When the checkpoint is created, it will continue the run - logger.error("ResumeBatchRunService: attempt is paused but there's no checkpoint event", { - batchRunId: batchRun.id, - dependentTaskAttempt: dependentTaskAttempt, - checkpointEventId: batchRun.checkpointEventId, - hasCheckpointEvent: !!batchRun.checkpointEventId, - }); - - return "ERROR"; - } - - // We need to update the batchRun status so we don't resume it again - const wasUpdated = await this.#setBatchToResumedOnce(batchRun); - - if (wasUpdated) { - logger.debug("ResumeBatchRunService: Resuming dependent run without checkpoint", { - batchRunId: batchRun.id, - dependentTaskAttempt: dependentTaskAttempt, - checkpointEventId: batchRun.checkpointEventId, - hasCheckpointEvent: !!batchRun.checkpointEventId, - }); - - await marqs.requeueMessage( - dependentRun.id, - { - type: "RESUME", - completedAttemptIds: batchRun.items - .map((item) => item.taskRunAttemptId) - .filter(Boolean), - resumableAttemptId: dependentTaskAttempt.id, - checkpointEventId: batchRun.checkpointEventId ?? undefined, - taskIdentifier: dependentTaskAttempt.taskRun.taskIdentifier, - projectId: environment.projectId, - environmentId: environment.id, - environmentType: environment.type, - }, - ( - dependentTaskAttempt.taskRun.queueTimestamp ?? dependentTaskAttempt.taskRun.createdAt - ).getTime(), - "resume" - ); - - return "COMPLETED"; - } else { - logger.debug("ResumeBatchRunService: without checkpoint was already completed", { - batchRunId: batchRun.id, - dependentTaskAttempt: dependentTaskAttempt, - checkpointEventId: batchRun.checkpointEventId, - hasCheckpointEvent: !!batchRun.checkpointEventId, - }); - - return "ALREADY_COMPLETED"; - } - } - } - - async #setBatchToResumedOnce(batchRun: BatchTaskRun) { - // v3 batches don't use the status for deciding whether a batch has been resumed - if (batchRun.batchVersion === "v3") { - const result = await this.runStore.updateManyBatchTaskRun({ - where: { - id: batchRun.id, - resumedAt: null, - }, - data: { - resumedAt: new Date(), - }, - }); - - if (result.count > 0) { - return true; - } else { - return false; - } - } - - const result = await this.runStore.updateManyBatchTaskRun({ - where: { - id: batchRun.id, - status: { - not: "COMPLETED", // Ensure the status is not already "COMPLETED" - }, - }, - data: { - status: "COMPLETED", - }, - }); - - if (result.count > 0) { - return true; - } else { - return false; - } - } - - static async enqueue( - batchRunId: string, - skipJobKey: boolean, - tx?: PrismaClientOrTransaction, - runAt?: Date - ) { - if (tx) { - logger.debug("ResumeBatchRunService: Enqueuing resume batch run using workerQueue", { - batchRunId, - skipJobKey, - runAt, - }); - - return await workerQueue.enqueue( - "v3.resumeBatchRun", - { - batchRunId, - }, - { - jobKey: skipJobKey ? undefined : `resumeBatchRun-${batchRunId}`, - runAt, - tx, - } - ); - } else { - logger.debug("ResumeBatchRunService: Enqueuing resume batch run using commonWorker", { - batchRunId, - skipJobKey, - runAt, - }); - - return await commonWorker.enqueue({ - id: skipJobKey ? undefined : `resumeBatchRun-${batchRunId}`, - job: "v3.resumeBatchRun", - payload: { - batchRunId, - }, - availableAt: runAt, - }); - } - } -} diff --git a/apps/webapp/app/v3/services/resumeDependentParents.server.ts b/apps/webapp/app/v3/services/resumeDependentParents.server.ts deleted file mode 100644 index e476d18d6b2..00000000000 --- a/apps/webapp/app/v3/services/resumeDependentParents.server.ts +++ /dev/null @@ -1,296 +0,0 @@ -import type { Prisma } from "@trigger.dev/database"; -import { logger } from "~/services/logger.server"; -import { isFinalAttemptStatus, isFinalRunStatus } from "../taskStatus"; -import { BaseService } from "./baseService.server"; -import { ResumeBatchRunService } from "./resumeBatchRun.server"; -import { ResumeTaskDependencyService } from "./resumeTaskDependency.server"; -import { $transaction } from "~/db.server"; -import { completeBatchTaskRunItemV3 } from "./batchTriggerV3.server"; - -type Output = - | { - success: true; - action: - | "resume-scheduled" - | "batch-resume-scheduled" - | "no-dependencies" - | "not-finished" - | "dev"; - } - | { - success: false; - error: string; - }; - -const taskRunDependencySelect = { - select: { - id: true, - taskRunId: true, - taskRun: { - select: { - id: true, - status: true, - friendlyId: true, - runtimeEnvironment: { - select: { - type: true, - }, - }, - }, - }, - dependentAttempt: { - select: { - id: true, - }, - }, - dependentBatchRun: { - select: { - id: true, - batchVersion: true, - }, - }, - }, -} as const; - -type Dependency = Prisma.TaskRunDependencyGetPayload; - -/** This will resume a dependent (parent) run if there is one and it makes sense. */ -export class ResumeDependentParentsService extends BaseService { - public async call({ id }: { id: string }): Promise { - try { - const dependency = await this._prisma.taskRunDependency.findFirst({ - ...taskRunDependencySelect, - where: { - taskRunId: id, - }, - }); - - logger.log("ResumeDependentParentsService: tried to find dependency", { - runId: id, - dependency: dependency, - }); - - if (!dependency) { - logger.log("ResumeDependentParentsService: dependency not found", { - runId: id, - }); - - //no dependency, that's fine most runs won't have one. - return { - success: true, - action: "no-dependencies", - }; - } - - if (dependency.taskRun.runtimeEnvironment.type === "DEVELOPMENT") { - return { - success: true, - action: "dev", - }; - } - - if (!isFinalRunStatus(dependency.taskRun.status)) { - logger.debug( - "ResumeDependentParentsService: run not finished yet, can't resume parent yet", - { - runId: id, - dependency, - } - ); - - // the child run isn't finished yet, so we can't resume the parent yet. - return { - success: true, - action: "not-finished", - }; - } - - if (dependency.dependentAttempt) { - return this.#singleRunDependency(dependency); - } else if (dependency.dependentBatchRun) { - return this.#batchRunDependency(dependency); - } else { - logger.error("ResumeDependentParentsService: dependency has no dependencies", { - runId: id, - dependency, - }); - - return { - success: false, - error: `Dependency has no dependencies (single or batch)`, - }; - } - } catch (error) { - return { - success: false, - error: error instanceof Error ? error.message : JSON.stringify(error), - }; - } - } - - async #singleRunDependency(dependency: Dependency): Promise { - logger.debug( - `ResumeDependentParentsService.singleRunDependency(): Resuming dependent parent for run`, - { - dependency, - } - ); - - const lastAttempt = await this._prisma.taskRunAttempt.findFirst({ - select: { - id: true, - status: true, - }, - where: { - taskRunId: dependency.taskRunId, - }, - orderBy: { - id: "desc", - }, - }); - - if (!lastAttempt) { - logger.error( - "ResumeDependentParentsService.singleRunDependency(): dependency child attempt not found", - { - dependency, - } - ); - - return { - success: false, - error: `Dependency child attempt not found for run ${dependency.taskRunId}`, - }; - } - - if (!isFinalAttemptStatus(lastAttempt.status)) { - //We still want to continue if this happens because the run is final but log it - logger.error( - "ResumeDependentParentsService.singleRunDependency(): dependency child attempt not final, but the run is.", - { - dependency, - lastAttempt, - } - ); - - return { - success: false, - error: `Dependency child attempt not final, but the run is`, - }; - } - - //resume the dependent task - await ResumeTaskDependencyService.enqueue(dependency.id, lastAttempt.id); - return { - success: true, - action: "resume-scheduled", - }; - } - - async #batchRunDependency(dependency: Dependency): Promise { - logger.debug( - `ResumeDependentParentsService.batchRunDependency(): Resuming dependent batch for run`, - { - dependency, - } - ); - - if (!dependency.dependentBatchRun) { - logger.error( - "ResumeDependentParentsService.batchRunDependency(): dependency has no dependent batch", - { - dependency, - } - ); - - return { - success: false, - error: `Dependency has no dependent batch`, - }; - } - - const lastAttempt = await this._prisma.taskRunAttempt.findFirst({ - select: { - id: true, - status: true, - }, - where: { - taskRunId: dependency.taskRunId, - }, - orderBy: { - id: "desc", - }, - }); - - if (!lastAttempt) { - logger.error( - "ResumeDependentParentsService.singleRunDependency(): dependency child attempt not found", - { - dependency, - } - ); - - return { - success: false, - error: `Dependency child attempt not found for run ${dependency.taskRunId}`, - }; - } - - logger.log( - "ResumeDependentParentsService.batchRunDependency(): Setting the batchTaskRunItem to COMPLETED", - { - dependency, - lastAttempt, - } - ); - - if (dependency.dependentBatchRun!.batchVersion === "v3") { - const batchTaskRunItem = await this._prisma.batchTaskRunItem.findFirst({ - where: { - batchTaskRunId: dependency.dependentBatchRun!.id, - taskRunId: dependency.taskRunId, - }, - }); - - if (batchTaskRunItem) { - await completeBatchTaskRunItemV3( - batchTaskRunItem.id, - batchTaskRunItem.batchTaskRunId, - this._prisma, - true, - lastAttempt.id - ); - } else { - logger.debug( - "ResumeDependentParentsService.batchRunDependency() v3: batchTaskRunItem not found", - { - dependency, - lastAttempt, - } - ); - } - } else { - await $transaction(this._prisma, async (tx) => { - await tx.batchTaskRunItem.update({ - where: { - batchTaskRunId_taskRunId: { - batchTaskRunId: dependency.dependentBatchRun!.id, - taskRunId: dependency.taskRunId, - }, - }, - data: { - status: "COMPLETED", - taskRunAttemptId: lastAttempt.id, - }, - }); - - await ResumeBatchRunService.enqueue(dependency.dependentBatchRun!.id, false, tx); - }); - } - - return { - success: true, - action: "batch-resume-scheduled", - }; - } -} diff --git a/apps/webapp/app/v3/services/resumeTaskDependency.server.ts b/apps/webapp/app/v3/services/resumeTaskDependency.server.ts deleted file mode 100644 index 83c2bb9a5d5..00000000000 --- a/apps/webapp/app/v3/services/resumeTaskDependency.server.ts +++ /dev/null @@ -1,175 +0,0 @@ -import type { TaskRunDependency } from "@trigger.dev/database"; -import { logger } from "~/services/logger.server"; -import { marqs } from "~/v3/marqs/index.server"; -import { commonWorker } from "../commonWorker.server"; -import { BaseService } from "./baseService.server"; -import { isV3Disabled } from "../engineDeprecation.server"; - -export class ResumeTaskDependencyService extends BaseService { - public async call(dependencyId: string, sourceTaskAttemptId: string) { - const dependency = await this._prisma.taskRunDependency.findFirst({ - where: { id: dependencyId }, - include: { - taskRun: { - include: { - runtimeEnvironment: { - include: { - project: true, - organization: true, - }, - }, - }, - }, - dependentAttempt: { - include: { - taskRun: true, - }, - }, - }, - }); - - // Dependencies with a dependentBatchRun are handled already by the ResumeBatchRunService - if (!dependency || !dependency.dependentAttempt) { - return; - } - - // v3 (engine V1) shutdown: don't resume dependencies for abandoned V1 runs. v4 is unaffected. - if (isV3Disabled() && dependency.taskRun.engine === "V1") { - logger.debug("[ResumeTaskDependencyService] Skipping resume for shut-down v3 run", { - dependencyId, - }); - return; - } - - if (dependency.taskRun.runtimeEnvironment.type === "DEVELOPMENT") { - return; - } - - const dependentRun = dependency.dependentAttempt.taskRun; - - if (dependency.dependentAttempt.status === "PAUSED" && dependency.checkpointEventId) { - logger.debug( - "Task dependency resume: Attempt is paused and there's a checkpoint. Enqueuing resume with checkpoint.", - { - attemptId: dependency.id, - dependentAttempt: dependency.dependentAttempt, - checkpointEventId: dependency.checkpointEventId, - hasCheckpointEvent: !!dependency.checkpointEventId, - runId: dependentRun.id, - } - ); - - const wasUpdated = await this.#setDependencyToResumedOnce(dependency); - - if (!wasUpdated) { - logger.debug("Task dependency resume: Attempt with checkpoint was already resumed", { - attemptId: dependency.id, - dependentAttempt: dependency.dependentAttempt, - checkpointEventId: dependency.checkpointEventId, - hasCheckpointEvent: !!dependency.checkpointEventId, - runId: dependentRun.id, - }); - return; - } - - // TODO: use the new priority queue thingie - await marqs?.enqueueMessage( - dependency.taskRun.runtimeEnvironment, - dependentRun.queue, - dependentRun.id, - { - type: "RESUME", - completedAttemptIds: [sourceTaskAttemptId], - resumableAttemptId: dependency.dependentAttempt.id, - checkpointEventId: dependency.checkpointEventId, - taskIdentifier: dependency.taskRun.taskIdentifier, - projectId: dependency.taskRun.runtimeEnvironment.projectId, - environmentId: dependency.taskRun.runtimeEnvironment.id, - environmentType: dependency.taskRun.runtimeEnvironment.type, - }, - dependentRun.concurrencyKey ?? undefined, - dependentRun.queueTimestamp ?? dependentRun.createdAt, - undefined, - "resume" - ); - } else { - logger.debug("Task dependency resume: Attempt is not paused or there's no checkpoint event", { - attemptId: dependency.id, - dependentAttempt: dependency.dependentAttempt, - checkpointEventId: dependency.checkpointEventId, - hasCheckpointEvent: !!dependency.checkpointEventId, - runId: dependentRun.id, - }); - - if (dependency.dependentAttempt.status === "PAUSED" && !dependency.checkpointEventId) { - // In case of race conditions the status can be PAUSED without a checkpoint event - // When the checkpoint is created, it will continue the run - logger.error("Task dependency resume: Attempt is paused but there's no checkpoint event", { - attemptId: dependency.id, - dependentAttemptId: dependency.dependentAttempt.id, - }); - return; - } - - const wasUpdated = await this.#setDependencyToResumedOnce(dependency); - - if (!wasUpdated) { - logger.debug("Task dependency resume: Attempt without checkpoint was already resumed", { - attemptId: dependency.id, - dependentAttempt: dependency.dependentAttempt, - checkpointEventId: dependency.checkpointEventId, - hasCheckpointEvent: !!dependency.checkpointEventId, - runId: dependentRun.id, - }); - return; - } - - await marqs.requeueMessage( - dependentRun.id, - { - type: "RESUME", - completedAttemptIds: [sourceTaskAttemptId], - resumableAttemptId: dependency.dependentAttempt.id, - checkpointEventId: dependency.checkpointEventId ?? undefined, - taskIdentifier: dependency.taskRun.taskIdentifier, - projectId: dependency.taskRun.runtimeEnvironment.projectId, - environmentId: dependency.taskRun.runtimeEnvironment.id, - environmentType: dependency.taskRun.runtimeEnvironment.type, - }, - (dependentRun.queueTimestamp ?? dependentRun.createdAt).getTime(), - "resume" - ); - } - } - - async #setDependencyToResumedOnce(dependency: TaskRunDependency) { - const result = await this._prisma.taskRunDependency.updateMany({ - where: { - id: dependency.id, - resumedAt: null, - }, - data: { - resumedAt: new Date(), - }, - }); - - // Check if any records were updated - if (result.count > 0) { - // The status was changed, so we return true - return true; - } else { - return false; - } - } - - static async enqueue(dependencyId: string, sourceTaskAttemptId: string, runAt?: Date) { - return await commonWorker.enqueue({ - job: "v3.resumeTaskDependency", - payload: { - dependencyId, - sourceTaskAttemptId, - }, - availableAt: runAt, - }); - } -} diff --git a/apps/webapp/app/v3/services/retryAttempt.server.ts b/apps/webapp/app/v3/services/retryAttempt.server.ts deleted file mode 100644 index dd1f8bf908a..00000000000 --- a/apps/webapp/app/v3/services/retryAttempt.server.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { logger } from "~/services/logger.server"; -import { commonWorker } from "../commonWorker.server"; -import { socketIo } from "../handleSocketIo.server"; -import { BaseService } from "./baseService.server"; -import { isV3Disabled } from "../engineDeprecation.server"; - -export class RetryAttemptService extends BaseService { - public async call(runId: string) { - const taskRun = await this.runStore.findRun({ id: runId }, this._prisma); - - if (!taskRun) { - logger.error("Task run not found", { runId }); - return; - } - - // v3 (engine V1) shutdown: don't retry abandoned V1 runs. v4 is unaffected. - if (isV3Disabled() && taskRun.engine === "V1") { - logger.debug("[RetryAttemptService] Skipping retry for shut-down v3 run", { runId }); - return; - } - - socketIo.coordinatorNamespace.emit("READY_FOR_RETRY", { - version: "v1", - runId, - }); - } - - static async enqueue(runId: string, runAt?: Date) { - return await commonWorker.enqueue({ - id: `retryAttempt:${runId}`, - job: "v3.retryAttempt", - payload: { - runId, - }, - availableAt: runAt, - }); - } -} diff --git a/apps/webapp/app/v3/services/taskRunConcurrencyTracker.server.ts b/apps/webapp/app/v3/services/taskRunConcurrencyTracker.server.ts deleted file mode 100644 index 98034761bc0..00000000000 --- a/apps/webapp/app/v3/services/taskRunConcurrencyTracker.server.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { env } from "~/env.server"; -import Redis, { type RedisOptions } from "ioredis"; -import { singleton } from "~/utils/singleton"; -import { type MessagePayload, type MessageQueueSubscriber } from "../marqs/types"; -import { z } from "zod"; -import { logger } from "~/services/logger.server"; - -type Options = { - redis: RedisOptions; -}; - -const ConcurrentMessageData = z.object({ - taskIdentifier: z.string(), - projectId: z.string(), - environmentId: z.string(), - environmentType: z.string(), -}); - -class TaskRunConcurrencyTracker implements MessageQueueSubscriber { - private redis: Redis; - - constructor(config: Options) { - this.redis = new Redis(config.redis); - } - - async messageEnqueued(message: MessagePayload): Promise {} - - async messageDequeued(message: MessagePayload): Promise { - logger.debug("TaskRunConcurrencyTracker.messageDequeued()", { - data: message.data, - messageId: message.messageId, - }); - - const data = this.getMessageData(message); - if (!data) { - logger.info( - `TaskRunConcurrencyTracker.messageDequeued(): could not parse message data`, - message - ); - return; - } - - await this.executionStarted({ - projectId: data.projectId, - taskId: data.taskIdentifier, - runId: message.messageId, - environmentId: data.environmentId, - deployed: data.environmentType !== "DEVELOPMENT", - }); - } - - async messageAcked(message: MessagePayload): Promise { - logger.debug("TaskRunConcurrencyTracker.messageAcked()", { - data: message.data, - messageId: message.messageId, - }); - - const data = this.getMessageData(message); - if (!data) { - logger.info( - `TaskRunConcurrencyTracker.messageAcked(): could not parse message data`, - message - ); - return; - } - - await this.executionFinished({ - projectId: data.projectId, - taskId: data.taskIdentifier, - runId: message.messageId, - environmentId: data.environmentId, - deployed: data.environmentType !== "DEVELOPMENT", - }); - } - - async messageNacked(message: MessagePayload): Promise { - logger.debug("TaskRunConcurrencyTracker.messageNacked()", { - data: message.data, - messageId: message.messageId, - }); - - const data = this.getMessageData(message); - if (!data) { - logger.info( - `TaskRunConcurrencyTracker.messageNacked(): could not parse message data`, - message - ); - return; - } - - await this.executionFinished({ - projectId: data.projectId, - taskId: data.taskIdentifier, - runId: message.messageId, - environmentId: data.environmentId, - deployed: data.environmentType !== "DEVELOPMENT", - }); - } - - async messageReplaced(message: MessagePayload): Promise { - logger.debug("TaskRunConcurrencyTracker.messageReplaced()", { - data: message.data, - messageId: message.messageId, - }); - - const data = this.getMessageData(message); - if (!data) { - logger.info( - `TaskRunConcurrencyTracker.messageReplaced(): could not parse message data`, - message - ); - return; - } - - await this.executionFinished({ - projectId: data.projectId, - taskId: data.taskIdentifier, - runId: message.messageId, - environmentId: data.environmentId, - deployed: data.environmentType !== "DEVELOPMENT", - }); - } - - async messageRequeued(message: MessagePayload): Promise { - logger.debug("TaskRunConcurrencyTracker.messageRequeued()", { - data: message.data, - messageId: message.messageId, - }); - - const data = this.getMessageData(message); - - if (!data) { - logger.info( - `TaskRunConcurrencyTracker.messageReplaced(): could not parse message data`, - message - ); - return; - } - - await this.executionFinished({ - projectId: data.projectId, - taskId: data.taskIdentifier, - runId: message.messageId, - environmentId: data.environmentId, - deployed: data.environmentType !== "DEVELOPMENT", - }); - } - - private getMessageData(message: MessagePayload) { - const result = ConcurrentMessageData.safeParse(message.data); - if (result.success) { - return result.data; - } - return; - } - - private async executionStarted({ - projectId, - taskId, - runId, - environmentId, - deployed, - }: { - projectId: string; - taskId: string; - runId: string; - environmentId: string; - deployed: boolean; - }): Promise { - try { - const pipeline = this.redis.pipeline(); - - pipeline.sadd(this.getTaskKey(projectId, taskId), runId); - pipeline.sadd(this.getTaskEnvironmentKey(projectId, taskId, environmentId), runId); - pipeline.sadd(this.getEnvironmentKey(projectId, environmentId), runId); - pipeline.sadd(this.getGlobalKey(deployed), runId); - - await pipeline.exec(); - } catch (error) { - logger.error("TaskRunConcurrencyTracker.executionStarted() error", { error }); - } - } - - private async executionFinished({ - projectId, - taskId, - runId, - environmentId, - deployed, - }: { - projectId: string; - taskId: string; - runId: string; - environmentId: string; - deployed: boolean; - }): Promise { - try { - const pipeline = this.redis.pipeline(); - - pipeline.srem(this.getTaskKey(projectId, taskId), runId); - pipeline.srem(this.getTaskEnvironmentKey(projectId, taskId, environmentId), runId); - pipeline.srem(this.getEnvironmentKey(projectId, environmentId), runId); - pipeline.srem(this.getGlobalKey(deployed), runId); - - await pipeline.exec(); - } catch (error) { - logger.error("TaskRunConcurrencyTracker.executionFinished() error", { error }); - } - } - - async taskConcurrentRunCount(projectId: string, taskId: string): Promise { - return await this.redis.scard(this.getTaskKey(projectId, taskId)); - } - - async globalConcurrentRunCount(deployed: boolean): Promise { - return await this.redis.scard(this.getGlobalKey(deployed)); - } - - async currentlyExecutingRuns(projectId: string, taskId: string): Promise { - return await this.redis.smembers(this.getTaskKey(projectId, taskId)); - } - - private async getTaskCounts(projectId: string, taskIds: string[]): Promise { - try { - const pipeline = this.redis.pipeline(); - taskIds.forEach((taskId) => { - pipeline.scard(this.getTaskKey(projectId, taskId)); - }); - const results = await pipeline.exec(); - if (!results) { - return []; - } - return results.map(([err, count]) => { - if (err) { - console.error("Error in getTaskCounts:", err); - return 0; - } - return count as number; - }); - } catch (error) { - logger.error("TaskRunConcurrencyTracker.getTaskCounts() error", { error }); - return []; - } - } - - async projectTotalConcurrentRunCount(projectId: string, taskIds: string[]): Promise { - const counts = await this.getTaskCounts(projectId, taskIds); - return counts.reduce((total, count) => total + count, 0); - } - - async taskConcurrentRunCounts( - projectId: string, - taskIds: string[] - ): Promise> { - const counts = await this.getTaskCounts(projectId, taskIds); - return taskIds.reduce( - (acc, taskId, index) => { - acc[taskId] = counts[index] ?? 0; - return acc; - }, - {} as Record - ); - } - - async environmentConcurrentRunCounts( - projectId: string, - environmentIds: string[] - ): Promise> { - try { - const pipeline = this.redis.pipeline(); - environmentIds.forEach((environmentId) => { - pipeline.scard(this.getEnvironmentKey(projectId, environmentId)); - }); - const results = await pipeline.exec(); - if (!results) { - return Object.fromEntries(environmentIds.map((id) => [id, 0])); - } - - return results.reduce( - (acc, [err, count], index) => { - if (err) { - console.error("Error in environmentConcurrentRunCounts:", err); - return acc; - } - acc[environmentIds[index]] = count as number; - return acc; - }, - {} as Record - ); - } catch (error) { - logger.error("TaskRunConcurrencyTracker.environmentConcurrentRunCounts() error", { error }); - return Object.fromEntries(environmentIds.map((id) => [id, 0])); - } - } - - private getTaskKey(projectId: string, taskId: string): string { - return `project:${projectId}:task:${taskId}`; - } - - private getTaskEnvironmentKey(projectId: string, taskId: string, environmentId: string): string { - return `project:${projectId}:task:${taskId}:env:${environmentId}`; - } - - private getGlobalKey(deployed: boolean): string { - return `global:${deployed ? "deployed" : "dev"}`; - } - - private getEnvironmentKey(projectId: string, environmentId: string): string { - return `project:${projectId}:env:${environmentId}`; - } -} - -export const concurrencyTracker = singleton("concurrency-tracker", getTracker); - -function getTracker() { - if (!env.REDIS_HOST || !env.REDIS_PORT) { - throw new Error( - "Could not initialize TaskRunConcurrencyTracker because process.env.REDIS_HOST and process.env.REDIS_PORT are required to be set. " - ); - } - - logger.debug("Initializing TaskRunConcurrencyTracker", { - redisHost: env.REDIS_HOST, - redisPort: env.REDIS_PORT, - }); - - return new TaskRunConcurrencyTracker({ - redis: { - keyPrefix: "concurrencytracker:", - port: env.REDIS_PORT, - host: env.REDIS_HOST, - username: env.REDIS_USERNAME, - password: env.REDIS_PASSWORD, - enableAutoPipelining: true, - ...(env.REDIS_TLS_DISABLED === "true" ? {} : { tls: {} }), - }, - }); -} diff --git a/apps/webapp/app/v3/services/timeoutDeployment.server.ts b/apps/webapp/app/v3/services/timeoutDeployment.server.ts index 512576836a0..fa3de698e36 100644 --- a/apps/webapp/app/v3/services/timeoutDeployment.server.ts +++ b/apps/webapp/app/v3/services/timeoutDeployment.server.ts @@ -3,7 +3,6 @@ import { BaseService } from "./baseService.server"; import { commonWorker } from "../commonWorker.server"; import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server"; import { type PrismaClientOrTransaction } from "~/db.server"; -import { workerQueue } from "~/services/worker.server"; import { DeploymentService } from "./deployment.server"; import { recordDeploymentOutcome } from "./recordDeploymentOutcome.server"; @@ -96,8 +95,6 @@ export class TimeoutDeploymentService extends BaseService { } static async dequeue(deploymentId: string, tx?: PrismaClientOrTransaction) { - // For backwards compatibility during transition, we need to dequeue/ack from both workers - await workerQueue.dequeue(`timeoutDeployment:${deploymentId}`, { tx }); await commonWorker.ack(`timeoutDeployment:${deploymentId}`); } } diff --git a/apps/webapp/app/v3/services/triggerTaskV1.server.ts b/apps/webapp/app/v3/services/triggerTaskV1.server.ts deleted file mode 100644 index e41e247bd05..00000000000 --- a/apps/webapp/app/v3/services/triggerTaskV1.server.ts +++ /dev/null @@ -1,762 +0,0 @@ -import type { IOPacket, TriggerTaskRequestBody } from "@trigger.dev/core/v3"; -import { - packetRequiresOffloading, - taskRunErrorEnhancer, - taskRunErrorToString, -} from "@trigger.dev/core/v3"; -import { - parseNaturalLanguageDuration, - sanitizeQueueName, - stringifyDuration, -} from "@trigger.dev/core/v3/isomorphic"; -import { Prisma } from "@trigger.dev/database"; -import { z } from "zod"; -import { env } from "~/env.server"; -import { MAX_TAGS_PER_RUN } from "~/models/taskRunTag.server"; -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { autoIncrementCounter } from "~/services/autoIncrementCounter.server"; -import { logger } from "~/services/logger.server"; -import { getEntitlement } from "~/services/platform.v3.server"; -import { parseDelay } from "~/utils/delays"; -import { resolveIdempotencyKeyTTL } from "~/utils/idempotencyKeys.server"; -import { handleMetadataPacket } from "~/utils/packets"; -import { marqs } from "~/v3/marqs/index.server"; -import { getV3EventRepository } from "../eventRepository/index.server"; -import { generateFriendlyId } from "../friendlyIdentifiers"; -import { findCurrentWorkerFromEnvironment } from "../models/workerDeployment.server"; -import { guardQueueSizeLimitsForEnv } from "../queueSizeLimits.server"; -import { uploadPacketToObjectStore } from "../objectStore.server"; -import { removeQueueConcurrencyLimits, updateQueueConcurrencyLimits } from "../runQueue.server"; -import { isFinalAttemptStatus, isFinalRunStatus } from "../taskStatus"; -import { startActiveSpan } from "../tracer.server"; -import { clampMaxDuration } from "../utils/maxDuration"; -import { BaseService, ServiceValidationError } from "./baseService.server"; -import { attemptInEnvironmentWhere, batchRunInEnvironmentWhere } from "./triggerV1Scoping"; -import { EnqueueDelayedRunService } from "./enqueueDelayedRun.server"; -import { enqueueRun } from "./enqueueRun.server"; -import { ExpireEnqueuedRunService } from "./expireEnqueuedRun.server"; -import type { TriggerTaskServiceOptions, TriggerTaskServiceResult } from "./triggerTask.server"; -import { MAX_ATTEMPTS, OutOfEntitlementError } from "./triggerTask.server"; - -// This is here for backwords compatibility for v3 users -const QueueOptions = z.object({ - name: z.string(), - concurrencyLimit: z.number().int().optional(), -}); - -/** @deprecated Use TriggerTaskService in `triggerTask.server.ts` instead. */ -export class TriggerTaskServiceV1 extends BaseService { - public async call( - taskId: string, - environment: AuthenticatedEnvironment, - body: TriggerTaskRequestBody, - options: TriggerTaskServiceOptions = {}, - attempt: number = 0 - ): Promise { - return await this.traceWithEnv("call()", environment, async (span) => { - span.setAttribute("taskId", taskId); - span.setAttribute("attempt", attempt); - - if (attempt > MAX_ATTEMPTS) { - throw new ServiceValidationError( - `Failed to trigger ${taskId} after ${MAX_ATTEMPTS} attempts.` - ); - } - - const idempotencyKey = options.idempotencyKey ?? body.options?.idempotencyKey; - const idempotencyKeyExpiresAt = - options.idempotencyKeyExpiresAt ?? - resolveIdempotencyKeyTTL(body.options?.idempotencyKeyTTL) ?? - new Date(Date.now() + 24 * 60 * 60 * 1000 * 30); // 30 days - - const delayUntil = await parseDelay(body.options?.delay); - - const ttl = - typeof body.options?.ttl === "number" - ? stringifyDuration(body.options?.ttl) - : (body.options?.ttl ?? (environment.type === "DEVELOPMENT" ? "10m" : undefined)); - - const existingRun = idempotencyKey - ? await this._prisma.taskRun.findFirst({ - where: { - runtimeEnvironmentId: environment.id, - idempotencyKey, - taskIdentifier: taskId, - }, - }) - : undefined; - - if (existingRun) { - if ( - existingRun.idempotencyKeyExpiresAt && - existingRun.idempotencyKeyExpiresAt < new Date() - ) { - logger.debug("[TriggerTaskService][call] Idempotency key has expired", { - idempotencyKey: options.idempotencyKey, - run: existingRun, - }); - - // Update the existing batch to remove the idempotency key - await this._prisma.taskRun.update({ - where: { id: existingRun.id }, - data: { idempotencyKey: null }, - }); - } else { - span.setAttribute("runId", existingRun.friendlyId); - - return { run: existingRun, isCached: true }; - } - } - - if (environment.type !== "DEVELOPMENT" && !options.skipChecks) { - const result = await getEntitlement(environment.organizationId); - if (result && result.hasAccess === false) { - throw new OutOfEntitlementError(); - } - } - - if (!options.skipChecks) { - const queueSizeGuard = await guardQueueSizeLimitsForEnv(environment, marqs); - - logger.debug("Queue size guard result", { - queueSizeGuard, - environment: { - id: environment.id, - type: environment.type, - organization: environment.organization, - project: environment.project, - }, - }); - - if (!queueSizeGuard.isWithinLimits) { - throw new ServiceValidationError( - `Cannot trigger ${taskId} as the queue size limit for this environment has been reached. The maximum size is ${queueSizeGuard.maximumSize}`, - undefined, - "warn" - ); - } - } - - if ( - body.options?.tags && - typeof body.options.tags !== "string" && - body.options.tags.length > MAX_TAGS_PER_RUN - ) { - throw new ServiceValidationError( - `Runs can only have ${MAX_TAGS_PER_RUN} tags, you're trying to set ${body.options.tags.length}.` - ); - } - - const runFriendlyId = options?.runFriendlyId ?? generateFriendlyId("run"); - - const payloadPacket = await this.#handlePayloadPacket( - body.payload, - body.options?.payloadType ?? "application/json", - runFriendlyId, - environment - ); - - const metadataPacket = body.options?.metadata - ? handleMetadataPacket( - body.options?.metadata, - body.options?.metadataType ?? "application/json", - env.TASK_RUN_METADATA_MAXIMUM_SIZE - ) - : undefined; - - const dependentAttempt = body.options?.dependentAttempt - ? await this._prisma.taskRunAttempt.findFirst({ - where: attemptInEnvironmentWhere(body.options.dependentAttempt, environment.id), - include: { - taskRun: { - select: { - id: true, - status: true, - taskIdentifier: true, - rootTaskRunId: true, - depth: true, - queueTimestamp: true, - queue: true, - taskEventStore: true, - }, - }, - }, - }) - : undefined; - - if ( - dependentAttempt && - (isFinalAttemptStatus(dependentAttempt.status) || - isFinalRunStatus(dependentAttempt.taskRun.status)) - ) { - logger.debug("Dependent attempt or run is in a terminal state", { - dependentAttempt: dependentAttempt, - }); - - if (isFinalAttemptStatus(dependentAttempt.status)) { - throw new ServiceValidationError( - `Cannot trigger ${taskId} as the parent attempt has a status of ${dependentAttempt.status}` - ); - } else { - throw new ServiceValidationError( - `Cannot trigger ${taskId} as the parent run has a status of ${dependentAttempt.taskRun.status}` - ); - } - } - - const parentAttempt = body.options?.parentAttempt - ? await this._prisma.taskRunAttempt.findFirst({ - where: attemptInEnvironmentWhere(body.options.parentAttempt, environment.id), - include: { - taskRun: { - select: { - id: true, - status: true, - taskIdentifier: true, - rootTaskRunId: true, - depth: true, - taskEventStore: true, - }, - }, - }, - }) - : undefined; - - const dependentBatchRun = body.options?.dependentBatch - ? await this._prisma.batchTaskRun.findFirst({ - where: batchRunInEnvironmentWhere(body.options.dependentBatch, environment.id), - include: { - dependentTaskAttempt: { - include: { - taskRun: { - select: { - id: true, - status: true, - taskIdentifier: true, - rootTaskRunId: true, - depth: true, - queueTimestamp: true, - queue: true, - taskEventStore: true, - }, - }, - }, - }, - }, - }) - : undefined; - - if ( - dependentBatchRun && - dependentBatchRun.dependentTaskAttempt && - (isFinalAttemptStatus(dependentBatchRun.dependentTaskAttempt.status) || - isFinalRunStatus(dependentBatchRun.dependentTaskAttempt.taskRun.status)) - ) { - logger.debug("Dependent batch run task attempt or run has been canceled", { - dependentBatchRunId: dependentBatchRun.id, - status: dependentBatchRun.status, - attempt: dependentBatchRun.dependentTaskAttempt, - }); - - if (isFinalAttemptStatus(dependentBatchRun.dependentTaskAttempt.status)) { - throw new ServiceValidationError( - `Cannot trigger ${taskId} as the parent attempt has a status of ${dependentBatchRun.dependentTaskAttempt.status}` - ); - } else { - throw new ServiceValidationError( - `Cannot trigger ${taskId} as the parent run has a status of ${dependentBatchRun.dependentTaskAttempt.taskRun.status}` - ); - } - } - - const parentBatchRun = body.options?.parentBatch - ? await this._prisma.batchTaskRun.findFirst({ - where: batchRunInEnvironmentWhere(body.options.parentBatch, environment.id), - include: { - dependentTaskAttempt: { - include: { - taskRun: { - select: { - id: true, - status: true, - taskIdentifier: true, - rootTaskRunId: true, - }, - }, - }, - }, - }, - }) - : undefined; - - const { repository, store } = await getV3EventRepository( - environment.organization.id, - dependentAttempt?.taskRun.taskEventStore ?? - parentAttempt?.taskRun.taskEventStore ?? - dependentBatchRun?.dependentTaskAttempt?.taskRun.taskEventStore - ); - - try { - const result = await repository.traceEvent( - taskId, - { - context: options.traceContext, - spanParentAsLink: options.spanParentAsLink, - kind: "SERVER", - environment, - taskSlug: taskId, - attributes: { - properties: {}, - style: { - icon: options.customIcon ?? "task", - }, - }, - incomplete: true, - immediate: true, - startTime: options.overrideCreatedAt - ? BigInt(options.overrideCreatedAt.getTime()) * BigInt(1000000) - : undefined, - }, - async (event, traceContext, traceparent) => { - const run = await autoIncrementCounter.incrementInTransaction( - `v3-run:${environment.id}:${taskId}`, - async (num, tx) => { - const lockedToBackgroundWorker = body.options?.lockToVersion - ? await tx.backgroundWorker.findFirst({ - where: { - projectId: environment.projectId, - runtimeEnvironmentId: environment.id, - version: body.options?.lockToVersion, - }, - }) - : undefined; - - let queueName = sanitizeQueueName( - await this.#getQueueName(taskId, environment, body.options?.queue?.name) - ); - - // Check that the queuename is not an empty string - if (!queueName) { - queueName = sanitizeQueueName(`task/${taskId}`); - } - - span.setAttribute("queueName", queueName); - - const bodyTags = - typeof body.options?.tags === "string" ? [body.options.tags] : body.options?.tags; - - const depth = dependentAttempt - ? dependentAttempt.taskRun.depth + 1 - : parentAttempt - ? parentAttempt.taskRun.depth + 1 - : dependentBatchRun?.dependentTaskAttempt - ? dependentBatchRun.dependentTaskAttempt.taskRun.depth + 1 - : 0; - - const queueTimestamp = - options.queueTimestamp ?? - dependentAttempt?.taskRun.queueTimestamp ?? - dependentBatchRun?.dependentTaskAttempt?.taskRun.queueTimestamp ?? - delayUntil ?? - new Date(); - - const taskRun = await tx.taskRun.create({ - data: { - status: delayUntil ? "DELAYED" : "PENDING", - number: num, - friendlyId: runFriendlyId, - runtimeEnvironmentId: environment.id, - environmentType: environment.type, - organizationId: environment.organizationId, - projectId: environment.projectId, - idempotencyKey, - idempotencyKeyExpiresAt: idempotencyKey ? idempotencyKeyExpiresAt : undefined, - taskIdentifier: taskId, - payload: payloadPacket.data ?? "", - payloadType: payloadPacket.dataType, - context: body.context, - traceContext: traceContext, - traceId: event.traceId, - spanId: event.spanId, - parentSpanId: - options.parentAsLinkType === "replay" ? undefined : traceparent?.spanId, - lockedToVersionId: lockedToBackgroundWorker?.id, - taskVersion: lockedToBackgroundWorker?.version, - sdkVersion: lockedToBackgroundWorker?.sdkVersion, - cliVersion: lockedToBackgroundWorker?.cliVersion, - concurrencyKey: body.options?.concurrencyKey, - queue: queueName, - isTest: body.options?.test ?? false, - delayUntil, - queuedAt: delayUntil ? undefined : new Date(), - queueTimestamp, - maxAttempts: body.options?.maxAttempts, - taskEventStore: store, - ttl, - parentTaskRunId: - dependentAttempt?.taskRun.id ?? - parentAttempt?.taskRun.id ?? - dependentBatchRun?.dependentTaskAttempt?.taskRun.id, - parentTaskRunAttemptId: - dependentAttempt?.id ?? - parentAttempt?.id ?? - dependentBatchRun?.dependentTaskAttempt?.id, - rootTaskRunId: - dependentAttempt?.taskRun.rootTaskRunId ?? - dependentAttempt?.taskRun.id ?? - parentAttempt?.taskRun.rootTaskRunId ?? - parentAttempt?.taskRun.id ?? - dependentBatchRun?.dependentTaskAttempt?.taskRun.rootTaskRunId ?? - dependentBatchRun?.dependentTaskAttempt?.taskRun.id, - replayedFromTaskRunFriendlyId: options.replayedFromTaskRunFriendlyId, - batchId: dependentBatchRun?.id ?? parentBatchRun?.id, - resumeParentOnCompletion: !!(dependentAttempt ?? dependentBatchRun), - depth, - metadata: metadataPacket?.data, - metadataType: metadataPacket?.dataType, - seedMetadata: metadataPacket?.data, - seedMetadataType: metadataPacket?.dataType, - maxDurationInSeconds: body.options?.maxDuration - ? clampMaxDuration(body.options.maxDuration) - : undefined, - runTags: bodyTags, - oneTimeUseToken: options.oneTimeUseToken, - machinePreset: body.options?.machine, - scheduleId: options.scheduleId, - scheduleInstanceId: options.scheduleInstanceId, - createdAt: options.overrideCreatedAt, - bulkActionGroupIds: body.options?.bulkActionId - ? [body.options.bulkActionId] - : undefined, - }, - }); - - event.setAttribute("runId", taskRun.friendlyId); - span.setAttribute("runId", taskRun.friendlyId); - - if (dependentAttempt) { - await tx.taskRunDependency.create({ - data: { - taskRunId: taskRun.id, - dependentAttemptId: dependentAttempt.id, - }, - }); - } else if (dependentBatchRun) { - await tx.taskRunDependency.create({ - data: { - taskRunId: taskRun.id, - dependentBatchRunId: dependentBatchRun.id, - }, - }); - } - - if (body.options?.queue) { - const concurrencyLimit = - typeof body.options.queue?.concurrencyLimit === "number" - ? Math.max( - Math.min( - body.options.queue.concurrencyLimit, - environment.maximumConcurrencyLimit - ), - 0 - ) - : body.options.queue?.concurrencyLimit; - - let taskQueue = await tx.taskQueue.findFirst({ - where: { - runtimeEnvironmentId: environment.id, - name: queueName, - }, - }); - - if (!taskQueue) { - // handle conflicts with existing queues - taskQueue = await tx.taskQueue.create({ - data: { - friendlyId: generateFriendlyId("queue"), - name: queueName, - concurrencyLimit, - runtimeEnvironmentId: environment.id, - projectId: environment.projectId, - type: "NAMED", - }, - }); - } - - if (typeof concurrencyLimit === "number") { - logger.debug("TriggerTaskService: updating concurrency limit", { - runId: taskRun.id, - friendlyId: taskRun.friendlyId, - taskQueue, - orgId: environment.organizationId, - projectId: environment.projectId, - concurrencyLimit, - queueOptions: body.options?.queue, - }); - - await updateQueueConcurrencyLimits( - environment, - taskQueue.name, - concurrencyLimit - ); - } else if (concurrencyLimit === null) { - logger.debug("TriggerTaskService: removing concurrency limit", { - runId: taskRun.id, - friendlyId: taskRun.friendlyId, - taskQueue, - orgId: environment.organizationId, - projectId: environment.projectId, - queueOptions: body.options?.queue, - }); - - await removeQueueConcurrencyLimits(environment, taskQueue.name); - } - } - - if (taskRun.delayUntil) { - await EnqueueDelayedRunService.enqueue(taskRun.id, taskRun.delayUntil); - } - - if (!taskRun.delayUntil && taskRun.ttl) { - const expireAt = parseNaturalLanguageDuration(taskRun.ttl); - - if (expireAt) { - await ExpireEnqueuedRunService.enqueue(taskRun.id, expireAt); - } - } - - return taskRun; - }, - async (_, tx) => { - const counter = await tx.taskRunNumberCounter.findUnique({ - where: { - taskIdentifier_environmentId: { - taskIdentifier: taskId, - environmentId: environment.id, - }, - }, - select: { lastNumber: true }, - }); - - return counter?.lastNumber; - }, - this._prisma - ); - - if (!run) { - return; - } - - // Now enqueue the run if it's not delayed - if (run.status === "PENDING") { - const enqueueResult = await enqueueRun({ - env: environment, - run, - dependentRun: - dependentAttempt?.taskRun ?? dependentBatchRun?.dependentTaskAttempt?.taskRun, - }); - - if (!enqueueResult.ok) { - // Now we need to fail the run with enqueueResult.error and make sure and - // set the traced event to failed as well - await this._prisma.taskRun.update({ - where: { id: run.id }, - data: { - status: "SYSTEM_FAILURE", - completedAt: new Date(), - error: enqueueResult.error, - }, - }); - - event.failWithError(enqueueResult.error); - - return { - run, - isCached: false, - error: enqueueResult.error, - }; - } - } - - return { run, isCached: false }; - } - ); - - if (result?.error) { - throw new ServiceValidationError( - taskRunErrorToString(taskRunErrorEnhancer(result.error)) - ); - } - - const run = result?.run; - - if (!run) { - return; - } - - return { - run, - isCached: result?.isCached, - }; - } catch (error) { - // Detect a prisma transaction Unique constraint violation - if (error instanceof Prisma.PrismaClientKnownRequestError) { - logger.debug("TriggerTask: Prisma transaction error", { - code: error.code, - message: error.message, - meta: error.meta, - }); - - if (error.code === "P2002") { - const target = error.meta?.target; - - if ( - Array.isArray(target) && - target.length > 0 && - typeof target[0] === "string" && - target[0].includes("oneTimeUseToken") - ) { - throw new ServiceValidationError( - `Cannot trigger ${taskId} with a one-time use token as it has already been used.` - ); - } else if ( - Array.isArray(target) && - target.length == 2 && - typeof target[0] === "string" && - typeof target[1] === "string" && - target[0] == "runtimeEnvironmentId" && - target[1] == "name" && - error.message.includes("prisma.taskQueue.create") - ) { - throw new Error( - `Failed to trigger ${taskId} as the queue could not be created do to a unique constraint error, please try again.` - ); - } else if ( - Array.isArray(target) && - target.length == 3 && - typeof target[0] === "string" && - typeof target[1] === "string" && - typeof target[2] === "string" && - target[0] == "runtimeEnvironmentId" && - target[1] == "taskIdentifier" && - target[2] == "idempotencyKey" - ) { - logger.debug("TriggerTask: Idempotency key violation, retrying...", { - taskId, - environmentId: environment.id, - idempotencyKey, - }); - // We need to retry the task run creation as the idempotency key has been used - return await this.call(taskId, environment, body, options, attempt + 1); - } else { - throw new ServiceValidationError( - `Cannot trigger ${taskId} as it has already been triggered with the same idempotency key.` - ); - } - } - } - - throw error; - } - }); - } - - async #getQueueName(taskId: string, environment: AuthenticatedEnvironment, queueName?: string) { - if (queueName) { - return queueName; - } - - const defaultQueueName = `task/${taskId}`; - - const worker = await findCurrentWorkerFromEnvironment(environment); - - if (!worker) { - logger.debug("Failed to get queue name: No worker found", { - taskId, - environmentId: environment.id, - }); - - return defaultQueueName; - } - - const task = await this._prisma.backgroundWorkerTask.findFirst({ - where: { - workerId: worker.id, - slug: taskId, - }, - }); - - if (!task) { - console.log("Failed to get queue name: No task found", { - taskId, - environmentId: environment.id, - }); - - return defaultQueueName; - } - - const queueConfig = QueueOptions.optional().nullable().safeParse(task.queueConfig); - - if (!queueConfig.success) { - console.log("Failed to get queue name: Invalid queue config", { - taskId, - environmentId: environment.id, - queueConfig: task.queueConfig, - }); - - return defaultQueueName; - } - - return queueConfig.data?.name ?? defaultQueueName; - } - - async #handlePayloadPacket( - payload: any, - payloadType: string, - pathPrefix: string, - environment: AuthenticatedEnvironment - ) { - return await startActiveSpan("handlePayloadPacket()", async (span) => { - const packet = this.#createPayloadPacket(payload, payloadType); - - if (!packet.data) { - return packet; - } - - const { needsOffloading, size: _size } = packetRequiresOffloading( - packet, - env.TASK_PAYLOAD_OFFLOAD_THRESHOLD - ); - - if (!needsOffloading) { - return packet; - } - - const filename = `${pathPrefix}/payload.json`; - - const uploadedFilename = await uploadPacketToObjectStore( - filename, - packet.data, - packet.dataType, - environment - ); - - return { - data: uploadedFilename, - dataType: "application/store", - }; - }); - } - - #createPayloadPacket(payload: any, payloadType: string): IOPacket { - if (payloadType === "application/json") { - return { data: JSON.stringify(payload), dataType: "application/json" }; - } - - if (typeof payload === "string") { - return { data: payload, dataType: payloadType }; - } - - return { dataType: payloadType }; - } -} diff --git a/apps/webapp/app/v3/services/updateFatalRunError.server.ts b/apps/webapp/app/v3/services/updateFatalRunError.server.ts deleted file mode 100644 index ffb816dd762..00000000000 --- a/apps/webapp/app/v3/services/updateFatalRunError.server.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { BaseService } from "./baseService.server"; -import { logger } from "~/services/logger.server"; -import { isFatalRunStatus } from "../taskStatus"; -import type { TaskRunInternalError } from "@trigger.dev/core/v3"; -import { TaskRunErrorCodes } from "@trigger.dev/core/v3"; -import { FinalizeTaskRunService } from "./finalizeTaskRun.server"; - -export type UpdateFatalRunErrorServiceOptions = { - reason?: string; - exitCode?: number; - logs?: string; - errorCode?: TaskRunInternalError["code"]; -}; - -export class UpdateFatalRunErrorService extends BaseService { - public async call(runId: string, options?: UpdateFatalRunErrorServiceOptions) { - const opts = { - reason: "Worker crashed", - ...options, - }; - - logger.debug("UpdateFatalRunErrorService.call", { runId, opts }); - - const taskRun = await this.runStore.findRun({ id: runId }, this._prisma); - - if (!taskRun) { - logger.error("[UpdateFatalRunErrorService] Task run not found", { runId }); - return; - } - - if (!isFatalRunStatus(taskRun.status)) { - logger.warn("[UpdateFatalRunErrorService] Task run is not in a fatal state", { - runId, - status: taskRun.status, - }); - - return; - } - - logger.debug("[UpdateFatalRunErrorService] Updating crash error", { runId, options }); - - const finalizeService = new FinalizeTaskRunService(); - await finalizeService.call({ - id: taskRun.id, - status: "CRASHED", - error: { - type: "INTERNAL_ERROR", - code: opts.errorCode ?? TaskRunErrorCodes.TASK_RUN_CRASHED, - message: opts.reason, - stackTrace: opts.logs, - }, - }); - } -} diff --git a/apps/webapp/app/v3/sharedSocketConnection.ts b/apps/webapp/app/v3/sharedSocketConnection.ts deleted file mode 100644 index 445f0900bb6..00000000000 --- a/apps/webapp/app/v3/sharedSocketConnection.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { clientWebsocketMessages, serverWebsocketMessages } from "@trigger.dev/core/v3"; -import type { StructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger"; -import type { MessageCatalogToSocketIoEvents } from "@trigger.dev/core/v3/zodMessageHandler"; -import { ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler"; -import { Evt } from "evt"; -import { randomUUID } from "node:crypto"; -import type { DisconnectReason, Namespace, Socket } from "socket.io"; -import { env } from "~/env.server"; -import { logger } from "~/services/logger.server"; -import { SharedQueueConsumer } from "./marqs/sharedQueueConsumer.server"; - -interface SharedQueueConsumerPoolOptions { - sender: ZodMessageSender; - poolSize: number; -} - -class SharedQueueConsumerPool { - #consumers: SharedQueueConsumer[]; - - constructor(opts: SharedQueueConsumerPoolOptions) { - this.#consumers = Array(opts.poolSize) - .fill(null) - .map( - () => - new SharedQueueConsumer(opts.sender, { - interval: env.SHARED_QUEUE_CONSUMER_INTERVAL_MS, - nextTickInterval: env.SHARED_QUEUE_CONSUMER_NEXT_TICK_INTERVAL_MS, - }) - ); - } - - async start() { - await Promise.allSettled(this.#consumers.map((consumer) => consumer.start())); - } - - async stop() { - await Promise.allSettled(this.#consumers.map((consumer) => consumer.stop())); - } -} - -interface SharedSocketConnectionOptions { - namespace: Namespace< - MessageCatalogToSocketIoEvents, - MessageCatalogToSocketIoEvents - >; - socket: Socket< - MessageCatalogToSocketIoEvents, - MessageCatalogToSocketIoEvents - >; - logger?: StructuredLogger; - poolSize?: number; -} - -export class SharedSocketConnection { - public id: string; - public onClose: Evt = new Evt(); - - private _sender: ZodMessageSender; - private _sharedQueueConsumerPool: SharedQueueConsumerPool; - private _messageHandler: ZodMessageHandler; - private _defaultPoolSize = 10; - - constructor(opts: SharedSocketConnectionOptions) { - this.id = randomUUID(); - - this._sender = new ZodMessageSender({ - schema: serverWebsocketMessages, - sender: async (message) => { - return new Promise((resolve, reject) => { - try { - const { type, ...payload } = message; - opts.namespace.emit(type, payload as any); - resolve(); - } catch (err) { - reject(err); - } - }); - }, - canSendMessage() { - // Return true if there is at least 1 connected socket on the namespace - if (opts.namespace.sockets.size === 0) { - return false; - } - - return Array.from(opts.namespace.sockets.values()).some((socket) => socket.connected); - }, - }); - - logger.debug("Starting SharedQueueConsumer pool", { - poolSize: opts.poolSize ?? this._defaultPoolSize, - }); - - this._sharedQueueConsumerPool = new SharedQueueConsumerPool({ - poolSize: opts.poolSize ?? this._defaultPoolSize, - sender: this._sender, - }); - - opts.socket.on("disconnect", this.#handleClose.bind(this)); - opts.socket.on("error", this.#handleError.bind(this)); - - this._messageHandler = new ZodMessageHandler({ - schema: clientWebsocketMessages, - logger, - messages: { - READY_FOR_TASKS: async (payload) => { - this._sharedQueueConsumerPool.start(); - }, - BACKGROUND_WORKER_DEPRECATED: async (payload) => { - // await this._sharedConsumer.deprecateBackgroundWorker(payload.backgroundWorkerId); - }, - BACKGROUND_WORKER_MESSAGE: async (payload) => { - switch (payload.data.type) { - case "TASK_RUN_COMPLETED": { - // handled in coordinator namespace - break; - } - case "TASK_HEARTBEAT": { - // handled in coordinator namespace - break; - } - } - }, - }, - }); - this._messageHandler.registerHandlers(opts.socket, opts.logger ?? logger); - } - - async initialize() { - this._sender.send("SERVER_READY", { id: this.id }); - } - - async #handleClose(ev: DisconnectReason) { - await this._sharedQueueConsumerPool.stop(); - - this.onClose.post(ev); - } - - async #handleError(ev: Error) { - logger.error("Websocket error", { ev }); - } -} diff --git a/apps/webapp/app/v3/taskRunHeartbeatFailed.server.ts b/apps/webapp/app/v3/taskRunHeartbeatFailed.server.ts deleted file mode 100644 index c7b2c4aebe4..00000000000 --- a/apps/webapp/app/v3/taskRunHeartbeatFailed.server.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { logger } from "~/services/logger.server"; -import { marqs } from "~/v3/marqs/index.server"; - -import assertNever from "assert-never"; -import { FailedTaskRunService } from "./failedTaskRun.server"; -import { BaseService } from "./services/baseService.server"; -import type { PrismaClientOrTransaction } from "~/db.server"; -import { workerQueue } from "~/services/worker.server"; -import { socketIo } from "./handleSocketIo.server"; -import { TaskRunErrorCodes } from "@trigger.dev/core/v3"; -import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server"; -import { isV3Disabled } from "./engineDeprecation.server"; - -export class TaskRunHeartbeatFailedService extends BaseService { - public async call(runId: string) { - const taskRun = await this.runStore.findRun( - { - id: runId, - }, - { - select: { - id: true, - engine: true, - friendlyId: true, - status: true, - lockedAt: true, - runtimeEnvironmentId: true, - lockedToVersionId: true, - _count: { - select: { - attempts: true, - }, - }, - }, - }, - this._prisma - ); - - if (!taskRun) { - logger.error("[TaskRunHeartbeatFailedService] Task run not found", { - runId, - }); - - return; - } - - // v3 (engine V1) shutdown: leave abandoned V1 runs as-is instead of doing - // MarQS/DB work to fail or requeue them. v4 (V2) is unaffected. - if (isV3Disabled() && taskRun.engine === "V1") { - logger.debug("[TaskRunHeartbeatFailedService] Skipping heartbeat for shut-down v3 run", { - runId, - }); - return; - } - - const env = await controlPlaneResolver.resolveEnv(taskRun.runtimeEnvironmentId); - const lockedWorker = await controlPlaneResolver.resolveRunLockedWorker({ - lockedToVersionId: taskRun.lockedToVersionId, - }); - - if (!env) { - logger.debug("TaskRunHeartbeatFailedService: environment not found", { runId }); - return; - } - - const service = new FailedTaskRunService(); - - switch (taskRun.status) { - case "PENDING": - case "DEQUEUED": - case "WAITING_TO_RESUME": - case "PAUSED": { - const backInQueue = await marqs?.nackMessage(taskRun.id); - - if (backInQueue) { - logger.debug( - `[TaskRunHeartbeatFailedService] ${taskRun.status} run is back in the queue run`, - { - taskRun, - } - ); - } else { - logger.debug( - `[TaskRunHeartbeatFailedService] ${taskRun.status} run not back in the queue, failing`, - { taskRun } - ); - await service.call(taskRun.friendlyId, { - ok: false, - id: taskRun.friendlyId, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_RUN_HEARTBEAT_TIMEOUT, - message: "Did not receive a heartbeat from the worker in time", - }, - }); - } - - break; - } - case "EXECUTING": - case "RETRYING_AFTER_FAILURE": { - logger.debug(`[RequeueTaskRunService] ${taskRun.status} failing task run`, { taskRun }); - - await service.call(taskRun.friendlyId, { - ok: false, - id: taskRun.friendlyId, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_RUN_HEARTBEAT_TIMEOUT, - message: "Did not receive a heartbeat from the worker in time", - }, - }); - - break; - } - case "DELAYED": - case "PENDING_VERSION": - case "WAITING_FOR_DEPLOY": { - logger.debug( - `[TaskRunHeartbeatFailedService] ${taskRun.status} Removing task run from queue`, - { taskRun } - ); - - await marqs?.acknowledgeMessage( - taskRun.id, - "Run is either DELAYED or WAITING_FOR_DEPLOY so we cannot requeue it in TaskRunHeartbeatFailedService" - ); - - break; - } - case "SYSTEM_FAILURE": - case "INTERRUPTED": - case "CRASHED": - case "COMPLETED_WITH_ERRORS": - case "COMPLETED_SUCCESSFULLY": - case "EXPIRED": - case "TIMED_OUT": - case "CANCELED": { - logger.debug("[TaskRunHeartbeatFailedService] Task run is completed", { taskRun }); - - await marqs?.acknowledgeMessage( - taskRun.id, - "Task run is already completed in TaskRunHeartbeatFailedService" - ); - - try { - if (env.type === "DEVELOPMENT") { - return; - } - - // Signal to exit any leftover containers - socketIo.coordinatorNamespace.emit("REQUEST_RUN_CANCELLATION", { - version: "v1", - runId: taskRun.id, - // Give the run a few seconds to exit to complete any flushing etc - delayInMs: lockedWorker?.lockedToVersion?.supportsLazyAttempts ? 5_000 : undefined, - }); - } catch (error) { - logger.error("[TaskRunHeartbeatFailedService] Error signaling run cancellation", { - runId: taskRun.id, - error: error instanceof Error ? error.message : error, - }); - } - - break; - } - default: { - assertNever(taskRun.status); - } - } - } - - public static async enqueue(runId: string, runAt?: Date, tx?: PrismaClientOrTransaction) { - return await workerQueue.enqueue( - "v3.requeueTaskRun", - { runId }, - { runAt, jobKey: `requeueTaskRun:${runId}` } - ); - } - - public static async dequeue(runId: string, tx?: PrismaClientOrTransaction) { - return await workerQueue.dequeue(`requeueTaskRun:${runId}`, { tx }); - } -} diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 5392a418f16..50c0ef56f94 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -65,7 +65,6 @@ "@internal/schedule-engine": "workspace:*", "@internal/tracing": "workspace:*", "@internal/tsql": "workspace:*", - "@internal/zod-worker": "workspace:*", "@internationalized/date": "^3.5.1", "@jsonhero/schema-infer": "^0.1.5", "@kapaai/react-sdk": "^0.1.3", @@ -161,7 +160,6 @@ "evt": "^2.4.13", "express": "4.20.0", "framer-motion": "^10.12.11", - "graphile-worker": "0.16.6", "humanize-duration": "^3.27.3", "input-otp": "^1.4.2", "intl-parse-accept-language": "^1.0.0", diff --git a/apps/webapp/test/fairDequeuingStrategy.test.ts b/apps/webapp/test/fairDequeuingStrategy.test.ts deleted file mode 100644 index c51edb733d8..00000000000 --- a/apps/webapp/test/fairDequeuingStrategy.test.ts +++ /dev/null @@ -1,1478 +0,0 @@ -import { describe, expect, vi } from "vitest"; - -// Mock the db prisma client -vi.mock("~/db.server", () => ({ - prisma: {}, - $replica: {}, -})); - -import { redisTest } from "@internal/testcontainers"; -import { FairDequeuingStrategy } from "../app/v3/marqs/fairDequeuingStrategy.server.js"; -import { - calculateStandardDeviation, - createKeyProducer, - setupConcurrency, - setupQueue, -} from "./utils/marqs.js"; -import { trace } from "@opentelemetry/api"; -import type { EnvQueues } from "~/v3/marqs/types.js"; -import { MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET } from "~/v3/marqs/constants.server.js"; -import { createRedisClient } from "@internal/redis"; - -const tracer = trace.getTracer("test"); - -vi.setConfig({ testTimeout: 30_000 }); // 30 seconds timeout - -describe("FairDequeuingStrategy", () => { - redisTest("should distribute a single queue from a single env", async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-1", // for deterministic shuffling - }); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: Date.now() - 1000, // 1 second ago - queueId: "queue-1", - orgId: "org-1", - envId: "env-1", - }); - - const result = await strategy.distributeFairQueuesFromParentQueue("parent-queue", "consumer-1"); - - expect(result).toHaveLength(1); - expect(result[0]).toEqual({ - envId: "env-1", - queues: ["org:org-1:env:env-1:queue:queue-1"], - }); - }); - - redisTest("should respect env concurrency limits", async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 2, - parentQueueLimit: 100, - seed: "test-seed-3", - }); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: Date.now() - 1000, - queueId: "queue-1", - orgId: "org-1", - envId: "env-1", - }); - - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-1", currentConcurrency: 2, limit: 2 }, - }); - - const result = await strategy.distributeFairQueuesFromParentQueue("parent-queue", "consumer-1"); - expect(result).toHaveLength(0); - }); - - redisTest( - "should give extra concurrency when the env has reserve concurrency", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 2, - parentQueueLimit: 100, - seed: "test-seed-3", - }); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: Date.now() - 1000, - queueId: "queue-1", - orgId: "org-1", - envId: "env-1", - }); - - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-1", currentConcurrency: 2, limit: 2, reserveConcurrency: 1 }, - }); - - const result = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - expect(result).toHaveLength(1); - expect(result[0]).toEqual({ - envId: "env-1", - queues: ["org:org-1:env:env-1:queue:queue-1"], - }); - } - ); - - redisTest("should respect parentQueueLimit", async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 2, // Only take 2 queues - seed: "test-seed-6", - }); - - const now = Date.now(); - - // Setup 3 queues but parentQueueLimit is 2 - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 3000, - queueId: "queue-1", - orgId: "org-1", - envId: "env-1", - }); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 2000, - queueId: "queue-2", - orgId: "org-1", - envId: "env-1", - }); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 1000, - queueId: "queue-3", - orgId: "org-1", - envId: "env-1", - }); - - const result = await strategy.distributeFairQueuesFromParentQueue("parent-queue", "consumer-1"); - - expect(result).toHaveLength(1); - const queue1 = keyProducer.queueKey("org-1", "env-1", "queue-1"); - const queue2 = keyProducer.queueKey("org-1", "env-1", "queue-2"); - expect(result[0]).toEqual({ - envId: "env-1", - queues: [queue1, queue2], - }); - }); - - redisTest( - "should reuse snapshots across calls for the same consumer", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 10, - seed: "test-seed-reuse-1", - reuseSnapshotCount: 1, - }); - - const now = Date.now(); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 3000, - queueId: "queue-1", - orgId: "org-1", - envId: "env-1", - }); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 2000, - queueId: "queue-2", - orgId: "org-2", - envId: "env-2", - }); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 1000, - queueId: "queue-3", - orgId: "org-3", - envId: "env-3", - }); - - const startDistribute1 = performance.now(); - - const envResult = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - const result = flattenResults(envResult); - - const distribute1Duration = performance.now() - startDistribute1; - - console.log("First distribution took", distribute1Duration, "ms"); - - expect(result).toHaveLength(3); - // Should only get the two oldest queues - const queue1 = keyProducer.queueKey("org-1", "env-1", "queue-1"); - const queue2 = keyProducer.queueKey("org-2", "env-2", "queue-2"); - const queue3 = keyProducer.queueKey("org-3", "env-3", "queue-3"); - expect(result).toEqual([queue2, queue1, queue3]); - - const startDistribute2 = performance.now(); - - const _result2 = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - - const tolerance = 0.15; - const withTolerance = (value: number) => value * (1 + tolerance); - - const distribute2Duration = performance.now() - startDistribute2; - - console.log("Second distribution took", distribute2Duration, "ms"); - - // Make sure the second call is faster than the first - expect(distribute2Duration).toBeLessThan(distribute1Duration); - - const startDistribute3 = performance.now(); - - const _result3 = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - - const distribute3Duration = performance.now() - startDistribute3; - - console.log("Third distribution took", distribute3Duration, "ms"); - - // Make sure the third call is faster than the second - expect(withTolerance(distribute3Duration)).toBeGreaterThan(distribute2Duration); - } - ); - - redisTest( - "should fairly distribute queues across environments over time", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-5", - }); - - const now = Date.now(); - - // Test configuration - const orgs = ["org-1", "org-2", "org-3"]; - const envsPerOrg = 3; // Each org has 3 environments - const queuesPerEnv = 5; // Each env has 5 queues - const iterations = 1000; - - // Setup queues - for (const orgId of orgs) { - for (let envNum = 1; envNum <= envsPerOrg; envNum++) { - const envId = `env-${orgId}-${envNum}`; - - for (let queueNum = 1; queueNum <= queuesPerEnv; queueNum++) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - // Vary the ages slightly - score: now - Math.random() * 10000, - queueId: `queue-${orgId}-${envId}-${queueNum}`, - orgId, - envId, - }); - } - - // Setup reasonable concurrency limits - await setupConcurrency({ - redis, - keyProducer, - env: { id: envId, currentConcurrency: 1, limit: 5 }, - }); - } - } - - // Track distribution statistics - type PositionStats = { - firstPosition: number; // Count of times this env/org was first - positionSums: number; // Sum of positions (for averaging) - appearances: number; // Total number of appearances - }; - - const envStats: Record = {}; - const orgStats: Record = {}; - - // Initialize stats objects - for (const orgId of orgs) { - orgStats[orgId] = { firstPosition: 0, positionSums: 0, appearances: 0 }; - for (let envNum = 1; envNum <= envsPerOrg; envNum++) { - const envId = `env-${orgId}-${envNum}`; - envStats[envId] = { firstPosition: 0, positionSums: 0, appearances: 0 }; - } - } - - // Run multiple iterations - for (let i = 0; i < iterations; i++) { - const envResult = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - `consumer-${i % 3}` // Simulate 3 different consumers - ); - const result = flattenResults(envResult); - - // Track positions of queues - result.forEach((queueId, position) => { - const orgId = keyProducer.orgIdFromQueue(queueId); - const envId = keyProducer.envIdFromQueue(queueId); - - // Update org stats - orgStats[orgId].appearances++; - orgStats[orgId].positionSums += position; - if (position === 0) orgStats[orgId].firstPosition++; - - // Update env stats - envStats[envId].appearances++; - envStats[envId].positionSums += position; - if (position === 0) envStats[envId].firstPosition++; - }); - } - - // Calculate and log statistics - console.log("\nOrganization Statistics:"); - for (const [orgId, stats] of Object.entries(orgStats)) { - const avgPosition = stats.positionSums / stats.appearances; - const firstPositionPercentage = (stats.firstPosition / iterations) * 100; - console.log(`${orgId}: - First Position: ${firstPositionPercentage.toFixed(2)}% - Average Position: ${avgPosition.toFixed(2)} - Total Appearances: ${stats.appearances}`); - } - - console.log("\nEnvironment Statistics:"); - for (const [envId, stats] of Object.entries(envStats)) { - const avgPosition = stats.positionSums / stats.appearances; - const firstPositionPercentage = (stats.firstPosition / iterations) * 100; - console.log(`${envId}: - First Position: ${firstPositionPercentage.toFixed(2)}% - Average Position: ${avgPosition.toFixed(2)} - Total Appearances: ${stats.appearances}`); - } - - // Verify fairness of first position distribution - const expectedFirstPositionPercentage = 100 / orgs.length; - const firstPositionStdDevOrgs = calculateStandardDeviation( - Object.values(orgStats).map((stats) => (stats.firstPosition / iterations) * 100) - ); - - const expectedEnvFirstPositionPercentage = 100 / (orgs.length * envsPerOrg); - const firstPositionStdDevEnvs = calculateStandardDeviation( - Object.values(envStats).map((stats) => (stats.firstPosition / iterations) * 100) - ); - - // Assert reasonable fairness for first position - expect(firstPositionStdDevOrgs).toBeLessThan(5); // Allow 5% standard deviation for orgs - expect(firstPositionStdDevEnvs).toBeLessThan(5); // Allow 5% standard deviation for envs - - // Verify that each org and env gets a fair chance at first position - for (const [_orgId, stats] of Object.entries(orgStats)) { - const firstPositionPercentage = (stats.firstPosition / iterations) * 100; - expect(firstPositionPercentage).toBeGreaterThan(expectedFirstPositionPercentage * 0.7); // Within 30% of expected - expect(firstPositionPercentage).toBeLessThan(expectedFirstPositionPercentage * 1.3); - } - - for (const [_envId, stats] of Object.entries(envStats)) { - const firstPositionPercentage = (stats.firstPosition / iterations) * 100; - expect(firstPositionPercentage).toBeGreaterThan(expectedEnvFirstPositionPercentage * 0.7); // Within 30% of expected - expect(firstPositionPercentage).toBeLessThan(expectedEnvFirstPositionPercentage * 1.3); - } - - // Verify average positions are reasonably distributed - const avgPositionsOrgs = Object.values(orgStats).map( - (stats) => stats.positionSums / stats.appearances - ); - const avgPositionsEnvs = Object.values(envStats).map( - (stats) => stats.positionSums / stats.appearances - ); - - const avgPositionStdDevOrgs = calculateStandardDeviation(avgPositionsOrgs); - const avgPositionStdDevEnvs = calculateStandardDeviation(avgPositionsEnvs); - - expect(avgPositionStdDevOrgs).toBeLessThan(1); // Average positions should be fairly consistent - expect(avgPositionStdDevEnvs).toBeLessThan(1); - } - ); - - redisTest( - "should shuffle environments while maintaining age order within environments", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "fixed-seed", - }); - - const now = Date.now(); - - // Setup three environments, each with two queues of different ages - await Promise.all([ - // env-1: one old queue (3000ms old) and one new queue (1000ms old) - setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 3000, - queueId: "queue-1-old", - orgId: "org-1", - envId: "env-1", - }), - setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 1000, - queueId: "queue-1-new", - orgId: "org-1", - envId: "env-1", - }), - - // env-2: same pattern - setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 3000, - queueId: "queue-2-old", - orgId: "org-1", - envId: "env-2", - }), - setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 1000, - queueId: "queue-2-new", - orgId: "org-1", - envId: "env-2", - }), - ]); - - // Setup basic concurrency settings - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-1", currentConcurrency: 0, limit: 5 }, - }); - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-2", currentConcurrency: 0, limit: 5 }, - }); - - const envResult = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - const result = flattenResults(envResult); - - // Group queues by environment - const queuesByEnv = result.reduce( - (acc, queueId) => { - const envId = keyProducer.envIdFromQueue(queueId); - if (!acc[envId]) { - acc[envId] = []; - } - acc[envId].push(queueId); - return acc; - }, - {} as Record - ); - - // Verify that: - // 1. We got all queues - expect(result).toHaveLength(4); - - // 2. Queues are grouped by environment - for (const envQueues of Object.values(queuesByEnv)) { - expect(envQueues).toHaveLength(2); - - // 3. Within each environment, older queue comes before newer queue - const [firstQueue, secondQueue] = envQueues; - expect(firstQueue).toContain("old"); - expect(secondQueue).toContain("new"); - } - } - ); - - redisTest( - "should bias shuffling based on concurrency limits and available capacity", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const now = Date.now(); - - // Setup three environments with different concurrency settings - const envSetups = [ - { - envId: "env-1", - limit: 100, - current: 20, // Lots of available capacity - queueCount: 3, - }, - { - envId: "env-2", - limit: 50, - current: 40, // Less available capacity - queueCount: 3, - }, - { - envId: "env-3", - limit: 10, - current: 5, // Some available capacity - queueCount: 3, - }, - ]; - - // Setup queues and concurrency for each environment - for (const setup of envSetups) { - await setupConcurrency({ - redis, - keyProducer, - env: { - id: setup.envId, - currentConcurrency: setup.current, - limit: setup.limit, - }, - }); - - for (let i = 0; i < setup.queueCount; i++) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 1000 * (i + 1), - queueId: `queue-${i}`, - orgId: "org-1", - envId: setup.envId, - }); - } - } - - // Create multiple strategies with different seeds - const numStrategies = 5; - const strategies = Array.from( - { length: numStrategies }, - (_, i) => - new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: `test-seed-${i}`, - biases: { - concurrencyLimitBias: 0.8, - availableCapacityBias: 0.5, - queueAgeRandomization: 0.0, - }, - }) - ); - - // Run iterations across all strategies - const iterationsPerStrategy = 100; - const allResults: Record[] = []; - - for (const strategy of strategies) { - const firstPositionCounts: Record = {}; - - for (let i = 0; i < iterationsPerStrategy; i++) { - const envResult = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - `consumer-${i % 3}` - ); - const result = flattenResults(envResult); - - expect(result.length).toBeGreaterThan(0); - - const firstEnv = keyProducer.envIdFromQueue(result[0]); - firstPositionCounts[firstEnv] = (firstPositionCounts[firstEnv] || 0) + 1; - } - - allResults.push(firstPositionCounts); - } - - // Calculate average distributions across all strategies - const avgDistribution: Record = {}; - const envIds = ["env-1", "env-2", "env-3"]; - - for (const envId of envIds) { - const sum = allResults.reduce((acc, result) => acc + (result[envId] || 0), 0); - avgDistribution[envId] = sum / numStrategies; - } - - // Log individual strategy results and the average - console.log("\nResults by strategy:"); - allResults.forEach((result, i) => { - console.log(`Strategy ${i + 1}:`, result); - }); - - console.log("\nAverage distribution:", avgDistribution); - - // Calculate percentages from average distribution - const totalCount = Object.values(avgDistribution).reduce((sum, count) => sum + count, 0); - const highLimitPercentage = (avgDistribution["env-1"] / totalCount) * 100; - const lowLimitPercentage = (avgDistribution["env-3"] / totalCount) * 100; - - console.log("\nPercentages:"); - console.log("High limit percentage:", highLimitPercentage); - console.log("Low limit percentage:", lowLimitPercentage); - - // Verify distribution across all strategies - expect(highLimitPercentage).toBeLessThan(60); - expect(lowLimitPercentage).toBeGreaterThan(10); - expect(highLimitPercentage).toBeGreaterThan(lowLimitPercentage); - } - ); - - redisTest( - "should respect ageInfluence parameter for queue ordering", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const now = Date.now(); - - // Setup queues with different ages in the same environment - const queueAges = [ - { id: "queue-1", age: 5000 }, // oldest - { id: "queue-2", age: 3000 }, - { id: "queue-3", age: 1000 }, // newest - ]; - - // Helper function to run iterations with a specific age influence - async function runWithQueueAgeRandomization(queueAgeRandomization: number) { - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "fixed-seed", - biases: { - concurrencyLimitBias: 0, - availableCapacityBias: 0, - queueAgeRandomization, - }, - }); - - const positionCounts: Record = { - "queue-1": [0, 0, 0], - "queue-2": [0, 0, 0], - "queue-3": [0, 0, 0], - }; - - const iterations = 1000; - for (let i = 0; i < iterations; i++) { - const envResult = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - const result = flattenResults(envResult); - - result.forEach((queueId, position) => { - const baseQueueId = queueId.split(":").pop()!; - positionCounts[baseQueueId][position]++; - }); - } - - return positionCounts; - } - - // Setup test data - for (const { id, age } of queueAges) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - age, - queueId: id, - orgId: "org-1", - envId: "env-1", - }); - } - - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-1", currentConcurrency: 0, limit: 5 }, - }); - - // Test with different age influence values - const strictAge = await runWithQueueAgeRandomization(0); // Strict age-based ordering - const mixed = await runWithQueueAgeRandomization(0.5); // Mix of age and random - const fullyRandom = await runWithQueueAgeRandomization(1); // Completely random - - console.log("Distribution with strict age ordering (0.0):", strictAge); - console.log("Distribution with mixed ordering (0.5):", mixed); - console.log("Distribution with random ordering (1.0):", fullyRandom); - - // With strict age ordering (0.0), oldest should always be first - expect(strictAge["queue-1"][0]).toBe(1000); // Always in first position - expect(strictAge["queue-3"][0]).toBe(0); // Never in first position - - // With fully random (1.0), positions should still allow for some age bias - const randomFirstPositionSpread = Math.abs( - fullyRandom["queue-1"][0] - fullyRandom["queue-3"][0] - ); - expect(randomFirstPositionSpread).toBeLessThan(200); // Allow for larger spread in distribution - - // With mixed (0.5), should show preference for age but not absolute - expect(mixed["queue-1"][0]).toBeGreaterThan(mixed["queue-3"][0]); // Older preferred - expect(mixed["queue-3"][0]).toBeGreaterThan(0); // But newer still gets chances - } - ); - - redisTest( - "should respect maximumEnvCount and select envs based on queue ages", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-max-orgs", - maximumEnvCount: 2, // Only select top 2 orgs - }); - - const now = Date.now(); - - // Setup 4 envs with different queue age profiles - const envSetups = [ - { - envId: "env-1", - queues: [ - { age: 1000 }, // Average age: 1000 - ], - }, - { - envId: "env-2", - queues: [ - { age: 5000 }, // Average age: 5000 - { age: 5000 }, - ], - }, - { - envId: "env-3", - queues: [ - { age: 2000 }, // Average age: 2000 - { age: 2000 }, - ], - }, - { - envId: "env-4", - queues: [ - { age: 500 }, // Average age: 500 - { age: 500 }, - ], - }, - ]; - - // Setup queues and concurrency for each org - for (const setup of envSetups) { - await setupConcurrency({ - redis, - keyProducer, - env: { id: setup.envId, currentConcurrency: 0, limit: 5 }, - }); - - for (let i = 0; i < setup.queues.length; i++) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - setup.queues[i].age, - queueId: `queue-${setup.envId}-${i}`, - orgId: `org-${setup.envId}`, - envId: setup.envId, - }); - } - } - - // Run multiple iterations to verify consistent behavior - const iterations = 100; - const selectedEnvCounts: Record = {}; - - for (let i = 0; i < iterations; i++) { - const envResult = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - `consumer-${i}` - ); - const result = flattenResults(envResult); - - // Track which orgs were included in the result - const selectedEnvs = new Set(result.map((queueId) => keyProducer.envIdFromQueue(queueId))); - - // Verify we never get more than maximumOrgCount orgs - expect(selectedEnvs.size).toBeLessThanOrEqual(2); - - for (const envId of selectedEnvs) { - selectedEnvCounts[envId] = (selectedEnvCounts[envId] || 0) + 1; - } - } - - console.log("Environment selection counts:", selectedEnvCounts); - - // org-2 should be selected most often (highest average age) - expect(selectedEnvCounts["env-2"]).toBeGreaterThan(selectedEnvCounts["env-4"] || 0); - - // org-4 should be selected least often (lowest average age) - const env4Count = selectedEnvCounts["env-4"] || 0; - expect(env4Count).toBeLessThan(selectedEnvCounts["env-2"]); - - // Verify that envs with higher average queue age are selected more frequently - const sortedEnvs = Object.entries(selectedEnvCounts).sort((a, b) => b[1] - a[1]); - console.log("Sorted environment frequencies:", sortedEnvs); - - // The top 2 most frequently selected orgs should be env-2 and env-3 - // as they have the highest average queue ages - const topTwoEnvs = new Set([sortedEnvs[0][0], sortedEnvs[1][0]]); - expect(topTwoEnvs).toContain("env-2"); // Highest average age - expect(topTwoEnvs).toContain("env-3"); // Second highest average age - - // Calculate selection percentages - const totalSelections = Object.values(selectedEnvCounts).reduce((a, b) => a + b, 0); - const selectionPercentages = Object.entries(selectedEnvCounts).reduce( - (acc, [orgId, count]) => { - acc[orgId] = (count / totalSelections) * 100; - return acc; - }, - {} as Record - ); - - console.log("Environment selection percentages:", selectionPercentages); - - // Verify that env-2 (highest average age) gets selected in at least 40% of iterations - expect(selectionPercentages["env-2"]).toBeGreaterThan(40); - - // Verify that env-4 (lowest average age) gets selected in less than 20% of iterations - expect(selectionPercentages["env-4"] || 0).toBeLessThan(20); - } - ); - - redisTest( - "should not overly bias picking environments when queue have priority offset ages", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-max-orgs", - maximumEnvCount: 2, // Only select top 2 orgs - }); - - const now = Date.now(); - - // Setup 4 envs with different queue age profiles - const envSetups = [ - { - envId: "env-1", - queues: [ - { age: 1000 }, // Average age: 1000 - ], - }, - { - envId: "env-2", - queues: [ - { age: 5000 + MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET }, // Average age: 5000 + 1 year - { age: 5000 + MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET }, - ], - }, - { - envId: "env-3", - queues: [ - { age: 2000 }, // Average age: 2000 - { age: 2000 }, - ], - }, - { - envId: "env-4", - queues: [ - { age: 500 }, // Average age: 500 - { age: 500 }, - ], - }, - ]; - - // Setup queues and concurrency for each org - for (const setup of envSetups) { - await setupConcurrency({ - redis, - keyProducer, - env: { id: setup.envId, currentConcurrency: 0, limit: 5 }, - }); - - for (let i = 0; i < setup.queues.length; i++) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - setup.queues[i].age, - queueId: `queue-${setup.envId}-${i}`, - orgId: `org-${setup.envId}`, - envId: setup.envId, - }); - } - } - - // Run multiple iterations to verify consistent behavior - const iterations = 100; - const selectedEnvCounts: Record = {}; - - for (let i = 0; i < iterations; i++) { - const envResult = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - `consumer-${i}` - ); - const result = flattenResults(envResult); - - // Track which orgs were included in the result - const selectedEnvs = new Set(result.map((queueId) => keyProducer.envIdFromQueue(queueId))); - - // Verify we never get more than maximumOrgCount orgs - expect(selectedEnvs.size).toBeLessThanOrEqual(2); - - for (const envId of selectedEnvs) { - selectedEnvCounts[envId] = (selectedEnvCounts[envId] || 0) + 1; - } - } - - console.log("Environment selection counts:", selectedEnvCounts); - - // org-2 should be selected most often (highest average age) - expect(selectedEnvCounts["env-2"]).toBeGreaterThan(selectedEnvCounts["env-4"] || 0); - - // org-4 should be selected least often (lowest average age) - const env4Count = selectedEnvCounts["env-4"] || 0; - expect(env4Count).toBeLessThan(selectedEnvCounts["env-2"]); - - // Verify that envs with higher average queue age are selected more frequently - const sortedEnvs = Object.entries(selectedEnvCounts).sort((a, b) => b[1] - a[1]); - console.log("Sorted environment frequencies:", sortedEnvs); - - // The top 2 most frequently selected orgs should be env-2 and env-3 - // as they have the highest average queue ages - const topTwoEnvs = new Set([sortedEnvs[0][0], sortedEnvs[1][0]]); - expect(topTwoEnvs).toContain("env-2"); // Highest average age - expect(topTwoEnvs).toContain("env-3"); // Second highest average age - - // Calculate selection percentages - const totalSelections = Object.values(selectedEnvCounts).reduce((a, b) => a + b, 0); - const selectionPercentages = Object.entries(selectedEnvCounts).reduce( - (acc, [orgId, count]) => { - acc[orgId] = (count / totalSelections) * 100; - return acc; - }, - {} as Record - ); - - console.log("Environment selection percentages:", selectionPercentages); - - // Verify that env-2 (highest average age) gets selected in at least 40% of iterations - expect(selectionPercentages["env-2"]).toBeGreaterThan(40); - - // Verify that env-4 (lowest average age) gets selected in less than 20% of iterations - expect(selectionPercentages["env-4"] || 0).toBeLessThan(20); - } - ); - - redisTest( - "should respect maximumQueuePerEnvCount when distributing queues", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-max-queues", - maximumQueuePerEnvCount: 2, // Only take 2 queues per env - }); - - const now = Date.now(); - - // Setup two environments with different numbers of queues - const envSetups = [ - { - envId: "env-1", - queues: [ - { age: 5000 }, // Oldest - { age: 4000 }, - { age: 3000 }, // This should be excluded due to maximumQueuePerEnvCount - ], - }, - { - envId: "env-2", - queues: [ - { age: 2000 }, - { age: 1000 }, // Newest - ], - }, - ]; - - // Setup queues and concurrency for each env - for (const setup of envSetups) { - await setupConcurrency({ - redis, - keyProducer, - env: { id: setup.envId, currentConcurrency: 0, limit: 5 }, - }); - - for (let i = 0; i < setup.queues.length; i++) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - setup.queues[i].age, - queueId: `queue-${setup.envId}-${i}`, - orgId: `org-${setup.envId}`, - envId: setup.envId, - }); - } - } - - const result = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - - // Verify that each environment has at most 2 queues - for (const envQueues of result) { - expect(envQueues.queues.length).toBeLessThanOrEqual(2); - } - - // Get queues for env-1 (which had 3 queues originally) - const env1Queues = result.find((eq) => eq.envId === "env-1")?.queues ?? []; - - // Should have exactly 2 queues - expect(env1Queues.length).toBe(2); - - // The queues should be the two oldest ones (queue-env-1-0 and queue-env-1-1) - expect(env1Queues).toContain(keyProducer.queueKey("org-env-1", "env-1", "queue-env-1-0")); - expect(env1Queues).toContain(keyProducer.queueKey("org-env-1", "env-1", "queue-env-1-1")); - expect(env1Queues).not.toContain(keyProducer.queueKey("org-env-1", "env-1", "queue-env-1-2")); - - // Get queues for env-2 (which had 2 queues originally) - const env2Queues = result.find((eq) => eq.envId === "env-2")?.queues ?? []; - - // Should still have both queues since it was within the limit - expect(env2Queues.length).toBe(2); - } - ); - - redisTest( - "should fairly distribute queues when using maximumQueuePerEnvCount over time", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-fair-distribution", - maximumQueuePerEnvCount: 2, // Only take 2 queues at a time - biases: { - concurrencyLimitBias: 0, - availableCapacityBias: 0, - queueAgeRandomization: 0.3, // Add some randomization to allow newer queues a chance - }, - }); - - const now = Date.now(); - - // Setup one environment with 5 queues of different ages - const queues = [ - { age: 5000, id: "queue-0" }, // Oldest - { age: 4000, id: "queue-1" }, - { age: 3000, id: "queue-2" }, - { age: 2000, id: "queue-3" }, - { age: 1000, id: "queue-4" }, // Newest - ]; - - // Setup the environment and its queues - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-1", currentConcurrency: 0, limit: 5 }, - }); - - for (const queue of queues) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - queue.age, - queueId: queue.id, - orgId: "org-1", - envId: "env-1", - }); - } - - // Run multiple iterations and track which queues are selected - const iterations = 1000; - const queueSelectionCounts: Record = {}; - const queuePairings: Record = {}; - - for (let i = 0; i < iterations; i++) { - const result = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - `consumer-${i}` - ); - - // There should be exactly one environment - expect(result.length).toBe(1); - const selectedQueues = result[0].queues; - - // Should always get exactly 2 queues due to maximumQueuePerEnvCount - expect(selectedQueues.length).toBe(2); - - // Track individual queue selections - for (const queueId of selectedQueues) { - const baseQueueId = queueId.split(":").pop()!; - queueSelectionCounts[baseQueueId] = (queueSelectionCounts[baseQueueId] || 0) + 1; - } - - // Track queue pairings to ensure variety - const [first, second] = selectedQueues.map((qId) => qId.split(":").pop()!).sort(); - const pairingKey = `${first}-${second}`; - queuePairings[pairingKey] = (queuePairings[pairingKey] || 0) + 1; - } - - console.log("\nQueue Selection Statistics:"); - for (const [queueId, count] of Object.entries(queueSelectionCounts)) { - const percentage = (count / (iterations * 2)) * 100; // Times 2 because we select 2 queues each time - console.log(`${queueId}: ${percentage.toFixed(2)}% (${count} times)`); - } - - console.log("\nQueue Pairing Statistics:"); - for (const [pair, count] of Object.entries(queuePairings)) { - const percentage = (count / iterations) * 100; - console.log(`${pair}: ${percentage.toFixed(2)}% (${count} times)`); - } - - // Verify that all queues were selected at least once - for (const queue of queues) { - expect(queueSelectionCounts[queue.id]).toBeGreaterThan(0); - } - - // Calculate standard deviation of selection percentages - const selectionPercentages = Object.values(queueSelectionCounts).map( - (count) => (count / (iterations * 2)) * 100 - ); - const stdDev = calculateStandardDeviation(selectionPercentages); - - // The standard deviation should be reasonable given our age bias - // Higher stdDev means more bias towards older queues - // We expect some bias due to queueAgeRandomization being 0.3 - expect(stdDev).toBeLessThan(15); // Allow for age-based bias but not extreme - - // Verify we get different pairings of queues - const uniquePairings = Object.keys(queuePairings).length; - // With 5 queues, we can have 10 possible unique pairs - expect(uniquePairings).toBeGreaterThan(5); // Should see at least half of possible combinations - } - ); - - redisTest( - "should handle maximumQueuePerEnvCount larger than available queues", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-max-larger", - maximumQueuePerEnvCount: 5, // Larger than the number of queues we'll create - }); - - const now = Date.now(); - - // Setup two environments with different numbers of queues - const envSetups = [ - { - envId: "env-1", - queues: [{ age: 5000 }, { age: 4000 }], - }, - { - envId: "env-2", - queues: [{ age: 3000 }], - }, - ]; - - // Setup queues and concurrency for each env - for (const setup of envSetups) { - await setupConcurrency({ - redis, - keyProducer, - env: { id: setup.envId, currentConcurrency: 0, limit: 5 }, - }); - - for (let i = 0; i < setup.queues.length; i++) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - setup.queues[i].age, - queueId: `queue-${setup.envId}-${i}`, - orgId: `org-${setup.envId}`, - envId: setup.envId, - }); - } - } - - const result = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - - // Should get all queues from both environments - const env1Queues = result.find((eq) => eq.envId === "env-1")?.queues ?? []; - const env2Queues = result.find((eq) => eq.envId === "env-2")?.queues ?? []; - - // env-1 should have both its queues - expect(env1Queues.length).toBe(2); - // env-2 should have its single queue - expect(env2Queues.length).toBe(1); - } - ); - - redisTest( - "should handle empty environments with maximumQueuePerEnvCount", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-empty-env", - maximumQueuePerEnvCount: 2, - }); - - const now = Date.now(); - - // Setup two environments, one with queues, one without - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-1", currentConcurrency: 0, limit: 5 }, - }); - - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-2", currentConcurrency: 0, limit: 5 }, - }); - - // Only add queues to env-1 - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 5000, - queueId: "queue-1", - orgId: "org-1", - envId: "env-1", - }); - - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - 4000, - queueId: "queue-2", - orgId: "org-1", - envId: "env-1", - }); - - const result = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - "consumer-1" - ); - - // Should only get one environment in the result - expect(result.length).toBe(1); - expect(result[0].envId).toBe("env-1"); - expect(result[0].queues.length).toBe(2); - } - ); - - redisTest( - "should respect maximumQueuePerEnvCount with priority offset queues", - async ({ redisOptions }) => { - const redis = createRedisClient(redisOptions); - - const keyProducer = createKeyProducer("test"); - const strategy = new FairDequeuingStrategy({ - tracer, - redis, - keys: keyProducer, - defaultEnvConcurrency: 5, - parentQueueLimit: 100, - seed: "test-seed-priority", - maximumQueuePerEnvCount: 2, - biases: { - concurrencyLimitBias: 0, - availableCapacityBias: 0, - queueAgeRandomization: 0.3, - }, - }); - - const now = Date.now(); - - // Setup queues with a mix of normal and priority offset ages - const queues = [ - { age: 5000, id: "queue-0" }, // Normal age - { age: 4000 + MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET, id: "queue-1" }, // Priority - { age: 3000, id: "queue-2" }, // Normal age - { age: 2000 + MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET, id: "queue-3" }, // Priority - { age: 1000, id: "queue-4" }, // Normal age - ]; - - await setupConcurrency({ - redis, - keyProducer, - env: { id: "env-1", currentConcurrency: 0, limit: 5 }, - }); - - for (const queue of queues) { - await setupQueue({ - redis, - keyProducer, - parentQueue: "parent-queue", - score: now - queue.age, - queueId: queue.id, - orgId: "org-1", - envId: "env-1", - }); - } - - // Run multiple iterations to check distribution - const iterations = 1000; - const queueSelectionCounts: Record = {}; - - for (let i = 0; i < iterations; i++) { - const result = await strategy.distributeFairQueuesFromParentQueue( - "parent-queue", - `consumer-${i}` - ); - - const selectedQueues = result[0].queues; - for (const queueId of selectedQueues) { - const baseQueueId = queueId.split(":").pop()!; - queueSelectionCounts[baseQueueId] = (queueSelectionCounts[baseQueueId] || 0) + 1; - } - } - - console.log("\nPriority Queue Selection Statistics:"); - for (const [queueId, count] of Object.entries(queueSelectionCounts)) { - const percentage = (count / (iterations * 2)) * 100; - const isPriority = - queues.find((q) => q.id === queueId)?.age! > MARQS_RESUME_PRIORITY_TIMESTAMP_OFFSET; - console.log( - `${queueId}${isPriority ? " (priority)" : ""}: ${percentage.toFixed(2)}% (${count} times)` - ); - } - - // Verify all queues get selected - for (const queue of queues) { - expect(queueSelectionCounts[queue.id]).toBeGreaterThan(0); - } - - // Even with priority queues, we should still see a reasonable distribution - const selectionPercentages = Object.values(queueSelectionCounts).map( - (count) => (count / (iterations * 2)) * 100 - ); - const stdDev = calculateStandardDeviation(selectionPercentages); - expect(stdDev).toBeLessThan(20); // Allow for slightly more variance due to priority queues - } - ); -}); - -// Helper function to flatten results for counting -function flattenResults(results: Array): string[] { - return results.flatMap((envQueue) => envQueue.queues); -} diff --git a/apps/webapp/test/marqsKeyProducer.test.ts b/apps/webapp/test/marqsKeyProducer.test.ts deleted file mode 100644 index 3cc798c7f86..00000000000 --- a/apps/webapp/test/marqsKeyProducer.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { MarQSShortKeyProducer } from "../app/v3/marqs/marqsKeyProducer.js"; -import type { MarQSKeyProducerEnv } from "~/v3/marqs/types.js"; - -describe("MarQSShortKeyProducer", () => { - const prefix = "test:"; - const producer = new MarQSShortKeyProducer(prefix); - - // Sample test data - const sampleEnv: MarQSKeyProducerEnv = { - id: "123456789012345678901234", - organizationId: "987654321098765432109876", - type: "PRODUCTION", - }; - - const devEnv: MarQSKeyProducerEnv = { - id: "123456789012345678901234", - organizationId: "987654321098765432109876", - type: "DEVELOPMENT", - }; - - describe("sharedQueueScanPattern", () => { - it("should return correct shared queue scan pattern", () => { - expect(producer.sharedQueueScanPattern()).toBe("test:*sharedQueue"); - }); - }); - - describe("queueCurrentConcurrencyScanPattern", () => { - it("should return correct queue current concurrency scan pattern", () => { - expect(producer.queueCurrentConcurrencyScanPattern()).toBe( - "test:org:*:env:*:queue:*:currentConcurrency" - ); - }); - }); - - describe("stripKeyPrefix", () => { - it("should strip prefix from key if present", () => { - expect(producer.stripKeyPrefix("test:someKey")).toBe("someKey"); - }); - - it("should return original key if prefix not present", () => { - expect(producer.stripKeyPrefix("someKey")).toBe("someKey"); - }); - }); - - describe("queueKey", () => { - it("should generate queue key with environment object", () => { - expect(producer.queueKey(sampleEnv, "testQueue")).toBe( - "org:765432109876:env:345678901234:queue:testQueue" - ); - }); - - it("should generate queue key with separate parameters", () => { - expect(producer.queueKey("org123", "env456", "testQueue")).toBe( - "org:org123:env:env456:queue:testQueue" - ); - }); - - it("should include concurrency key when provided", () => { - expect(producer.queueKey(sampleEnv, "testQueue", "concKey")).toBe( - "org:765432109876:env:345678901234:queue:testQueue:ck:concKey" - ); - }); - }); - - describe("queueKeyFromQueue", () => { - it("should generate queue key", () => { - expect(producer.queueKeyFromQueue("org:765432109876:env:345678901234:queue:testQueue")).toBe( - "org:765432109876:env:345678901234:queue:testQueue" - ); - }); - - it("should include concurrency key when provided", () => { - expect( - producer.queueKeyFromQueue("org:765432109876:env:345678901234:queue:testQueue:ck:concKey") - ).toBe("org:765432109876:env:345678901234:queue:testQueue:ck:concKey"); - }); - }); - - describe("envSharedQueueKey", () => { - it("should return organization-specific shared queue for development environment", () => { - expect(producer.envSharedQueueKey(devEnv)).toBe( - "org:765432109876:env:345678901234:sharedQueue" - ); - }); - - it("should return global shared queue for production environment", () => { - expect(producer.envSharedQueueKey(sampleEnv)).toBe("sharedQueue"); - }); - }); - - describe("queueDescriptorFromQueue", () => { - it("should parse queue string into descriptor", () => { - const queueString = "org:123:env:456:queue:testQueue:ck:concKey"; - const descriptor = producer.queueDescriptorFromQueue(queueString); - - expect(descriptor).toEqual({ - name: "testQueue", - environment: "456", - organization: "123", - concurrencyKey: "concKey", - }); - }); - - it("should parse queue string without optional parameters", () => { - const queueString = "org:123:env:456:queue:testQueue"; - const descriptor = producer.queueDescriptorFromQueue(queueString); - - expect(descriptor).toEqual({ - name: "testQueue", - environment: "456", - organization: "123", - concurrencyKey: undefined, - }); - }); - - it("should throw error for invalid queue string", () => { - const invalidQueue = "invalid:queue:string"; - expect(() => producer.queueDescriptorFromQueue(invalidQueue)).toThrow("Invalid queue"); - }); - }); - - describe("messageKey", () => { - it("should generate correct message key", () => { - expect(producer.messageKey("msg123")).toBe("message:msg123"); - }); - }); - - describe("nackCounterKey", () => { - it("should generate correct nack counter key", () => { - expect(producer.nackCounterKey("msg123")).toBe("message:msg123:nacks"); - }); - }); - - describe("currentConcurrencyKey", () => { - it("should generate correct current concurrency key", () => { - expect(producer.queueCurrentConcurrencyKey(sampleEnv, "testQueue")).toBe( - "org:765432109876:env:345678901234:queue:testQueue:currentConcurrency" - ); - }); - - it("should include concurrency key when provided", () => { - expect(producer.queueCurrentConcurrencyKey(sampleEnv, "testQueue", "concKey")).toBe( - "org:765432109876:env:345678901234:queue:testQueue:ck:concKey:currentConcurrency" - ); - }); - }); - - describe("currentConcurrencyKeyFromQueue", () => { - it("should generate correct current concurrency key", () => { - expect( - producer.queueCurrentConcurrencyKeyFromQueue( - "org:765432109876:env:345678901234:queue:testQueue" - ) - ).toBe("org:765432109876:env:345678901234:queue:testQueue:currentConcurrency"); - }); - - it("should include concurrency key when provided", () => { - expect( - producer.queueCurrentConcurrencyKeyFromQueue( - "org:765432109876:env:345678901234:queue:testQueue:ck:concKey" - ) - ).toBe("org:765432109876:env:345678901234:queue:testQueue:ck:concKey:currentConcurrency"); - }); - }); - - describe("queueReserveConcurrencyKeyFromQueue", () => { - it("should generate correct queue reserve concurrency key", () => { - expect( - producer.queueReserveConcurrencyKeyFromQueue( - "org:765432109876:env:345678901234:queue:testQueue" - ) - ).toBe("org:765432109876:env:345678901234:queue:testQueue:reserveConcurrency"); - }); - - it("should NOT include the concurrency key when provided", () => { - expect( - producer.queueReserveConcurrencyKeyFromQueue( - "org:765432109876:env:345678901234:queue:testQueue:ck:concKey" - ) - ).toBe("org:765432109876:env:345678901234:queue:testQueue:reserveConcurrency"); - }); - }); - - describe("queueConcurrencyLimitKeyFromQueue", () => { - it("should generate correct queue concurrency limit key", () => { - expect( - producer.queueConcurrencyLimitKeyFromQueue( - "org:765432109876:env:345678901234:queue:testQueue" - ) - ).toBe("org:765432109876:env:345678901234:queue:testQueue:concurrency"); - }); - - it("should NOT include the concurrency key when provided", () => { - expect( - producer.queueConcurrencyLimitKeyFromQueue( - "org:765432109876:env:345678901234:queue:testQueue:ck:concKey" - ) - ).toBe("org:765432109876:env:345678901234:queue:testQueue:concurrency"); - }); - }); - - describe("envCurrentConcurrencyKey", () => { - it("should generate correct env current concurrency key with environment object", () => { - expect(producer.envCurrentConcurrencyKey(sampleEnv)).toBe( - "env:345678901234:currentConcurrency" - ); - }); - - it("should generate correct env current concurrency key with env id", () => { - expect(producer.envCurrentConcurrencyKey("env456")).toBe("env:env456:currentConcurrency"); - }); - }); - - describe("orgIdFromQueue and envIdFromQueue", () => { - it("should extract org id from queue string", () => { - const queue = "org:123:env:456:queue:testQueue"; - expect(producer.orgIdFromQueue(queue)).toBe("123"); - }); - - it("should extract env id from queue string", () => { - const queue = "org:123:env:456:queue:testQueue"; - expect(producer.envIdFromQueue(queue)).toBe("456"); - }); - }); -}); diff --git a/apps/webapp/test/utils/marqs.ts b/apps/webapp/test/utils/marqs.ts deleted file mode 100644 index e724c2457cf..00000000000 --- a/apps/webapp/test/utils/marqs.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { MarQSKeyProducer } from "~/v3/marqs/types"; -import { MarQSShortKeyProducer } from "~/v3/marqs/marqsKeyProducer.js"; -import type Redis from "ioredis"; - -export function createKeyProducer(prefix: string): MarQSKeyProducer { - return new MarQSShortKeyProducer(prefix); -} - -export type SetupQueueOptions = { - parentQueue: string; - redis: Redis; - score: number; - queueId: string; - orgId: string; - envId: string; - keyProducer: MarQSKeyProducer; -}; - -export type ConcurrencySetupOptions = { - keyProducer: MarQSKeyProducer; - redis: Redis; - orgId: string; - envId: string; - currentConcurrency?: number; - orgLimit?: number; - envLimit?: number; - isOrgDisabled?: boolean; -}; - -/** - * Adds a queue to Redis with the given parameters - */ -export async function setupQueue({ - redis, - keyProducer, - parentQueue, - score, - queueId, - orgId, - envId, -}: SetupQueueOptions) { - // Add the queue to the parent queue's sorted set - const queue = keyProducer.queueKey(orgId, envId, queueId); - - await redis.zadd(parentQueue, score, queue); -} - -type SetupConcurrencyOptions = { - redis: Redis; - keyProducer: MarQSKeyProducer; - env: { id: string; currentConcurrency: number; limit?: number; reserveConcurrency?: number }; -}; - -/** - * Sets up concurrency-related Redis keys for orgs and envs - */ -export async function setupConcurrency({ redis, keyProducer, env }: SetupConcurrencyOptions) { - // Set env concurrency limit - if (typeof env.limit === "number") { - await redis.set(keyProducer.envConcurrencyLimitKey(env.id), env.limit.toString()); - } - - if (env.currentConcurrency > 0) { - // Set current concurrency by adding dummy members to the set - const envCurrentKey = keyProducer.envCurrentConcurrencyKey(env.id); - - // Add dummy running job IDs to simulate current concurrency - const dummyJobs = Array.from( - { length: env.currentConcurrency }, - (_, i) => `dummy-job-${i}-${Date.now()}` - ); - - await redis.sadd(envCurrentKey, ...dummyJobs); - } - - if (env.reserveConcurrency && env.reserveConcurrency > 0) { - // Set reserved concurrency by adding dummy members to the set - const envReservedKey = keyProducer.envReserveConcurrencyKey(env.id); - - // Add dummy reserved job IDs to simulate reserved concurrency - const dummyJobs = Array.from( - { length: env.reserveConcurrency }, - (_, i) => `dummy-reserved-job-${i}-${Date.now()}` - ); - - await redis.sadd(envReservedKey, ...dummyJobs); - } -} - -/** - * Calculates the standard deviation of a set of numbers. - * Standard deviation measures the amount of variation of a set of values from their mean. - * A low standard deviation indicates that the values tend to be close to the mean. - * - * @param values Array of numbers to calculate standard deviation for - * @returns The standard deviation of the values - */ -export function calculateStandardDeviation(values: number[]): number { - // If there are no values or only one value, the standard deviation is 0 - if (values.length <= 1) { - return 0; - } - - // Calculate the mean (average) of the values - const mean = values.reduce((sum, value) => sum + value, 0) / values.length; - - // Calculate the sum of squared differences from the mean - const squaredDifferences = values.map((value) => Math.pow(value - mean, 2)); - const sumOfSquaredDifferences = squaredDifferences.reduce((sum, value) => sum + value, 0); - - // Calculate the variance (average of squared differences) - const variance = sumOfSquaredDifferences / (values.length - 1); // Using n-1 for sample standard deviation - - // Standard deviation is the square root of the variance - return Math.sqrt(variance); -} diff --git a/internal-packages/zod-worker/package.json b/internal-packages/zod-worker/package.json deleted file mode 100644 index 190dd1facd3..00000000000 --- a/internal-packages/zod-worker/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "@internal/zod-worker", - "private": true, - "version": "0.0.1", - "main": "./src/index.ts", - "types": "./src/index.ts", - "dependencies": { - "@internal/tracing": "workspace:*", - "@trigger.dev/core": "workspace:*", - "@trigger.dev/database": "workspace:*", - "graphile-worker": "0.16.6", - "lodash.omit": "^4.5.0", - "zod": "3.25.76" - }, - "devDependencies": { - "@types/lodash.omit": "^4.5.7", - "@types/pg": "8.6.6" - }, - "scripts": { - "typecheck": "tsc --noEmit" - } -} diff --git a/internal-packages/zod-worker/src/index.ts b/internal-packages/zod-worker/src/index.ts deleted file mode 100644 index 9597f3abdb0..00000000000 --- a/internal-packages/zod-worker/src/index.ts +++ /dev/null @@ -1,869 +0,0 @@ -import { SpanKind, SpanStatusCode, trace } from "@internal/tracing"; -import { flattenAttributes } from "@trigger.dev/core/v3"; -import type { - CronItem, - CronItemOptions, - DbJob as GraphileJob, - Runner as GraphileRunner, - JobHelpers, - RunnerOptions, - Task, - TaskList, - TaskSpec, - WorkerUtils, -} from "graphile-worker"; -import { - Logger as GraphileLogger, - run as graphileRun, - makeWorkerUtils, - parseCronItems, -} from "graphile-worker"; -import omit from "lodash.omit"; -import { z } from "zod"; -import type { Logger } from "@trigger.dev/core/logger"; -import type { - PrismaClient, - PrismaClientOrTransaction, - PrismaReplicaClient, -} from "@trigger.dev/database"; -import { PgListenService } from "./pgListen.server"; - -export type { RunnerOptions }; - -const tracer = trace.getTracer("zodWorker", "3.0.0.dp.1"); - -export interface MessageCatalogSchema { - [key: string]: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion; -} - -const RawCronPayloadSchema = z.object({ - _cron: z.object({ - ts: z.coerce.date(), - backfilled: z.boolean(), - }), -}); - -const GraphileJobSchema = z.object({ - id: z.coerce.string(), - job_queue_id: z.number().nullable(), - task_id: z.number(), - payload: z.unknown(), - priority: z.number(), - run_at: z.coerce.date(), - attempts: z.number(), - max_attempts: z.number(), - last_error: z.string().nullable(), - created_at: z.coerce.date(), - updated_at: z.coerce.date(), - key: z.string().nullable(), - revision: z.number(), - locked_at: z.coerce.date().nullable(), - locked_by: z.string().nullable(), - flags: z.record(z.boolean()).nullable(), -}); - -const AddJobResultsSchema = z.array(GraphileJobSchema); - -export type ZodTasks = { - [K in keyof TConsumerSchema]: { - jobKey?: string | ((payload: z.infer) => string | undefined); - priority?: number; - maxAttempts?: number; - jobKeyMode?: "replace" | "preserve_run_at" | "unsafe_dedupe"; - flags?: string[]; - handler: ( - payload: z.infer, - job: GraphileJob, - helpers: JobHelpers - ) => Promise; - }; -}; - -type RecurringTaskPayload = { - ts: Date; - backfilled: boolean; -}; - -export type ZodRecurringTasks = { - [key: string]: { - match: string; - options?: CronItemOptions; - handler: ( - payload: RecurringTaskPayload, - job: GraphileJob, - helpers: JobHelpers - ) => Promise; - }; -}; - -type ZodTaskSpec = Omit; - -export type ZodWorkerEnqueueOptions = ZodTaskSpec & { - tx?: PrismaClientOrTransaction; -}; - -export type ZodWorkerDequeueOptions = { - tx?: PrismaClientOrTransaction; -}; - -const CLEANUP_TASK_NAME = "__cleanupOldJobs"; -const REPORTER_TASK_NAME = "__reporter"; - -export type ZodWorkerCleanupOptions = { - frequencyExpression: string; // cron expression - ttl: number; - maxCount: number; - taskOptions?: CronItemOptions; -}; - -type ZodWorkerReporter = (event: string, properties: Record) => Promise; - -export interface ZodWorkerRateLimiter { - forbiddenFlags(): Promise; - wrapTask(t: Task, rescheduler: Task): Task; -} - -export type ZodWorkerOptions = { - name: string; - runnerOptions: RunnerOptions; - prisma: PrismaClient; - replica: PrismaReplicaClient; - schema: TMessageCatalog; - tasks: ZodTasks; - recurringTasks?: ZodRecurringTasks; - cleanup?: ZodWorkerCleanupOptions; - reporter?: ZodWorkerReporter; - shutdownTimeoutInMs?: number; - rateLimiter?: ZodWorkerRateLimiter; - verboseLogging?: boolean; - logger: Logger; -}; - -export class ZodWorker { - #name: string; - #schema: TMessageCatalog; - #prisma: PrismaClient; - #replica: PrismaReplicaClient; - #runnerOptions: RunnerOptions; - #tasks: ZodTasks; - #recurringTasks?: ZodRecurringTasks; - #runner?: GraphileRunner; - #cleanup: ZodWorkerCleanupOptions | undefined; - #reporter?: ZodWorkerReporter; - #rateLimiter?: ZodWorkerRateLimiter; - #shutdownTimeoutInMs?: number; - #shuttingDown = false; - #workerUtils?: WorkerUtils; - #verboseLogging?: boolean; - #logger: Logger; - - constructor(options: ZodWorkerOptions) { - this.#name = options.name; - this.#schema = options.schema; - this.#prisma = options.prisma; - this.#replica = options.replica; - this.#runnerOptions = options.runnerOptions; - this.#tasks = options.tasks; - this.#recurringTasks = options.recurringTasks; - this.#cleanup = options.cleanup; - this.#reporter = options.reporter; - this.#rateLimiter = options.rateLimiter; - this.#shutdownTimeoutInMs = options.shutdownTimeoutInMs ?? 60000; // default to 60 seconds - this.#verboseLogging = options.verboseLogging; - this.#logger = options.logger; - } - - get graphileWorkerSchema() { - return this.#runnerOptions.schema ?? "graphile_worker"; - } - - public async initialize(): Promise { - if (this.#runner) { - return true; - } - - this.#logDebug("Initializing graphile worker queue with options", { - runnerOptions: this.#runnerOptions, - }); - - const parsedCronItems = parseCronItems(this.#createCronItemsFromRecurringTasks()); - - this.#workerUtils = await makeWorkerUtils(this.#runnerOptions); - - const graphileLogger = new GraphileLogger((scope) => { - return (level, message, meta) => { - if (this.#verboseLogging !== true) return; - - this.#logger.debug(`[graphile-worker][${this.#name}][${level}] ${message}`, { - scope, - meta, - workerName: this.#name, - }); - }; - }); - - this.#runner = await graphileRun({ - ...this.#runnerOptions, - noHandleSignals: true, - taskList: this.#createTaskListFromTasks(), - parsedCronItems, - forbiddenFlags: this.#rateLimiter?.forbiddenFlags.bind(this.#rateLimiter), - logger: graphileLogger, - }); - - if (!this.#runner) { - throw new Error("Failed to initialize graphile worker queue"); - } - - this.#runner?.events.on("pool:create", ({ workerPool }) => { - this.#logDebug("pool:create"); - }); - - this.#runner?.events.on("pool:listen:connecting", ({ workerPool, attempts }) => { - this.#logDebug("pool:create", { attempts }); - }); - - this.#runner?.events.on("pool:listen:success", async ({ workerPool, client }) => { - this.#logDebug("pool:listen:success"); - - // hijack client instance to listen and react to incoming NOTIFY events - const pgListen = new PgListenService(client, this.#logger, this.#name); - - await pgListen.on("trigger:graphile:migrate", async ({ latestMigration }) => { - this.#logDebug("Detected incoming migration", { latestMigration }); - - if (latestMigration > 10) { - this.#logDebug("Already migrated past v0.14 - nothing to do", { latestMigration }); - return; - } - - // simulate SIGTERM to trigger graceful shutdown - this._handleSignal("SIGTERM"); - }); - }); - - this.#runner?.events.on("pool:listen:error", ({ error }) => { - this.#logDebug("pool:listen:error", { error }); - }); - - this.#runner?.events.on("pool:gracefulShutdown", ({ message }) => { - this.#logDebug("pool:gracefulShutdown", { workerMessage: message }); - }); - - this.#runner?.events.on("pool:gracefulShutdown:error", ({ error }) => { - this.#logDebug("pool:gracefulShutdown:error", { error }); - }); - - this.#runner?.events.on("worker:create", ({ worker }) => { - this.#logDebug("worker:create", { workerId: worker.workerId }); - }); - - this.#runner?.events.on("worker:release", ({ worker }) => { - this.#logDebug("worker:release", { workerId: worker.workerId }); - }); - - this.#runner?.events.on("worker:stop", ({ worker, error }) => { - this.#logDebug("worker:stop", { workerId: worker.workerId, error }); - }); - - this.#runner?.events.on("worker:fatalError", ({ worker, error, jobError }) => { - this.#logDebug("worker:fatalError", { workerId: worker.workerId, error, jobError }); - }); - - this.#runner?.events.on("gracefulShutdown", ({ signal }) => { - this.#logDebug("gracefulShutdown", { signal }); - }); - - this.#runner?.events.on("stop", () => { - this.#logDebug("stop"); - }); - - this.#runner?.events.on("worker:getJob:error", ({ worker, error }) => { - this.#logDebug("worker:getJob:error", { workerId: worker.workerId, error }); - }); - - this.#runner?.events.on("worker:getJob:start", ({ worker }) => { - if (this.#verboseLogging !== true) return; - this.#logDebug("worker:getJob:start", { workerId: worker.workerId }); - }); - - this.#runner?.events.on("job:start", ({ worker, job }) => { - if (this.#verboseLogging !== true) return; - this.#logDebug("job:start", { workerId: worker.workerId, job }); - }); - - process.on("SIGTERM", this._handleSignal.bind(this)); - process.on("SIGINT", this._handleSignal.bind(this)); - - return true; - } - - private _handleSignal(signal: string) { - if (this.#shuttingDown) { - return; - } - - this.#shuttingDown = true; - - this.#logDebug( - `Received ${signal}, shutting down zodWorker with timeout ${this.#shutdownTimeoutInMs}ms` - ); - - if (this.#shutdownTimeoutInMs) { - setTimeout(() => { - this.#logDebug(`Shutdown timeout of ${this.#shutdownTimeoutInMs} reached, exiting process`); - - process.exit(0); - }, this.#shutdownTimeoutInMs); - } - - this.stop().finally(() => { - this.#logDebug("zodWorker stopped"); - }); - } - - public async stop() { - await this.#runner?.stop(); - await this.#workerUtils?.release(); - } - - public async enqueue( - identifier: K, - payload: z.infer, - options?: ZodWorkerEnqueueOptions - ): Promise { - const task = this.#tasks[identifier]; - - const optionsWithoutTx = removeUndefinedKeys(omit(options ?? {}, ["tx"])); - const taskWithoutJobKey = omit(task, ["jobKey"]); - - // Make sure options passed in to enqueue take precedence over task options - const spec = { - ...taskWithoutJobKey, - ...optionsWithoutTx, - }; - - if (typeof task.jobKey === "function") { - const jobKey = task.jobKey(payload); - - if (jobKey) { - spec.jobKey = jobKey; - } - } - - const { job, durationInMs } = await tracer.startActiveSpan( - `Enqueue ${identifier as string}`, - { - kind: SpanKind.PRODUCER, - attributes: { - "job.task_identifier": identifier as string, - "job.payload": payload, - "job.priority": spec.priority, - "job.run_at": spec.runAt?.toISOString(), - "job.jobKey": spec.jobKey, - "job.flags": spec.flags, - "job.max_attempts": spec.maxAttempts, - "worker.name": this.#name, - }, - }, - async (span) => { - try { - const results = await this.#addJob( - identifier as string, - payload, - spec, - options?.tx ?? this.#prisma - ); - - return results; - } catch (error) { - if (error instanceof Error) { - span.recordException(error); - } else { - span.recordException(new Error(String(error))); - } - - span.setStatus({ code: SpanStatusCode.ERROR }); - - throw error; - } finally { - span.end(); - } - } - ); - - this.#logger.debug("Enqueued worker task", { - identifier, - payload, - spec, - job, - durationInMs, - }); - - return job; - } - - public async dequeue( - jobKey: string, - option?: ZodWorkerDequeueOptions - ): Promise { - const results = await this.#removeJob(jobKey, option?.tx ?? this.#prisma); - - this.#logger.debug("dequeued worker task", { results, jobKey }); - - return results; - } - - async #addJob( - identifier: string, - payload: unknown, - spec: TaskSpec, - tx: PrismaClientOrTransaction - ) { - const now = performance.now(); - - const results = await tx.$queryRawUnsafe( - `SELECT * FROM ${this.graphileWorkerSchema}.add_job( - identifier => $1::text, - payload => $2::json, - run_at => $3::timestamptz, - max_attempts => $4::int, - job_key => $5::text, - priority => $6::int, - flags => $7::text[], - job_key_mode => $8::text - )`, - identifier, - JSON.stringify(payload), - spec.runAt || null, - spec.maxAttempts || null, - spec.jobKey || null, - spec.priority || null, - spec.flags || null, - spec.jobKeyMode || null - ); - - const durationInMs = performance.now() - now; - - const rows = AddJobResultsSchema.safeParse(results); - - if (!rows.success) { - this.#logger.debug("results returned from add_job could not be parsed", { - identifier, - payload, - spec, - error: JSON.stringify(rows.error), - }); - return { job: undefined, durationInMs: Math.floor(durationInMs) }; - } - - const job = rows.data[0]; - - return { job: job as GraphileJob, durationInMs: Math.floor(durationInMs) }; - } - - async #removeJob(jobKey: string, tx: PrismaClientOrTransaction) { - try { - const result = await tx.$queryRawUnsafe( - `SELECT * FROM ${this.graphileWorkerSchema}.remove_job( - job_key => $1::text - )`, - jobKey - ); - const job = AddJobResultsSchema.safeParse(result); - - if (!job.success) { - this.#logger.debug("could not remove job, job_key did not exist", { - jobKey, - }); - - return; - } - - return job.data[0] as GraphileJob; - } catch (e) { - throw new Error(`Failed to remove job from queue, ${e}}`); - } - } - - #createTaskListFromTasks() { - const taskList: TaskList = {}; - - for (const [key] of Object.entries(this.#tasks)) { - const task: Task = (payload, helpers) => { - return this.#handleMessage(key, payload, helpers); - }; - - if (this.#rateLimiter) { - taskList[key] = this.#rateLimiter.wrapTask(task, this.#rescheduleTask.bind(this)); - } else { - taskList[key] = task; - } - } - - for (const [key] of Object.entries(this.#recurringTasks ?? {})) { - const task: Task = (payload, helpers) => { - return this.#handleRecurringTask(key, payload, helpers); - }; - - taskList[key] = task; - } - - if (this.#cleanup) { - const task: Task = (payload, helpers) => { - return this.#handleCleanup(payload, helpers); - }; - - taskList[CLEANUP_TASK_NAME] = task; - } - - if (this.#reporter) { - const task: Task = (payload, helpers) => { - return this.#handleReporter(payload, helpers); - }; - - taskList[REPORTER_TASK_NAME] = task; - } - - return taskList; - } - - async #rescheduleTask(payload: unknown, helpers: JobHelpers) { - this.#logDebug("Rescheduling task", { payload, job: helpers.job }); - - await this.enqueue(helpers.job.task_identifier, payload, { - runAt: helpers.job.run_at, - priority: helpers.job.priority, - jobKey: helpers.job.key ?? undefined, - flags: Object.keys(helpers.job.flags ?? []), - maxAttempts: helpers.job.max_attempts - (helpers.job.attempts - 1), - }); - } - - #createCronItemsFromRecurringTasks() { - const cronItems: CronItem[] = []; - - if (this.#cleanup) { - cronItems.push({ - match: this.#cleanup.frequencyExpression, - identifier: CLEANUP_TASK_NAME, - task: CLEANUP_TASK_NAME, - options: this.#cleanup.taskOptions, - }); - } - - if (this.#reporter) { - cronItems.push({ - match: "50 * * * *", // Every hour at 50 minutes past the hour - identifier: REPORTER_TASK_NAME, - task: REPORTER_TASK_NAME, - }); - } - - if (!this.#recurringTasks) { - return cronItems; - } - - for (const [key, task] of Object.entries(this.#recurringTasks)) { - const cronItem: CronItem = { - match: task.match, - identifier: key, - task: key, - options: task.options, - }; - - cronItems.push(cronItem); - } - - return cronItems; - } - - async #handleMessage( - typeName: K, - rawPayload: unknown, - helpers: JobHelpers - ): Promise { - const subscriberSchema = this.#schema; - type TypeKeys = keyof typeof subscriberSchema; - - const messageSchema: TMessageCatalog[TypeKeys] | undefined = subscriberSchema[typeName]; - - if (!messageSchema) { - throw new Error(`Unknown message type: ${String(typeName)}`); - } - - const payload = messageSchema.parse(rawPayload); - const job = helpers.job; - - this.#logger.debug("Received worker task, calling handler", { - type: String(typeName), - payload, - job, - }); - - const task = this.#tasks[typeName]; - - if (!task) { - throw new Error(`No task for message type: ${String(typeName)}`); - } - - await tracer.startActiveSpan( - `Run ${typeName as string}`, - { - kind: SpanKind.CONSUMER, - attributes: { - "job.task_identifier": job.task_identifier, - "job.id": job.id, - ...(job.job_queue_id ? { "job.queue_id": job.job_queue_id } : {}), - ...flattenAttributes(job.payload as Record, "job.payload"), - "job.priority": job.priority, - "job.run_at": job.run_at.toISOString(), - "job.attempts": job.attempts, - "job.max_attempts": job.max_attempts, - "job.created_at": job.created_at.toISOString(), - "job.updated_at": job.updated_at.toISOString(), - ...(job.key ? { "job.key": job.key } : {}), - "job.revision": job.revision, - ...(job.locked_at ? { "job.locked_at": job.locked_at.toISOString() } : {}), - ...(job.locked_by ? { "job.locked_by": job.locked_by } : {}), - ...(job.flags ? flattenAttributes(job.flags, "job.flags") : {}), - "worker.name": this.#name, - }, - }, - async (span) => { - try { - await task.handler(payload, job, helpers); - } catch (error) { - if (error instanceof Error) { - span.recordException(error); - } else { - span.recordException(new Error(String(error))); - } - - if (job.attempts >= job.max_attempts) { - this.#logger.debug("Job failed after max attempts", { - job, - attempts: job.attempts, - max_attempts: job.max_attempts, - error: error instanceof Error ? error.message : error, - }); - - return; - } - - throw error; - } finally { - span.end(); - } - } - ); - } - - async #handleRecurringTask( - typeName: string, - rawPayload: unknown, - helpers: JobHelpers - ): Promise { - const job = helpers.job; - - this.#logger.debug("Received recurring task, calling handler", { - type: String(typeName), - payload: rawPayload, - job, - }); - - const recurringTask = this.#recurringTasks?.[typeName]; - - if (!recurringTask) { - throw new Error(`No recurring task for message type: ${String(typeName)}`); - } - - const parsedPayload = RawCronPayloadSchema.safeParse(rawPayload); - - if (!parsedPayload.success) { - throw new Error( - `Failed to parse recurring task payload: ${JSON.stringify(parsedPayload.error)}` - ); - } - - const payload = parsedPayload.data; - - await tracer.startActiveSpan( - `Run ${typeName as string} recurring`, - { - kind: SpanKind.CONSUMER, - attributes: { - "job.task_identifier": job.task_identifier, - "job.id": job.id, - ...(job.job_queue_id ? { "job.queue_id": job.job_queue_id } : {}), - ...flattenAttributes(job.payload as Record, "job.payload"), - "job.priority": job.priority, - "job.run_at": job.run_at.toISOString(), - "job.attempts": job.attempts, - "job.max_attempts": job.max_attempts, - "job.created_at": job.created_at.toISOString(), - "job.updated_at": job.updated_at.toISOString(), - ...(job.key ? { "job.key": job.key } : {}), - "job.revision": job.revision, - ...(job.locked_at ? { "job.locked_at": job.locked_at.toISOString() } : {}), - ...(job.locked_by ? { "job.locked_by": job.locked_by } : {}), - ...(job.flags ? flattenAttributes(job.flags, "job.flags") : {}), - "worker.name": this.#name, - }, - }, - async (span) => { - try { - await recurringTask.handler(payload._cron, job, helpers); - } catch (error) { - if (error instanceof Error) { - span.recordException(error); - } else { - span.recordException(new Error(String(error))); - } - - throw error; - } finally { - span.end(); - } - } - ); - } - - async #handleCleanup(rawPayload: unknown, helpers: JobHelpers): Promise { - if (!this.#cleanup) { - return; - } - - if (!this.#workerUtils) { - throw new Error("WorkerUtils need to be initialized before running job cleanup."); - } - - const job = helpers.job; - - this.#logger.debug("Received cleanup task", { - payload: rawPayload, - job, - }); - - const parsedPayload = RawCronPayloadSchema.safeParse(rawPayload); - - if (!parsedPayload.success) { - throw new Error( - `Failed to parse cleanup task payload: ${JSON.stringify(parsedPayload.error)}` - ); - } - - const payload = parsedPayload.data; - - // Add the this.#cleanup.ttl to the payload._cron.ts - const expirationDate = new Date(payload._cron.ts.getTime() - this.#cleanup.ttl); - - this.#logger.debug("Cleaning up old jobs", { - expirationDate, - payload, - }); - - const rawResults = await this.#replica.$queryRawUnsafe( - `SELECT id - FROM ${this.graphileWorkerSchema}.jobs - WHERE run_at < $1 - AND locked_at IS NULL - AND max_attempts = attempts - LIMIT $2`, - expirationDate, - this.#cleanup.maxCount - ); - - const results = z - .array( - z.object({ - id: z.coerce.string(), - }) - ) - .parse(rawResults); - - const completedJobs = await this.#workerUtils.completeJobs(results.map((job) => job.id)); - - this.#logger.debug("Cleaned up old jobs", { - found: results.length, - deleted: completedJobs.length, - expirationDate, - payload, - }); - - if (this.#reporter) { - await this.#reporter("cleanup_stats", { - found: results.length, - deleted: completedJobs.length, - expirationDate, - ts: payload._cron.ts, - }); - } - } - - async #handleReporter(rawPayload: unknown, helpers: JobHelpers): Promise { - if (!this.#reporter) { - return; - } - - this.#logger.debug("Received reporter task", { - payload: rawPayload, - }); - - const parsedPayload = RawCronPayloadSchema.safeParse(rawPayload); - - if (!parsedPayload.success) { - throw new Error( - `Failed to parse cleanup task payload: ${JSON.stringify(parsedPayload.error)}` - ); - } - - const payload = parsedPayload.data; - - // Subtract an hour from the payload._cron.ts - const startAt = new Date(payload._cron.ts.getTime() - 1000 * 60 * 60); - - const schema = z.array(z.object({ count: z.coerce.number() })); - - // Count the number of jobs that have been added since the startAt date and before the payload._cron.ts date - const rawAddedResults = await this.#replica.$queryRawUnsafe( - `SELECT COUNT(*) FROM ${this.graphileWorkerSchema}.jobs WHERE created_at > $1 AND created_at < $2`, - startAt, - payload._cron.ts - ); - - const addedCountResults = schema.parse(rawAddedResults)[0]; - - // Count the total number of jobs in the jobs table - const rawTotalResults = await this.#replica.$queryRawUnsafe( - `SELECT COUNT(*) FROM ${this.graphileWorkerSchema}.jobs` - ); - - const totalCountResults = schema.parse(rawTotalResults)[0]; - - this.#logger.debug("Calculated metrics about the jobs table", { - rawAddedResults, - rawTotalResults, - payload, - }); - - await this.#reporter("queue_metrics", { - addedCount: addedCountResults.count, - totalCount: totalCountResults.count, - ts: payload._cron.ts, - }); - } - - #logDebug(message: string, args?: any) { - this.#logger.debug(`[worker][${this.#name}] ${message}`, args); - } -} - -function removeUndefinedKeys(obj: T): T { - for (let key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key] === undefined) { - delete obj[key]; - } - } - return obj; -} diff --git a/internal-packages/zod-worker/src/pgListen.server.ts b/internal-packages/zod-worker/src/pgListen.server.ts deleted file mode 100644 index 1a33035198c..00000000000 --- a/internal-packages/zod-worker/src/pgListen.server.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { Logger } from "@trigger.dev/core/logger"; -import type { PoolClient } from "pg"; -import type { z } from "zod"; -import type { NotificationCatalog, NotificationChannel } from "./types"; -import { notificationCatalog } from "./types"; - -export class PgListenService { - #poolClient: PoolClient; - #logger: Logger; - #loggerNamespace: string; - - constructor(poolClient: PoolClient, logger: Logger, loggerNamespace?: string) { - this.#poolClient = poolClient; - this.#logger = logger; - this.#loggerNamespace = loggerNamespace ?? ""; - } - - public async on( - channelName: TChannel, - callback: (payload: z.infer) => Promise - ) { - this.#logDebug("Registering notification handler", { channelName }); - - const isValidChannel = channelName.match(/^[a-zA-Z0-9:-_]+$/); - - if (!isValidChannel) { - throw new Error(`Invalid channel name: ${channelName}`); - } - - this.#poolClient.query(`LISTEN "${channelName}"`).then(null, (error) => { - this.#logDebug("LISTEN error", error); - }); - - this.#poolClient.on("notification", async (notification) => { - if (notification.channel !== channelName) { - return; - } - - this.#logDebug("Notification received", { notification }); - - if (!notification.payload) { - return; - } - - const payload = safeJsonParse(notification.payload); - - const parsedPayload = notificationCatalog[channelName].safeParse(payload); - - if (!parsedPayload.success) { - throw new Error( - `Failed to parse notification payload: ${channelName} - ${JSON.stringify( - parsedPayload.error - )}` - ); - } - - await callback(parsedPayload.data); - }); - } - - #logDebug(message: string, args?: any) { - const namespace = this.#loggerNamespace ? `[${this.#loggerNamespace}]` : ""; - this.#logger.debug(`[pgListen]${namespace} ${message}`, args); - } -} - -export function safeJsonParse(json?: string): unknown { - if (!json) { - return; - } - - try { - return JSON.parse(json); - } catch (_e) { - return null; - } -} diff --git a/internal-packages/zod-worker/src/types.ts b/internal-packages/zod-worker/src/types.ts deleted file mode 100644 index 2e5b6d222fd..00000000000 --- a/internal-packages/zod-worker/src/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from "zod"; - -export const notificationCatalog = { - "trigger:graphile:migrate": z.object({ - latestMigration: z.number(), - }), -}; - -export type NotificationCatalog = typeof notificationCatalog; - -export type NotificationChannel = keyof NotificationCatalog; diff --git a/internal-packages/zod-worker/tsconfig.json b/internal-packages/zod-worker/tsconfig.json deleted file mode 100644 index e43f8c04903..00000000000 --- a/internal-packages/zod-worker/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2019", - "lib": ["ES2019", "DOM", "DOM.Iterable", "DOM.AsyncIterable"], - "module": "CommonJS", - "moduleResolution": "Node", - "moduleDetection": "force", - "verbatimModuleSyntax": false, - "types": ["vitest/globals"], - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "preserveWatchOutput": true, - "skipLibCheck": true, - "noEmit": true, - "strict": true, - "paths": { - "@trigger.dev/core": ["../../packages/core/src/index"], - "@trigger.dev/core/*": ["../../packages/core/src/*"], - "@trigger.dev/database": ["../database/src/index"], - "@trigger.dev/database/*": ["../database/src/*"] - } - }, - "exclude": ["node_modules"] -} diff --git a/package.json b/package.json index 2922196e30a..3ccfacdfeb3 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ "patchedDependencies": { "@changesets/assemble-release-plan@5.2.4": "patches/@changesets__assemble-release-plan@5.2.4.patch", "engine.io-parser@5.2.2": "patches/engine.io-parser@5.2.2.patch", - "graphile-worker@0.16.6": "patches/graphile-worker@0.16.6.patch", "redlock@5.0.0-beta.2": "patches/redlock@5.0.0-beta.2.patch", "@kubernetes/client-node@1.0.0": "patches/@kubernetes__client-node@1.0.0.patch", "@sentry/remix@9.46.0": "patches/@sentry__remix@9.46.0.patch", diff --git a/packages/core/package.json b/packages/core/package.json index d1997c5116e..57522a3dc1b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -47,7 +47,6 @@ "./v3/test": "./src/v3/test/index.ts", "./v3/zodfetch": "./src/v3/zodfetch.ts", "./v3/zodMessageHandler": "./src/v3/zodMessageHandler.ts", - "./v3/zodNamespace": "./src/v3/zodNamespace.ts", "./v3/zodSocket": "./src/v3/zodSocket.ts", "./v3/zodIpc": "./src/v3/zodIpc.ts", "./v3/utils/timers": "./src/v3/utils/timers.ts", @@ -538,17 +537,6 @@ "default": "./dist/commonjs/v3/zodMessageHandler.js" } }, - "./v3/zodNamespace": { - "import": { - "@triggerdotdev/source": "./src/v3/zodNamespace.ts", - "types": "./dist/esm/v3/zodNamespace.d.ts", - "default": "./dist/esm/v3/zodNamespace.js" - }, - "require": { - "types": "./dist/commonjs/v3/zodNamespace.d.ts", - "default": "./dist/commonjs/v3/zodNamespace.js" - } - }, "./v3/zodSocket": { "import": { "@triggerdotdev/source": "./src/v3/zodSocket.ts", diff --git a/packages/core/src/v3/zodNamespace.ts b/packages/core/src/v3/zodNamespace.ts deleted file mode 100644 index 91833307566..00000000000 --- a/packages/core/src/v3/zodNamespace.ts +++ /dev/null @@ -1,203 +0,0 @@ -import type { DisconnectReason, Namespace, Server, Socket } from "socket.io"; -import { ZodMessageSender } from "./zodMessageHandler.js"; -import type { - ZodMessageCatalogToSocketIoEvents, - ZodSocketMessageCatalogSchema, - ZodSocketMessageHandlers, -} from "./zodSocket.js"; -import { ZodSocketMessageHandler } from "./zodSocket.js"; -// @ts-ignore -import type { DefaultEventsMap, EventsMap } from "socket.io/dist/typed-events"; -import type { z } from "zod"; -import type { StructuredLogger } from "./utils/structuredLogger.js"; -import { SimpleStructuredLogger } from "./utils/structuredLogger.js"; - -interface ExtendedError extends Error { - data?: any; -} - -export type ZodNamespaceSocket< - TClientMessages extends ZodSocketMessageCatalogSchema, - TServerMessages extends ZodSocketMessageCatalogSchema, - TServerSideEvents extends EventsMap = DefaultEventsMap, - TSocketData extends z.ZodObject = any, -> = Socket< - ZodMessageCatalogToSocketIoEvents, - ZodMessageCatalogToSocketIoEvents, - TServerSideEvents, - z.infer ->; - -interface ZodNamespaceOptions< - TClientMessages extends ZodSocketMessageCatalogSchema, - TServerMessages extends ZodSocketMessageCatalogSchema, - TServerSideEvents extends EventsMap = DefaultEventsMap, - TSocketData extends z.ZodObject = any, -> { - io: Server; - name: string; - clientMessages: TClientMessages; - serverMessages: TServerMessages; - socketData?: TSocketData; - handlers?: ZodSocketMessageHandlers; - authToken?: string; - logger?: StructuredLogger; - logHandlerPayloads?: boolean; - preAuth?: ( - socket: ZodNamespaceSocket, - next: (err?: ExtendedError) => void, - logger: StructuredLogger - ) => Promise; - postAuth?: ( - socket: ZodNamespaceSocket, - next: (err?: ExtendedError) => void, - logger: StructuredLogger - ) => Promise; - onConnection?: ( - socket: ZodNamespaceSocket, - handler: ZodSocketMessageHandler, - sender: ZodMessageSender, - logger: StructuredLogger - ) => Promise; - onDisconnect?: ( - socket: ZodNamespaceSocket, - reason: DisconnectReason, - description: any, - logger: StructuredLogger - ) => Promise; - onError?: ( - socket: ZodNamespaceSocket, - err: Error, - logger: StructuredLogger - ) => Promise; -} - -export class ZodNamespace< - TClientMessages extends ZodSocketMessageCatalogSchema, - TServerMessages extends ZodSocketMessageCatalogSchema, - TSocketData extends z.ZodObject = any, - TServerSideEvents extends EventsMap = DefaultEventsMap, -> { - #logger: StructuredLogger; - #handler: ZodSocketMessageHandler; - sender: ZodMessageSender; - - io: Server; - namespace: Namespace< - ZodMessageCatalogToSocketIoEvents, - ZodMessageCatalogToSocketIoEvents, - TServerSideEvents, - z.infer - >; - - constructor( - opts: ZodNamespaceOptions - ) { - this.#logger = - opts.logger ?? - new SimpleStructuredLogger(`ns-${opts.name}`, undefined, { - namespace: opts.name, - }); - - this.#handler = new ZodSocketMessageHandler({ - schema: opts.clientMessages, - handlers: opts.handlers, - logPayloads: opts.logHandlerPayloads, - }); - - this.io = opts.io; - - this.namespace = this.io.of(opts.name); - - // FIXME: There's a bug here, this sender should not accept Socket schemas with callbacks - this.sender = new ZodMessageSender({ - schema: opts.serverMessages, - sender: async (message) => { - return new Promise((resolve, reject) => { - try { - // @ts-expect-error - this.namespace.emit(message.type, message.payload); - resolve(); - } catch (err) { - reject(err); - } - }); - }, - }); - - if (opts.preAuth) { - this.namespace.use(async (socket, next) => { - const logger = this.#logger.child({ socketId: socket.id, socketStage: "preAuth" }); - - if (typeof opts.preAuth === "function") { - await opts.preAuth(socket, next, logger); - } - }); - } - - if (opts.authToken) { - this.namespace.use((socket, next) => { - const logger = this.#logger.child({ socketId: socket.id, socketStage: "auth" }); - - const { auth } = socket.handshake; - - if (!("token" in auth)) { - logger.error("no token"); - return socket.disconnect(true); - } - - if (auth.token !== opts.authToken) { - logger.error("invalid token"); - return socket.disconnect(true); - } - - logger.info("success"); - - next(); - - return; - }); - } - - if (opts.postAuth) { - this.namespace.use(async (socket, next) => { - const logger = this.#logger.child({ socketId: socket.id, socketStage: "auth" }); - - if (typeof opts.postAuth === "function") { - await opts.postAuth(socket, next, logger); - } - }); - } - - this.namespace.on("connection", async (socket) => { - const logger = this.#logger.child({ socketId: socket.id, socketStage: "connection" }); - logger.info("connected"); - - this.#handler.registerHandlers(socket, logger); - - socket.on("disconnect", async (reason, description) => { - logger.info("disconnect", { reason, description }); - - if (opts.onDisconnect) { - await opts.onDisconnect(socket, reason, description, logger); - } - }); - - socket.on("error", async (error) => { - logger.error("error", { error }); - - if (opts.onError) { - await opts.onError(socket, error, logger); - } - }); - - if (opts.onConnection) { - await opts.onConnection(socket, this.#handler, this.sender, logger); - } - }); - } - - fetchSockets() { - return this.namespace.fetchSockets(); - } -} diff --git a/patches/graphile-worker@0.16.6.patch b/patches/graphile-worker@0.16.6.patch deleted file mode 100644 index 68f01bab9a5..00000000000 --- a/patches/graphile-worker@0.16.6.patch +++ /dev/null @@ -1,27 +0,0 @@ -diff --git a/dist/sql/getJob.js b/dist/sql/getJob.js -index 70cc9b49d7d08c8dd32214f15c463b2a568abd15..5bcb50a4544046e56f6d5dd70e96e26e59998ad5 100644 ---- a/dist/sql/getJob.js -+++ b/dist/sql/getJob.js -@@ -61,7 +61,7 @@ async function getJob(compiledSharedOptions, withPgClient, tasks, workerId, flag - * - * I recommend you either use strat 0 if you can, or strat 2 otherwise. - */ -- const strategy = 2; -+ const strategy = 0; - const queueClause = strategy === 0 - ? `and jobs.job_queue_id is null` - : strategy === 1 -@@ -153,6 +153,13 @@ with j as ( - const name = !preparedStatements - ? undefined - : `get_job${hasFlags ? "F" : ""}${useNodeTime ? "N" : ""}/${workerSchema}`; -+ -+ logger.debug("Running getJob query...", { -+ text, -+ values, -+ name -+ }); -+ - const { rows: [jobRow], } = await withPgClient.withRetries((client) => client.query({ - text, - values, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a41e0c22fe..288c8820a64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,9 +73,6 @@ patchedDependencies: engine.io-parser@5.2.2: hash: 9011e7e16547017450c9baec0fceff0e070310c20d9e62f8d1383bb1da77104f path: patches/engine.io-parser@5.2.2.patch - graphile-worker@0.16.6: - hash: 798129c99ed02177430fc90a1fdef800ec94e5fd1d491b931297dc52f4c98ab1 - path: patches/graphile-worker@0.16.6.patch redlock@5.0.0-beta.2: hash: 52b17ac642f5f9776bf05e2229f4dd79588d37b0039d835c7684c478464632f2 path: patches/redlock@5.0.0-beta.2.patch @@ -317,9 +314,6 @@ importers: '@internal/tsql': specifier: workspace:* version: link:../../internal-packages/tsql - '@internal/zod-worker': - specifier: workspace:* - version: link:../../internal-packages/zod-worker '@internationalized/date': specifier: ^3.5.1 version: 3.5.1 @@ -605,9 +599,6 @@ importers: framer-motion: specifier: ^10.12.11 version: 10.12.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - graphile-worker: - specifier: 0.16.6 - version: 0.16.6(patch_hash=798129c99ed02177430fc90a1fdef800ec94e5fd1d491b931297dc52f4c98ab1)(typescript@5.5.4) humanize-duration: specifier: ^3.27.3 version: 3.27.3 @@ -1491,34 +1482,6 @@ importers: specifier: 0.5.0-alpha.4 version: 0.5.0-alpha.4 - internal-packages/zod-worker: - dependencies: - '@internal/tracing': - specifier: workspace:* - version: link:../tracing - '@trigger.dev/core': - specifier: workspace:* - version: link:../../packages/core - '@trigger.dev/database': - specifier: workspace:* - version: link:../database - graphile-worker: - specifier: 0.16.6 - version: 0.16.6(patch_hash=798129c99ed02177430fc90a1fdef800ec94e5fd1d491b931297dc52f4c98ab1)(typescript@5.5.4) - lodash.omit: - specifier: ^4.5.0 - version: 4.5.0 - zod: - specifier: 3.25.76 - version: 3.25.76 - devDependencies: - '@types/lodash.omit': - specifier: ^4.5.7 - version: 4.5.7 - '@types/pg': - specifier: 8.6.6 - version: 8.6.6 - packages/build: dependencies: '@prisma/config': @@ -3074,18 +3037,10 @@ packages: resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} @@ -3185,10 +3140,6 @@ packages: resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} @@ -4595,9 +4546,6 @@ packages: resolution: {integrity: sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==} engines: {node: '>=14.0.0'} - '@graphile/logger@0.2.0': - resolution: {integrity: sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==} - '@graphql-typed-document-node/core@3.2.0': resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} peerDependencies: @@ -8532,9 +8480,6 @@ packages: '@types/ini@4.1.1': resolution: {integrity: sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==} - '@types/interpret@1.1.3': - resolution: {integrity: sha512-uBaBhj/BhilG58r64mtDb/BEdH51HIQLgP5bmWzc5qCtFMja8dCk/IOJmk36j0lbi9QHwI6sbtUNGuqXdKCAtQ==} - '@types/is-ci@3.0.0': resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} @@ -8625,9 +8570,6 @@ packages: '@types/pg@8.11.14': resolution: {integrity: sha512-qyD11E5R3u0eJmd1lB0WnWKXJGA7s015nyARWljfz5DcX83TKAIlY+QrmvzQTsbIe+hkiFtkyL2gHC6qwF6Fbg==} - '@types/pg@8.11.6': - resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} - '@types/pg@8.6.1': resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} @@ -9842,15 +9784,6 @@ packages: cose-base@2.2.0: resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} - cosmiconfig@8.3.6: - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: 5.5.4 - peerDependenciesMeta: - typescript: - optional: true - cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -11446,15 +11379,6 @@ packages: grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - graphile-config@0.0.1-beta.8: - resolution: {integrity: sha512-H8MinryZewvUigVLnkVDhKJgHrcNYGcLvgYWfSnR1d6l76iV9E8m4ZfN9estSHKVm6cyHhRfHBfL1G5QfXmS5A==} - engines: {node: '>=16'} - - graphile-worker@0.16.6: - resolution: {integrity: sha512-e7gGYDmGqzju2l83MpzX8vNG/lOtVJiSzI3eZpAFubSxh/cxs7sRrRGBGjzBP1kNG0H+c95etPpNRNlH65PYhw==} - engines: {node: '>=14.0.0'} - hasBin: true - graphql@16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -11724,10 +11648,6 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - interpret@3.1.1: - resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} - engines: {node: '>=10.13.0'} - intl-messageformat@10.5.8: resolution: {integrity: sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA==} @@ -13600,9 +13520,6 @@ packages: pg-connection-string@2.8.5: resolution: {integrity: sha512-Ni8FuZ8yAF+sWZzojvtLE2b03cqjO5jNULcHFfM9ZZ0/JXrgom5pBREbtnAw7oxsxJqHw9Nz/XWORUEL3/IFow==} - pg-connection-string@2.9.1: - resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} - pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -13611,11 +13528,6 @@ packages: resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} engines: {node: '>=4'} - pg-pool@3.10.1: - resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} - peerDependencies: - pg: '>=8.0' - pg-pool@3.9.6: resolution: {integrity: sha512-rFen0G7adh1YmgvrmE5IPIqbb+IgEzENUm+tzm6MLLDSlPRoZVhzU1WdML9PV2W5GOdRA9qBKURlbt1OsXOsPw==} peerDependencies: @@ -13638,15 +13550,6 @@ packages: resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} engines: {node: '>=10'} - pg@8.11.5: - resolution: {integrity: sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==} - engines: {node: '>= 8.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - pg@8.15.6: resolution: {integrity: sha512-yvao7YI3GdmmrslNVsZgx9PfntfWrnXwtR+K/DjI0I/sTKif4Z623um+sjVZ1hk5670B+ODjvHDAckKdjmPTsg==} engines: {node: '>= 8.0.0'} @@ -18591,14 +18494,8 @@ snapshots: dependencies: '@babel/types': 7.29.7 - '@babel/helper-string-parser@7.27.1': - optional: true - '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': - optional: true - '@babel/helper-validator-identifier@7.29.7': {} '@babel/helper-validator-option@7.22.15': {} @@ -18706,12 +18603,6 @@ snapshots: '@babel/helper-validator-identifier': 7.29.7 to-fast-properties: 2.0.0 - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - optional: true - '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 @@ -19105,7 +18996,7 @@ snapshots: '@epic-web/test-server@0.1.0(bufferutil@4.0.9)': dependencies: - '@hono/node-server': 1.12.2(hono@4.5.11) + '@hono/node-server': 1.12.2(hono@4.12.15) '@hono/node-ws': 1.0.4(@hono/node-server@1.12.2(hono@4.5.11))(bufferutil@4.0.9) '@open-draft/deferred-promise': 2.2.0 '@types/ws': 8.5.12 @@ -19775,8 +19666,6 @@ snapshots: '@google-cloud/precise-date@4.0.0': {} - '@graphile/logger@0.2.0': {} - '@graphql-typed-document-node/core@3.2.0(graphql@16.6.0)': dependencies: graphql: 16.6.0 @@ -19846,9 +19735,9 @@ snapshots: dependencies: react: 18.3.1 - '@hono/node-server@1.12.2(hono@4.5.11)': + '@hono/node-server@1.12.2(hono@4.12.15)': dependencies: - hono: 4.5.11 + hono: 4.12.15 '@hono/node-server@1.19.9(hono@4.11.8)': dependencies: @@ -19860,7 +19749,7 @@ snapshots: '@hono/node-ws@1.0.4(@hono/node-server@1.12.2(hono@4.5.11))(bufferutil@4.0.9)': dependencies: - '@hono/node-server': 1.12.2(hono@4.5.11) + '@hono/node-server': 1.12.2(hono@4.12.15) ws: 8.20.1(bufferutil@4.0.9) transitivePeerDependencies: - bufferutil @@ -24834,10 +24723,6 @@ snapshots: '@types/ini@4.1.1': {} - '@types/interpret@1.1.3': - dependencies: - '@types/node': 22.20.0 - '@types/is-ci@3.0.0': dependencies: ci-info: 3.8.0 @@ -24936,12 +24821,6 @@ snapshots: pg-protocol: 1.9.5 pg-types: 4.0.2 - '@types/pg@8.11.6': - dependencies: - '@types/node': 22.20.0 - pg-protocol: 1.10.3 - pg-types: 4.0.2 - '@types/pg@8.6.1': dependencies: '@types/node': 22.20.0 @@ -26357,15 +26236,6 @@ snapshots: dependencies: layout-base: 2.0.1 - cosmiconfig@8.3.6(typescript@5.5.4): - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.1 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 5.5.4 - cosmiconfig@9.0.0(typescript@5.5.4): dependencies: env-paths: 2.2.1 @@ -28297,36 +28167,6 @@ snapshots: grapheme-splitter@1.0.4: {} - graphile-config@0.0.1-beta.8: - dependencies: - '@types/interpret': 1.1.3 - '@types/node': 22.20.0 - '@types/semver': 7.5.1 - chalk: 4.1.2 - debug: 4.4.3(supports-color@10.0.0) - interpret: 3.1.1 - semver: 7.8.1 - tslib: 2.8.1 - yargs: 17.7.2 - transitivePeerDependencies: - - supports-color - - graphile-worker@0.16.6(patch_hash=798129c99ed02177430fc90a1fdef800ec94e5fd1d491b931297dc52f4c98ab1)(typescript@5.5.4): - dependencies: - '@graphile/logger': 0.2.0 - '@types/debug': 4.1.12 - '@types/pg': 8.11.6 - cosmiconfig: 8.3.6(typescript@5.5.4) - graphile-config: 0.0.1-beta.8 - json5: 2.2.3 - pg: 8.11.5 - tslib: 2.6.2 - yargs: 17.7.2 - transitivePeerDependencies: - - pg-native - - supports-color - - typescript - graphql@16.6.0: {} gunzip-maybe@1.4.2: @@ -28653,8 +28493,6 @@ snapshots: internmap@2.0.3: {} - interpret@3.1.1: {} - intl-messageformat@10.5.8: dependencies: '@formatjs/ecma402-abstract': 1.18.0 @@ -29272,8 +29110,8 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 source-map-js: 1.2.1 optional: true @@ -30311,7 +30149,7 @@ snapshots: node-abi@3.89.0: dependencies: - semver: 7.7.3 + semver: 7.8.1 optional: true node-abort-controller@3.1.1: {} @@ -30846,16 +30684,10 @@ snapshots: pg-connection-string@2.8.5: {} - pg-connection-string@2.9.1: {} - pg-int8@1.0.1: {} pg-numeric@1.0.2: {} - pg-pool@3.10.1(pg@8.11.5): - dependencies: - pg: 8.11.5 - pg-pool@3.9.6(pg@8.15.6): dependencies: pg: 8.15.6 @@ -30884,16 +30716,6 @@ snapshots: postgres-interval: 3.0.0 postgres-range: 1.1.4 - pg@8.11.5: - dependencies: - pg-connection-string: 2.9.1 - pg-pool: 3.10.1(pg@8.11.5) - pg-protocol: 1.10.3 - pg-types: 2.2.0 - pgpass: 1.0.5 - optionalDependencies: - pg-cloudflare: 1.2.7 - pg@8.15.6: dependencies: pg-connection-string: 2.8.5 From 5d13224564de014bab60e19fa112142bd23ab5e8 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 12 Jul 2026 23:13:29 +0100 Subject: [PATCH 03/12] docs(webapp): update agent guidance for the v3 engine removal Refresh the CLAUDE.md / AGENTS.md / legacy-v3-code rule sections that described the now-deleted V1 files, and drop stale comment references to removed modules. --- AGENTS.md | 4 ++-- apps/webapp/CLAUDE.md | 12 ++---------- apps/webapp/app/v3/mollifierDrainerWorker.server.ts | 9 +++------ apps/webapp/app/v3/services/batchTriggerV3.server.ts | 5 ++--- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 536ec0188d7..23f21bad31e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -140,9 +140,9 @@ User API call -> Webapp routes -> Services -> RunEngine -> Redis Queue -> Superv - **internal-packages/schedule-engine**: Durable cron scheduling - **internal-packages/zod-worker**: Graphile-worker wrapper (DEPRECATED - use redis-worker) -### Legacy V1 Engine Code +### v3 (engine V1) removed -The `apps/webapp/app/v3/` directory name is misleading - most code there is actively used by V2. Only specific files are V1-only legacy (MarQS queue, triggerTaskV1, cancelTaskRunV1, etc.). See `apps/webapp/CLAUDE.md` for the exact list. When you encounter V1/V2 branching in services, only modify V2 code paths. All new work uses Run Engine 2.0 (`@internal/run-engine`) and redis-worker. +v3 (engine V1: MarQS + Graphile worker) is end-of-life and its execution code has been removed. The `apps/webapp/app/v3/` directory name is historical - everything there now serves V2 (Run Engine 2.0, `@internal/run-engine` + redis-worker). There is no V1 execution path: a `RunEngineVersion` `V1` branch only rejects gracefully so v3 clients get a clean 4xx, never a 5xx. Do not reintroduce V1. See `apps/webapp/CLAUDE.md` and `.claude/rules/legacy-v3-code.md`. ### Documentation diff --git a/apps/webapp/CLAUDE.md b/apps/webapp/CLAUDE.md index 68efaffd41e..9daf1a8db89 100644 --- a/apps/webapp/CLAUDE.md +++ b/apps/webapp/CLAUDE.md @@ -98,17 +98,9 @@ Do NOT add new jobs using zodworker/graphile-worker (legacy). - Socket.io: `app/v3/handleSocketIo.server.ts`, `app/v3/handleWebsockets.server.ts` - Electric SQL: Powers real-time data sync for the dashboard -## Legacy V1 Code +## v3 (engine V1) removed -The `app/v3/` directory name is misleading - most code is actively used by V2. Only these specific files are V1-only legacy: -- `app/v3/marqs/` (old MarQS queue system) -- `app/v3/legacyRunEngineWorker.server.ts` -- `app/v3/services/triggerTaskV1.server.ts` -- `app/v3/services/cancelTaskRunV1.server.ts` -- `app/v3/authenticatedSocketConnection.server.ts` -- `app/v3/sharedSocketConnection.ts` - -Some services (e.g., `cancelTaskRun.server.ts`, `batchTriggerV3.server.ts`) branch on `RunEngineVersion` to support both V1 and V2. When editing these, only modify V2 code paths. +v3 (engine V1: MarQS + Graphile worker) is end-of-life and its execution code is gone. The `app/v3/` directory name is historical; everything under it now serves V2. There is no V1 execution path: a `RunEngineVersion` `V1` branch (e.g. in `triggerTask.server.ts`, `cancelTaskRun.server.ts`) only rejects/finalizes gracefully so v3 clients get a clean 4xx, never a 5xx. Do not reintroduce V1. See `.claude/rules/legacy-v3-code.md` for the deprecation boundary. ## Performance: Trigger Hot Path diff --git a/apps/webapp/app/v3/mollifierDrainerWorker.server.ts b/apps/webapp/app/v3/mollifierDrainerWorker.server.ts index 1ffa48f1cd7..a6adf219983 100644 --- a/apps/webapp/app/v3/mollifierDrainerWorker.server.ts +++ b/apps/webapp/app/v3/mollifierDrainerWorker.server.ts @@ -26,12 +26,9 @@ declare global { * factory) guarantees a signal landing during boot can never find * the polling loop running without a graceful-stop path. * - * The drainer is intentionally NOT wired through `~/services/worker.server` - * — that file is the legacy ZodWorker / graphile-worker setup. The - * mollifier drainer is a custom polling loop over `MollifierBuffer`, not - * a graphile-worker job, so it gets its own lifecycle file alongside the - * redis-worker workers (`commonWorker`, `alertsWorker`, - * `batchTriggerWorker`). + * The mollifier drainer is a custom polling loop over `MollifierBuffer`, not a + * redis-worker job, so it gets its own lifecycle file alongside the redis-worker + * workers (`commonWorker`, `alertsWorker`, `batchTriggerWorker`). * * Gating order: * - `TRIGGER_MOLLIFIER_DRAINER_ENABLED !== "1"` → early return. Unset defaults diff --git a/apps/webapp/app/v3/services/batchTriggerV3.server.ts b/apps/webapp/app/v3/services/batchTriggerV3.server.ts index b3f25efab89..0a99acf029f 100644 --- a/apps/webapp/app/v3/services/batchTriggerV3.server.ts +++ b/apps/webapp/app/v3/services/batchTriggerV3.server.ts @@ -86,9 +86,8 @@ type RunItemData = { * we increment the BatchTaskRun's completed count. Once the completed count is equal to the expected count, and the * batch is sealed, we can consider the batch completed. * - * So now when the v3 batch is considered completed, we will enqueue the ResumeBatchRunService to resume the dependent - * task attempt if there is one. This is in contrast to v2 batches where every time a task was completed, we would schedule - * the ResumeBatchRunService to check if the batch was completed and set it to completed if it was. + * When the v3 batch is considered completed it is marked COMPLETED. (Dependent-attempt + * batches from batchTriggerAndWait only existed on the retired V1 engine.) * * We've also introduced a new column "resumedAt" that will be set when the batch is resumed. Previously in v2 batches, the status == "COMPLETED" was overloaded * to mean that the batch was completed and resumed. Now we have a separate column to track when the batch was resumed (and to make sure it's only resumed once). From 385f82eff090ff266358d375e1076e264fff245d Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 07:02:49 +0100 Subject: [PATCH 04/12] chore(core,webapp): remove dead v3 socket message catalogs and MarQS env vars After confirming no cross-repo consumers (infra/cloud/compute), remove the now-dead v3 socket message catalogs from @trigger.dev/core (coordinator/provider/shared-queue/ prod-worker + the legacy background-worker websocket catalogs) and the unreferenced MarQS / shared-queue-consumer environment variables from the webapp. Keeps the v4 IPC catalogs, ServerBackgroundWorker, and the still-used CHECKPOINT_THRESHOLD_IN_MS. --- apps/webapp/app/env.server.ts | 46 -- packages/core/src/v3/schemas/messages.ts | 785 +---------------------- 2 files changed, 2 insertions(+), 829 deletions(-) diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 91253cb63b1..11460511c3f 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -612,7 +612,6 @@ const EnvironmentSchema = z // log-only mode before enforcement. DEPRECATE_V3_CLI_DEPLOYS_ENABLED: z.string().default("0"), - // Verify the deploy image exists before promoting. Disable for out-of-band/air-gapped push. ECR only. DEPLOY_IMAGE_VERIFICATION_ENABLED: BoolEnv.default(true), @@ -646,11 +645,6 @@ const EnvironmentSchema = z EVENTS_MEMORY_PRESSURE_THRESHOLD: z.coerce.number().int().default(5000), EVENTS_LOAD_SHEDDING_THRESHOLD: z.coerce.number().int().default(100000), EVENTS_LOAD_SHEDDING_ENABLED: z.string().default("1"), - SHARED_QUEUE_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10), - SHARED_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(100), - SHARED_QUEUE_CONSUMER_NEXT_TICK_INTERVAL_MS: z.coerce.number().int().default(100), - SHARED_QUEUE_CONSUMER_EMIT_RESUME_DEPENDENCY_TIMEOUT_MS: z.coerce.number().int().default(1000), - SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE: z.coerce.number().int().default(25), MANAGED_WORKER_SECRET: z.string().default("managed-secret"), @@ -770,49 +764,9 @@ const EnvironmentSchema = z LOOPS_API_KEY: z.string().optional(), ATTIO_API_KEY: z.string().optional(), - MARQS_DISABLE_REBALANCING: BoolEnv.default(false), - MARQS_VISIBILITY_TIMEOUT_MS: z.coerce - .number() - .int() - .default(60 * 1000 * 15), - MARQS_SHARED_QUEUE_LIMIT: z.coerce.number().int().default(1000), - MARQS_MAXIMUM_QUEUE_PER_ENV_COUNT: z.coerce.number().int().default(50), - MARQS_DEV_QUEUE_LIMIT: z.coerce.number().int().default(1000), - MARQS_MAXIMUM_NACK_COUNT: z.coerce.number().int().default(64), - MARQS_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75), - MARQS_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3), - MARQS_QUEUE_AGE_RANDOMIZATION_BIAS: z.coerce.number().default(0.25), - MARQS_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0), - MARQS_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(), - MARQS_SHARED_WORKER_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(250), - MARQS_SHARED_WORKER_QUEUE_MAX_MESSAGE_COUNT: z.coerce.number().int().default(10), - - MARQS_SHARED_WORKER_QUEUE_EAGER_DEQUEUE_ENABLED: z.string().default("0"), - MARQS_WORKER_ENABLED: z.string().default("0"), - MARQS_WORKER_COUNT: z.coerce.number().int().default(2), - MARQS_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), - MARQS_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(5), - MARQS_WORKER_POLL_INTERVAL_MS: z.coerce.number().int().default(100), - MARQS_WORKER_IMMEDIATE_POLL_INTERVAL_MS: z.coerce.number().int().default(100), - MARQS_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - MARQS_SHARED_WORKER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10), - MARQS_SHARED_WORKER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(5_000), PROD_TASK_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(), - V2_MARQS_ENABLED: z.string().default("0"), - V2_MARQS_CONSUMER_POOL_ENABLED: z.string().default("0"), - V2_MARQS_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10), - V2_MARQS_CONSUMER_POLL_INTERVAL_MS: z.coerce.number().int().default(1000), - V2_MARQS_QUEUE_SELECTION_COUNT: z.coerce.number().int().default(36), - V2_MARQS_VISIBILITY_TIMEOUT_MS: z.coerce - .number() - .int() - .default(60 * 1000 * 15), - V2_MARQS_DEFAULT_ENV_CONCURRENCY: z.coerce.number().int().default(100), - V2_MARQS_VERBOSE: z.string().default("0"), - V3_MARQS_CONCURRENCY_MONITOR_ENABLED: z.string().default("0"), - V2_MARQS_CONCURRENCY_MONITOR_ENABLED: z.string().default("0"), /* Usage settings */ USAGE_EVENT_URL: z.string().optional(), PROD_USAGE_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(), diff --git a/packages/core/src/v3/schemas/messages.ts b/packages/core/src/v3/schemas/messages.ts index 2c7c357c3b7..1389fcddc40 100644 --- a/packages/core/src/v3/schemas/messages.ts +++ b/packages/core/src/v3/schemas/messages.ts @@ -1,110 +1,10 @@ import { z } from "zod"; import { ImportTaskFileErrors, WorkerManifest } from "./build.js"; -import { - MachinePreset, - TaskRunExecution, - TaskRunExecutionResult, - TaskRunFailedExecutionResult, - TaskRunInternalError, - V3TaskRunExecution, -} from "./common.js"; -import { TaskResource } from "./resources.js"; -import { - EnvironmentType, - V3ProdTaskRunExecution, - V3ProdTaskRunExecutionPayload, - RunEngineVersionSchema, - TaskRunExecutionLazyAttemptPayload, - TaskRunExecutionMetrics, - WaitReason, -} from "./schemas.js"; +import { TaskRunExecution, TaskRunExecutionResult } from "./common.js"; +import { RunEngineVersionSchema, TaskRunExecutionMetrics } from "./schemas.js"; import { CompletedWaitpoint } from "./runEngine.js"; import { DebugLogPropertiesInput } from "../runEngineWorker/supervisor/schemas.js"; -export const AckCallbackResult = z.discriminatedUnion("success", [ - z.object({ - success: z.literal(false), - error: z.object({ - name: z.string(), - message: z.string(), - stack: z.string().optional(), - stderr: z.string().optional(), - }), - }), - z.object({ - success: z.literal(true), - }), -]); - -export type AckCallbackResult = z.infer; - -export const BackgroundWorkerServerMessages = z.discriminatedUnion("type", [ - z.object({ - type: z.literal("CANCEL_ATTEMPT"), - taskAttemptId: z.string(), - taskRunId: z.string(), - }), - z.object({ - type: z.literal("SCHEDULE_ATTEMPT"), - image: z.string(), - version: z.string(), - machine: MachinePreset, - nextAttemptNumber: z.number().optional(), - // identifiers - id: z.string().optional(), // TODO: Remove this completely in a future release - envId: z.string(), - envType: EnvironmentType, - orgId: z.string(), - projectId: z.string(), - runId: z.string(), - dequeuedAt: z.number().optional(), - }), - z.object({ - type: z.literal("EXECUTE_RUN_LAZY_ATTEMPT"), - payload: TaskRunExecutionLazyAttemptPayload, - }), -]); - -export type BackgroundWorkerServerMessages = z.infer; - -export const serverWebsocketMessages = { - SERVER_READY: z.object({ - version: z.literal("v1").default("v1"), - id: z.string(), - }), - BACKGROUND_WORKER_MESSAGE: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - data: BackgroundWorkerServerMessages, - }), -}; - -export const BackgroundWorkerClientMessages = z.discriminatedUnion("type", [ - z.object({ - version: z.literal("v1").default("v1"), - type: z.literal("TASK_RUN_COMPLETED"), - completion: TaskRunExecutionResult, - execution: V3TaskRunExecution, - }), - z.object({ - version: z.literal("v1").default("v1"), - type: z.literal("TASK_RUN_FAILED_TO_RUN"), - completion: TaskRunFailedExecutionResult, - }), - z.object({ - version: z.literal("v1").default("v1"), - type: z.literal("TASK_HEARTBEAT"), - id: z.string(), - }), - z.object({ - version: z.literal("v1").default("v1"), - type: z.literal("TASK_RUN_HEARTBEAT"), - id: z.string(), - }), -]); - -export type BackgroundWorkerClientMessages = z.infer; - export const ServerBackgroundWorker = z.object({ id: z.string(), version: z.string(), @@ -114,23 +14,6 @@ export const ServerBackgroundWorker = z.object({ export type ServerBackgroundWorker = z.infer; -export const clientWebsocketMessages = { - READY_FOR_TASKS: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - inProgressRuns: z.string().array().optional(), - }), - BACKGROUND_WORKER_DEPRECATED: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - }), - BACKGROUND_WORKER_MESSAGE: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - data: BackgroundWorkerClientMessages, - }), -}; - export const UncaughtExceptionMessage = z.object({ version: z.literal("v1").default("v1"), error: z.object({ @@ -234,667 +117,3 @@ export const WorkerToExecutorMessageCatalog = { }), }, }; - -export const ProviderToPlatformMessages = { - LOG: { - message: z.object({ - version: z.literal("v1").default("v1"), - data: z.string(), - }), - }, - LOG_WITH_ACK: { - message: z.object({ - version: z.literal("v1").default("v1"), - data: z.string(), - }), - callback: z.object({ - status: z.literal("ok"), - }), - }, - WORKER_CRASHED: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - reason: z.string().optional(), - exitCode: z.number().optional(), - message: z.string().optional(), - logs: z.string().optional(), - /** This means we should only update the error if one exists */ - overrideCompletion: z.boolean().optional(), - errorCode: TaskRunInternalError.shape.code.optional(), - }), - }, - INDEXING_FAILED: { - message: z.object({ - version: z.literal("v1").default("v1"), - deploymentId: z.string(), - error: z.object({ - name: z.string(), - message: z.string(), - stack: z.string().optional(), - stderr: z.string().optional(), - }), - overrideCompletion: z.boolean().optional(), - }), - }, -}; - -export const PlatformToProviderMessages = { - INDEX: { - message: z.object({ - version: z.literal("v1").default("v1"), - imageTag: z.string(), - shortCode: z.string(), - apiKey: z.string(), - apiUrl: z.string(), - // identifiers - envId: z.string(), - envType: EnvironmentType, - orgId: z.string(), - projectId: z.string(), - deploymentId: z.string(), - }), - callback: AckCallbackResult, - }, - RESTORE: { - message: z.object({ - version: z.literal("v1").default("v1"), - type: z.enum(["DOCKER", "KUBERNETES"]), - location: z.string(), - reason: z.string().optional(), - imageRef: z.string(), - attemptNumber: z.number().optional(), - machine: MachinePreset, - // identifiers - checkpointId: z.string(), - envId: z.string(), - envType: EnvironmentType, - orgId: z.string(), - projectId: z.string(), - runId: z.string(), - }), - }, - PRE_PULL_DEPLOYMENT: { - message: z.object({ - version: z.literal("v1").default("v1"), - imageRef: z.string(), - shortCode: z.string(), - // identifiers - envId: z.string(), - envType: EnvironmentType, - orgId: z.string(), - projectId: z.string(), - deploymentId: z.string(), - }), - }, -}; - -const CreateWorkerMessage = z.object({ - projectRef: z.string(), - envId: z.string(), - deploymentId: z.string(), - metadata: z.object({ - cliPackageVersion: z.string().optional(), - contentHash: z.string(), - packageVersion: z.string(), - tasks: TaskResource.array(), - }), -}); - -export const CoordinatorToPlatformMessages = { - LOG: { - message: z.object({ - version: z.literal("v1").default("v1"), - metadata: z.any(), - text: z.string(), - }), - }, - CREATE_WORKER: { - message: z.discriminatedUnion("version", [ - CreateWorkerMessage.extend({ - version: z.literal("v1"), - }), - CreateWorkerMessage.extend({ - version: z.literal("v2"), - supportsLazyAttempts: z.boolean(), - }), - ]), - callback: z.discriminatedUnion("success", [ - z.object({ - success: z.literal(false), - }), - z.object({ - success: z.literal(true), - }), - ]), - }, - CREATE_TASK_RUN_ATTEMPT: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - envId: z.string(), - }), - callback: z.discriminatedUnion("success", [ - z.object({ - success: z.literal(false), - reason: z.string().optional(), - }), - z.object({ - success: z.literal(true), - executionPayload: V3ProdTaskRunExecutionPayload, - }), - ]), - }, - // Deprecated: Only workers without lazy attempt support will use this - READY_FOR_EXECUTION: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - totalCompletions: z.number(), - }), - callback: z.discriminatedUnion("success", [ - z.object({ - success: z.literal(false), - }), - z.object({ - success: z.literal(true), - payload: V3ProdTaskRunExecutionPayload, - }), - ]), - }, - READY_FOR_LAZY_ATTEMPT: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - envId: z.string(), - totalCompletions: z.number(), - }), - callback: z.discriminatedUnion("success", [ - z.object({ - success: z.literal(false), - reason: z.string().optional(), - }), - z.object({ - success: z.literal(true), - lazyPayload: TaskRunExecutionLazyAttemptPayload, - }), - ]), - }, - READY_FOR_RESUME: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptFriendlyId: z.string(), - type: WaitReason, - }), - }, - TASK_RUN_COMPLETED: { - message: z.object({ - version: z.enum(["v1", "v2"]).default("v1"), - execution: V3ProdTaskRunExecution, - completion: TaskRunExecutionResult, - checkpoint: z - .object({ - docker: z.boolean(), - location: z.string(), - }) - .optional(), - }), - }, - TASK_RUN_COMPLETED_WITH_ACK: { - message: z.object({ - version: z.enum(["v1", "v2"]).default("v2"), - execution: V3ProdTaskRunExecution, - completion: TaskRunExecutionResult, - checkpoint: z - .object({ - docker: z.boolean(), - location: z.string(), - }) - .optional(), - }), - callback: AckCallbackResult, - }, - TASK_RUN_FAILED_TO_RUN: { - message: z.object({ - version: z.literal("v1").default("v1"), - completion: TaskRunFailedExecutionResult, - }), - }, - TASK_HEARTBEAT: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptFriendlyId: z.string(), - }), - }, - TASK_RUN_HEARTBEAT: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - }), - }, - CHECKPOINT_CREATED: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string().optional(), - attemptFriendlyId: z.string(), - docker: z.boolean(), - location: z.string(), - reason: z.discriminatedUnion("type", [ - z.object({ - type: z.literal("WAIT_FOR_DURATION"), - ms: z.number(), - now: z.number(), - }), - z.object({ - type: z.literal("WAIT_FOR_BATCH"), - batchFriendlyId: z.string(), - runFriendlyIds: z.string().array(), - }), - z.object({ - type: z.literal("WAIT_FOR_TASK"), - friendlyId: z.string(), - }), - z.object({ - type: z.literal("RETRYING_AFTER_FAILURE"), - attemptNumber: z.number(), - }), - z.object({ - type: z.literal("MANUAL"), - /** If unspecified it will be restored immediately, e.g. for live migration */ - restoreAtUnixTimeMs: z.number().optional(), - }), - ]), - }), - callback: z.object({ - version: z.literal("v1").default("v1"), - keepRunAlive: z.boolean(), - }), - }, - INDEXING_FAILED: { - message: z.object({ - version: z.literal("v1").default("v1"), - deploymentId: z.string(), - error: z.object({ - name: z.string(), - message: z.string(), - stack: z.string().optional(), - stderr: z.string().optional(), - }), - }), - }, - RUN_CRASHED: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - error: z.object({ - name: z.string(), - message: z.string(), - stack: z.string().optional(), - }), - }), - }, -}; - -export const PlatformToCoordinatorMessages = { - /** @deprecated use RESUME_AFTER_DEPENDENCY_WITH_ACK instead */ - RESUME_AFTER_DEPENDENCY: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - attemptId: z.string(), - attemptFriendlyId: z.string(), - completions: TaskRunExecutionResult.array(), - executions: TaskRunExecution.array(), - }), - }, - RESUME_AFTER_DEPENDENCY_WITH_ACK: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - attemptId: z.string(), - attemptFriendlyId: z.string(), - completions: TaskRunExecutionResult.array(), - executions: TaskRunExecution.array(), - }), - callback: AckCallbackResult, - }, - RESUME_AFTER_DURATION: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptId: z.string(), - attemptFriendlyId: z.string(), - }), - }, - REQUEST_ATTEMPT_CANCELLATION: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptId: z.string(), - attemptFriendlyId: z.string(), - }), - }, - REQUEST_RUN_CANCELLATION: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - delayInMs: z.number().optional(), - }), - }, - READY_FOR_RETRY: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - }), - }, - DYNAMIC_CONFIG: { - message: z.object({ - version: z.literal("v1").default("v1"), - checkpointThresholdInMs: z.number(), - }), - }, -}; - -export const ClientToSharedQueueMessages = { - READY_FOR_TASKS: { - message: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - }), - }, - BACKGROUND_WORKER_DEPRECATED: { - message: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - }), - }, - BACKGROUND_WORKER_MESSAGE: { - message: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - data: BackgroundWorkerClientMessages, - }), - }, -}; - -export const SharedQueueToClientMessages = { - SERVER_READY: { - message: z.object({ - version: z.literal("v1").default("v1"), - id: z.string(), - }), - }, - BACKGROUND_WORKER_MESSAGE: { - message: z.object({ - version: z.literal("v1").default("v1"), - backgroundWorkerId: z.string(), - data: BackgroundWorkerServerMessages, - }), - }, -}; - -const IndexTasksMessage = z.object({ - version: z.literal("v1"), - deploymentId: z.string(), - tasks: TaskResource.array(), - packageVersion: z.string(), -}); - -export const ProdWorkerToCoordinatorMessages = { - TEST: { - message: z.object({ - version: z.literal("v1").default("v1"), - }), - callback: z.void(), - }, - INDEX_TASKS: { - message: z.discriminatedUnion("version", [ - IndexTasksMessage.extend({ - version: z.literal("v1"), - }), - IndexTasksMessage.extend({ - version: z.literal("v2"), - supportsLazyAttempts: z.boolean(), - }), - ]), - callback: z.discriminatedUnion("success", [ - z.object({ - success: z.literal(false), - }), - z.object({ - success: z.literal(true), - }), - ]), - }, - // Deprecated: Only workers without lazy attempt support will use this - READY_FOR_EXECUTION: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - totalCompletions: z.number(), - }), - }, - READY_FOR_LAZY_ATTEMPT: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - totalCompletions: z.number(), - startTime: z.number().optional(), - }), - }, - READY_FOR_RESUME: { - message: z.discriminatedUnion("version", [ - z.object({ - version: z.literal("v1"), - attemptFriendlyId: z.string(), - type: WaitReason, - }), - z.object({ - version: z.literal("v2"), - attemptFriendlyId: z.string(), - attemptNumber: z.number(), - type: WaitReason, - }), - ]), - }, - READY_FOR_CHECKPOINT: { - message: z.object({ - version: z.literal("v1").default("v1"), - }), - }, - CANCEL_CHECKPOINT: { - message: z - .discriminatedUnion("version", [ - z.object({ - version: z.literal("v1"), - }), - z.object({ - version: z.literal("v2"), - reason: WaitReason.optional(), - }), - ]) - .default({ version: "v1" }), - callback: z.object({ - version: z.literal("v2").default("v2"), - checkpointCanceled: z.boolean(), - reason: WaitReason.optional(), - }), - }, - TASK_HEARTBEAT: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptFriendlyId: z.string(), - }), - }, - TASK_RUN_HEARTBEAT: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - }), - }, - TASK_RUN_COMPLETED: { - message: z.object({ - version: z.enum(["v1", "v2"]).default("v1"), - execution: V3ProdTaskRunExecution, - completion: TaskRunExecutionResult, - }), - callback: z.object({ - willCheckpointAndRestore: z.boolean(), - shouldExit: z.boolean(), - }), - }, - TASK_RUN_FAILED_TO_RUN: { - message: z.object({ - version: z.literal("v1").default("v1"), - completion: TaskRunFailedExecutionResult, - }), - }, - WAIT_FOR_DURATION: { - message: z.object({ - version: z.literal("v1").default("v1"), - ms: z.number(), - now: z.number(), - attemptFriendlyId: z.string(), - }), - callback: z.object({ - willCheckpointAndRestore: z.boolean(), - }), - }, - WAIT_FOR_TASK: { - message: z.object({ - version: z.enum(["v1", "v2"]).default("v1"), - friendlyId: z.string(), - // This is the attempt that is waiting - attemptFriendlyId: z.string(), - }), - callback: z.object({ - willCheckpointAndRestore: z.boolean(), - }), - }, - WAIT_FOR_BATCH: { - message: z.object({ - version: z.enum(["v1", "v2"]).default("v1"), - batchFriendlyId: z.string(), - runFriendlyIds: z.string().array(), - // This is the attempt that is waiting - attemptFriendlyId: z.string(), - }), - callback: z.object({ - willCheckpointAndRestore: z.boolean(), - }), - }, - INDEXING_FAILED: { - message: z.object({ - version: z.literal("v1").default("v1"), - deploymentId: z.string(), - error: z.object({ - name: z.string(), - message: z.string(), - stack: z.string().optional(), - stderr: z.string().optional(), - }), - }), - }, - CREATE_TASK_RUN_ATTEMPT: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - }), - callback: z.discriminatedUnion("success", [ - z.object({ - success: z.literal(false), - reason: z.string().optional(), - }), - z.object({ - success: z.literal(true), - executionPayload: V3ProdTaskRunExecutionPayload, - }), - ]), - }, - UNRECOVERABLE_ERROR: { - message: z.object({ - version: z.literal("v1").default("v1"), - error: z.object({ - name: z.string(), - message: z.string(), - stack: z.string().optional(), - }), - }), - }, - SET_STATE: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptFriendlyId: z.string().optional(), - attemptNumber: z.string().optional(), - }), - }, -}; - -// TODO: The coordinator can only safely use v1 worker messages, higher versions will need a new flag, e.g. SUPPORTS_VERSIONED_MESSAGES -export const CoordinatorToProdWorkerMessages = { - RESUME_AFTER_DEPENDENCY: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptId: z.string(), - completions: TaskRunExecutionResult.array(), - executions: TaskRunExecution.array(), - }), - }, - RESUME_AFTER_DURATION: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptId: z.string(), - }), - }, - // Deprecated: Only workers without lazy attempt support will use this - EXECUTE_TASK_RUN: { - message: z.object({ - version: z.literal("v1").default("v1"), - executionPayload: V3ProdTaskRunExecutionPayload, - }), - }, - EXECUTE_TASK_RUN_LAZY_ATTEMPT: { - message: z.object({ - version: z.literal("v1").default("v1"), - lazyPayload: TaskRunExecutionLazyAttemptPayload, - }), - }, - REQUEST_ATTEMPT_CANCELLATION: { - message: z.object({ - version: z.literal("v1").default("v1"), - attemptId: z.string(), - }), - }, - REQUEST_EXIT: { - message: z.discriminatedUnion("version", [ - z.object({ - version: z.literal("v1"), - }), - z.object({ - version: z.literal("v2"), - delayInMs: z.number().optional(), - }), - ]), - }, - READY_FOR_RETRY: { - message: z.object({ - version: z.literal("v1").default("v1"), - runId: z.string(), - }), - }, -}; - -export const ProdWorkerSocketData = z.object({ - contentHash: z.string(), - projectRef: z.string(), - envId: z.string(), - runId: z.string(), - attemptFriendlyId: z.string().optional(), - attemptNumber: z.string().optional(), - podName: z.string(), - deploymentId: z.string(), - deploymentVersion: z.string(), - requiresCheckpointResumeWithMessage: z.string().optional(), -}); - -export const CoordinatorSocketData = z.object({ - supportsDynamicConfig: z.string().optional(), -}); From 078037592ad81d135975417661ee03e0459950e2 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 07:03:54 +0100 Subject: [PATCH 05/12] chore: expand core changeset to cover removed v3 message catalogs --- .changeset/retire-v3-zod-namespace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/retire-v3-zod-namespace.md b/.changeset/retire-v3-zod-namespace.md index c17ddcb72a2..e2e68dd5eb7 100644 --- a/.changeset/retire-v3-zod-namespace.md +++ b/.changeset/retire-v3-zod-namespace.md @@ -2,4 +2,4 @@ "@trigger.dev/core": patch --- -Removed the unused `@trigger.dev/core/v3/zodNamespace` export, which was only used by the retired v3 engine. +Removed unused v3 (engine V1) exports that are no longer referenced now that the v3 engine is retired: the `@trigger.dev/core/v3/zodNamespace` subpath and the legacy v3 socket message catalogs (coordinator, provider, shared-queue, prod-worker, and background-worker websocket). From c0469a578d3527b1bc32a280233424eda41cdc22 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 08:14:11 +0100 Subject: [PATCH 06/12] docs(self-hosting): drop retired v3 graphile worker and coordinator/provider references --- docs/self-hosting/docker.mdx | 2 -- docs/self-hosting/env/webapp.mdx | 9 ++------- docs/self-hosting/kubernetes.mdx | 1 - 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/self-hosting/docker.mdx b/docs/self-hosting/docker.mdx index 7d541083461..50b30607f1e 100644 --- a/docs/self-hosting/docker.mdx +++ b/docs/self-hosting/docker.mdx @@ -390,8 +390,6 @@ See the [webapp environment variables](/self-hosting/env/webapp) for the full li docker compose logs -f webapp ``` -- **Deploy fails with `ERROR: schema "graphile_worker" does not exist`.** This error occurs when Graphile Worker migrations fail to run during webapp startup. Check the webapp logs for certificate-related errors like `self-signed certificate in certificate chain`. This is often caused by PostgreSQL SSL certificate issues when using an external PostgreSQL instance with SSL enabled. Ensure that both the webapp and supervisor containers have access to the same CA certificate used by your PostgreSQL instance. You can configure this by mounting the certificate file and setting the `NODE_EXTRA_CA_CERTS` environment variable to point to the certificate path. Once the certificate issue is resolved, the migrations will complete and create the required `graphile_worker` schema. - - **ClickHouse migrations say "no migrations to run" but schema is missing.** The goose migration tracker is out of sync. Exec into the webapp container, set the GOOSE env vars (from webapp startup logs), and run `goose reset && goose up`. diff --git a/docs/self-hosting/env/webapp.mdx b/docs/self-hosting/env/webapp.mdx index db9fc0dbe37..d4e8b16ff39 100644 --- a/docs/self-hosting/env/webapp.mdx +++ b/docs/self-hosting/env/webapp.mdx @@ -52,11 +52,8 @@ mode: "wide" | `AWS_REGION` | No | — | AWS region for SES. | | `AWS_ACCESS_KEY_ID` | No | — | AWS access key ID for SES. | | `AWS_SECRET_ACCESS_KEY` | No | — | AWS secret access key for SES. | -| **Graphile & Redis worker** | | | | -| `WORKER_CONCURRENCY` | No | 10 | Redis worker concurrency. | -| `WORKER_POLL_INTERVAL` | No | 1000 | Redis worker poll interval (ms). | -| `WORKER_SCHEMA` | No | graphile_worker | Graphile worker schema. | -| `GRACEFUL_SHUTDOWN_TIMEOUT` | No | 60000 (1m) | Graphile graceful shutdown timeout (ms). Affects shutdown time. | +| **Worker** | | | | +| `GRACEFUL_SHUTDOWN_TIMEOUT` | No | 60000 (1m) | Graceful shutdown timeout (ms). Affects shutdown time. | | **Concurrency limits** | | | | | `DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT` | No | 100 | Default env execution concurrency. | | `DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT` | No | 300 | Default org execution concurrency, needs to be 3x env concurrency. | @@ -168,8 +165,6 @@ mode: "wide" | `MAXIMUM_DEV_QUEUE_SIZE` | No | — | Maximum queued runs per queue in development environments. | | `MAXIMUM_DEPLOYED_QUEUE_SIZE` | No | — | Maximum queued runs per queue in deployed (staging/prod) environments. | | **Misc** | | | | -| `PROVIDER_SECRET` | No | provider-secret | Secret for provider auth. **Must be set to a secure value in self-hosted/production**; the default is insecure. | -| `COORDINATOR_SECRET` | No | coordinator-secret | Secret for coordinator auth. **Must be set to a secure value in self-hosted/production**; the default is insecure. | | `TRIGGER_TELEMETRY_DISABLED` | No | — | Disable telemetry. | | `NODE_MAX_OLD_SPACE_SIZE` | No | 8192 | Maximum memory allocation for Node.js heap in MiB (e.g. "4096" for 4GB). | | `OPENAI_API_KEY` | No | — | OpenAI API key. | diff --git a/docs/self-hosting/kubernetes.mdx b/docs/self-hosting/kubernetes.mdx index 47fc08b4a99..2a08db56481 100644 --- a/docs/self-hosting/kubernetes.mdx +++ b/docs/self-hosting/kubernetes.mdx @@ -602,7 +602,6 @@ kubectl delete namespace trigger - **Deploy fails**: Verify registry access and authentication - **Pods stuck pending**: Describe the pod and check the events - **Worker token issues**: Check webapp and supervisor logs for errors -- **Deploy fails with `ERROR: schema "graphile_worker" does not exist`**: See the [Docker troubleshooting](/self-hosting/docker#troubleshooting) section for details on resolving PostgreSQL SSL certificate issues that prevent Graphile Worker migrations. See the [Docker troubleshooting](/self-hosting/docker#troubleshooting) section for more information. From 6b31d5c4996070c96fb66d2f05d2ff1262b8f9ca Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 08:22:11 +0100 Subject: [PATCH 07/12] chore(webapp): remove tests for deleted v3 services and tidy review nits Delete the unit tests for the removed createCheckpoint and cancelDevSessionRuns services, apply formatting, and reword the changeset plus agent-guidance docs to note that a V1 branch may finalize (not only reject) gracefully. --- .changeset/retire-v3-zod-namespace.md | 2 +- .claude/rules/legacy-v3-code.md | 2 +- AGENTS.md | 2 +- apps/webapp/app/components/admin/debugRun.tsx | 6 +- .../changeCurrentDeployment.server.ts | 1 - .../cancelDevSessionRunsStoreRouting.test.ts | 249 ------------------ .../createCheckpoint.batchReplicaLag.test.ts | 65 ----- 7 files changed, 4 insertions(+), 323 deletions(-) delete mode 100644 apps/webapp/test/cancelDevSessionRunsStoreRouting.test.ts delete mode 100644 apps/webapp/test/createCheckpoint.batchReplicaLag.test.ts diff --git a/.changeset/retire-v3-zod-namespace.md b/.changeset/retire-v3-zod-namespace.md index e2e68dd5eb7..c8ad9e4d30a 100644 --- a/.changeset/retire-v3-zod-namespace.md +++ b/.changeset/retire-v3-zod-namespace.md @@ -2,4 +2,4 @@ "@trigger.dev/core": patch --- -Removed unused v3 (engine V1) exports that are no longer referenced now that the v3 engine is retired: the `@trigger.dev/core/v3/zodNamespace` subpath and the legacy v3 socket message catalogs (coordinator, provider, shared-queue, prod-worker, and background-worker websocket). +Removed the unused `@trigger.dev/core/v3/zodNamespace` export and the legacy v3 socket message schemas. These were only used by the now-retired v3 engine and have no v4 consumers. diff --git a/.claude/rules/legacy-v3-code.md b/.claude/rules/legacy-v3-code.md index f34d768525f..c5e0d465973 100644 --- a/.claude/rules/legacy-v3-code.md +++ b/.claude/rules/legacy-v3-code.md @@ -7,7 +7,7 @@ paths: The v3 engine (RunEngineVersion `V1`: MarQS queue + Graphile worker) is end-of-life and its execution code has been removed from the webapp. The `app/v3/` directory name is historical: everything under it now serves the current V2 engine (`@internal/run-engine` + `@trigger.dev/redis-worker`). -There is no `V1` execution path anymore. If you find a `RunEngineVersion` branch, the `V1` arm should only ever produce a graceful rejection, never run work. Do not reintroduce MarQS, the graphile worker, or the v3 socket.io namespaces. +There is no `V1` execution path anymore. If you find a `RunEngineVersion` branch, the `V1` arm should only reject or finalize gracefully (for example, mark a historical run cancelled in the DB), never run V1 work. Do not reintroduce MarQS, the graphile worker, or the v3 socket.io namespaces. ## The deprecation boundary (keep this) diff --git a/AGENTS.md b/AGENTS.md index 23f21bad31e..3ae04b09f67 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,7 +142,7 @@ User API call -> Webapp routes -> Services -> RunEngine -> Redis Queue -> Superv ### v3 (engine V1) removed -v3 (engine V1: MarQS + Graphile worker) is end-of-life and its execution code has been removed. The `apps/webapp/app/v3/` directory name is historical - everything there now serves V2 (Run Engine 2.0, `@internal/run-engine` + redis-worker). There is no V1 execution path: a `RunEngineVersion` `V1` branch only rejects gracefully so v3 clients get a clean 4xx, never a 5xx. Do not reintroduce V1. See `apps/webapp/CLAUDE.md` and `.claude/rules/legacy-v3-code.md`. +v3 (engine V1: MarQS + Graphile worker) is end-of-life and its execution code has been removed. The `apps/webapp/app/v3/` directory name is historical - everything there now serves V2 (Run Engine 2.0, `@internal/run-engine` + redis-worker). There is no V1 execution path: a `RunEngineVersion` `V1` branch only rejects or finalizes gracefully so v3 clients get a clean 4xx, never a 5xx. Do not reintroduce V1. See `apps/webapp/CLAUDE.md` and `.claude/rules/legacy-v3-code.md`. ### Documentation diff --git a/apps/webapp/app/components/admin/debugRun.tsx b/apps/webapp/app/components/admin/debugRun.tsx index 9ab630addc5..705e3169a8b 100644 --- a/apps/webapp/app/components/admin/debugRun.tsx +++ b/apps/webapp/app/components/admin/debugRun.tsx @@ -74,11 +74,7 @@ function DebugRunData(props: UseDataFunctionReturn) { return ; } -function DebugRunDataEngineV1({ - run, -}: { - run: UseDataFunctionReturn["run"]; -}) { +function DebugRunDataEngineV1({ run }: { run: UseDataFunctionReturn["run"] }) { return ( diff --git a/apps/webapp/app/v3/services/changeCurrentDeployment.server.ts b/apps/webapp/app/v3/services/changeCurrentDeployment.server.ts index 067b4be2f33..9782d4d8cb7 100644 --- a/apps/webapp/app/v3/services/changeCurrentDeployment.server.ts +++ b/apps/webapp/app/v3/services/changeCurrentDeployment.server.ts @@ -173,7 +173,6 @@ export class ChangeCurrentDeploymentService extends BaseService { error: scheduleSyncError, }); } - } async #syncSchedulesForDeployment(deployment: WorkerDeployment) { diff --git a/apps/webapp/test/cancelDevSessionRunsStoreRouting.test.ts b/apps/webapp/test/cancelDevSessionRunsStoreRouting.test.ts deleted file mode 100644 index 9c91385071f..00000000000 --- a/apps/webapp/test/cancelDevSessionRunsStoreRouting.test.ts +++ /dev/null @@ -1,249 +0,0 @@ -// Real PG14 (legacy) + PG17 (new) proof for the dev-session-cancel TaskRun read. -// The DB is never mocked: reads hit the two real containers. Only the pure -// splitEnabled boundary and recording client wrappers are injected. -import { heteroPostgresTest, postgresTest } from "@internal/testcontainers"; -import type { PrismaClient } from "@trigger.dev/database"; -import { generateRunOpsId } from "@trigger.dev/core/v3/isomorphic"; -import { describe, expect, vi } from "vitest"; -import type { PrismaReplicaClient } from "~/db.server"; -import { CancelDevSessionRunsService } from "~/v3/services/cancelDevSessionRuns.server"; - -vi.setConfig({ testTimeout: 60_000 }); - -// 25-char cuid body (no v1 version marker) → LEGACY residency. -function generateLegacyCuid() { - const suffix = Array.from( - { length: 24 }, - () => "0123456789abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 36)] - ).join(""); - return `c${suffix}`; -} - -async function seedOrgProjectEnv(prisma: PrismaClient, suffix: string) { - const organization = await prisma.organization.create({ - data: { title: `test-${suffix}`, slug: `test-${suffix}` }, - }); - const project = await prisma.project.create({ - data: { - name: `test-${suffix}`, - slug: `test-${suffix}`, - organizationId: organization.id, - externalRef: `test-${suffix}`, - }, - }); - const runtimeEnvironment = await prisma.runtimeEnvironment.create({ - data: { - slug: `test-${suffix}`, - type: "DEVELOPMENT", - projectId: project.id, - organizationId: organization.id, - apiKey: `test-${suffix}`, - pkApiKey: `test-${suffix}`, - shortcode: `test-${suffix}`, - }, - }); - return { organization, project, runtimeEnvironment }; -} - -async function seedRun( - prisma: PrismaClient, - ids: { id: string; friendlyId: string }, - env: { runtimeEnvironmentId: string; projectId: string; organizationId: string } -) { - return prisma.taskRun.create({ - data: { - id: ids.id, - friendlyId: ids.friendlyId, - taskIdentifier: "my-task", - payload: JSON.stringify({ foo: "bar" }), - payloadType: "application/json", - traceId: "1234", - spanId: "1234", - queue: "test", - runtimeEnvironmentId: env.runtimeEnvironmentId, - projectId: env.projectId, - organizationId: env.organizationId, - environmentType: "DEVELOPMENT", - // V1 so the (best-effort, error-swallowed) cancel does not require the V2 engine; - // the unit under test is the READ resolution, not the cancel side effect. - engine: "V1", - status: "EXECUTING", - }, - }); -} - -// A read client whose taskRun.findFirst is recorded; throws if used after being marked -// forbidden, so we can prove a store was NEVER read. -function recording(client: PrismaClient, opts: { forbidden?: boolean } = {}) { - const calls: unknown[] = []; - const taskRun = { - findFirst: (args: unknown) => { - calls.push(args); - if (opts.forbidden) { - throw new Error("this store must never be read"); - } - return (client as unknown as PrismaReplicaClient).taskRun.findFirst(args as never); - }, - }; - return { handle: { ...client, taskRun } as unknown as PrismaReplicaClient, calls }; -} - -describe("CancelDevSessionRunsService store routing (hetero)", () => { - heteroPostgresTest( - "a NEW run (run-ops id) resolves on the new store via read-through, by friendlyId and by id", - async ({ prisma17, prisma14 }) => { - const id = generateRunOpsId(); - expect(id.length).toBe(26); - const friendlyId = `run_${id}`; - - const { project, organization, runtimeEnvironment } = await seedOrgProjectEnv( - prisma17, - "new" - ); - await seedRun( - prisma17, - { id, friendlyId }, - { - runtimeEnvironmentId: runtimeEnvironment.id, - projectId: project.id, - organizationId: organization.id, - } - ); - - // by friendlyId - { - const newClient = recording(prisma17); - const legacy = recording(prisma14, { forbidden: true }); - const service = new CancelDevSessionRunsService({ - prisma: prisma17, - readThroughDeps: { - splitEnabled: true, - newClient: newClient.handle, - legacyReplica: legacy.handle, - }, - }); - await service.call({ - runIds: [friendlyId], - cancelledAt: new Date(), - reason: "test", - }); - // run-ops id → NEW: new store served the read, legacy never touched. - expect(newClient.calls.length).toBe(1); - expect(legacy.calls.length).toBe(0); - } - - // by internal id - { - const newClient = recording(prisma17); - const legacy = recording(prisma14, { forbidden: true }); - const service = new CancelDevSessionRunsService({ - prisma: prisma17, - readThroughDeps: { - splitEnabled: true, - newClient: newClient.handle, - legacyReplica: legacy.handle, - }, - }); - await service.call({ - runIds: [id], - cancelledAt: new Date(), - reason: "test", - }); - expect(newClient.calls.length).toBe(1); - expect(legacy.calls.length).toBe(0); - } - } - ); - - heteroPostgresTest( - "an OLD in-retention run (cuid) resolves off the LEGACY replica, never a legacy primary", - async ({ prisma17, prisma14 }) => { - const id = generateLegacyCuid(); - expect(id.length).toBe(25); - const friendlyId = `run_${id}`; - - const { project, organization, runtimeEnvironment } = await seedOrgProjectEnv( - prisma14, - "legacy" - ); - await seedRun( - prisma14, - { id, friendlyId }, - { - runtimeEnvironmentId: runtimeEnvironment.id, - projectId: project.id, - organizationId: organization.id, - } - ); - - const newClient = recording(prisma17); - const legacy = recording(prisma14); - const service = new CancelDevSessionRunsService({ - prisma: prisma14, - readThroughDeps: { - splitEnabled: true, - newClient: newClient.handle, - legacyReplica: legacy.handle, - }, - }); - - await service.call({ - runIds: [id], - cancelledAt: new Date(), - reason: "test", - }); - - // NEW first (miss) → resolved off the LEGACY REPLICA handle (no primary handle exists). - expect(newClient.calls.length).toBe(1); - expect(legacy.calls.length).toBe(1); - } - ); -}); - -describe("CancelDevSessionRunsService passthrough (single-DB)", () => { - postgresTest( - "with no read-through deps, the run is read from the single DB and session reads stay on it", - async ({ prisma }) => { - const id = generateRunOpsId(); - const friendlyId = `run_${id}`; - - const { project, organization, runtimeEnvironment } = await seedOrgProjectEnv(prisma, "pt"); - await seedRun( - prisma, - { id, friendlyId }, - { - runtimeEnvironmentId: runtimeEnvironment.id, - projectId: project.id, - organizationId: organization.id, - } - ); - - const session = await prisma.runtimeEnvironmentSession.create({ - data: { environmentId: runtimeEnvironment.id, ipAddress: "127.0.0.1" }, - }); - - // splitEnabled=false → single plain read against the one client; the session - // control-plane read runs on the same prisma. - const service = new CancelDevSessionRunsService({ - prisma, - replica: prisma, - readThroughDeps: { - splitEnabled: false, - newClient: prisma as unknown as PrismaReplicaClient, - }, - }); - - await service.call({ - runIds: [id], - cancelledAt: new Date(), - reason: "test", - cancelledSessionId: session.id, - }); - - // Run found + handed to cancel against the single DB; confirm the row is present. - const row = await prisma.taskRun.findFirst({ where: { id } }); - expect(row).not.toBeNull(); - expect(row?.friendlyId).toBe(friendlyId); - } - ); -}); diff --git a/apps/webapp/test/createCheckpoint.batchReplicaLag.test.ts b/apps/webapp/test/createCheckpoint.batchReplicaLag.test.ts deleted file mode 100644 index 39dbe7b3a07..00000000000 --- a/apps/webapp/test/createCheckpoint.batchReplicaLag.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Unit red-green for the checkpoint WAIT_FOR_BATCH replica-lag fix (createCheckpoint.server.ts). -// The service decides whether to suspend a run on `batchRun.resumedAt`; reading it from a lagging -// replica makes a just-resumed batch look unresumed -> it suspends an already-resumed run -> stall. -// The fix threads the primary (`this._prisma`) into `runStore.findBatchTaskRunByFriendlyId`. Here a -// spy runStore records which client the service passed and simulates the lag (only the primary read -// sees the fresh resumedAt): RED = no client -> stale null -> no early return; GREEN = primary -> kept alive. - -import { describe, expect, it, vi } from "vitest"; - -vi.mock("~/services/logger.server", () => ({ - logger: { debug: vi.fn(), info: vi.fn(), log: vi.fn(), error: vi.fn(), warn: vi.fn() }, -})); -vi.mock("~/v3/marqs/index.server", () => ({ - marqs: { replaceMessage: vi.fn(), cancelHeartbeat: vi.fn() }, -})); - -import { CreateCheckpointService } from "~/v3/services/createCheckpoint.server"; - -describe("checkpoint WAIT_FOR_BATCH reads the primary, not a lagging replica", () => { - it("threads the primary so an already-resumed batch keeps the run alive", async () => { - // A freezable attempt so control reaches the WAIT_FOR_BATCH arm. This object IS the primary the - // fix must thread into the batch read. - const prisma = { - taskRunAttempt: { - findFirst: async () => ({ - id: "attempt_1", - status: "EXECUTING", - taskRunId: "run_1", - taskRun: { id: "run_1", status: "EXECUTING", runtimeEnvironmentId: "env_1" }, - backgroundWorker: { id: "bw_1", deployment: { imageReference: "img:1" } }, - }), - }, - }; - - let seenClient: unknown = "NOT_CALLED"; - const runStore = { - findBatchTaskRunByFriendlyId: async ( - _friendlyId: string, - _environmentId: string, - _args: unknown, - client?: unknown - ) => { - seenClient = client; - // Lagging replica: only a read handed the primary sees the just-committed resumedAt. - return { resumedAt: client === prisma ? new Date() : null }; - }, - }; - - const service = new CreateCheckpointService(prisma as never, {} as never, runStore as never); - - let result: unknown; - try { - result = await service.call({ - attemptFriendlyId: "attempt_1", - reason: { type: "WAIT_FOR_BATCH", batchFriendlyId: "batch_1" }, - } as never); - } catch { - // Buggy path falls through the pre-check into checkpoint creation (unstubbed) and throws; the - // recorded client below is what distinguishes RED from GREEN. - } - - expect(seenClient).toBe(prisma); // the fix: primary threaded into the batch read - expect(result).toEqual({ success: false, keepRunAlive: true }); // early-return, run kept alive - }); -}); From e392f2ee7229589fc5096e72f2e7f9a27b4d5366 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 09:07:32 +0100 Subject: [PATCH 08/12] docs: scrub retired v3 references from agent instruction files Drop mentions of the removed graphile/zod-worker background system, the deleted zod-worker package, and the v3 coordinator from the repo's CLAUDE.md / AGENTS.md guidance now that v3 execution is gone. --- AGENTS.md | 1 - apps/supervisor/CLAUDE.md | 2 +- apps/webapp/CLAUDE.md | 2 -- internal-packages/database/CLAUDE.md | 2 +- packages/redis-worker/CLAUDE.md | 4 ++-- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3ae04b09f67..cc947c7a57d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -138,7 +138,6 @@ User API call -> Webapp routes -> Services -> RunEngine -> Redis Queue -> Superv - **internal-packages/redis**: Redis client creation utilities (ioredis) - **internal-packages/testcontainers**: Test helpers for Redis/PostgreSQL containers - **internal-packages/schedule-engine**: Durable cron scheduling -- **internal-packages/zod-worker**: Graphile-worker wrapper (DEPRECATED - use redis-worker) ### v3 (engine V1) removed diff --git a/apps/supervisor/CLAUDE.md b/apps/supervisor/CLAUDE.md index ded836c6069..9cc7c82fd9d 100644 --- a/apps/supervisor/CLAUDE.md +++ b/apps/supervisor/CLAUDE.md @@ -7,7 +7,7 @@ Node.js app that manages task execution containers. Receives work from the platf - `src/services/` - Core service logic - `src/workloadManager/` - Container orchestration abstraction (Docker or Kubernetes) - `src/workloadServer/` - HTTP server for workload communication (heartbeats, snapshots) -- `src/clients/` - Platform communication (webapp/coordinator) +- `src/clients/` - Platform communication (webapp) - `src/env.ts` - Environment configuration ## Architecture diff --git a/apps/webapp/CLAUDE.md b/apps/webapp/CLAUDE.md index 9daf1a8db89..bf30ee7562e 100644 --- a/apps/webapp/CLAUDE.md +++ b/apps/webapp/CLAUDE.md @@ -91,8 +91,6 @@ Background job workers use `@trigger.dev/redis-worker`: - `app/v3/alertsWorker.server.ts` - `app/v3/batchTriggerWorker.server.ts` -Do NOT add new jobs using zodworker/graphile-worker (legacy). - ## Real-time - Socket.io: `app/v3/handleSocketIo.server.ts`, `app/v3/handleWebsockets.server.ts` diff --git a/internal-packages/database/CLAUDE.md b/internal-packages/database/CLAUDE.md index 56ba681b8a3..d73fec25450 100644 --- a/internal-packages/database/CLAUDE.md +++ b/internal-packages/database/CLAUDE.md @@ -10,7 +10,7 @@ Located at `prisma/schema.prisma`. Key models include TaskRun, BackgroundWorker, ```prisma enum RunEngineVersion { - V1 // Legacy (MarQS + Graphile) - DEPRECATED + V1 // Retired v3 engine - no longer executes; kept for historical rows and rejection V2 // Current (run-engine + redis-worker) } ``` diff --git a/packages/redis-worker/CLAUDE.md b/packages/redis-worker/CLAUDE.md index 511b9828c10..67f4f8e1810 100644 --- a/packages/redis-worker/CLAUDE.md +++ b/packages/redis-worker/CLAUDE.md @@ -1,6 +1,6 @@ # Redis Worker -`@trigger.dev/redis-worker` - custom Redis-based background job system. **This replaces graphile-worker/zodworker** for all new background job needs. +`@trigger.dev/redis-worker` - custom Redis-based background job system. This is the background job system for the webapp and run engine. ## Key Files @@ -12,7 +12,7 @@ Used by the webapp for background jobs (alerting, batch processing, common tasks) and by the run engine for TTL expiration and batch operations. -All new background jobs in the webapp should use redis-worker. Do NOT add new jobs to zodworker (`@internal/zodworker`) or graphile-worker. +All background jobs in the webapp use redis-worker. ## Testing From 60786491343619524698ba11815982a6fd8219d2 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 09:16:31 +0100 Subject: [PATCH 09/12] fix(webapp): drop the removed /admin/concurrency admin page reference The global-concurrency admin page was a v3 (MarQS) debug view and was removed with the v3 engine. Remove its admin-nav tab and repoint the two dashboard auth e2e tests (which used it only as a representative super-admin route) to /admin/feature-flags. --- apps/webapp/app/routes/admin.tsx | 4 ---- apps/webapp/test/api-auth.e2e.test.ts | 4 ++-- apps/webapp/test/auth-dashboard.e2e.full.test.ts | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/webapp/app/routes/admin.tsx b/apps/webapp/app/routes/admin.tsx index a95b016ca5b..34ba6c62ca1 100644 --- a/apps/webapp/app/routes/admin.tsx +++ b/apps/webapp/app/routes/admin.tsx @@ -26,10 +26,6 @@ export default function Page() { label: "Organizations", to: `/admin/orgs${searchSuffix}`, }, - { - label: "Concurrency", - to: "/admin/concurrency", - }, { label: "LLM Models", to: "/admin/llm-models", diff --git a/apps/webapp/test/api-auth.e2e.test.ts b/apps/webapp/test/api-auth.e2e.test.ts index 9e0cc2b55be..58852768bc1 100644 --- a/apps/webapp/test/api-auth.e2e.test.ts +++ b/apps/webapp/test/api-auth.e2e.test.ts @@ -126,13 +126,13 @@ describe("JWT bearer auth — baseline behavior", () => { // Exercises the RBAC plugin loader end-to-end. The test server boots // with RBAC_FORCE_FALLBACK=1 (see internal-packages/testcontainers/src/webapp.ts), // which makes rbac.server.ts use the default fallback regardless of -// whether a plugin is installed in node_modules. /admin/concurrency +// whether a plugin is installed in node_modules. /admin/feature-flags // uses rbac.authenticateSession internally; an unauthenticated request // must flow through LazyController → RoleBaseAccessFallback → // redirect("/login"). describe("RBAC plugin — fallback wiring", () => { it("unauthenticated dashboard route redirects to /login via the fallback", async () => { - const res = await server.webapp.fetch("/admin/concurrency", { redirect: "manual" }); + const res = await server.webapp.fetch("/admin/feature-flags", { redirect: "manual" }); expect(res.status).toBe(302); const location = res.headers.get("location") ?? ""; expect(new URL(location, "http://placeholder").pathname).toBe("/login"); diff --git a/apps/webapp/test/auth-dashboard.e2e.full.test.ts b/apps/webapp/test/auth-dashboard.e2e.full.test.ts index 728e9c220c5..3fb47057d35 100644 --- a/apps/webapp/test/auth-dashboard.e2e.full.test.ts +++ b/apps/webapp/test/auth-dashboard.e2e.full.test.ts @@ -7,9 +7,9 @@ import { getTestServer } from "./helpers/sharedTestServer"; import { seedTestSession, seedTestUser } from "./helpers/seedTestSession"; describe("Dashboard", () => { - it("shared webapp container redirects /admin/concurrency to /login when unauthenticated", async () => { + it("shared webapp container redirects /admin/feature-flags to /login when unauthenticated", async () => { const server = getTestServer(); - const res = await server.webapp.fetch("/admin/concurrency", { redirect: "manual" }); + const res = await server.webapp.fetch("/admin/feature-flags", { redirect: "manual" }); expect(res.status).toBe(302); }); @@ -26,7 +26,7 @@ describe("Dashboard", () => { // already proves. If the wrapper config drifts per-route in the // future, add targeted tests for the divergent ones. describe("Admin pages — requireSuper gate", () => { - const adminRoutes = ["/admin", "/admin/concurrency", "/admin/back-office"]; + const adminRoutes = ["/admin", "/admin/feature-flags", "/admin/back-office"]; for (const path of adminRoutes) { describe(`GET ${path}`, () => { From ac436e5644cb211d6b46e860a1f9b249b8efd2bb Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 10:10:13 +0100 Subject: [PATCH 10/12] chore(webapp,core): remove leftover v3 dead code and the legacy run engine worker The legacy worker was a closed loop: the only enqueuers lived inside completeBatchTaskRunItemV3, whose only caller was the worker's own job handler. Run-engine's batchSystem owns batch completion now, and the synchronous tryCompleteBatchV3 path used by the check-completion route stays. --- ai/references/repo.md | 1 - apps/webapp/app/env.server.ts | 49 ----- apps/webapp/app/models/admin.server.ts | 1 - .../route.tsx | 4 - .../app/v3/legacyRunEngineWorker.server.ts | 90 -------- .../app/v3/services/batchTriggerV3.server.ts | 86 +------- .../createCheckpointRestoreEvent.server.ts | 196 ------------------ .../app/v3/services/triggerV1Scoping.ts | 20 -- apps/webapp/package.json | 1 - apps/webapp/test/setup.ts | 9 +- apps/webapp/test/triggerV1Scoping.test.ts | 22 -- .../testcontainers/src/webapp.ts | 1 - packages/core/package.json | 3 - pnpm-lock.yaml | 3 - scripts/analyze_marqs.mjs | 146 ------------- 15 files changed, 3 insertions(+), 629 deletions(-) delete mode 100644 apps/webapp/app/v3/legacyRunEngineWorker.server.ts delete mode 100644 apps/webapp/app/v3/services/createCheckpointRestoreEvent.server.ts delete mode 100644 apps/webapp/app/v3/services/triggerV1Scoping.ts delete mode 100644 apps/webapp/test/triggerV1Scoping.test.ts delete mode 100755 scripts/analyze_marqs.mjs diff --git a/ai/references/repo.md b/ai/references/repo.md index 6e0ff056716..68286729ca3 100644 --- a/ai/references/repo.md +++ b/ai/references/repo.md @@ -23,7 +23,6 @@ This is a pnpm 10.33.2 monorepo that uses turborepo @turbo.json. The following w - /internal-packages/run-engine is the `@internal/run-engine` package that is "Run Engine 2.0" and handles moving a run all the way through it's lifecycle - /internal-packages/redis is the `@internal/redis` package that exports Redis types and the `createRedisClient` function to unify how we create redis clients in the repo. It's not used everywhere yet, but it's the preferred way to create redis clients from now on. - /internal-packages/testcontainers is the `@internal/testcontainers` package that exports a few useful functions for spinning up local testcontainers when writing vitest tests. See our [tests.md](./tests.md) file for more information. -- /internal-packages/zodworker is the `@internal/zodworker` package that implements a wrapper around graphile-worker that allows us to use zod to validate our background jobs. We are moving away from using graphile-worker as our background job system, replacing it with our own redis-worker package. ## References diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 11460511c3f..191cf440300 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -1106,55 +1106,6 @@ const EnvironmentSchema = z /** The CLI should connect to this for dev runs */ DEV_ENGINE_URL: z.string().default(process.env.APP_ORIGIN ?? "http://localhost:3030"), - LEGACY_RUN_ENGINE_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), - LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), - LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(1), - LEGACY_RUN_ENGINE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), - LEGACY_RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), - LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), - LEGACY_RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - LEGACY_RUN_ENGINE_WORKER_LOG_LEVEL: z - .enum(["log", "error", "warn", "info", "debug"]) - .default("info"), - - LEGACY_RUN_ENGINE_WORKER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - LEGACY_RUN_ENGINE_WORKER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - LEGACY_RUN_ENGINE_WORKER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - LEGACY_RUN_ENGINE_WORKER_REDIS_PORT: z.coerce - .number() - .optional() - .transform( - (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) - ), - LEGACY_RUN_ENGINE_WORKER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - LEGACY_RUN_ENGINE_WORKER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - LEGACY_RUN_ENGINE_WORKER_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - LEGACY_RUN_ENGINE_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_SIZE: z.coerce.number().int().default(100), - LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_STAGGER_MS: z.coerce.number().int().default(1_000), - LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_DISABLED: z.string().default("0"), - COMMON_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), COMMON_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), COMMON_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), diff --git a/apps/webapp/app/models/admin.server.ts b/apps/webapp/app/models/admin.server.ts index 406845e5859..09811c99c2d 100644 --- a/apps/webapp/app/models/admin.server.ts +++ b/apps/webapp/app/models/admin.server.ts @@ -130,7 +130,6 @@ export async function adminGetOrganizations(userId: string, { page, search }: Se id: true, slug: true, title: true, - v2Enabled: true, isActivated: true, deletedAt: true, members: { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx index c9e762b3da9..70c07a6dfcb 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx @@ -119,8 +119,6 @@ export async function loader({ params, request }: LoaderFunctionArgs) { id: true, title: true, isActivated: true, - v2Enabled: true, - hasRequestedV3: true, _count: { select: { projects: { @@ -152,8 +150,6 @@ export async function loader({ params, request }: LoaderFunctionArgs) { slug: organizationSlug, projectsCount: organization._count.projects, isActivated: organization.isActivated, - v2Enabled: organization.v2Enabled, - hasRequestedV3: organization.hasRequestedV3, }, defaultVersion: url.searchParams.get("version") ?? "v2", message: message ? decodeURIComponent(message) : undefined, diff --git a/apps/webapp/app/v3/legacyRunEngineWorker.server.ts b/apps/webapp/app/v3/legacyRunEngineWorker.server.ts deleted file mode 100644 index d6b670e847c..00000000000 --- a/apps/webapp/app/v3/legacyRunEngineWorker.server.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Worker as RedisWorker } from "@trigger.dev/redis-worker"; -import { Logger } from "@trigger.dev/core/logger"; -import { z } from "zod"; -import { env } from "~/env.server"; -import { logger } from "~/services/logger.server"; -import { singleton } from "~/utils/singleton"; -import { completeBatchTaskRunItemV3, tryCompleteBatchV3 } from "./services/batchTriggerV3.server"; -import { prisma } from "~/db.server"; - -function initializeWorker() { - const redisOptions = { - keyPrefix: "legacy-run-engine:worker:", - host: env.LEGACY_RUN_ENGINE_WORKER_REDIS_HOST, - port: env.LEGACY_RUN_ENGINE_WORKER_REDIS_PORT, - username: env.LEGACY_RUN_ENGINE_WORKER_REDIS_USERNAME, - password: env.LEGACY_RUN_ENGINE_WORKER_REDIS_PASSWORD, - enableAutoPipelining: true, - ...(env.LEGACY_RUN_ENGINE_WORKER_REDIS_TLS_DISABLED === "true" ? {} : { tls: {} }), - }; - - logger.debug( - `👨‍🏭 Initializing legacy run engine worker at host ${env.LEGACY_RUN_ENGINE_WORKER_REDIS_HOST}` - ); - - const worker = new RedisWorker({ - name: "legacy-run-engine-worker", - redisOptions, - catalog: { - completeBatchTaskRunItem: { - schema: z.object({ - itemId: z.string(), - batchTaskRunId: z.string(), - scheduleResumeOnComplete: z.boolean(), - taskRunAttemptId: z.string().optional(), - attempt: z.number().optional(), - }), - visibilityTimeoutMs: 60_000, - retry: { - maxAttempts: 10, - }, - }, - tryCompleteBatchV3: { - schema: z.object({ - batchId: z.string(), - scheduleResumeOnComplete: z.boolean(), - }), - visibilityTimeoutMs: 30_000, - retry: { - maxAttempts: 5, - }, - }, - }, - concurrency: { - workers: env.LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_WORKERS, - tasksPerWorker: env.LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_TASKS_PER_WORKER, - limit: env.LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_LIMIT, - }, - pollIntervalMs: env.LEGACY_RUN_ENGINE_WORKER_POLL_INTERVAL, - immediatePollIntervalMs: env.LEGACY_RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL, - shutdownTimeoutMs: env.LEGACY_RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS, - logger: new Logger("LegacyRunEngineWorker", env.LEGACY_RUN_ENGINE_WORKER_LOG_LEVEL), - jobs: { - completeBatchTaskRunItem: async ({ payload, attempt }) => { - await completeBatchTaskRunItemV3( - payload.itemId, - payload.batchTaskRunId, - prisma, - payload.scheduleResumeOnComplete, - payload.taskRunAttemptId, - attempt - ); - }, - tryCompleteBatchV3: async ({ payload }) => { - await tryCompleteBatchV3(payload.batchId, prisma, payload.scheduleResumeOnComplete); - }, - }, - }); - - if (env.LEGACY_RUN_ENGINE_WORKER_ENABLED === "true") { - logger.debug( - `👨‍🏭 Starting legacy run engine worker at host ${env.LEGACY_RUN_ENGINE_WORKER_REDIS_HOST}, pollInterval = ${env.LEGACY_RUN_ENGINE_WORKER_POLL_INTERVAL}, immediatePollInterval = ${env.LEGACY_RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL}, workers = ${env.LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_WORKERS}, tasksPerWorker = ${env.LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_TASKS_PER_WORKER}, concurrencyLimit = ${env.LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_LIMIT}` - ); - - worker.start(); - } - - return worker; -} - -export const legacyRunEngineWorker = singleton("legacyRunEngineWorker", initializeWorker); diff --git a/apps/webapp/app/v3/services/batchTriggerV3.server.ts b/apps/webapp/app/v3/services/batchTriggerV3.server.ts index 0a99acf029f..5b94d80194f 100644 --- a/apps/webapp/app/v3/services/batchTriggerV3.server.ts +++ b/apps/webapp/app/v3/services/batchTriggerV3.server.ts @@ -5,12 +5,7 @@ import type { } from "@trigger.dev/core/v3"; import { packetRequiresOffloading, parsePacket } from "@trigger.dev/core/v3"; import type { BatchTaskRun, TaskRunAttempt } from "@trigger.dev/database"; -import { - isPrismaRaceConditionError, - isPrismaRetriableError, - isUniqueConstraintError, - Prisma, -} from "@trigger.dev/database"; +import { isUniqueConstraintError, Prisma } from "@trigger.dev/database"; import type { RunStore } from "@internal/run-store"; import { z } from "zod"; import type { PrismaClientOrTransaction } from "~/db.server"; @@ -29,7 +24,6 @@ import { resolveInheritedMintKind } from "~/v3/runOpsMigration/resolveInheritedM import { mintFriendlyIdForKind } from "~/v3/runOpsMigration/mintAnchoredRunFriendlyId.server"; import { mintBatchFriendlyId } from "~/v3/runOpsMigration/mintBatchFriendlyId.server"; import { batchTriggerWorker } from "../batchTriggerWorker.server"; -import { legacyRunEngineWorker } from "../legacyRunEngineWorker.server"; import { guardQueueSizeLimitsForEnv } from "../queueSizeLimits.server"; import { downloadPacketFromObjectStore, uploadPacketToObjectStore } from "../objectStore.server"; import { isFinalAttemptStatus, isFinalRunStatus } from "../taskStatus"; @@ -1049,81 +1043,3 @@ export async function tryCompleteBatchV3( // Dependent-attempt batches (batchTriggerAndWait) only exist on the retired V1 engine, so there is no parent to resume here. } - -export async function completeBatchTaskRunItemV3( - itemId: string, - batchTaskRunId: string, - tx: PrismaClientOrTransaction, - scheduleResumeOnComplete = false, - taskRunAttemptId?: string, - retryAttempt?: number, - // Threaded in so a run-ops id (NEW-resident) batch's item lands on the owning store; route by - // batchTaskRunId (items co-reside with their batch). Defaults to the singleton. - runStore: RunStore = defaultRunStore -) { - const isRetry = retryAttempt !== undefined; - - logger.debug("completeBatchTaskRunItemV3", { - itemId, - batchTaskRunId, - scheduleResumeOnComplete, - taskRunAttemptId, - retryAttempt, - isRetry, - }); - - try { - // Update item to COMPLETED (no transaction needed, no contention). Routed by - // batchTaskRunId so the item write lands on the batch's owning DB. - const updated = await runStore.updateManyBatchTaskRunItems({ - where: { id: itemId, batchTaskRunId, status: "PENDING" }, - data: { status: "COMPLETED", taskRunAttemptId }, - }); - - if (updated.count === 0) { - logger.debug("completeBatchTaskRunItemV3: Item already completed", { - itemId, - batchTaskRunId, - }); - return; - } - - // Schedule debounced completion check - // enqueue with same ID overwrites, resetting the 200ms timer (debounce behavior) - await legacyRunEngineWorker.enqueue({ - id: `tryCompleteBatchV3:${batchTaskRunId}`, - job: "tryCompleteBatchV3", - payload: { batchId: batchTaskRunId, scheduleResumeOnComplete }, - availableAt: new Date(Date.now() + 200), - }); - } catch (error) { - if (isPrismaRetriableError(error) || isPrismaRaceConditionError(error)) { - logger.error("completeBatchTaskRunItemV3 failed, scheduling retry", { - itemId, - batchTaskRunId, - error, - retryAttempt, - isRetry, - }); - - if (isRetry) { - throw error; - } else { - await legacyRunEngineWorker.enqueue({ - id: `completeBatchTaskRunItem:${itemId}`, - job: "completeBatchTaskRunItem", - payload: { itemId, batchTaskRunId, scheduleResumeOnComplete, taskRunAttemptId }, - availableAt: new Date(Date.now() + 2_000), - }); - } - } else { - logger.error("completeBatchTaskRunItemV3 failed with non-retriable error", { - itemId, - batchTaskRunId, - error, - retryAttempt, - isRetry, - }); - } - } -} diff --git a/apps/webapp/app/v3/services/createCheckpointRestoreEvent.server.ts b/apps/webapp/app/v3/services/createCheckpointRestoreEvent.server.ts deleted file mode 100644 index 70cce53dbca..00000000000 --- a/apps/webapp/app/v3/services/createCheckpointRestoreEvent.server.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { ManualCheckpointMetadata } from "@trigger.dev/core/v3"; -import type { - Checkpoint, - CheckpointRestoreEvent, - CheckpointRestoreEventType, -} from "@trigger.dev/database"; -import { isTaskRunAttemptStatus, isTaskRunStatus } from "~/database-types"; -import { logger } from "~/services/logger.server"; -import { safeJsonParse } from "~/utils/json"; -import { BaseService } from "./baseService.server"; - -interface CheckpointRestoreEventCallParams { - checkpointId: string; - type: CheckpointRestoreEventType; - dependencyFriendlyRunId?: string; - batchDependencyFriendlyId?: string; -} - -type CheckpointRestoreEventParams = Omit; - -export class CreateCheckpointRestoreEventService extends BaseService { - async checkpoint(params: CheckpointRestoreEventParams) { - return this.#call({ ...params, type: "CHECKPOINT" }); - } - - async restore(params: CheckpointRestoreEventParams) { - return this.#call({ ...params, type: "RESTORE" }); - } - - async #call( - params: CheckpointRestoreEventCallParams - ): Promise { - if (params.dependencyFriendlyRunId && params.batchDependencyFriendlyId) { - logger.error("Only one dependency can be set", { params }); - return; - } - - const checkpoint = await this._prisma.checkpoint.findFirst({ - where: { - id: params.checkpointId, - }, - }); - - if (!checkpoint) { - logger.error("Checkpoint not found", { id: params.checkpointId }); - return; - } - - if (params.type === "RESTORE" && checkpoint.reason === "MANUAL") { - const manualRestoreSuccess = await this.#handleManualCheckpointRestore(checkpoint); - if (!manualRestoreSuccess) { - return; - } - } - - logger.debug(`Creating checkpoint/restore event`, { params }); - - let taskRunDependencyId: string | undefined; - - if (params.dependencyFriendlyRunId) { - const run = await this.runStore.findRun( - { - friendlyId: params.dependencyFriendlyRunId, - }, - { - select: { - id: true, - dependency: { - select: { - id: true, - }, - }, - }, - }, - this._prisma - ); - - taskRunDependencyId = run?.dependency?.id; - - if (!taskRunDependencyId) { - logger.error("Dependency or run not found", { runId: params.dependencyFriendlyRunId }); - return; - } - } - - const checkpointEvent = await this._prisma.checkpointRestoreEvent.create({ - data: { - checkpointId: checkpoint.id, - runtimeEnvironmentId: checkpoint.runtimeEnvironmentId, - projectId: checkpoint.projectId, - attemptId: checkpoint.attemptId, - runId: checkpoint.runId, - type: params.type, - reason: checkpoint.reason, - metadata: checkpoint.metadata, - ...(taskRunDependencyId - ? { - taskRunDependency: { - connect: { - id: taskRunDependencyId, - }, - }, - } - : undefined), - ...(params.batchDependencyFriendlyId - ? { - batchTaskRunDependency: { - connect: { - friendlyId: params.batchDependencyFriendlyId, - }, - }, - } - : undefined), - }, - }); - - return checkpointEvent; - } - - async #handleManualCheckpointRestore(checkpoint: Checkpoint): Promise { - const json = checkpoint.metadata ? safeJsonParse(checkpoint.metadata) : undefined; - - // We need to restore the previous run and attempt status as saved in the metadata - const metadata = ManualCheckpointMetadata.safeParse(json); - - if (!metadata.success) { - logger.error("Invalid metadata", { metadata }); - return false; - } - - const { attemptId, previousAttemptStatus, previousRunStatus } = metadata.data; - - if (!isTaskRunAttemptStatus(previousAttemptStatus)) { - logger.error("Invalid previous attempt status", { previousAttemptStatus }); - return false; - } - - if (!isTaskRunStatus(previousRunStatus)) { - logger.error("Invalid previous run status", { previousRunStatus }); - return false; - } - - try { - const updatedAttempt = await this._prisma.taskRunAttempt.update({ - where: { - id: attemptId, - }, - data: { - status: previousAttemptStatus, - taskRun: { - update: { - data: { - status: previousRunStatus, - }, - }, - }, - }, - select: { - id: true, - status: true, - taskRun: { - select: { - id: true, - status: true, - }, - }, - }, - }); - - logger.debug("Set post resume statuses after manual checkpoint", { - run: { - id: updatedAttempt.taskRun.id, - status: updatedAttempt.taskRun.status, - }, - attempt: { - id: updatedAttempt.id, - status: updatedAttempt.status, - }, - }); - - return true; - } catch (error) { - logger.error("Failed to set post resume statuses", { - error: - error instanceof Error - ? { - name: error.name, - message: error.message, - stack: error.stack, - } - : error, - }); - return false; - } - } -} diff --git a/apps/webapp/app/v3/services/triggerV1Scoping.ts b/apps/webapp/app/v3/services/triggerV1Scoping.ts deleted file mode 100644 index bd9abc89bca..00000000000 --- a/apps/webapp/app/v3/services/triggerV1Scoping.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Prisma } from "@trigger.dev/database"; - -// Where-clauses for resolving caller-supplied parent/dependent attempt & batch -// friendlyIds in the V1 trigger path, scoped to the caller's environment so a -// foreign friendlyId can't be wired onto the new run/batch. Standalone builders -// so the scope can be asserted directly in tests. - -export function attemptInEnvironmentWhere( - friendlyId: string, - environmentId: string -): Prisma.TaskRunAttemptWhereInput { - return { friendlyId, taskRun: { runtimeEnvironmentId: environmentId } }; -} - -export function batchRunInEnvironmentWhere( - friendlyId: string, - environmentId: string -): Prisma.BatchTaskRunWhereInput { - return { friendlyId, runtimeEnvironmentId: environmentId }; -} diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 50c0ef56f94..b522331bfdd 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -218,7 +218,6 @@ "slug": "^6.0.0", "socket.io": "4.7.4", "socket.io-adapter": "^2.5.4", - "socket.io-client": "4.7.5", "sonner": "^1.0.3", "sql-formatter": "^15.4.10", "sqs-consumer": "^7.4.0", diff --git a/apps/webapp/test/setup.ts b/apps/webapp/test/setup.ts index 4da9d260aa2..db07cb1402a 100644 --- a/apps/webapp/test/setup.ts +++ b/apps/webapp/test/setup.ts @@ -33,13 +33,10 @@ function createWorkerStub() { vi.mock("~/v3/commonWorker.server", () => ({ commonWorker: createWorkerStub() })); vi.mock("~/v3/batchTriggerWorker.server", () => ({ batchTriggerWorker: createWorkerStub() })); -vi.mock("~/v3/legacyRunEngineWorker.server", () => ({ - legacyRunEngineWorker: createWorkerStub(), -})); vi.mock("~/v3/alertsWorker.server", () => ({ alertsWorker: createWorkerStub() })); -// RunEngine, MarQS, devPubSub and the socket.io server are further singletons -// that open eager ioredis connections at import via the same pattern. No test +// RunEngine and the socket.io server are further singletons that open eager +// ioredis connections at import via the same pattern. No test // uses these app-level singletons directly (store-routing tests build their own // engine and run store), so stub them to no-op proxies. // Recursive no-op proxy: property access at any depth returns another callable @@ -157,8 +154,6 @@ vi.mock("~/services/dataStores/organizationDataStoresRegistryInstance.server", ( })); vi.mock("~/v3/runEngine.server", () => ({ engine: noopProxy() })); -vi.mock("~/v3/marqs/index.server", () => ({ marqs: noopProxy(), MarQS: class {} })); -vi.mock("~/v3/marqs/devPubSub.server", () => ({ devPubSub: noopProxy() })); vi.mock("~/v3/handleSocketIo.server", () => ({ socketIo: noopProxy(), roomFromFriendlyRunId: (id: string) => `room:${id}`, diff --git a/apps/webapp/test/triggerV1Scoping.test.ts b/apps/webapp/test/triggerV1Scoping.test.ts deleted file mode 100644 index c4450f3487a..00000000000 --- a/apps/webapp/test/triggerV1Scoping.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - attemptInEnvironmentWhere, - batchRunInEnvironmentWhere, -} from "../app/v3/services/triggerV1Scoping.js"; - -// Caller-supplied parent/dependent attempt & batch friendlyIds must be resolved -// scoped to the caller's environment — a where clause missing that constraint -// lets a foreign id resolve (the cross-tenant bug). Pins the scope on each query. -describe("triggerV1 scoping where-clauses", () => { - it("scopes attempt lookups to the env via the related run", () => { - const where = attemptInEnvironmentWhere("attempt_x", "env_caller"); - expect(where.friendlyId).toBe("attempt_x"); - expect(where.taskRun).toEqual({ runtimeEnvironmentId: "env_caller" }); - }); - - it("scopes batch-run lookups to the env directly", () => { - const where = batchRunInEnvironmentWhere("batch_x", "env_caller"); - expect(where.friendlyId).toBe("batch_x"); - expect(where.runtimeEnvironmentId).toBe("env_caller"); - }); -}); diff --git a/internal-packages/testcontainers/src/webapp.ts b/internal-packages/testcontainers/src/webapp.ts index 02f36dd9c49..2d2774df420 100644 --- a/internal-packages/testcontainers/src/webapp.ts +++ b/internal-packages/testcontainers/src/webapp.ts @@ -116,7 +116,6 @@ export async function startWebapp( RUN_ENGINE_WORKER_ENABLED: "0", // disables run engine workers (checked === "0", default "1") SCHEDULE_WORKER_ENABLED: "0", // disables schedule engine worker (checked === "0") BATCH_QUEUE_WORKER_ENABLED: "false", // disables batch queue consumers (BoolEnv) - LEGACY_RUN_ENGINE_WORKER_ENABLED: "0", // disables legacy run engine worker COMMON_WORKER_ENABLED: "0", // disables common worker RUN_ENGINE_TTL_SYSTEM_DISABLED: "true", // disables TTL expiry system (BoolEnv) RUN_ENGINE_TTL_CONSUMERS_DISABLED: "true", // disables TTL consumers (BoolEnv) diff --git a/packages/core/package.json b/packages/core/package.json index 57522a3dc1b..99e1112a6d2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -133,9 +133,6 @@ "v3/zodMessageHandler": [ "dist/commonjs/v3/zodMessageHandler.d.ts" ], - "v3/zodNamespace": [ - "dist/commonjs/v3/zodNamespace.d.ts" - ], "v3/zodSocket": [ "dist/commonjs/v3/zodSocket.d.ts" ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 288c8820a64..d2ad5ab2b14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -773,9 +773,6 @@ importers: socket.io-adapter: specifier: ^2.5.4 version: 2.5.4(bufferutil@4.0.9) - socket.io-client: - specifier: 4.7.5 - version: 4.7.5(bufferutil@4.0.9)(supports-color@10.0.0) sonner: specifier: ^1.0.3 version: 1.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) diff --git a/scripts/analyze_marqs.mjs b/scripts/analyze_marqs.mjs deleted file mode 100755 index e210a507572..00000000000 --- a/scripts/analyze_marqs.mjs +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env node - -const filename = process.argv[2]; - -if (!filename) { - console.error("Usage: analyze_marqs.mjs "); - process.exit(1); -} - -import fs from "fs/promises"; - -(async () => { - try { - const input = await fs.readFile(filename, "utf-8"); - - await processInput(input); - } catch (err) { - console.error(`Error reading file: ${err}`); - process.exit(1); - } -})(); - -// input is jsonl format, we want to split by line and then JSON parse each line -async function processInput(input) { - const rows = []; - const lines = input.split("\n"); - for (const line of lines) { - if (!line) { - continue; - } - - const row = JSON.parse(line); - - // process each row - rows.push(row); - } - - const queueChoiceCounts = {}; - const queueMaxAges = {}; - const queueMaxSizes = {}; - const nextRangeOffsetCounts = {}; - const consumerStats = {}; - const rowsByConsumer = {}; - - console.log(`Processed ${rows.length} rows`); - - // console.log(util.inspect(rows[0], { depth: 20 })); - - for (const row of rows) { - const queueChoice = row.queueChoice; - - if (queueChoice) { - if (!queueChoiceCounts[queueChoice]) { - queueChoiceCounts[queueChoice] = 0; - } - queueChoiceCounts[queueChoice]++; - } - } - - for (const row of rows) { - rowsByConsumer[row.consumerId] = rowsByConsumer[row.consumerId] || []; - rowsByConsumer[row.consumerId].push(row); - - const queueChoice = row.queueChoice; - - if (!consumerStats[row.consumerId]) { - consumerStats[row.consumerId] = { - queueChoiceCounts: {}, - totalQueueChoices: 0, - noQueueChoiceCount: 0, - }; - } - - if (queueChoice) { - const queueData = row.queuesWithScores.find((queue) => queue.queue === queueChoice); - - console.log( - `[${row.timestamp}] -> ${queueChoice} [age:${queueData.age}] [size:${queueData.size}] [nextRange.offset=${row.nextRange.offset}] [queuesWithScores=${row.queuesWithScores.length}] [${row.consumerId}]` - ); - - if (!queueMaxAges[queueChoice] || queueData.age > queueMaxAges[queueChoice]) { - queueMaxAges[queueChoice] = queueData.age; - } - - if (!queueMaxSizes[queueChoice] || queueData.size > queueMaxSizes[queueChoice]) { - queueMaxSizes[queueChoice] = queueData.size; - } - - if (!consumerStats[row.consumerId].queueChoiceCounts[queueChoice]) { - consumerStats[row.consumerId].queueChoiceCounts[queueChoice] = 0; - } - - consumerStats[row.consumerId].queueChoiceCounts[queueChoice]++; - consumerStats[row.consumerId].totalQueueChoices++; - } else { - console.log( - `[${row.timestamp}] -> No queue choice [nextRange.offset=${row.nextRange.offset}] [queuesWithScores=${row.queuesWithScores.length}] [${row.consumerId}]` - ); - - consumerStats[row.consumerId].noQueueChoiceCount++; - } - - if (!nextRangeOffsetCounts[row.nextRange.offset]) { - nextRangeOffsetCounts[row.nextRange.offset] = 0; - } - - nextRangeOffsetCounts[row.nextRange.offset]++; - } - - console.log("Queue choice counts:"); - console.log(queueChoiceCounts); - - console.log("Queue max ages:"); - console.log(queueMaxAges); - - console.log("Queue max sizes:"); - console.log(queueMaxSizes); - - console.log("Next range offset counts:"); - console.log(nextRangeOffsetCounts); - - for (const consumerId in consumerStats) { - console.log(`Consumer ${consumerId}:`); - console.log(consumerStats[consumerId]); - } - - for (const consumerId in rowsByConsumer) { - console.log(`\n## Consumer ${consumerId}:`); - - for (const row of rowsByConsumer[consumerId]) { - const queueChoice = row.queueChoice; - - if (queueChoice) { - const queueData = row.queuesWithScores.find((queue) => queue.queue === queueChoice); - - console.log( - `[${row.timestamp}] -> ${queueChoice} [age:${queueData.age}] [size:${queueData.size}] [nextRange.offset=${row.nextRange.offset}] [queuesWithScores=${row.queuesWithScores.length}] [${row.consumerId}]` - ); - } else { - console.log( - `[${row.timestamp}] -> No queue choice [nextRange.offset=${row.nextRange.offset}] [queuesWithScores=${row.queuesWithScores.length}] [${row.consumerId}]` - ); - } - } - } -} From a0658490d06e5cb4a1f1ce4451a478918003320c Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 10:10:39 +0100 Subject: [PATCH 11/12] feat(webapp): create new projects on the v2 engine The engine column still defaults to V1 for historical rows, so a brand-new project was transiently V1 until its first worker registration or deploy upgraded it, and any trigger in that window hit the v3 upgrade error. New projects are V2 up front. The V1 to V2 upgrade guards for existing legacy projects are untouched. --- apps/webapp/app/models/project.server.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/webapp/app/models/project.server.ts b/apps/webapp/app/models/project.server.ts index a9feb394754..cd04804c66b 100644 --- a/apps/webapp/app/models/project.server.ts +++ b/apps/webapp/app/models/project.server.ts @@ -101,6 +101,10 @@ export async function createProject( }, externalRef: `proj_${externalRefGenerator()}`, version: version === "v3" ? "V3" : "V2", + // New projects run on the v2 engine. The Prisma column still defaults to V1 + // for historical rows; the V1->V2 upgrade guards on worker-register / deploy + // stay in place to migrate existing legacy projects. + engine: "V2", onboardingData, }, include: { From 40065fbff6fb2922bf35380d26b8cbe130c52e3a Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 13 Jul 2026 11:05:06 +0100 Subject: [PATCH 12/12] chore(webapp): remove unused dependencies and add a knip check Removes 27 unused production dependencies from the webapp, plus 6 orphaned @types packages and two dead files whose only importers were those packages (SimpleSelect.tsx and ulid.server.ts). seedrandom and semver are among them: they were only used by the v3 code this PR removes. Adds knip as a dev dependency with a knip:deps script and a knip.json config so unused dependencies can be found the same way from now on. knip catches deps reachable only through otherwise-dead code, which a plain grep cannot. non.geist is ignored: it is a font imported for its side effects via tailwind.css. --- .../components/primitives/SimpleSelect.tsx | 143 - apps/webapp/app/services/ulid.server.ts | 7 - apps/webapp/package.json | 33 - knip.json | 4 + package.json | 2 + pnpm-lock.yaml | 3562 ++++------------- 6 files changed, 698 insertions(+), 3053 deletions(-) delete mode 100644 apps/webapp/app/components/primitives/SimpleSelect.tsx delete mode 100644 apps/webapp/app/services/ulid.server.ts create mode 100644 knip.json diff --git a/apps/webapp/app/components/primitives/SimpleSelect.tsx b/apps/webapp/app/components/primitives/SimpleSelect.tsx deleted file mode 100644 index 60ef1f65dd2..00000000000 --- a/apps/webapp/app/components/primitives/SimpleSelect.tsx +++ /dev/null @@ -1,143 +0,0 @@ -"use client"; - -import * as SelectPrimitive from "@radix-ui/react-select"; -import { Check, ChevronDown } from "lucide-react"; -import * as React from "react"; -import { cn } from "~/utils/cn"; - -const sizes = { - "secondary/small": - "text-xs h-6 bg-tertiary border border-tertiary group-hover:text-text-bright hover:border-border-bright pr-2 pl-1.5", - medium: "text-sm h-8 bg-tertiary border border-tertiary hover:border-border-bright px-2.5", - minimal: "text-xs h-6 bg-transparent hover:bg-tertiary pl-1.5 pr-2", -}; - -export type SelectProps = { - size?: keyof typeof sizes; - width?: "content" | "full"; -}; - -const Select = SelectPrimitive.Root; -const SelectGroup = SelectPrimitive.Group; -const SelectValue = SelectPrimitive.Value; -const SelectTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & SelectProps ->(({ className, children, width = "content", size = "secondary/small", ...props }, ref) => { - const sizeClassName = sizes[size]; - return ( - - {children} - - - - - ); -}); -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; - -const SelectContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = "popper", ...props }, ref) => ( - - - - {children} - - - -)); -SelectContent.displayName = SelectPrimitive.Content.displayName; - -const SelectLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -SelectLabel.displayName = SelectPrimitive.Label.displayName; - -type SelectItemProps = React.ComponentPropsWithoutRef & { - contentClassName?: string; -}; - -const SelectItem = React.forwardRef, SelectItemProps>( - ({ className, children, contentClassName, ...props }, ref) => ( - - - - - - - - {children} - - ) -); -SelectItem.displayName = SelectPrimitive.Item.displayName; - -const SelectSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -SelectSeparator.displayName = SelectPrimitive.Separator.displayName; - -export { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectSeparator, - SelectTrigger, - SelectValue, -}; diff --git a/apps/webapp/app/services/ulid.server.ts b/apps/webapp/app/services/ulid.server.ts deleted file mode 100644 index 19675e6bed6..00000000000 --- a/apps/webapp/app/services/ulid.server.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { monotonicFactory } from "ulid"; - -const factory = monotonicFactory(); - -export function ulid(): ReturnType { - return factory().toLowerCase(); -} diff --git a/apps/webapp/package.json b/apps/webapp/package.json index b522331bfdd..ba2388572cf 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -30,15 +30,12 @@ "@ariakit/react-core": "^0.4.6", "@aws-sdk/client-ecr": "^3.931.0", "@aws-sdk/client-s3": "^3.936.0", - "@aws-sdk/client-sqs": "^3.445.0", "@aws-sdk/client-sts": "^3.840.0", - "@aws-sdk/credential-provider-node": "^3.936.0", "@aws-sdk/s3-presigned-post": "^3.936.0", "@aws-sdk/s3-request-presigner": "^3.936.0", "@better-auth/utils": "^0.2.6", "@codemirror/autocomplete": "6.4.0", "@codemirror/commands": "6.1.3", - "@codemirror/lang-javascript": "6.1.2", "@codemirror/lang-json": "6.0.1", "@codemirror/lang-sql": "6.5.5", "@codemirror/language": "6.3.2", @@ -50,7 +47,6 @@ "@conform-to/zod": "^1.2.2", "@depot/cli": "0.0.1-cli.2.80.0", "@depot/sdk-node": "^1.0.0", - "@electric-sql/react": "^0.3.5", "@headlessui/react": "^1.7.8", "@heroicons/react": "^2.0.12", "@internal/cache": "workspace:*", @@ -84,7 +80,6 @@ "@opentelemetry/resources": "2.7.1", "@opentelemetry/sdk-logs": "0.218.0", "@opentelemetry/sdk-metrics": "2.7.1", - "@opentelemetry/sdk-node": "0.218.0", "@opentelemetry/sdk-trace-base": "2.7.1", "@opentelemetry/sdk-trace-node": "2.7.1", "@opentelemetry/semantic-conventions": "1.41.1", @@ -93,11 +88,8 @@ "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.3", - "@radix-ui/react-label": "^2.0.1", "@radix-ui/react-popover": "^1.0.5", - "@radix-ui/react-portal": "^1.1.9", "@radix-ui/react-radio-group": "^1.1.3", - "@radix-ui/react-select": "^1.2.1", "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.3", @@ -109,9 +101,7 @@ "@remix-run/node": "2.17.5", "@remix-run/react": "2.17.5", "@remix-run/router": "^1.23.3", - "@remix-run/serve": "2.17.5", "@remix-run/server-runtime": "2.17.5", - "@remix-run/v1-meta": "^0.1.3", "@s2-dev/streamstore": "^0.22.10", "@sentry/remix": "9.46.0", "@slack/web-api": "7.16.0", @@ -132,13 +122,11 @@ "@trigger.dev/redis-worker": "workspace:*", "@trigger.dev/sdk": "workspace:*", "@trigger.dev/sso": "workspace:*", - "@types/pg": "8.6.6", "@uiw/react-codemirror": "^4.19.5", "@unkey/cache": "^1.5.0", "@unkey/error": "^0.2.0", "@upstash/ratelimit": "^1.1.3", "@vercel/sdk": "^1.19.1", - "@whatwg-node/fetch": "^0.9.14", "@window-splitter/react": "1.1.3", "ai": "^6.0.116", "assert-never": "^1.2.1", @@ -156,19 +144,15 @@ "dotenv": "^16.4.5", "effect": "^3.21.2", "emails": "workspace:*", - "eventsource": "^4.0.0", "evt": "^2.4.13", "express": "4.20.0", "framer-motion": "^10.12.11", - "humanize-duration": "^3.27.3", "input-otp": "^1.4.2", "intl-parse-accept-language": "^1.0.0", "ioredis": "~5.6.0", "isbot": "^3.6.5", "jose": "^5.4.0", "json-stable-stringify": "^1.3.0", - "jsonpointer": "^5.0.1", - "lodash.omit": "^4.5.0", "lucide-react": "^0.229.0", "marked": "^4.0.18", "match-sorter": "^6.3.4", @@ -177,7 +161,6 @@ "neverthrow": "^8.2.0", "non.geist": "^1.0.2", "octokit": "^3.2.1", - "ohash": "^1.1.3", "openai": "^4.33.1", "p-limit": "^6.2.0", "p-map": "^6.0.0", @@ -191,8 +174,6 @@ "qrcode.react": "^4.2.0", "random-words": "^2.0.0", "react": "^18.2.0", - "react-aria": "^3.31.1", - "react-collapse": "^5.1.1", "react-day-picker": "^9.13.0", "react-dom": "^18.2.0", "react-grid-layout": "^2.2.2", @@ -200,8 +181,6 @@ "react-markdown": "^10.1.0", "react-popper": "^2.3.0", "react-resizable": "^3.1.3", - "react-resizable-panels": "^2.0.9", - "react-stately": "^3.29.1", "react-use": "17.5.1", "recharts": "^2.15.2", "regression": "^2.0.1", @@ -211,24 +190,18 @@ "remix-auth-google": "^2.0.0", "remix-typedjson": "0.3.1", "remix-utils": "^7.7.0", - "seedrandom": "^3.0.5", - "semver": "^7.5.0", - "simple-oauth2": "^5.0.0", "simplur": "^3.0.1", "slug": "^6.0.0", "socket.io": "4.7.4", "socket.io-adapter": "^2.5.4", "sonner": "^1.0.3", "sql-formatter": "^15.4.10", - "sqs-consumer": "^7.4.0", "streamdown": "^2.5.0", "superjson": "^2.2.1", "tailwind-merge": "^3.6.0", "tailwind-scrollbar-hide": "^4.0.0", "tw-animate-css": "^1.4.0", "tiny-invariant": "^1.2.0", - "ulid": "^2.3.0", - "ulidx": "^2.2.1", "uuid": "^14.0.0", "ws": "^8.11.0", "zod": "3.25.76", @@ -253,21 +226,15 @@ "@types/compression": "^1.7.2", "@types/cookie": "^0.6.0", "@types/express": "^4.17.13", - "@types/humanize-duration": "^3.27.1", "@types/json-query": "^2.2.3", - "@types/lodash.omit": "^4.5.7", "@types/marked": "^4.0.3", "@types/morgan": "^1.9.3", "@types/node-fetch": "^2.6.2", "@types/prismjs": "^1.26.0", "@types/qs": "^6.9.7", "@types/react": "18.2.69", - "@types/react-collapse": "^5.0.4", "@types/react-dom": "18.2.7", "@types/regression": "^2.0.6", - "@types/seedrandom": "^3.0.8", - "@types/semver": "^7.5.0", - "@types/simple-oauth2": "^5.0.4", "@types/slug": "^5.0.3", "@types/supertest": "^6.0.2", "@types/tar": "^6.1.4", diff --git a/knip.json b/knip.json new file mode 100644 index 00000000000..51bddfe4751 --- /dev/null +++ b/knip.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "ignoreDependencies": ["non.geist"] +} diff --git a/package.json b/package.json index 3ccfacdfeb3..0bb6c6b5c28 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "format:prisma": "pnpm --filter @trigger.dev/database run format:prisma && pnpm --filter @internal/run-ops-database run format:prisma", "lint": "oxlint", "lint:fix": "oxlint --fix", + "knip:deps": "knip --production --dependencies", "docker": "node scripts/docker.mjs -f docker/docker-compose.yml up -d --build --remove-orphans", "docker:stop": "node scripts/docker.mjs -f docker/docker-compose.yml stop", "docker:full": "node scripts/docker.mjs -f docker/docker-compose.yml -f docker/docker-compose.extras.yml up -d --build --remove-orphans", @@ -60,6 +61,7 @@ "@types/node": "22.20.0", "@vitest/coverage-v8": "4.1.7", "autoprefixer": "^10.4.12", + "knip": "6.25.0", "oxfmt": "^0.54.0", "oxlint": "^1.69.0", "pkg-pr-new": "0.0.75", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2ad5ab2b14..c046a7c8226 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,9 @@ importers: autoprefixer: specifier: ^10.4.12 version: 10.4.13(postcss@8.5.15) + knip: + specifier: 6.25.0 + version: 6.25.0 oxfmt: specifier: ^0.54.0 version: 0.54.0 @@ -209,15 +212,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.936.0 version: 3.940.0 - '@aws-sdk/client-sqs': - specifier: ^3.445.0 - version: 3.454.0 '@aws-sdk/client-sts': specifier: ^3.840.0 version: 3.840.0 - '@aws-sdk/credential-provider-node': - specifier: ^3.936.0 - version: 3.940.0 '@aws-sdk/s3-presigned-post': specifier: ^3.936.0 version: 3.940.0 @@ -233,9 +230,6 @@ importers: '@codemirror/commands': specifier: 6.1.3 version: 6.1.3 - '@codemirror/lang-javascript': - specifier: 6.1.2 - version: 6.1.2 '@codemirror/lang-json': specifier: 6.0.1 version: 6.0.1 @@ -269,9 +263,6 @@ importers: '@depot/sdk-node': specifier: ^1.0.0 version: 1.0.0 - '@electric-sql/react': - specifier: ^0.3.5 - version: 0.3.5(react@18.3.1) '@headlessui/react': specifier: ^1.7.8 version: 1.7.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -371,9 +362,6 @@ importers: '@opentelemetry/sdk-metrics': specifier: 2.7.1 version: 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-node': - specifier: 0.218.0 - version: 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': specifier: 2.7.1 version: 2.7.1(@opentelemetry/api@1.9.1) @@ -398,21 +386,12 @@ importers: '@radix-ui/react-dialog': specifier: ^1.0.3 version: 1.0.3(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-label': - specifier: ^2.0.1 - version: 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-popover': specifier: ^1.0.5 version: 1.0.5(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': - specifier: ^1.1.9 - version: 1.1.9(@types/react-dom@18.2.7)(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-radio-group': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.2.7)(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-select': - specifier: ^1.2.1 - version: 1.2.1(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slider': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.2.7)(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -446,15 +425,9 @@ importers: '@remix-run/router': specifier: ^1.23.3 version: 1.23.3(patch_hash=33e2966e9ef36aa09955cec9922c5063227b83429157a71ba226369a08627ac9) - '@remix-run/serve': - specifier: 2.17.5 - version: 2.17.5(typescript@5.5.4) '@remix-run/server-runtime': specifier: 2.17.5 version: 2.17.5(typescript@5.5.4) - '@remix-run/v1-meta': - specifier: ^0.1.3 - version: 0.1.3(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4))(@remix-run/server-runtime@2.17.5(typescript@5.5.4)) '@s2-dev/streamstore': specifier: ^0.22.10 version: 0.22.10(supports-color@10.0.0) @@ -515,9 +488,6 @@ importers: '@trigger.dev/sso': specifier: workspace:* version: link:../../internal-packages/sso - '@types/pg': - specifier: 8.6.6 - version: 8.6.6 '@uiw/react-codemirror': specifier: ^4.19.5 version: 4.19.5(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.4.0(@codemirror/language@6.3.2)(@codemirror/state@6.2.0)(@codemirror/view@6.7.2)(@lezer/common@1.3.0))(@codemirror/language@6.3.2)(@codemirror/lint@6.4.2)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.7.2)(codemirror@6.0.2(@lezer/common@1.3.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -533,9 +503,6 @@ importers: '@vercel/sdk': specifier: ^1.19.1 version: 1.19.1 - '@whatwg-node/fetch': - specifier: ^0.9.14 - version: 0.9.14 '@window-splitter/react': specifier: 1.1.3 version: 1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -587,9 +554,6 @@ importers: emails: specifier: workspace:* version: link:../../internal-packages/emails - eventsource: - specifier: ^4.0.0 - version: 4.0.0 evt: specifier: ^2.4.13 version: 2.4.13 @@ -599,9 +563,6 @@ importers: framer-motion: specifier: ^10.12.11 version: 10.12.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - humanize-duration: - specifier: ^3.27.3 - version: 3.27.3 input-otp: specifier: ^1.4.2 version: 1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -620,12 +581,6 @@ importers: json-stable-stringify: specifier: ^1.3.0 version: 1.3.0 - jsonpointer: - specifier: ^5.0.1 - version: 5.0.1 - lodash.omit: - specifier: ^4.5.0 - version: 4.5.0 lucide-react: specifier: ^0.229.0 version: 0.229.0(react@18.3.1) @@ -650,9 +605,6 @@ importers: octokit: specifier: ^3.2.1 version: 3.2.2 - ohash: - specifier: ^1.1.3 - version: 1.1.3 openai: specifier: ^4.33.1 version: 4.33.1(encoding@0.1.13) @@ -692,12 +644,6 @@ importers: react: specifier: 18.3.1 version: 18.3.1 - react-aria: - specifier: ^3.31.1 - version: 3.31.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-collapse: - specifier: ^5.1.1 - version: 5.1.1(react@18.3.1) react-day-picker: specifier: ^9.13.0 version: 9.13.0(react@18.3.1) @@ -719,12 +665,6 @@ importers: react-resizable: specifier: ^3.1.3 version: 3.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-resizable-panels: - specifier: ^2.0.9 - version: 2.0.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-stately: - specifier: ^3.29.1 - version: 3.29.1(react@18.3.1) react-use: specifier: 17.5.1 version: 17.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -752,15 +692,6 @@ importers: remix-utils: specifier: ^7.7.0 version: 7.7.0(@remix-run/node@2.17.5(typescript@5.5.4))(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4))(@remix-run/router@1.23.3(patch_hash=33e2966e9ef36aa09955cec9922c5063227b83429157a71ba226369a08627ac9))(crypto-js@4.2.0)(intl-parse-accept-language@1.0.0)(react@18.3.1)(zod@3.25.76) - seedrandom: - specifier: ^3.0.5 - version: 3.0.5 - semver: - specifier: ^7.5.0 - version: 7.6.3 - simple-oauth2: - specifier: ^5.0.0 - version: 5.0.0 simplur: specifier: ^3.0.1 version: 3.0.1 @@ -779,9 +710,6 @@ importers: sql-formatter: specifier: ^15.4.10 version: 15.6.12 - sqs-consumer: - specifier: ^7.4.0 - version: 7.5.0(@aws-sdk/client-sqs@3.454.0) streamdown: specifier: ^2.5.0 version: 2.5.0(patch_hash=36211d09153a59c880b6a2bce2a0a0f011c99c73c20c8ceca78cc77e47623f06)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -800,12 +728,6 @@ importers: tw-animate-css: specifier: ^1.4.0 version: 1.4.0 - ulid: - specifier: ^2.3.0 - version: 2.3.0 - ulidx: - specifier: ^2.2.1 - version: 2.2.1 uuid: specifier: ^14.0.0 version: 14.0.0 @@ -873,15 +795,9 @@ importers: '@types/express': specifier: ^4.17.13 version: 4.17.15 - '@types/humanize-duration': - specifier: ^3.27.1 - version: 3.27.1 '@types/json-query': specifier: ^2.2.3 version: 2.2.3 - '@types/lodash.omit': - specifier: ^4.5.7 - version: 4.5.7 '@types/marked': specifier: ^4.0.3 version: 4.0.8 @@ -900,24 +816,12 @@ importers: '@types/react': specifier: 18.2.69 version: 18.2.69 - '@types/react-collapse': - specifier: ^5.0.4 - version: 5.0.4 '@types/react-dom': specifier: 18.2.7 version: 18.2.7 '@types/regression': specifier: ^2.0.6 version: 2.0.6 - '@types/seedrandom': - specifier: ^3.0.8 - version: 3.0.8 - '@types/semver': - specifier: ^7.5.0 - version: 7.5.1 - '@types/simple-oauth2': - specifier: ^5.0.4 - version: 5.0.4 '@types/slug': specifier: ^5.0.3 version: 5.0.3 @@ -2388,9 +2292,6 @@ packages: resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==} engines: {node: '>=4'} - '@aws-crypto/crc32@3.0.0': - resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} - '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -2398,34 +2299,19 @@ packages: '@aws-crypto/crc32c@5.2.0': resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} - '@aws-crypto/ie11-detection@3.0.0': - resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} - '@aws-crypto/sha1-browser@5.2.0': resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} - '@aws-crypto/sha256-browser@3.0.0': - resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} - '@aws-crypto/sha256-browser@5.2.0': resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - '@aws-crypto/sha256-js@3.0.0': - resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} - '@aws-crypto/sha256-js@5.2.0': resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} engines: {node: '>=16.0.0'} - '@aws-crypto/supports-web-crypto@3.0.0': - resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} - '@aws-crypto/supports-web-crypto@5.2.0': resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - '@aws-crypto/util@3.0.0': - resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} - '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} @@ -2445,14 +2331,6 @@ packages: resolution: {integrity: sha512-jDQ4x2HwB2/UXBS7CTeSDiIb+sVsYGDyxTeXdrRAtqNdGv8kC54fbwokDiJ/mnMyB2gyXWw57BqeDJNkZuLmsw==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sqs@3.454.0': - resolution: {integrity: sha512-eBviLavFxmXAbqL/wPlVqYnmS/hYUPj63ilQvFuj8zM3WYy0DX7GgvYXz5AUWswGsEk/4F88QFjlBM7N5miepw==} - engines: {node: '>=14.0.0'} - - '@aws-sdk/client-sso@3.451.0': - resolution: {integrity: sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==} - engines: {node: '>=14.0.0'} - '@aws-sdk/client-sso@3.839.0': resolution: {integrity: sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==} engines: {node: '>=18.0.0'} @@ -2469,18 +2347,10 @@ packages: resolution: {integrity: sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sts@3.454.0': - resolution: {integrity: sha512-0fDvr8WeB6IYO8BUCzcivWmahgGl/zDbaYfakzGnt4mrl5ztYaXE875WI6b7+oFcKMRvN+KLvwu5TtyFuNY+GQ==} - engines: {node: '>=14.0.0'} - '@aws-sdk/client-sts@3.840.0': resolution: {integrity: sha512-h+mu89Wk81Ne+B624GT/pBM5VjuAZueSeQNixhgtQ1QHi6bZzrpz8+lvMSibKO+kXFyQsTLzkyibbxnhLpWQZA==} engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.451.0': - resolution: {integrity: sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==} - engines: {node: '>=14.0.0'} - '@aws-sdk/core@3.839.0': resolution: {integrity: sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==} engines: {node: '>=18.0.0'} @@ -2497,10 +2367,6 @@ packages: resolution: {integrity: sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.451.0': - resolution: {integrity: sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==} - engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-env@3.839.0': resolution: {integrity: sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==} engines: {node: '>=18.0.0'} @@ -2533,10 +2399,6 @@ packages: resolution: {integrity: sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.451.0': - resolution: {integrity: sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==} - engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-ini@3.839.0': resolution: {integrity: sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==} engines: {node: '>=18.0.0'} @@ -2557,10 +2419,6 @@ packages: resolution: {integrity: sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.451.0': - resolution: {integrity: sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==} - engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-node@3.839.0': resolution: {integrity: sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==} engines: {node: '>=18.0.0'} @@ -2577,10 +2435,6 @@ packages: resolution: {integrity: sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-process@3.451.0': - resolution: {integrity: sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==} - engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-process@3.839.0': resolution: {integrity: sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==} engines: {node: '>=18.0.0'} @@ -2597,10 +2451,6 @@ packages: resolution: {integrity: sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-sso@3.451.0': - resolution: {integrity: sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==} - engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-sso@3.839.0': resolution: {integrity: sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==} engines: {node: '>=18.0.0'} @@ -2617,10 +2467,6 @@ packages: resolution: {integrity: sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.451.0': - resolution: {integrity: sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==} - engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-web-identity@3.839.0': resolution: {integrity: sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==} engines: {node: '>=18.0.0'} @@ -2649,10 +2495,6 @@ packages: resolution: {integrity: sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-host-header@3.451.0': - resolution: {integrity: sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==} - engines: {node: '>=14.0.0'} - '@aws-sdk/middleware-host-header@3.821.0': resolution: {integrity: sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==} engines: {node: '>=18.0.0'} @@ -2673,10 +2515,6 @@ packages: resolution: {integrity: sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-logger@3.451.0': - resolution: {integrity: sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==} - engines: {node: '>=14.0.0'} - '@aws-sdk/middleware-logger@3.821.0': resolution: {integrity: sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==} engines: {node: '>=18.0.0'} @@ -2693,10 +2531,6 @@ packages: resolution: {integrity: sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.451.0': - resolution: {integrity: sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==} - engines: {node: '>=14.0.0'} - '@aws-sdk/middleware-recursion-detection@3.821.0': resolution: {integrity: sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==} engines: {node: '>=18.0.0'} @@ -2717,26 +2551,10 @@ packages: resolution: {integrity: sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-sqs@3.451.0': - resolution: {integrity: sha512-GXpFSc9Ji4IAT/OaTkmnDrxzZrrAsJctUAC9vihpgGDof79A1Oz4R+r2uBMTKGxCIFkqyYW5Se7y2ijjTNa3ZA==} - engines: {node: '>=14.0.0'} - - '@aws-sdk/middleware-sdk-sts@3.451.0': - resolution: {integrity: sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==} - engines: {node: '>=14.0.0'} - - '@aws-sdk/middleware-signing@3.451.0': - resolution: {integrity: sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==} - engines: {node: '>=14.0.0'} - '@aws-sdk/middleware-ssec@3.936.0': resolution: {integrity: sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.451.0': - resolution: {integrity: sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==} - engines: {node: '>=14.0.0'} - '@aws-sdk/middleware-user-agent@3.839.0': resolution: {integrity: sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==} engines: {node: '>=18.0.0'} @@ -2769,10 +2587,6 @@ packages: resolution: {integrity: sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw==} engines: {node: '>=18.0.0'} - '@aws-sdk/region-config-resolver@3.451.0': - resolution: {integrity: sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==} - engines: {node: '>=14.0.0'} - '@aws-sdk/region-config-resolver@3.821.0': resolution: {integrity: sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==} engines: {node: '>=18.0.0'} @@ -2801,10 +2615,6 @@ packages: resolution: {integrity: sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.451.0': - resolution: {integrity: sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==} - engines: {node: '>=14.0.0'} - '@aws-sdk/token-providers@3.839.0': resolution: {integrity: sha512-2nlafqdSbet/2WtYIoZ7KEGFowFonPBDYlTjrUvwU2yooE10VhvzhLSCTB2aKIVzo2Z2wL5WGFQsqAY5QwK6Bw==} engines: {node: '>=18.0.0'} @@ -2821,10 +2631,6 @@ packages: resolution: {integrity: sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg==} engines: {node: '>=18.0.0'} - '@aws-sdk/types@3.451.0': - resolution: {integrity: sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==} - engines: {node: '>=14.0.0'} - '@aws-sdk/types@3.821.0': resolution: {integrity: sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==} engines: {node: '>=18.0.0'} @@ -2845,10 +2651,6 @@ packages: resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-endpoints@3.451.0': - resolution: {integrity: sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==} - engines: {node: '>=14.0.0'} - '@aws-sdk/util-endpoints@3.828.0': resolution: {integrity: sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg==} engines: {node: '>=18.0.0'} @@ -2869,17 +2671,10 @@ packages: resolution: {integrity: sha512-MS5eSEtDUFIAMHrJaMERiHAvDPdfxc/T869ZjDNFAIiZhyc037REw0aoTNeimNXDNy2txRNZJaAUn/kE4RwN+g==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-locate-window@3.310.0': - resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} - engines: {node: '>=14.0.0'} - '@aws-sdk/util-locate-window@3.893.0': resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-user-agent-browser@3.451.0': - resolution: {integrity: sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==} - '@aws-sdk/util-user-agent-browser@3.821.0': resolution: {integrity: sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==} @@ -2892,15 +2687,6 @@ packages: '@aws-sdk/util-user-agent-browser@3.936.0': resolution: {integrity: sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==} - '@aws-sdk/util-user-agent-node@3.451.0': - resolution: {integrity: sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==} - engines: {node: '>=14.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - '@aws-sdk/util-user-agent-node@3.839.0': resolution: {integrity: sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==} engines: {node: '>=18.0.0'} @@ -2937,9 +2723,6 @@ packages: aws-crt: optional: true - '@aws-sdk/util-utf8-browser@3.259.0': - resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} - '@aws-sdk/xml-builder@3.821.0': resolution: {integrity: sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==} engines: {node: '>=18.0.0'} @@ -3261,9 +3044,6 @@ packages: '@codemirror/commands@6.1.3': resolution: {integrity: sha512-wUw1+vb34Ultv0Q9m/OVB7yizGXgtoDbkI5f5ErM8bebwLyUYjicdhJTKhTvPTpgkv8dq/BK0lQ3K5pRf2DAJw==} - '@codemirror/lang-javascript@6.1.2': - resolution: {integrity: sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==} - '@codemirror/lang-json@6.0.1': resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==} @@ -3386,19 +3166,23 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} - '@electric-sql/client@0.4.0': - resolution: {integrity: sha512-YVYSqHitqVIDC1RBTfmHMfAfqDNAKMK9/AFVTDFQQxN3Q85dIQS49zThAuJVecYiuYRJvTiqf40c4n39jZSNrQ==} - '@electric-sql/client@1.0.14': resolution: {integrity: sha512-LtPAfeMxXRiYS0hyDQ5hue2PjljUiK9stvzsVyVb4nwxWQxfOWTSF42bHTs/o5i3x1T4kAQ7mwHpxa4A+f8X7Q==} - '@electric-sql/react@0.3.5': - resolution: {integrity: sha512-qPrlF3BsRg5L8zAn1sLGzc3pkswfEHyQI3lNOu7Xllv1DBx85RvHR1zgGGPAUfC8iwyWupQu9pFPE63GdbeuhA==} - peerDependencies: - react: 18.3.1 - peerDependenciesMeta: - react: - optional: true + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} '@emotion/hash@0.9.0': resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} @@ -4552,41 +4336,11 @@ packages: resolution: {integrity: sha512-JXUj6PI0oqqzTGvKtzOkxtpsyPRNsrmhh41TtIz/zEB6J+AUiZZ0dxWzcMwO9Ns5rmSPuMdghlTbUuqIM48d3Q==} engines: {node: '>=12.10.0'} - '@grpc/grpc-js@1.14.3': - resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} - engines: {node: '>=12.10.0'} - '@grpc/proto-loader@0.7.13': resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} engines: {node: '>=6'} hasBin: true - '@grpc/proto-loader@0.8.0': - resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} - engines: {node: '>=6'} - hasBin: true - - '@hapi/boom@10.0.1': - resolution: {integrity: sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==} - - '@hapi/bourne@3.0.0': - resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} - - '@hapi/hoek@10.0.1': - resolution: {integrity: sha512-CvlW7jmOhWzuqOqiJQ3rQVLMcREh0eel4IBnxDx2FAcK8g7qoJRQK4L1CPBASoCY6y8e6zuCy3f2g+HWdkzcMw==} - - '@hapi/hoek@11.0.2': - resolution: {integrity: sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==} - - '@hapi/hoek@9.3.0': - resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} - - '@hapi/topo@5.1.0': - resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - - '@hapi/wreck@18.0.1': - resolution: {integrity: sha512-OLHER70+rZxvDl75xq3xXOfd3e8XIvz8fWY0dqg92UvhZ29zo24vQgfqgHSYhB5ZiuFpSLeriOisAlxAo/1jWg==} - '@hcaptcha/loader@2.0.0': resolution: {integrity: sha512-fFQH6ApU/zCCl6Y1bnbsxsp1Er/lKX+qlgljrpWDeFcenpEtoP68hExlKSXECospzKLeSWcr06cbTjlR/x3IJA==} @@ -4745,9 +4499,6 @@ packages: '@lezer/highlight@1.2.3': resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} - '@lezer/javascript@1.4.1': - resolution: {integrity: sha512-Hqx36DJeYhKtdpc7wBYPR0XF56ZzIp0IkMO/zNNj80xcaFOV4Oj/P7TQc/8k2TxNhzl7tV5tXS8ZOCPbT4L3nA==} - '@lezer/json@1.0.0': resolution: {integrity: sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==} @@ -4797,6 +4548,12 @@ packages: '@cfworker/json-schema': optional: true + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nodable/entities@2.1.0': resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} @@ -4977,12 +4734,6 @@ packages: resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@opentelemetry/configuration@0.218.0': - resolution: {integrity: sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.9.0 - '@opentelemetry/context-async-hooks@1.30.1': resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} engines: {node: '>=14'} @@ -5007,30 +4758,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-logs-otlp-grpc@0.218.0': - resolution: {integrity: sha512-hoxrNH1l/Xy6F9WTJ5IK+6j1r9nQFlPOmrnTlhYHTySdunfXLmUCPv3bQtKYntxag9h3wLYBZQ2HI6FOx+BT2g==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-http@0.218.0': resolution: {integrity: sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-proto@0.218.0': - resolution: {integrity: sha512-1/noQNsp9gXD75HPzgjBrcF1+XTtry7pFAUfxVEJgg7mPv2AawKQuYkhMmJ8qjxz4Ubc3Y8bwvfxevXsKTq4cg==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/exporter-metrics-otlp-grpc@0.218.0': - resolution: {integrity: sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-http@0.218.0': resolution: {integrity: sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -5043,36 +4776,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-prometheus@0.218.0': - resolution: {integrity: sha512-RT5oEyu1kddZJ1vt7/BUo5wV+P7hpNAESsR3dUd3+8deHuX7gWNoCOZn+SfDT+hJHlIJ5h/AxiCLXIrutswDJg==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/exporter-trace-otlp-grpc@0.218.0': - resolution: {integrity: sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-http@0.218.0': resolution: {integrity: sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-proto@0.218.0': - resolution: {integrity: sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/exporter-zipkin@2.7.1': - resolution: {integrity: sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.0.0 - '@opentelemetry/host-metrics@0.38.3': resolution: {integrity: sha512-8iSOA8VPGoB5p/RIC8n/dcSe4cluCEWoznWENZfXR8sWQOQvergFu7v798xp7S5WQlZo1zfn1nVXx8dbyQ9m6Q==} engines: {node: ^18.19.0 || >=20.6.0} @@ -5259,30 +4968,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-grpc-exporter-base@0.218.0': - resolution: {integrity: sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.218.0': resolution: {integrity: sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/propagator-b3@2.7.1': - resolution: {integrity: sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - - '@opentelemetry/propagator-jaeger@2.7.1': - resolution: {integrity: sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/redis-common@0.36.2': resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} engines: {node: '>=14'} @@ -5317,12 +5008,6 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-node@0.218.0': - resolution: {integrity: sha512-tPMjHrLV5gsfNdYqoRHjeGbCAZBXXD9c1Qo/2ut7VwnUABDNh76xNxrT0SEhkIIJuCN45bbN1vZnYL1gY0IkOg==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@1.30.1': resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} engines: {node: '>=14'} @@ -5361,193 +5046,426 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@oxfmt/binding-android-arm-eabi@0.54.0': - resolution: {integrity: sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==} + '@oxc-parser/binding-android-arm-eabi@0.137.0': + resolution: {integrity: sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.54.0': - resolution: {integrity: sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==} + '@oxc-parser/binding-android-arm64@0.137.0': + resolution: {integrity: sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.54.0': - resolution: {integrity: sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==} + '@oxc-parser/binding-darwin-arm64@0.137.0': + resolution: {integrity: sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.54.0': - resolution: {integrity: sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==} + '@oxc-parser/binding-darwin-x64@0.137.0': + resolution: {integrity: sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.54.0': - resolution: {integrity: sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==} + '@oxc-parser/binding-freebsd-x64@0.137.0': + resolution: {integrity: sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.54.0': - resolution: {integrity: sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==} + '@oxc-parser/binding-linux-arm-gnueabihf@0.137.0': + resolution: {integrity: sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.54.0': - resolution: {integrity: sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==} + '@oxc-parser/binding-linux-arm-musleabihf@0.137.0': + resolution: {integrity: sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.54.0': - resolution: {integrity: sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==} + '@oxc-parser/binding-linux-arm64-gnu@0.137.0': + resolution: {integrity: sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.54.0': - resolution: {integrity: sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==} + '@oxc-parser/binding-linux-arm64-musl@0.137.0': + resolution: {integrity: sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.54.0': - resolution: {integrity: sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==} + '@oxc-parser/binding-linux-ppc64-gnu@0.137.0': + resolution: {integrity: sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.54.0': - resolution: {integrity: sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==} + '@oxc-parser/binding-linux-riscv64-gnu@0.137.0': + resolution: {integrity: sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.54.0': - resolution: {integrity: sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==} + '@oxc-parser/binding-linux-riscv64-musl@0.137.0': + resolution: {integrity: sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.54.0': - resolution: {integrity: sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==} + '@oxc-parser/binding-linux-s390x-gnu@0.137.0': + resolution: {integrity: sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.54.0': - resolution: {integrity: sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==} + '@oxc-parser/binding-linux-x64-gnu@0.137.0': + resolution: {integrity: sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.54.0': - resolution: {integrity: sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==} + '@oxc-parser/binding-linux-x64-musl@0.137.0': + resolution: {integrity: sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.54.0': - resolution: {integrity: sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==} + '@oxc-parser/binding-openharmony-arm64@0.137.0': + resolution: {integrity: sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.54.0': - resolution: {integrity: sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==} + '@oxc-parser/binding-wasm32-wasi@0.137.0': + resolution: {integrity: sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.137.0': + resolution: {integrity: sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.54.0': - resolution: {integrity: sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==} + '@oxc-parser/binding-win32-ia32-msvc@0.137.0': + resolution: {integrity: sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.54.0': - resolution: {integrity: sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==} + '@oxc-parser/binding-win32-x64-msvc@0.137.0': + resolution: {integrity: sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.70.0': - resolution: {integrity: sha512-zFh0P4cswmRvw6nkyb89dr18rRanuaCPAsEXsFDoQY8WdaquI8Pt4NWFjaMJg6L23cy5NeN8J9cBnREbWzZhaw==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + + '@oxc-resolver/binding-android-arm-eabi@11.21.3': + resolution: {integrity: sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.70.0': - resolution: {integrity: sha512-qI8o4HZjeGiBrWv+pJv4lH0Yi2Gl/JSp/EumBUApezJprIKa5PS4nU0lQsQngtky8k+SplQIOjv6hwu0SSxeyg==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-android-arm64@11.21.3': + resolution: {integrity: sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.70.0': - resolution: {integrity: sha512-8KjgVVHI5F9nVwHCRwwA78Ty7zNKP4Wd9OeN5PSv3iu/F/u1RVXoOCgLhWqust6HmwQG6xc8c+RCyaWENy24+w==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-darwin-arm64@11.21.3': + resolution: {integrity: sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.70.0': - resolution: {integrity: sha512-WVydssv5PSUBXFJTdNBWlmGkbNmvPGaFt/2SUT/EZRB6bq6bEOHmMlbnupZD5jmlEvi9+mZJHi8TCw15lyfSfQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-darwin-x64@11.21.3': + resolution: {integrity: sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.70.0': - resolution: {integrity: sha512-hJucmUf8OlinHNb1R7fI4Fw6WsAstOz7i8nmkWQfiHoZXtbufNm+MxiDTIMk1ggh2Ro4vLzgQ+bKvRY54MZoRA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-freebsd-x64@11.21.3': + resolution: {integrity: sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.70.0': - resolution: {integrity: sha512-1BnS7wbCYDSXwWzJJ+mc3NURoha6m6m6RT5c6vgAY3oz7C3OVXP+S0awo2mRq97arrJkVvO3qRQfyAHL+76xtQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3': + resolution: {integrity: sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.70.0': - resolution: {integrity: sha512-yKy/UdbR55+M2yEcuiV5DCNC/gdQAjr/GioUy50QwBzSrKm8ueWADqyRLS9Xk+qjNeCYGg6A8FvUBds56ttfqg==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.3': + resolution: {integrity: sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.70.0': - resolution: {integrity: sha512-0A5XJ4alvmqFUFP/4oYSyaO+qLto/HrKEWTSaegiVl+HOufFngK2BjYw9x4RbwBt/du5QG6l5q1zeWiJYYG5yg==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-linux-arm64-gnu@11.21.3': + resolution: {integrity: sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.70.0': - resolution: {integrity: sha512-JiylyurlB0CLSedNtx1gzv3FvfWPF1h/2Y3BJszPLNt5XQFlBsH5ke0Jle3iJb3uqu5m2e7A/DwzpuCAHdiU+A==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-linux-arm64-musl@11.21.3': + resolution: {integrity: sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.70.0': - resolution: {integrity: sha512-J8VPG7I3/HmgaU4u8pNU2kFx2+0U+vPLS1dXFxXOaR/2TQ0f8AC7DRz0SRGRI1bfphnX2hVYTTtLuhL4nYKL+Q==} - engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.3': + resolution: {integrity: sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.70.0': - resolution: {integrity: sha512-N2+4lV2KLN+oXTIIIwmWDhwkrnvqf5oX7Hw0zPjk+RuIVgiBQSOlJWF7uQoFx2siEYX0ZQ5cfSbEAHm+J3t7Wg==} + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.3': + resolution: {integrity: sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.3': + resolution: {integrity: sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.3': + resolution: {integrity: sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.21.3': + resolution: {integrity: sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.21.3': + resolution: {integrity: sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.21.3': + resolution: {integrity: sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.21.3': + resolution: {integrity: sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': + resolution: {integrity: sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.21.3': + resolution: {integrity: sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==} + cpu: [x64] + os: [win32] + + '@oxfmt/binding-android-arm-eabi@0.54.0': + resolution: {integrity: sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.54.0': + resolution: {integrity: sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.54.0': + resolution: {integrity: sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.54.0': + resolution: {integrity: sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.54.0': + resolution: {integrity: sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.54.0': + resolution: {integrity: sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.54.0': + resolution: {integrity: sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.54.0': + resolution: {integrity: sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.54.0': + resolution: {integrity: sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.54.0': + resolution: {integrity: sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.54.0': + resolution: {integrity: sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.54.0': + resolution: {integrity: sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.54.0': + resolution: {integrity: sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.54.0': + resolution: {integrity: sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.54.0': + resolution: {integrity: sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.54.0': + resolution: {integrity: sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.54.0': + resolution: {integrity: sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.54.0': + resolution: {integrity: sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.54.0': + resolution: {integrity: sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.70.0': + resolution: {integrity: sha512-zFh0P4cswmRvw6nkyb89dr18rRanuaCPAsEXsFDoQY8WdaquI8Pt4NWFjaMJg6L23cy5NeN8J9cBnREbWzZhaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.70.0': + resolution: {integrity: sha512-qI8o4HZjeGiBrWv+pJv4lH0Yi2Gl/JSp/EumBUApezJprIKa5PS4nU0lQsQngtky8k+SplQIOjv6hwu0SSxeyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.70.0': + resolution: {integrity: sha512-8KjgVVHI5F9nVwHCRwwA78Ty7zNKP4Wd9OeN5PSv3iu/F/u1RVXoOCgLhWqust6HmwQG6xc8c+RCyaWENy24+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.70.0': + resolution: {integrity: sha512-WVydssv5PSUBXFJTdNBWlmGkbNmvPGaFt/2SUT/EZRB6bq6bEOHmMlbnupZD5jmlEvi9+mZJHi8TCw15lyfSfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.70.0': + resolution: {integrity: sha512-hJucmUf8OlinHNb1R7fI4Fw6WsAstOz7i8nmkWQfiHoZXtbufNm+MxiDTIMk1ggh2Ro4vLzgQ+bKvRY54MZoRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.70.0': + resolution: {integrity: sha512-1BnS7wbCYDSXwWzJJ+mc3NURoha6m6m6RT5c6vgAY3oz7C3OVXP+S0awo2mRq97arrJkVvO3qRQfyAHL+76xtQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.70.0': + resolution: {integrity: sha512-yKy/UdbR55+M2yEcuiV5DCNC/gdQAjr/GioUy50QwBzSrKm8ueWADqyRLS9Xk+qjNeCYGg6A8FvUBds56ttfqg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.70.0': + resolution: {integrity: sha512-0A5XJ4alvmqFUFP/4oYSyaO+qLto/HrKEWTSaegiVl+HOufFngK2BjYw9x4RbwBt/du5QG6l5q1zeWiJYYG5yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.70.0': + resolution: {integrity: sha512-JiylyurlB0CLSedNtx1gzv3FvfWPF1h/2Y3BJszPLNt5XQFlBsH5ke0Jle3iJb3uqu5m2e7A/DwzpuCAHdiU+A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.70.0': + resolution: {integrity: sha512-J8VPG7I3/HmgaU4u8pNU2kFx2+0U+vPLS1dXFxXOaR/2TQ0f8AC7DRz0SRGRI1bfphnX2hVYTTtLuhL4nYKL+Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.70.0': + resolution: {integrity: sha512-N2+4lV2KLN+oXTIIIwmWDhwkrnvqf5oX7Hw0zPjk+RuIVgiBQSOlJWF7uQoFx2siEYX0ZQ5cfSbEAHm+J3t7Wg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] @@ -5705,9 +5623,6 @@ packages: '@protobufjs/utf8@1.1.1': resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} - '@radix-ui/number@1.0.0': - resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==} - '@radix-ui/number@1.0.1': resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} @@ -5960,12 +5875,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-label@2.0.1': - resolution: {integrity: sha512-qcfbS3B8hTYmEO44RNcXB6pegkxRsJIbdxTMu0PEX0Luv5O2DvTIwwVYxQfUwLpM88EL84QRPLOLgwUSApMsLQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - '@radix-ui/react-popover@1.0.5': resolution: {integrity: sha512-GRHZ8yD12MrN2NLobHPE8Rb5uHTxd9x372DE9PPNnBjpczAQHcZ5ne0KXG4xpf+RDdXSzdLv9ym6mYJCDTaUZg==} peerDependencies: @@ -5997,19 +5906,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.9': - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: 18.3.1 - react-dom: 18.3.1 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-presence@1.0.0': resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} peerDependencies: @@ -6106,12 +6002,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-select@1.2.1': - resolution: {integrity: sha512-GULRMITaOHNj79BZvQs3iZO0+f2IgI8g5HDhMi7Bnc13t7IlG86NFtOCfTLme4PNZdEtU+no+oGgcl6IFiphpQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - '@radix-ui/react-slider@1.1.2': resolution: {integrity: sha512-NKs15MJylfzVsCagVSWKhGGLNR1W9qWs+HtgbmjjVUB3B9+lb3PYoXxVju3kOrpf0VKyVCtZp+iTwVoqpa1Chw==} peerDependencies: @@ -6256,11 +6146,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-previous@1.0.0': - resolution: {integrity: sha512-RG2K8z/K7InnOKpq6YLDmT49HGjNmrK+fr82UCVKT2sW0GYfVnYp4wZWBooT/EYfQ5faA9uIjvsuMMhH61rheg==} - peerDependencies: - react: 18.3.1 - '@radix-ui/react-use-previous@1.0.1': resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} peerDependencies: @@ -6298,80 +6183,29 @@ packages: '@radix-ui/rect@1.0.0': resolution: {integrity: sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==} - '@react-aria/breadcrumbs@3.5.9': - resolution: {integrity: sha512-asbXTL5NjeHl1+YIF0K70y8tNHk8Lb6VneYH8yOkpLO49ejyNDYBK0tp0jtI9IZAQiTa2qkhYq58c9LloTwebQ==} + '@react-aria/datepicker@3.9.1': + resolution: {integrity: sha512-bdlY2H/zwe3hQf64Lp1oGTf7Va8ennDyAv4Ffowb+BOoL8+FB9smtGyONKe87zXu7VJL2M5xYAi4n7c004PM+w==} peerDependencies: react: 18.3.1 + react-dom: 18.3.1 - '@react-aria/button@3.9.1': - resolution: {integrity: sha512-nAnLMUAnwIVcRkKzS1G2IU6LZSkIWPJGu9amz/g7Y02cGUwFp3lk5bEw2LdoaXiSDJNSX8g0SZFU8FROg57jfQ==} + '@react-aria/focus@3.16.0': + resolution: {integrity: sha512-GP6EYI07E8NKQQcXHjpIocEU0vh0oi0Vcsd+/71fKS0NnTR0TUOEeil0JuuQ9ymkmPDTu51Aaaa4FxVsuN/23A==} peerDependencies: react: 18.3.1 - '@react-aria/calendar@3.5.4': - resolution: {integrity: sha512-8k7khgea5kwfWriZJWCADNB0R2d7g5A6tTjUEktK4FFZcTb0RCubFejts4hRyzKlF9XHUro2dfh6sbZrzfMKDQ==} + '@react-aria/form@3.0.1': + resolution: {integrity: sha512-6586oODMDR4/ciGRwXjpvEAg7tWGSDrXE//waK0n5e5sMuzlPOo1DHc5SpPTvz0XdJsu6VDt2rHdVWVIC9LEyw==} peerDependencies: react: 18.3.1 - react-dom: 18.3.1 - '@react-aria/checkbox@3.13.0': - resolution: {integrity: sha512-eylJwtADIPKJ1Y5rITNJm/8JD8sXG2nhiZBIg1ko44Szxrpu+Le53NoGtg8nlrfh9vbUrXVvuFtf2jxbPXR5Jw==} + '@react-aria/i18n@3.10.0': + resolution: {integrity: sha512-sviD5Y1pLPG49HHRmVjR+5nONrp0HK219+nu9Y7cDfUhXu2EjyhMS9t/n9/VZ69hHChZ2PnHYLEE2visu9CuCg==} peerDependencies: react: 18.3.1 - '@react-aria/combobox@3.8.2': - resolution: {integrity: sha512-q8Kdw1mx6nSSydXqRagRuyKH1NPGvpSOFjUfgxdO8ZqaEEuZX3ObOoiO/DLtXDndViNc03dMbMpfuJoLYXfCtg==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/datepicker@3.9.1': - resolution: {integrity: sha512-bdlY2H/zwe3hQf64Lp1oGTf7Va8ennDyAv4Ffowb+BOoL8+FB9smtGyONKe87zXu7VJL2M5xYAi4n7c004PM+w==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/dialog@3.5.10': - resolution: {integrity: sha512-H2BNVLOfaum6/4irH5XUU/wIcXSs/ymxmTPGmucRG1hzaUh8H3tupdl/qCZ+SsW9oYDFlphY172uM1nsPjBMiQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/dnd@3.5.1': - resolution: {integrity: sha512-7OPGePdle+xNYHAIAUOvIETRMfnkRt7h/C0bCkxUR2GYefEbTzfraso4ppNH2JZ7fCRd0K/Qe+jvQklwusHAKA==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/focus@3.16.0': - resolution: {integrity: sha512-GP6EYI07E8NKQQcXHjpIocEU0vh0oi0Vcsd+/71fKS0NnTR0TUOEeil0JuuQ9ymkmPDTu51Aaaa4FxVsuN/23A==} - peerDependencies: - react: 18.3.1 - - '@react-aria/form@3.0.1': - resolution: {integrity: sha512-6586oODMDR4/ciGRwXjpvEAg7tWGSDrXE//waK0n5e5sMuzlPOo1DHc5SpPTvz0XdJsu6VDt2rHdVWVIC9LEyw==} - peerDependencies: - react: 18.3.1 - - '@react-aria/grid@3.8.6': - resolution: {integrity: sha512-JlQDkdm5heG1FfRyy5KnB8b6s/hRqSI6Xt2xN2AccLX5kcbfFr2/d5KVxyf6ahfa4Gfd46alN6477ju5eTWJew==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/gridlist@3.7.3': - resolution: {integrity: sha512-rkkepYM7xJiebR0g3uC4zzkdR7a8z0fLaM+sg9lSTbdElHMLAlrebS2ytEyZnhiu9nbOnw13GN1OC4/ZenzbHQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/i18n@3.10.0': - resolution: {integrity: sha512-sviD5Y1pLPG49HHRmVjR+5nONrp0HK219+nu9Y7cDfUhXu2EjyhMS9t/n9/VZ69hHChZ2PnHYLEE2visu9CuCg==} - peerDependencies: - react: 18.3.1 - - '@react-aria/interactions@3.20.1': - resolution: {integrity: sha512-PLNBr87+SzRhe9PvvF9qvzYeP4ofTwfKSorwmO+hjr3qoczrSXf4LRQlb27wB6hF10C7ZE/XVbUI1lj4QQrZ/g==} + '@react-aria/interactions@3.20.1': + resolution: {integrity: sha512-PLNBr87+SzRhe9PvvF9qvzYeP4ofTwfKSorwmO+hjr3qoczrSXf4LRQlb27wB6hF10C7ZE/XVbUI1lj4QQrZ/g==} peerDependencies: react: 18.3.1 @@ -6380,80 +6214,9 @@ packages: peerDependencies: react: 18.3.1 - '@react-aria/link@3.6.3': - resolution: {integrity: sha512-8kPWc4u/lDow3Ll0LDxeMgaxt9Y3sl8UldKLGli8tzRSltYFugNh/n+i9sCnmo4Qv9Tp9kYv+yxBK50Uk9sINw==} - peerDependencies: - react: 18.3.1 - - '@react-aria/listbox@3.11.3': - resolution: {integrity: sha512-PBrnldmyEYUUJvfDeljW8ITvZyBTfGpLNf0b5kfBPK3TDgRH4niEH2vYEcaZvSqb0FrpdvcunuTRXcOpfb+gCQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - '@react-aria/live-announcer@3.3.1': resolution: {integrity: sha512-hsc77U7S16trM86d+peqJCOCQ7/smO1cybgdpOuzXyiwcHQw8RQ4GrXrS37P4Ux/44E9nMZkOwATQRT2aK8+Ew==} - '@react-aria/menu@3.12.0': - resolution: {integrity: sha512-Nsujv3b61WR0gybDKnBjAeyxDVJOfPLMggRUf9SQDfPWnrPXEsAFxaPaVcAkzlfI4HiQs1IxNwsKFNpc3PPZTQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/meter@3.4.9': - resolution: {integrity: sha512-1/FHFmFmSyfQBJ2oH152lp4nps76v1UdhnFbIsmRIH+0g0IfMv1yDT2M9dIZ/b9DgVZSx527FmWOXm0eHGKD6w==} - peerDependencies: - react: 18.3.1 - - '@react-aria/numberfield@3.10.2': - resolution: {integrity: sha512-KjGTXq3lIhN4DEdEeHzfS/k9Qq0sDEpLgLr/hgSfGN4Q7Syu4Ck/n2HXmrDn//z08/wNvcukuP6Ioers138DcQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/overlays@3.20.0': - resolution: {integrity: sha512-2m7MpRJL5UucbEuu08lMHsiFJoDowkJV4JAIFBZYK1NzVH0vF/A+w9HRNM7jRwx2DUxE+iIsZnl8yKV/7KY8OQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/progress@3.4.9': - resolution: {integrity: sha512-CME1ZLsJHOmSgK8IAPOC/+vYO5Oc614mkEw5MluT/yclw5rMyjAkK1XsHLjEXy81uwPeiRyoQQIMPKG2/sMxFQ==} - peerDependencies: - react: 18.3.1 - - '@react-aria/radio@3.10.0': - resolution: {integrity: sha512-6NaKzdGymdcVWLYgHT0cHsVmNzPOp89o8r41w29OPBQWu8w2c9mxg4366OiIZn/uXIBS4abhQ4nL4toBRLgBrg==} - peerDependencies: - react: 18.3.1 - - '@react-aria/searchfield@3.7.1': - resolution: {integrity: sha512-ebhnV/reNByIZzpcQLHIo1RQ+BrYS8HdwX624i9R7dep1gxGHXYEaqL9aSY+RdngNerB4OeiWmB75em9beSpjQ==} - peerDependencies: - react: 18.3.1 - - '@react-aria/select@3.14.1': - resolution: {integrity: sha512-pAy/+Xbj11Lx6bi/O1hWH0NSIDRxFb6V7N0ry2L8x7MALljh516VbpnAc5RgvbjbuKq0cHUAcdINOzOzpYWm4A==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/selection@3.17.3': - resolution: {integrity: sha512-xl2sgeGH61ngQeE05WOWWPVpGRTPMjQEFmsAWEprArFi4Z7ihSZgpGX22l1w7uSmtXM/eN/v0W8hUYUju5iXlQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/separator@3.3.9': - resolution: {integrity: sha512-1wEXiaSJjq2+DR5TC0RKnUBsfZN+YXTzyI7XMzjQoc3YlclumX8wQtzPAOGOEjHB1JKUgo1Gw70FtupVXz58QQ==} - peerDependencies: - react: 18.3.1 - - '@react-aria/slider@3.7.4': - resolution: {integrity: sha512-OFJWeGSL2duVDFs/kcjlWsY6bqCVKZgM0aFn2QN4wmID+vfBvBnqGHAgWv3BCePTAPS3+GBjMN002TrftorjwQ==} - peerDependencies: - react: 18.3.1 - '@react-aria/spinbutton@3.6.1': resolution: {integrity: sha512-u5GuOP3k4Zis055iY0fZJNHU7dUNCoSfUq5LKwJ1iNaCqDcavdstAnAg+X1a7rhpp5zCnJmAMseo3Qmzi9P+Ew==} peerDependencies: @@ -6466,44 +6229,6 @@ packages: peerDependencies: react: 18.3.1 - '@react-aria/switch@3.6.0': - resolution: {integrity: sha512-YNWc5fGLNXE4XlmDAKyqAdllRiClGR7ki4KGFY7nL+xR5jxzjCGU3S3ToMK5Op3QSMGZLxY/aYmC4O+MvcoADQ==} - peerDependencies: - react: 18.3.1 - - '@react-aria/table@3.13.3': - resolution: {integrity: sha512-AzmETpyxwNqISTzwHJPs85x9gujG40IIsSOBUdp49oKhB85RbPLvMwhadp4wCVAoHw3erOC/TJxHtVc7o2K1LA==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/tabs@3.8.3': - resolution: {integrity: sha512-Plw0K/5Qv35vYq7pHZFfQB2BF5OClFx4Abzo9hLVx4oMy3qb7i5lxmLBVbt81yPX/MdjYeP4zO1EHGBl4zMRhA==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/tag@3.3.1': - resolution: {integrity: sha512-w7d8sVZqxTo8VFfeg2ixLp5kawtrcguGznVY4mt5aE6K8LMJOeNVDqNNfolfyia80VjOWjeX+RpVdVJRdrv/GQ==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - - '@react-aria/textfield@3.14.1': - resolution: {integrity: sha512-UMepuYtDdCgrUF4dMphNxrUm23xOmR54aZD1pbp9cJyfioVkJN35BTXZVkD0D07gHLn4RhxKIZxBortQQrLB9g==} - peerDependencies: - react: 18.3.1 - - '@react-aria/toggle@3.10.0': - resolution: {integrity: sha512-6cUf4V9TuG2J7AvXUdU/GspEPFCubUOID3mrselSe563RViy+mMZk0vUEOdyoNanDcEXl58W4dE3SGWxFn71vg==} - peerDependencies: - react: 18.3.1 - - '@react-aria/tooltip@3.7.0': - resolution: {integrity: sha512-+u9Sftkfe09IDyPEnbbreFKS50vh9X/WTa7n1u2y3PenI9VreLpUR6czyzda4BlvQ95e9jQz1cVxUjxTNaZmBw==} - peerDependencies: - react: 18.3.1 - '@react-aria/utils@3.23.0': resolution: {integrity: sha512-fJA63/VU4iQNT8WUvrmll3kvToqMurD69CcgVmbQ56V7ZbvlzFi44E7BpnoaofScYLLtFWRjVdaHsohT6O/big==} peerDependencies: @@ -6515,11 +6240,6 @@ packages: react: 18.3.1 react-dom: 18.3.1 - '@react-aria/visually-hidden@3.8.8': - resolution: {integrity: sha512-Cn2PYKD4ijGDtF0+dvsh8qa4y7KTNAlkTG6h20r8Q+6UTyRNmtE2/26QEaApRF8CBiNy9/BZC/ZC4FK2OjvCoA==} - peerDependencies: - react: 18.3.1 - '@react-email/body@0.3.0': resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==} engines: {node: '>=20.0.0'} @@ -6690,139 +6410,26 @@ packages: peerDependencies: react: 18.3.1 - '@react-stately/calendar@3.4.3': - resolution: {integrity: sha512-OrEcdskszDjnjVnFuSiDC2PVBJ6lWMCJROD5s6W1LUehUtBp8LX9wPavAGHV43LbhN9ldj560sxaQ4WCddrRCA==} - peerDependencies: - react: 18.3.1 - - '@react-stately/checkbox@3.6.1': - resolution: {integrity: sha512-rOjFeVBy32edYwhKiHj3ZLdLeO+xZ2fnBwxnOBjcygnw4Neygm8FJH/dB1J0hdYYR349yby86ED2x0wRc84zPw==} - peerDependencies: - react: 18.3.1 - - '@react-stately/collections@3.10.4': - resolution: {integrity: sha512-OHhCrItGt4zB2bSrgObRo0H2SC7QlkH8ReGxo+NVIWchXRLRoiWBP7S+IwleewEo5gOqDVPY3hqA9n4iiI8twg==} - peerDependencies: - react: 18.3.1 - - '@react-stately/combobox@3.8.1': - resolution: {integrity: sha512-FaWkqTXQdWg7ptaeU4iPcqF/kxbRg2ZNUcvW/hiL/enciV5tRCsddvfNqvDvy1L30z9AUwlp9MWqzm/DhBITCw==} - peerDependencies: - react: 18.3.1 - - '@react-stately/data@3.11.0': - resolution: {integrity: sha512-0BlPT58WrAtUvpiEfUuyvIsGFTzp/9vA5y+pk53kGJhOdc5tqBGHi9cg40pYE/i1vdHJGMpyHGRD9nkQb8wN3Q==} - peerDependencies: - react: 18.3.1 - '@react-stately/datepicker@3.9.1': resolution: {integrity: sha512-o5xLvlZGJyAbTev2yruGlV2fzQyIDuYTgL19TTt0W0WCfjGGr/AAA9GjGXXmyoRA7sZMxqIPnnv7lNrdA38ofA==} peerDependencies: react: 18.3.1 - '@react-stately/dnd@3.2.7': - resolution: {integrity: sha512-QqSCvE9Rhp+Mr8Mt/SrByze24BFX1cy7gmXbwoqAYgHNIx3gWCVdBLqxfpfgYIhZdF9H72EWS8lQkfkZla06Ng==} - peerDependencies: - react: 18.3.1 - - '@react-stately/flags@3.0.0': - resolution: {integrity: sha512-e3i2ItHbIa0eEwmSXAnPdD7K8syW76JjGe8ENxwFJPW/H1Pu9RJfjkCb/Mq0WSPN/TpxBb54+I9TgrGhbCoZ9w==} - '@react-stately/form@3.0.0': resolution: {integrity: sha512-C8wkfFmtx1escizibhdka5JvTy9/Vp173CS9cakjvWTmnjYYC1nOlzwp7BsYWTgerCFbRY/BU/Cf/bJDxPiUKQ==} peerDependencies: react: 18.3.1 - '@react-stately/grid@3.8.4': - resolution: {integrity: sha512-rwqV1K4lVhaiaqJkt4TfYqdJoVIyqvSm98rKAYfCNzrKcivVpoiCMJ2EMt6WlYCjDVBdEOQ7fMV1I60IV0pntA==} - peerDependencies: - react: 18.3.1 - - '@react-stately/list@3.10.2': - resolution: {integrity: sha512-INt+zofkIg2KN8B95xPi9pJG7ZFWAm30oIm/lCPBqM3K1Nm03/QaAbiQj2QeJcOsG3lb7oqI6D6iwTolwJkjIQ==} - peerDependencies: - react: 18.3.1 - - '@react-stately/menu@3.6.0': - resolution: {integrity: sha512-OB6CjNyfOkAuirqx1oTL8z8epS9WDzLyrXjmRnxdiCU9EgRXLGAQNECuO7VIpl58oDry8tgRJiJ8fn8FivWSQA==} - peerDependencies: - react: 18.3.1 - - '@react-stately/numberfield@3.8.0': - resolution: {integrity: sha512-1XvB8tDOvZKcFnMM6qNLEaTVJcIc0jRFS/9jtS8MzalZvh8DbKi0Ucm1bGU7S5rkCx2QWqZ0rGOIm2h/RlcpkA==} - peerDependencies: - react: 18.3.1 - '@react-stately/overlays@3.6.4': resolution: {integrity: sha512-tHEaoAGpE9dSnsskqLPVKum59yGteoSqsniTopodM+miQozbpPlSjdiQnzGLroy5Afx5OZYClE616muNHUILXA==} peerDependencies: react: 18.3.1 - '@react-stately/radio@3.10.1': - resolution: {integrity: sha512-MsBYbcLCvjKsqTAKe43T681F2XwKMsS7PLG0eplZgWP9210AMY78GeY1XPYZKHPAau8XkbYiuJqbqTerIJ3DBw==} - peerDependencies: - react: 18.3.1 - - '@react-stately/searchfield@3.5.0': - resolution: {integrity: sha512-SStjChkn/33pEn40slKQPnBnmQYyxVazVwPjiBkdeVejC42lUVairUTrGJgF0PNoZTbxn0so2/XzjqTC9T8iCw==} - peerDependencies: - react: 18.3.1 - - '@react-stately/select@3.6.1': - resolution: {integrity: sha512-e5ixtLiYLlFWM8z1msDqXWhflF9esIRfroptZsltMn1lt2iImUlDRlOTZlMtPQzUrDWoiHXRX88sSKUM/jXjQQ==} - peerDependencies: - react: 18.3.1 - - '@react-stately/selection@3.14.2': - resolution: {integrity: sha512-mL7OoiUgVWaaF7ks5XSxgbXeShijYmD4G3bkBHhqkpugU600QH6BM2hloCq8KOUupk1y8oTljPtF9EmCv375DA==} - peerDependencies: - react: 18.3.1 - - '@react-stately/slider@3.5.0': - resolution: {integrity: sha512-dOVpIxb7XKuiRxgpHt1bUSlsklciFki100tKIyBPR+Okar9iC/CwLYROYgVfLkGe77jEBNkor9tDLjDGEWcc1w==} - peerDependencies: - react: 18.3.1 - - '@react-stately/table@3.11.4': - resolution: {integrity: sha512-dWINJIEOKQl4qq3moq+S8xCD3m+yJqBj0dahr+rOkS+t2uqORwzsusTM35D2T/ZHZi49S2GpE7QuDa+edCynPw==} - peerDependencies: - react: 18.3.1 - - '@react-stately/tabs@3.6.3': - resolution: {integrity: sha512-Nj+Gacwa2SIzYIvHW40GsyX4Q6c8kF7GOuXESeQswbCjnwqhrSbDBp+ngPcUPUJxqFh6JhDCVwAS3wMhUoyUwA==} - peerDependencies: - react: 18.3.1 - - '@react-stately/toggle@3.7.0': - resolution: {integrity: sha512-TRksHkCJk/Xogq4181g3CYgJf+EfsJCqX5UZDSw1Z1Kgpvonjmdf6FAfQfCh9QR2OuXUL6hOLUDVLte5OPI+5g==} - peerDependencies: - react: 18.3.1 - - '@react-stately/tooltip@3.4.6': - resolution: {integrity: sha512-uL93bmsXf+OOgpKLPEKfpDH4z+MK2CuqlqVxx7rshN0vjWOSoezE5nzwgee90+RpDrLNNNWTNa7n+NkDRpI1jA==} - peerDependencies: - react: 18.3.1 - - '@react-stately/tree@3.7.5': - resolution: {integrity: sha512-xTJVwvhAeY0N5rui4N/TxN7f8hjXdqApDuGDxMZeFAWoQz8Abf7LFKBVQ3OkT6qVr7P+23dgoisUDBhD5a45Hg==} - peerDependencies: - react: 18.3.1 - '@react-stately/utils@3.9.0': resolution: {integrity: sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==} peerDependencies: react: 18.3.1 - '@react-stately/virtualizer@3.6.6': - resolution: {integrity: sha512-9hWvfITdE/028q4YFve6FxlmA3PdSMkUwpYA+vfaGCXI/4DFZIssBMspUeu4PTRJoV+k+m0z1wYHPmufrq6a3g==} - peerDependencies: - react: 18.3.1 - - '@react-types/breadcrumbs@3.7.2': - resolution: {integrity: sha512-esl6RucDW2CNMsApJxNYfMtDaUcfLlwKMPH/loYsOBbKxGl2HsgVLMcdpjEkTRs2HCTNCbBXWpeU8AY77t+bsw==} - peerDependencies: - react: 18.3.1 - '@react-types/button@3.9.1': resolution: {integrity: sha512-bf9iTar3PtqnyV9rA+wyFyrskZKhwmOuOd/ifYIjPs56YNVXWH5Wfqj6Dx3xdFBgtKx8mEVQxVhoX+WkHX+rtw==} peerDependencies: @@ -6833,16 +6440,6 @@ packages: peerDependencies: react: 18.3.1 - '@react-types/checkbox@3.6.0': - resolution: {integrity: sha512-vgbuJzQpVCNT5AZWV0OozXCnihqrXxoZKfJFIw0xro47pT2sn3t5UC4RA9wfjDGMoK4frw1K/4HQLsQIOsPBkw==} - peerDependencies: - react: 18.3.1 - - '@react-types/combobox@3.10.0': - resolution: {integrity: sha512-1IXSNS02TPbguyYopaW2snU6sZusbClHrEyVr4zPeexTV4kpUUBNXOzFQ+eSQRR0r2XW57Z0yRW4GJ6FGU0yCA==} - peerDependencies: - react: 18.3.1 - '@react-types/datepicker@3.7.1': resolution: {integrity: sha512-5juVDULOytNzkotqX8j5mYKJckeIpkgbHqVSGkPgLw0++FceIaSZ6RH56cqLup0pO45paqIt9zHh+QXBYX+syg==} peerDependencies: @@ -6853,61 +6450,11 @@ packages: peerDependencies: react: 18.3.1 - '@react-types/grid@3.2.3': - resolution: {integrity: sha512-GQM4RDmYhstcYZ0Odjq+xUwh1fhLmRebG6qMM8OXHTPQ77nhl3wc1UTGRhZm6mzEionplSRx4GCpEMEHMJIU0w==} - peerDependencies: - react: 18.3.1 - - '@react-types/link@3.5.2': - resolution: {integrity: sha512-/s51/WejmpLiyxOgP89s4txgxYoGaPe8pVDItVo1h4+BhU1Puyvgv/Jx8t9dPvo6LUXbraaN+SgKk/QDxaiirw==} - peerDependencies: - react: 18.3.1 - - '@react-types/listbox@3.4.6': - resolution: {integrity: sha512-XOQvrTqNh5WIPDvKiWiep8T07RAsMfjAXTjDbnjxVlKACUXkcwpts9kFaLnJ9LJRFt6DwItfP+WMkzvmx63/NQ==} - peerDependencies: - react: 18.3.1 - - '@react-types/menu@3.9.6': - resolution: {integrity: sha512-w/RbFInOf4nNayQDv5c2L8IMJbcFOkBhsT3xvvpTy+CHvJcQdjggwaV1sRiw7eF/PwB81k2CwigmidUzHJhKDg==} - peerDependencies: - react: 18.3.1 - - '@react-types/meter@3.3.6': - resolution: {integrity: sha512-1XYp1fA9UU0lO6kjf3TwVE8mppOJa64mBKAcLWtTyq1e/cYIAbx5o6CsuUx0YDpXKF6gdtvIWvfmxeWsmqJ1jQ==} - peerDependencies: - react: 18.3.1 - - '@react-types/numberfield@3.7.0': - resolution: {integrity: sha512-gaGi+vqm1Y8LCWRsWYUjcGftPIzl+8W2VOfkgKMLM8y76nnwTPtmAqs+Ap1cg7sEJSfsiKMq93e9yvP3udrC2w==} - peerDependencies: - react: 18.3.1 - '@react-types/overlays@3.8.4': resolution: {integrity: sha512-pfgNlQnbF6RB/R2oSxyqAP3Uzz0xE/k5q4n5gUeCDNLjY5qxFHGE8xniZZ503nZYw6VBa9XMN1efDOKQyeiO0w==} peerDependencies: react: 18.3.1 - '@react-types/progress@3.5.1': - resolution: {integrity: sha512-CqsUjczUK/SfuFzDcajBBaXRTW0D3G9S/yqLDj9e8E0ii+lGDLt1PHj24t1J7E88U2rVYqmM9VL4NHTt8o3IYA==} - peerDependencies: - react: 18.3.1 - - '@react-types/radio@3.7.0': - resolution: {integrity: sha512-EcwGAXzSHjSqpFZha7xn3IUrhPiJLj+0yb1Ip0qPmhWz0VVw2DwrkY7q/jfaKroVvQhTo2TbfGhcsAQrt0fRqg==} - peerDependencies: - react: 18.3.1 - - '@react-types/searchfield@3.5.2': - resolution: {integrity: sha512-JAK2/Kg4Dr393FYfbRw0TlXKnJPX77sq1x/ZBxtO6p64+MuuIYKqw0i9PwDlo1PViw2QI5u8GFhKA2TgemY9uA==} - peerDependencies: - react: 18.3.1 - - '@react-types/select@3.9.1': - resolution: {integrity: sha512-EpKSxrnh8HdZvOF9dHQkjivAcdIp1K81FaxmvosH8Lygqh0iYXxAdZGtKLMyBoPI8YFhA+rotIzTcOqgCCnqWA==} - peerDependencies: - react: 18.3.1 - '@react-types/shared@3.22.0': resolution: {integrity: sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==} peerDependencies: @@ -6918,36 +6465,6 @@ packages: peerDependencies: react: 18.3.1 - '@react-types/slider@3.7.0': - resolution: {integrity: sha512-uyQXUVFfqc9SPUW0LZLMan2n232F/OflRafiHXz9viLFa9tVOupVa7GhASRAoHojwkjoJ1LjFlPih7g5dOZ0/Q==} - peerDependencies: - react: 18.3.1 - - '@react-types/switch@3.5.0': - resolution: {integrity: sha512-/wNmUGjk69bP6t5k2QkAdrNN5Eb9Rz4dOyp0pCPmoeE+5haW6sV5NmtkvWX1NSc4DQz1xL/a5b+A0vxPCP22Jw==} - peerDependencies: - react: 18.3.1 - - '@react-types/table@3.9.2': - resolution: {integrity: sha512-brw5JUANOzBa2rYNpN8AIl9nDZ9RwRZC6G/wTM/JhtirjC1S42oCtf8Ap5rWJBdmMG/5KOfcGNcAl/huyqb3gg==} - peerDependencies: - react: 18.3.1 - - '@react-types/tabs@3.3.4': - resolution: {integrity: sha512-4mCTtFrwMRypyGTZCvNYVT9CkknexO/UYvqwDm2jMYb8JgjRvxnomu776Yh7uyiYKWyql2upm20jqasEOm620w==} - peerDependencies: - react: 18.3.1 - - '@react-types/textfield@3.9.0': - resolution: {integrity: sha512-D/DiwzsfkwlAg3uv8hoIfwju+zhB/hWDEdTvxQbPkntDr0kmN/QfI17NMSzbOBCInC4ABX87ViXLGxr940ykGA==} - peerDependencies: - react: 18.3.1 - - '@react-types/tooltip@3.4.6': - resolution: {integrity: sha512-RaZewdER7ZcsNL99RhVHs8kSLyzIBkwc0W6eFZrxST2MD9J5GzkVWRhIiqtFOd5U1aYnxdJ6woq72Ef+le6Vfw==} - peerDependencies: - react: 18.3.1 - '@remix-run/changelog-github@0.0.5': resolution: {integrity: sha512-43tqwUqWqirbv6D9uzo55ASPsCJ61Ein1k/M8qn+Qpros0MmbmuzjLVPmtaxfxfe2ANX0LefLvCD0pAgr1tp4g==} @@ -7029,12 +6546,6 @@ packages: typescript: optional: true - '@remix-run/v1-meta@0.1.3': - resolution: {integrity: sha512-pYTJSrT8ouipv9gL7sqk4hjgVrfj6Jcp9KDnjZKPFUF+91S1J5PNjipAHE1aXh0LyHo2rDbTW/OjtFeJ6BKhYg==} - peerDependencies: - '@remix-run/react': ^1.15.0 || ^2.0.0 - '@remix-run/server-runtime': ^1.15.0 || ^2.0.0 - '@remix-run/web-blob@3.1.0': resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==} @@ -7342,15 +6853,6 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sideway/address@4.1.4': - resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} - - '@sideway/formula@3.0.1': - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} - - '@sideway/pinpoint@2.0.0': - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - '@sinclair/typebox@0.34.38': resolution: {integrity: sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==} @@ -7378,10 +6880,6 @@ packages: resolution: {integrity: sha512-68SAV77uuGKuhyyaRytX8UijVnqSLsTSKslGXw17cjQYXn+jtNl7gbaEjHgC5x2rhCuFdahBrEC2VCLppbzReg==} engines: {node: '>= 18', npm: '>= 8.6.0'} - '@smithy/abort-controller@2.0.14': - resolution: {integrity: sha512-zXtteuYLWbSXnzI3O6xq3FYvigYZFW8mdytGibfarLL2lxHto9L3ILtGVnVGmFZa7SDh62l39EnU5hesLN87Fw==} - engines: {node: '>=14.0.0'} - '@smithy/abort-controller@4.0.4': resolution: {integrity: sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==} engines: {node: '>=18.0.0'} @@ -7398,10 +6896,6 @@ packages: resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} engines: {node: '>=18.0.0'} - '@smithy/config-resolver@2.0.19': - resolution: {integrity: sha512-JsghnQ5zjWmjEVY8TFOulLdEOCj09SjRLugrHlkPZTIBBm7PQitCFVLThbsKPZQOP7N3ME1DU1nKUc1UaVnBog==} - engines: {node: '>=14.0.0'} - '@smithy/config-resolver@4.1.4': resolution: {integrity: sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==} engines: {node: '>=18.0.0'} @@ -7422,10 +6916,6 @@ packages: resolution: {integrity: sha512-Pgvfb+TQ4wUNLyHzvgCP4aYZMh16y7GcfF59oirRHcgGgkH1e/s9C0nv/v3WP+Quymyr5je71HeFQCwh+44XLg==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@2.1.2': - resolution: {integrity: sha512-Y62jBWdoLPSYjr9fFvJf+KwTa1EunjVr6NryTEWCnwIY93OJxwV4t0qxjwdPl/XMsUkq79ppNJSEQN6Ohnhxjw==} - engines: {node: '>=14.0.0'} - '@smithy/credential-provider-imds@4.0.6': resolution: {integrity: sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==} engines: {node: '>=18.0.0'} @@ -7434,9 +6924,6 @@ packages: resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-codec@2.0.14': - resolution: {integrity: sha512-g/OU/MeWGfHDygoXgMWfG/Xb0QqDnAGcM9t2FRrVAhleXYRddGOEnfanR5cmHgB9ue52MJsyorqFjckzXsylaA==} - '@smithy/eventstream-codec@4.2.5': resolution: {integrity: sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==} engines: {node: '>=18.0.0'} @@ -7457,9 +6944,6 @@ packages: resolution: {integrity: sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@2.2.7': - resolution: {integrity: sha512-iSDBjxuH9TgrtMYAr7j5evjvkvgwLY3y+9D547uep+JNkZ1ZT+BaeU20j6I/bO/i26ilCWFImrlXTPsfQtZdIQ==} - '@smithy/fetch-http-handler@5.0.4': resolution: {integrity: sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==} engines: {node: '>=18.0.0'} @@ -7472,10 +6956,6 @@ packages: resolution: {integrity: sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw==} engines: {node: '>=18.0.0'} - '@smithy/hash-node@2.0.16': - resolution: {integrity: sha512-Wbi9A0PacMYUOwjAulQP90Wl3mQ6NDwnyrZQzFjDz+UzjXOSyQMgBrTkUBz+pVoYVlX3DUu24gWMZBcit+wOGg==} - engines: {node: '>=14.0.0'} - '@smithy/hash-node@4.0.4': resolution: {integrity: sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==} engines: {node: '>=18.0.0'} @@ -7488,9 +6968,6 @@ packages: resolution: {integrity: sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q==} engines: {node: '>=18.0.0'} - '@smithy/invalid-dependency@2.0.14': - resolution: {integrity: sha512-d8ohpwZo9RzTpGlAfsWtfm1SHBSU7+N4iuZ6MzR10xDTujJJWtmXYHK1uzcr7rggbpUTaWyHpPFgnf91q0EFqQ==} - '@smithy/invalid-dependency@4.0.4': resolution: {integrity: sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==} engines: {node: '>=18.0.0'} @@ -7507,17 +6984,10 @@ packages: resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} engines: {node: '>=18.0.0'} - '@smithy/md5-js@2.0.16': - resolution: {integrity: sha512-YhWt9aKl+EMSNXyUTUo7I01WHf3HcCkPu/Hl2QmTNwrHT49eWaY7hptAMaERZuHFH0V5xHgPKgKZo2I93DFtgQ==} - '@smithy/md5-js@4.2.5': resolution: {integrity: sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==} engines: {node: '>=18.0.0'} - '@smithy/middleware-content-length@2.0.16': - resolution: {integrity: sha512-9ddDia3pp1d3XzLXKcm7QebGxLq9iwKf+J1LapvlSOhpF8EM9SjMeSrMOOFgG+2TfW5K3+qz4IAJYYm7INYCng==} - engines: {node: '>=14.0.0'} - '@smithy/middleware-content-length@4.0.4': resolution: {integrity: sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==} engines: {node: '>=18.0.0'} @@ -7526,10 +6996,6 @@ packages: resolution: {integrity: sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@2.2.1': - resolution: {integrity: sha512-dVDS7HNJl/wb0lpByXor6whqDbb1YlLoaoWYoelyYzLHioXOE7y/0iDwJWtDcN36/tVCw9EPBFZ3aans84jLpg==} - engines: {node: '>=14.0.0'} - '@smithy/middleware-endpoint@4.1.13': resolution: {integrity: sha512-xg3EHV/Q5ZdAO5b0UiIMj3RIOCobuS40pBBODguUDVdko6YK6QIzCVRrHTogVuEKglBWqWenRnZ71iZnLL3ZAQ==} engines: {node: '>=18.0.0'} @@ -7542,10 +7008,6 @@ packages: resolution: {integrity: sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@2.0.21': - resolution: {integrity: sha512-EZS1EXv1k6IJX6hyu/0yNQuPcPaXwG8SWljQHYueyRbOxmqYgoWMWPtfZj0xRRQ4YtLawQSpBgAeiJltq8/MPw==} - engines: {node: '>=14.0.0'} - '@smithy/middleware-retry@4.1.14': resolution: {integrity: sha512-eoXaLlDGpKvdmvt+YBfRXE7HmIEtFF+DJCbTPwuLunP0YUnrydl+C4tS+vEM0+nyxXrX3PSUFqC+lP1+EHB1Tw==} engines: {node: '>=18.0.0'} @@ -7558,10 +7020,6 @@ packages: resolution: {integrity: sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@2.0.14': - resolution: {integrity: sha512-hFi3FqoYWDntCYA2IGY6gJ6FKjq2gye+1tfxF2HnIJB5uW8y2DhpRNBSUMoqP+qvYzRqZ6ntv4kgbG+o3pX57g==} - engines: {node: '>=14.0.0'} - '@smithy/middleware-serde@4.0.8': resolution: {integrity: sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==} engines: {node: '>=18.0.0'} @@ -7574,10 +7032,6 @@ packages: resolution: {integrity: sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-stack@2.0.8': - resolution: {integrity: sha512-7/N59j0zWqVEKExJcA14MrLDZ/IeN+d6nbkN8ucs+eURyaDUXWYlZrQmMOd/TyptcQv0+RDlgag/zSTTV62y/Q==} - engines: {node: '>=14.0.0'} - '@smithy/middleware-stack@4.0.4': resolution: {integrity: sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==} engines: {node: '>=18.0.0'} @@ -7586,10 +7040,6 @@ packages: resolution: {integrity: sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==} engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@2.1.6': - resolution: {integrity: sha512-HLqTs6O78m3M3z1cPLFxddxhEPv5MkVatfPuxoVO3A+cHZanNd/H5I6btcdHy6N2CB1MJ/lihJC92h30SESsBA==} - engines: {node: '>=14.0.0'} - '@smithy/node-config-provider@4.1.3': resolution: {integrity: sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==} engines: {node: '>=18.0.0'} @@ -7598,10 +7048,6 @@ packages: resolution: {integrity: sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@2.1.10': - resolution: {integrity: sha512-lkALAwtN6odygIM4nB8aHDahINM6WXXjNrZmWQAh0RSossySRT2qa31cFv0ZBuAYVWeprskRk13AFvvLmf1WLw==} - engines: {node: '>=14.0.0'} - '@smithy/node-http-handler@4.0.6': resolution: {integrity: sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==} engines: {node: '>=18.0.0'} @@ -7610,10 +7056,6 @@ packages: resolution: {integrity: sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==} engines: {node: '>=18.0.0'} - '@smithy/property-provider@2.0.15': - resolution: {integrity: sha512-YbRFBn8oiiC3o1Kn3a4KjGa6k47rCM9++5W9cWqYn9WnkyH+hBWgfJAckuxpyA2Hq6Ys4eFrWzXq6fqHEw7iew==} - engines: {node: '>=14.0.0'} - '@smithy/property-provider@4.0.4': resolution: {integrity: sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==} engines: {node: '>=18.0.0'} @@ -7622,10 +7064,6 @@ packages: resolution: {integrity: sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==} engines: {node: '>=18.0.0'} - '@smithy/protocol-http@3.0.10': - resolution: {integrity: sha512-6+tjNk7rXW7YTeGo9qwxXj/2BFpJTe37kTj3EnZCoX/nH+NP/WLA7O83fz8XhkGqsaAhLUPo/bB12vvd47nsmg==} - engines: {node: '>=14.0.0'} - '@smithy/protocol-http@5.1.2': resolution: {integrity: sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==} engines: {node: '>=18.0.0'} @@ -7634,10 +7072,6 @@ packages: resolution: {integrity: sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==} engines: {node: '>=18.0.0'} - '@smithy/querystring-builder@2.0.14': - resolution: {integrity: sha512-lQ4pm9vTv9nIhl5jt6uVMPludr6syE2FyJmHsIJJuOD7QPIJnrf9HhUGf1iHh9KJ4CUv21tpOU3X6s0rB6uJ0g==} - engines: {node: '>=14.0.0'} - '@smithy/querystring-builder@4.0.4': resolution: {integrity: sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==} engines: {node: '>=18.0.0'} @@ -7646,10 +7080,6 @@ packages: resolution: {integrity: sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==} engines: {node: '>=18.0.0'} - '@smithy/querystring-parser@2.0.14': - resolution: {integrity: sha512-+cbtXWI9tNtQjlgQg3CA+pvL3zKTAxPnG3Pj6MP89CR3vi3QMmD0SOWoq84tqZDnJCxlsusbgIXk1ngMReXo+A==} - engines: {node: '>=14.0.0'} - '@smithy/querystring-parser@4.0.4': resolution: {integrity: sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==} engines: {node: '>=18.0.0'} @@ -7658,10 +7088,6 @@ packages: resolution: {integrity: sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==} engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@2.0.7': - resolution: {integrity: sha512-LLxgW12qGz8doYto15kZ4x1rHjtXl0BnCG6T6Wb8z2DI4PT9cJfOSvzbuLzy7+5I24PAepKgFeWHRd9GYy3Z9w==} - engines: {node: '>=14.0.0'} - '@smithy/service-error-classification@4.0.6': resolution: {integrity: sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==} engines: {node: '>=18.0.0'} @@ -7670,10 +7096,6 @@ packages: resolution: {integrity: sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==} engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@2.2.5': - resolution: {integrity: sha512-LHA68Iu7SmNwfAVe8egmjDCy648/7iJR/fK1UnVw+iAOUJoEYhX2DLgVd5pWllqdDiRbQQzgaHLcRokM+UFR1w==} - engines: {node: '>=14.0.0'} - '@smithy/shared-ini-file-loader@4.0.4': resolution: {integrity: sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==} engines: {node: '>=18.0.0'} @@ -7682,10 +7104,6 @@ packages: resolution: {integrity: sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@2.0.16': - resolution: {integrity: sha512-ilLY85xS2kZZzTb83diQKYLIYALvart0KnBaKnIRnMBHAGEio5aHSlANQoxVn0VsonwmQ3CnWhnCT0sERD8uTg==} - engines: {node: '>=14.0.0'} - '@smithy/signature-v4@5.1.2': resolution: {integrity: sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==} engines: {node: '>=18.0.0'} @@ -7694,10 +7112,6 @@ packages: resolution: {integrity: sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@2.1.16': - resolution: {integrity: sha512-Lw67+yQSpLl4YkDLUzI2KgS8TXclXmbzSeOJUmRFS4ueT56B4pw3RZRF/SRzvgyxM/HxgkUan8oSHXCujPDafQ==} - engines: {node: '>=14.0.0'} - '@smithy/smithy-client@4.4.5': resolution: {integrity: sha512-+lynZjGuUFJaMdDYSTMnP/uPBBXXukVfrJlP+1U/Dp5SFTEI++w6NMga8DjOENxecOF71V9Z2DllaVDYRnGlkg==} engines: {node: '>=18.0.0'} @@ -7710,10 +7124,6 @@ packages: resolution: {integrity: sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==} engines: {node: '>=18.0.0'} - '@smithy/types@2.6.0': - resolution: {integrity: sha512-PgqxJq2IcdMF9iAasxcqZqqoOXBHufEfmbEUdN1pmJrJltT42b0Sc8UiYSWWzKkciIp9/mZDpzYi4qYG1qqg6g==} - engines: {node: '>=14.0.0'} - '@smithy/types@4.3.1': resolution: {integrity: sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==} engines: {node: '>=18.0.0'} @@ -7722,9 +7132,6 @@ packages: resolution: {integrity: sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==} engines: {node: '>=18.0.0'} - '@smithy/url-parser@2.0.14': - resolution: {integrity: sha512-kbu17Y1AFXi5lNlySdDj7ZzmvupyWKCX/0jNZ8ffquRyGdbDZb+eBh0QnWqsSmnZa/ctyWaTf7n4l/pXLExrnw==} - '@smithy/url-parser@4.0.4': resolution: {integrity: sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==} engines: {node: '>=18.0.0'} @@ -7733,10 +7140,6 @@ packages: resolution: {integrity: sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==} engines: {node: '>=18.0.0'} - '@smithy/util-base64@2.0.1': - resolution: {integrity: sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==} - engines: {node: '>=14.0.0'} - '@smithy/util-base64@4.0.0': resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==} engines: {node: '>=18.0.0'} @@ -7745,9 +7148,6 @@ packages: resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} engines: {node: '>=18.0.0'} - '@smithy/util-body-length-browser@2.0.0': - resolution: {integrity: sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==} - '@smithy/util-body-length-browser@4.0.0': resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==} engines: {node: '>=18.0.0'} @@ -7756,10 +7156,6 @@ packages: resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} engines: {node: '>=18.0.0'} - '@smithy/util-body-length-node@2.1.0': - resolution: {integrity: sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==} - engines: {node: '>=14.0.0'} - '@smithy/util-body-length-node@4.0.0': resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==} engines: {node: '>=18.0.0'} @@ -7768,10 +7164,6 @@ packages: resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} engines: {node: '>=18.0.0'} - '@smithy/util-buffer-from@2.0.0': - resolution: {integrity: sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==} - engines: {node: '>=14.0.0'} - '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} @@ -7784,10 +7176,6 @@ packages: resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} engines: {node: '>=18.0.0'} - '@smithy/util-config-provider@2.0.0': - resolution: {integrity: sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==} - engines: {node: '>=14.0.0'} - '@smithy/util-config-provider@4.0.0': resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==} engines: {node: '>=18.0.0'} @@ -7796,10 +7184,6 @@ packages: resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@2.0.20': - resolution: {integrity: sha512-QJtnbTIl0/BbEASkx1MUFf6EaoWqWW1/IM90N++8NNscePvPf77GheYfpoPis6CBQawUWq8QepTP2QUSAdrVkw==} - engines: {node: '>= 10.0.0'} - '@smithy/util-defaults-mode-browser@4.0.21': resolution: {integrity: sha512-wM0jhTytgXu3wzJoIqpbBAG5U6BwiubZ6QKzSbP7/VbmF1v96xlAbX2Am/mz0Zep0NLvLh84JT0tuZnk3wmYQA==} engines: {node: '>=18.0.0'} @@ -7812,10 +7196,6 @@ packages: resolution: {integrity: sha512-Bh5bU40BgdkXE2BcaNazhNtEXi1TC0S+1d84vUwv5srWfvbeRNUKFzwKQgC6p6MXPvEgw+9+HdX3pOwT6ut5aw==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@2.0.26': - resolution: {integrity: sha512-lGFPOFCHv1ql019oegYqa54BZH7HREw6EBqjDLbAr0wquMX0BDi2sg8TJ6Eq+JGLijkZbJB73m4+aK8OFAapMg==} - engines: {node: '>= 10.0.0'} - '@smithy/util-defaults-mode-node@4.0.21': resolution: {integrity: sha512-/F34zkoU0GzpUgLJydHY8Rxu9lBn8xQC/s/0M0U9lLBkYbA1htaAFjWYJzpzsbXPuri5D1H8gjp2jBum05qBrA==} engines: {node: '>=18.0.0'} @@ -7828,10 +7208,6 @@ packages: resolution: {integrity: sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==} engines: {node: '>=18.0.0'} - '@smithy/util-endpoints@1.0.5': - resolution: {integrity: sha512-K7qNuCOD5K/90MjHvHm9kJldrfm40UxWYQxNEShMFxV/lCCCRIg8R4uu1PFAxRvPxNpIdcrh1uK6I1ISjDXZJw==} - engines: {node: '>= 14.0.0'} - '@smithy/util-endpoints@3.0.6': resolution: {integrity: sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==} engines: {node: '>=18.0.0'} @@ -7840,18 +7216,10 @@ packages: resolution: {integrity: sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==} engines: {node: '>=18.0.0'} - '@smithy/util-hex-encoding@2.0.0': - resolution: {integrity: sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==} - engines: {node: '>=14.0.0'} - '@smithy/util-hex-encoding@4.2.0': resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} engines: {node: '>=18.0.0'} - '@smithy/util-middleware@2.0.7': - resolution: {integrity: sha512-tRINOTlf1G9B0ECarFQAtTgMhpnrMPSa+5j4ZEwEawCLfTFTavk6757sxhE4RY5RMlD/I3x+DCS8ZUiR8ho9Pw==} - engines: {node: '>=14.0.0'} - '@smithy/util-middleware@4.0.4': resolution: {integrity: sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==} engines: {node: '>=18.0.0'} @@ -7860,10 +7228,6 @@ packages: resolution: {integrity: sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==} engines: {node: '>=18.0.0'} - '@smithy/util-retry@2.0.7': - resolution: {integrity: sha512-fIe5yARaF0+xVT1XKcrdnHKTJ1Vc4+3e3tLDjCuIcE9b6fkBzzGFY7AFiX4M+vj6yM98DrwkuZeHf7/hmtVp0Q==} - engines: {node: '>= 14.0.0'} - '@smithy/util-retry@4.0.6': resolution: {integrity: sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==} engines: {node: '>=18.0.0'} @@ -7872,10 +7236,6 @@ packages: resolution: {integrity: sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==} engines: {node: '>=18.0.0'} - '@smithy/util-stream@2.0.21': - resolution: {integrity: sha512-0BUE16d7n1x7pi1YluXJdB33jOTyBChT0j/BlOkFa9uxfg6YqXieHxjHNuCdJRARa7AZEj32LLLEPJ1fSa4inA==} - engines: {node: '>=14.0.0'} - '@smithy/util-stream@4.2.2': resolution: {integrity: sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==} engines: {node: '>=18.0.0'} @@ -7884,10 +7244,6 @@ packages: resolution: {integrity: sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==} engines: {node: '>=18.0.0'} - '@smithy/util-uri-escape@2.0.0': - resolution: {integrity: sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==} - engines: {node: '>=14.0.0'} - '@smithy/util-uri-escape@4.0.0': resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==} engines: {node: '>=18.0.0'} @@ -7896,10 +7252,6 @@ packages: resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} engines: {node: '>=18.0.0'} - '@smithy/util-utf8@2.0.2': - resolution: {integrity: sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==} - engines: {node: '>=14.0.0'} - '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} @@ -8282,6 +7634,9 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} @@ -8570,9 +7925,6 @@ packages: '@types/pg@8.6.1': resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} - '@types/pg@8.6.6': - resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} - '@types/polka@0.5.7': resolution: {integrity: sha512-TH8CDXM8zoskPCNmWabtK7ziGv9Q21s4hMZLVYK5HFEfqmGXBqq/Wgi7jNELWXftZK/1J/9CezYa06x1RKeQ+g==} @@ -8588,9 +7940,6 @@ packages: '@types/range-parser@1.2.4': resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - '@types/react-collapse@5.0.4': - resolution: {integrity: sha512-tM5cVB6skGLneNYnRK2E3R56VOHguSeJQHslGPTIMC58ytL3oelT8L/l1onkwHGn5vSEs2BEq2Olzrur+YdliA==} - '@types/react-dom@18.2.7': resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} @@ -8645,9 +7994,6 @@ packages: '@types/shimmer@1.2.0': resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} - '@types/simple-oauth2@5.0.4': - resolution: {integrity: sha512-4SvTfmAa1fGUa1d07j9vIiC4o92bGh0ihPXmtS05udMMmNwVIaU2nZ706cC4wI8cJxOlHD4P/d5tzqvWYd+KxA==} - '@types/slug@5.0.3': resolution: {integrity: sha512-yPX0bb1SvrpaGlHuSiz6EicgRI4VBE+LO7IANlZagQwtaoKjLLcZc8y6s13vKp41mYvMCSzjtObxvU7/0JRPaA==} @@ -8853,18 +8199,6 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - '@whatwg-node/events@0.1.1': - resolution: {integrity: sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==} - engines: {node: '>=16.0.0'} - - '@whatwg-node/fetch@0.9.14': - resolution: {integrity: sha512-wurZC82zzZwXRDSW0OS9l141DynaJQh7Yt0FD1xZ8niX7/Et/7RoiLiltbVU1fSF1RR9z6ndEaTUQBAmddTm1w==} - engines: {node: '>=16.0.0'} - - '@whatwg-node/node-fetch@0.5.0': - resolution: {integrity: sha512-q76lDAafvHNGWedNAVHrz/EyYTS8qwRLcwne8SJQdRN5P3HydxU6XROFvJfTML6KZXQX2FDdGY4/SnaNyd7M0Q==} - engines: {node: '>=16.0.0'} - '@window-splitter/interface@1.1.3': resolution: {integrity: sha512-GV7nunGpSqrlbR8pyI65aFMYlyFTO1VgWhT2cFsPkfYwmh5xBNWAWkJJtDMvbfwBHtvTGf4kTztx6e9LZSiSeQ==} engines: {node: '>=18.0.0'} @@ -9371,10 +8705,6 @@ packages: peerDependencies: esbuild: '>=0.18' - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - byline@5.0.0: resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} engines: {node: '>=0.10.0'} @@ -10887,10 +10217,6 @@ packages: resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} engines: {node: '>=18.0.0'} - eventsource-parser@3.0.3: - resolution: {integrity: sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==} - engines: {node: '>=20.0.0'} - eventsource-parser@3.0.6: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} @@ -10903,10 +10229,6 @@ packages: resolution: {integrity: sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==} engines: {node: '>=18.0.0'} - eventsource@4.0.0: - resolution: {integrity: sha512-fvIkb9qZzdMxgZrEQDyll+9oJsyaVvY92I2Re+qK0qEJ+w5s0X3dtz+M0VAPOjP1gtU3iqWyjQ0G3nvd5CLZ2g==} - engines: {node: '>=20.0.0'} - evt@2.4.13: resolution: {integrity: sha512-haTVOsmjzk+28zpzvVwan9Zw2rLQF2izgi7BKjAPRzZAfcv+8scL0TpM8MzvGNKFYHiy+Bq3r6FYIIUPl9kt3A==} @@ -11013,9 +10335,6 @@ packages: fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} - fast-url-parser@1.1.3: - resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} - fast-xml-builder@1.2.0: resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} @@ -11045,6 +10364,9 @@ packages: fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fdir@6.2.0: resolution: {integrity: sha512-9XaWcDl0riOX5j2kYfy0kKdg7skw3IY6kA4LFT8Tk2yF9UdrADUy8D6AJuBLtf7ISm/MksumwAHE3WVbMRyCLw==} peerDependencies: @@ -11156,6 +10478,11 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + formdata-node@4.4.1: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} engines: {node: '>= 12.20'} @@ -11298,6 +10625,9 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@4.7.6: resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} @@ -11940,9 +11270,6 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - joi@17.7.0: - resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==} - jose@5.4.0: resolution: {integrity: sha512-6rpxTHPAQyWMb9A35BroFl1Sp0ST3DpPcm5EVIxZxdH+e0Hv9fwhyB3XLKFUcHNpdSDnETmBfuPPTTlYz5+USw==} @@ -12049,10 +11376,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - jsonpointer@5.0.1: - resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} - engines: {node: '>=0.10.0'} - jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} @@ -12092,6 +11415,11 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + knip@6.25.0: + resolution: {integrity: sha512-Q3n41VjOOB/aqsbxb8kallAcFKrUz3b2S5fD5pTODljVpP01t+rvAgy2x3j0Cq8yEpRRHNdar1vHuqFfGuIakQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -12099,9 +11427,6 @@ packages: resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==} engines: {node: '>=20.10.0', npm: '>=10.2.3'} - layerr@2.0.1: - resolution: {integrity: sha512-z0730CwG/JO24evdORnyDkwG1Q7b7mF2Tp1qRQ0YvrMMARbt1DFG694SOv439Gm7hYKolyZyaB49YIrYIfZBdg==} - layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -13256,6 +12581,13 @@ packages: outdent@0.8.0: resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} + oxc-parser@0.137.0: + resolution: {integrity: sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.21.3: + resolution: {integrity: sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==} + oxfmt@0.54.0: resolution: {integrity: sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13533,9 +12865,6 @@ packages: pg-protocol@1.10.3: resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} - pg-protocol@1.6.1: - resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} - pg-protocol@1.9.5: resolution: {integrity: sha512-DYTWtWpfd5FOro3UnAfwvhD8jh59r2ig8bPtc9H8Ds7MscE/9NYruUQWFAOuraRl29jwcT2kyMFQ3MxeaVjUhg==} @@ -13959,9 +13288,6 @@ packages: pumpify@1.5.1: resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} - punycode@1.4.1: - resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} - punycode@2.2.0: resolution: {integrity: sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==} engines: {node: '>=6'} @@ -14024,23 +13350,12 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-aria@3.31.1: - resolution: {integrity: sha512-q4jRCVDKO6V2o4Sgir5S2obssw/YnMx6QOy10+p0dYqROHpSnMFNkONrKT1w/nA+Nx4ptfPqZbaNra1hR1bUWg==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - react-aria@3.48.0: resolution: {integrity: sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==} peerDependencies: react: 18.3.1 react-dom: 18.3.1 - react-collapse@5.1.1: - resolution: {integrity: sha512-k6cd7csF1o9LBhQ4AGBIdxB60SUEUMQDAnL2z1YvYNr9KoKr+nDkhN6FK7uGaBd/rYrYfrMpzpmJEIeHRYogBw==} - peerDependencies: - react: 18.3.1 - react-day-picker@9.13.0: resolution: {integrity: sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==} engines: {node: '>=18'} @@ -14129,12 +13444,6 @@ packages: '@types/react': optional: true - react-resizable-panels@2.0.9: - resolution: {integrity: sha512-ZylBvs7oG7Y/INWw3oYGolqgpFvoPW8MPeg9l1fURDeKpxrmUuCHBUmPj47BdZ11MODImu3kZYXG85rbySab7w==} - peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 - react-resizable@3.1.3: resolution: {integrity: sha512-liJBNayhX7qA4tBJiBD321FDhJxgGTJ07uzH5zSORXoE8h7PyEZ8mLqmosST7ppf6C4zUsbd2gzDMmBCfFp9Lw==} peerDependencies: @@ -14160,11 +13469,6 @@ packages: react: 18.3.1 react-dom: 18.3.1 - react-stately@3.29.1: - resolution: {integrity: sha512-hc4ZHy/ahvMwr6z7XMjYJ7EgzNVrXhzM4l2Qj17rdRhERo7/ovWmQencf9pF7K8kD5TraEHxPHLrYzGN4fxfUQ==} - peerDependencies: - react: 18.3.1 - react-stately@3.46.0: resolution: {integrity: sha512-OdxhWvHgs2L4OJGIs7hnuTr5WjjMM6enhNEAMRqiekhF8+ITvA2LRwNftOZwcogaoCslGYq5S2VQTQwnm0GbCA==} peerDependencies: @@ -14760,9 +14064,6 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - simple-oauth2@5.0.0: - resolution: {integrity: sha512-8291lo/z5ZdpmiOFzOs1kF3cxn22bMj5FFH+DNUppLJrpoIlM1QnFiE7KpshHu3J3i21TVcx4yW+gXYjdCKDLQ==} - simplur@3.0.1: resolution: {integrity: sha512-bBAoTn75tuKh83opmZ1VoyVoQIsvLCKzSxuasAxbnKofrT8eGyOEIaXSuNfhi/hI160+fwsR7ObcbBpOyzDvXg==} @@ -14793,6 +14094,10 @@ packages: engines: {node: '>=6'} hasBin: true + smol-toml@1.7.0: + resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} + engines: {node: '>= 18'} + socket.io-adapter@2.5.4: resolution: {integrity: sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==} @@ -14883,12 +14188,6 @@ packages: resolution: {integrity: sha512-mkpF+RG402P66VMsnQkWewTRzDBWfu9iLbOfxaW/nAKOS/2A9MheQmcU5cmX0D0At9azrorZwpvcBRNNBozACQ==} hasBin: true - sqs-consumer@7.5.0: - resolution: {integrity: sha512-aY3akgMjuK1aj4E7ZVAURUUnC8aNgUBES+b4SN+6ccMmJhi37MamWl7g1JbPow8sjIp1fBPz1bXCCDJmtjOTAg==} - engines: {node: '>=18.0.0'} - peerDependencies: - '@aws-sdk/client-sqs': ^3.428.0 - ssh-remote-port-forward@1.0.4: resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} @@ -14957,10 +14256,6 @@ packages: react: 18.3.1 react-dom: 18.3.1 - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - streamx@2.22.0: resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} @@ -15037,6 +14332,10 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} @@ -15284,6 +14583,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinyglobby@0.2.2: resolution: {integrity: sha512-mZ2sDMaySvi1PkTp4lTo1In2zjU+cY8OvZsfwrDrx3YGRbXPX1/cbPwCR9zkm3O/Fz9Jo0F1HNgIQ1b8BepqyQ==} engines: {node: '>=12.0.0'} @@ -15439,9 +14742,6 @@ packages: engines: {node: 20 || >=22} hasBin: true - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - tslib@2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} @@ -15628,9 +14928,9 @@ packages: resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==} hasBin: true - ulidx@2.2.1: - resolution: {integrity: sha512-DU9F5t1tihdafuNyW3fIrXUFHHiHxmwuQSGVGIbSpqkc93IH4P0dU8nPhk0gOW7ARxaFu4+P/9cxVwn6PdnIaQ==} - engines: {node: '>=16'} + unbash@4.0.2: + resolution: {integrity: sha512-8gwNZ29+0/3zmXw7ToIHZtg6wK37xnniRUdBt7B27xZxaxfgR5tGMaGHT0t0dLtBV9fXE7zurh0s6Z1DHVjfWg==} + engines: {node: '>=14'} unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -15752,9 +15052,6 @@ packages: resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} engines: {node: '>=4'} - urlpattern-polyfill@9.0.0: - resolution: {integrity: sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==} - use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -16328,6 +15625,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -16494,12 +15794,6 @@ snapshots: '@arr/every@1.0.1': {} - '@aws-crypto/crc32@3.0.0': - dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.936.0 - tslib: 1.14.1 - '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -16512,10 +15806,6 @@ snapshots: '@aws-sdk/types': 3.936.0 tslib: 2.8.1 - '@aws-crypto/ie11-detection@3.0.0': - dependencies: - tslib: 1.14.1 - '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 @@ -16525,17 +15815,6 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-crypto/sha256-browser@3.0.0': - dependencies: - '@aws-crypto/ie11-detection': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-crypto/supports-web-crypto': 3.0.0 - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.936.0 - '@aws-sdk/util-locate-window': 3.310.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - '@aws-crypto/sha256-browser@5.2.0': dependencies: '@aws-crypto/sha256-js': 5.2.0 @@ -16546,32 +15825,16 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-crypto/sha256-js@3.0.0': - dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.936.0 - tslib: 1.14.1 - '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.930.0 tslib: 2.8.1 - '@aws-crypto/supports-web-crypto@3.0.0': - dependencies: - tslib: 1.14.1 - '@aws-crypto/supports-web-crypto@5.2.0': dependencies: tslib: 2.8.1 - '@aws-crypto/util@3.0.0': - dependencies: - '@aws-sdk/types': 3.936.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - '@aws-crypto/util@5.2.0': dependencies: '@aws-sdk/types': 3.936.0 @@ -16773,93 +16036,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sqs@3.454.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.454.0 - '@aws-sdk/core': 3.451.0 - '@aws-sdk/credential-provider-node': 3.451.0 - '@aws-sdk/middleware-host-header': 3.451.0 - '@aws-sdk/middleware-logger': 3.451.0 - '@aws-sdk/middleware-recursion-detection': 3.451.0 - '@aws-sdk/middleware-sdk-sqs': 3.451.0 - '@aws-sdk/middleware-signing': 3.451.0 - '@aws-sdk/middleware-user-agent': 3.451.0 - '@aws-sdk/region-config-resolver': 3.451.0 - '@aws-sdk/types': 3.451.0 - '@aws-sdk/util-endpoints': 3.451.0 - '@aws-sdk/util-user-agent-browser': 3.451.0 - '@aws-sdk/util-user-agent-node': 3.451.0 - '@smithy/config-resolver': 2.0.19 - '@smithy/fetch-http-handler': 2.2.7 - '@smithy/hash-node': 2.0.16 - '@smithy/invalid-dependency': 2.0.14 - '@smithy/md5-js': 2.0.16 - '@smithy/middleware-content-length': 2.0.16 - '@smithy/middleware-endpoint': 2.2.1 - '@smithy/middleware-retry': 2.0.21 - '@smithy/middleware-serde': 2.0.14 - '@smithy/middleware-stack': 2.0.8 - '@smithy/node-config-provider': 2.1.6 - '@smithy/node-http-handler': 2.1.10 - '@smithy/protocol-http': 3.0.10 - '@smithy/smithy-client': 2.1.16 - '@smithy/types': 2.6.0 - '@smithy/url-parser': 2.0.14 - '@smithy/util-base64': 2.0.1 - '@smithy/util-body-length-browser': 2.0.0 - '@smithy/util-body-length-node': 2.1.0 - '@smithy/util-defaults-mode-browser': 2.0.20 - '@smithy/util-defaults-mode-node': 2.0.26 - '@smithy/util-endpoints': 1.0.5 - '@smithy/util-retry': 2.0.7 - '@smithy/util-utf8': 2.0.2 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sso@3.451.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/core': 3.451.0 - '@aws-sdk/middleware-host-header': 3.451.0 - '@aws-sdk/middleware-logger': 3.451.0 - '@aws-sdk/middleware-recursion-detection': 3.451.0 - '@aws-sdk/middleware-user-agent': 3.451.0 - '@aws-sdk/region-config-resolver': 3.451.0 - '@aws-sdk/types': 3.451.0 - '@aws-sdk/util-endpoints': 3.451.0 - '@aws-sdk/util-user-agent-browser': 3.451.0 - '@aws-sdk/util-user-agent-node': 3.451.0 - '@smithy/config-resolver': 2.0.19 - '@smithy/fetch-http-handler': 2.2.7 - '@smithy/hash-node': 2.0.16 - '@smithy/invalid-dependency': 2.0.14 - '@smithy/middleware-content-length': 2.0.16 - '@smithy/middleware-endpoint': 2.2.1 - '@smithy/middleware-retry': 2.0.21 - '@smithy/middleware-serde': 2.0.14 - '@smithy/middleware-stack': 2.0.8 - '@smithy/node-config-provider': 2.1.6 - '@smithy/node-http-handler': 2.1.10 - '@smithy/protocol-http': 3.0.10 - '@smithy/smithy-client': 2.1.16 - '@smithy/types': 2.6.0 - '@smithy/url-parser': 2.0.14 - '@smithy/util-base64': 2.0.1 - '@smithy/util-body-length-browser': 2.0.0 - '@smithy/util-body-length-node': 2.1.0 - '@smithy/util-defaults-mode-browser': 2.0.20 - '@smithy/util-defaults-mode-node': 2.0.26 - '@smithy/util-endpoints': 1.0.5 - '@smithy/util-retry': 2.0.7 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/client-sso@3.839.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -17032,51 +16208,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sts@3.454.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/core': 3.451.0 - '@aws-sdk/credential-provider-node': 3.451.0 - '@aws-sdk/middleware-host-header': 3.451.0 - '@aws-sdk/middleware-logger': 3.451.0 - '@aws-sdk/middleware-recursion-detection': 3.451.0 - '@aws-sdk/middleware-sdk-sts': 3.451.0 - '@aws-sdk/middleware-signing': 3.451.0 - '@aws-sdk/middleware-user-agent': 3.451.0 - '@aws-sdk/region-config-resolver': 3.451.0 - '@aws-sdk/types': 3.451.0 - '@aws-sdk/util-endpoints': 3.451.0 - '@aws-sdk/util-user-agent-browser': 3.451.0 - '@aws-sdk/util-user-agent-node': 3.451.0 - '@smithy/config-resolver': 2.0.19 - '@smithy/fetch-http-handler': 2.2.7 - '@smithy/hash-node': 2.0.16 - '@smithy/invalid-dependency': 2.0.14 - '@smithy/middleware-content-length': 2.0.16 - '@smithy/middleware-endpoint': 2.2.1 - '@smithy/middleware-retry': 2.0.21 - '@smithy/middleware-serde': 2.0.14 - '@smithy/middleware-stack': 2.0.8 - '@smithy/node-config-provider': 2.1.6 - '@smithy/node-http-handler': 2.1.10 - '@smithy/protocol-http': 3.0.10 - '@smithy/smithy-client': 2.1.16 - '@smithy/types': 2.6.0 - '@smithy/url-parser': 2.0.14 - '@smithy/util-base64': 2.0.1 - '@smithy/util-body-length-browser': 2.0.0 - '@smithy/util-body-length-node': 2.1.0 - '@smithy/util-defaults-mode-browser': 2.0.20 - '@smithy/util-defaults-mode-node': 2.0.26 - '@smithy/util-endpoints': 1.0.5 - '@smithy/util-retry': 2.0.7 - '@smithy/util-utf8': 2.3.0 - fast-xml-parser: 4.5.6 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/client-sts@3.840.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -17121,11 +16252,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.451.0': - dependencies: - '@smithy/smithy-client': 2.1.16 - tslib: 2.8.1 - '@aws-sdk/core@3.839.0': dependencies: '@aws-sdk/types': 3.821.0 @@ -17194,13 +16320,6 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/property-provider': 2.0.15 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.839.0': dependencies: '@aws-sdk/core': 3.839.0 @@ -17285,21 +16404,6 @@ snapshots: '@smithy/util-stream': 4.5.6 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.451.0': - dependencies: - '@aws-sdk/credential-provider-env': 3.451.0 - '@aws-sdk/credential-provider-process': 3.451.0 - '@aws-sdk/credential-provider-sso': 3.451.0 - '@aws-sdk/credential-provider-web-identity': 3.451.0 - '@aws-sdk/types': 3.451.0 - '@smithy/credential-provider-imds': 2.1.2 - '@smithy/property-provider': 2.0.15 - '@smithy/shared-ini-file-loader': 2.2.5 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-ini@3.839.0': dependencies: '@aws-sdk/core': 3.839.0 @@ -17386,22 +16490,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.451.0': - dependencies: - '@aws-sdk/credential-provider-env': 3.451.0 - '@aws-sdk/credential-provider-ini': 3.451.0 - '@aws-sdk/credential-provider-process': 3.451.0 - '@aws-sdk/credential-provider-sso': 3.451.0 - '@aws-sdk/credential-provider-web-identity': 3.451.0 - '@aws-sdk/types': 3.451.0 - '@smithy/credential-provider-imds': 2.1.2 - '@smithy/property-provider': 2.0.15 - '@smithy/shared-ini-file-loader': 2.2.5 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-node@3.839.0': dependencies: '@aws-sdk/credential-provider-env': 3.839.0 @@ -17470,14 +16558,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/property-provider': 2.0.15 - '@smithy/shared-ini-file-loader': 2.2.5 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.839.0': dependencies: '@aws-sdk/core': 3.839.0 @@ -17514,18 +16594,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.451.0': - dependencies: - '@aws-sdk/client-sso': 3.451.0 - '@aws-sdk/token-providers': 3.451.0 - '@aws-sdk/types': 3.451.0 - '@smithy/property-provider': 2.0.15 - '@smithy/shared-ini-file-loader': 2.2.5 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-sso@3.839.0': dependencies: '@aws-sdk/client-sso': 3.839.0 @@ -17578,13 +16646,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/property-provider': 2.0.15 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.839.0': dependencies: '@aws-sdk/core': 3.839.0 @@ -17664,13 +16725,6 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/protocol-http': 3.0.10 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 @@ -17705,12 +16759,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 @@ -17735,13 +16783,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/protocol-http': 3.0.10 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 @@ -17789,45 +16830,12 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-sqs@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/types': 2.6.0 - '@smithy/util-hex-encoding': 2.0.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-sdk-sts@3.451.0': - dependencies: - '@aws-sdk/middleware-signing': 3.451.0 - '@aws-sdk/types': 3.451.0 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-signing@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/property-provider': 2.0.15 - '@smithy/protocol-http': 3.0.10 - '@smithy/signature-v4': 2.0.16 - '@smithy/types': 2.6.0 - '@smithy/util-middleware': 2.0.7 - tslib: 2.8.1 - '@aws-sdk/middleware-ssec@3.936.0': dependencies: '@aws-sdk/types': 3.936.0 '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@aws-sdk/util-endpoints': 3.451.0 - '@smithy/protocol-http': 3.0.10 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.839.0': dependencies: '@aws-sdk/core': 3.839.0 @@ -18040,14 +17048,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/region-config-resolver@3.451.0': - dependencies: - '@smithy/node-config-provider': 2.1.6 - '@smithy/types': 2.6.0 - '@smithy/util-config-provider': 2.0.0 - '@smithy/util-middleware': 2.0.7 - tslib: 2.8.1 - '@aws-sdk/region-config-resolver@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 @@ -18116,48 +17116,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.451.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/middleware-host-header': 3.451.0 - '@aws-sdk/middleware-logger': 3.451.0 - '@aws-sdk/middleware-recursion-detection': 3.451.0 - '@aws-sdk/middleware-user-agent': 3.451.0 - '@aws-sdk/region-config-resolver': 3.451.0 - '@aws-sdk/types': 3.451.0 - '@aws-sdk/util-endpoints': 3.451.0 - '@aws-sdk/util-user-agent-browser': 3.451.0 - '@aws-sdk/util-user-agent-node': 3.451.0 - '@smithy/config-resolver': 2.0.19 - '@smithy/fetch-http-handler': 2.2.7 - '@smithy/hash-node': 2.0.16 - '@smithy/invalid-dependency': 2.0.14 - '@smithy/middleware-content-length': 2.0.16 - '@smithy/middleware-endpoint': 2.2.1 - '@smithy/middleware-retry': 2.0.21 - '@smithy/middleware-serde': 2.0.14 - '@smithy/middleware-stack': 2.0.8 - '@smithy/node-config-provider': 2.1.6 - '@smithy/node-http-handler': 2.1.10 - '@smithy/property-provider': 2.0.15 - '@smithy/protocol-http': 3.0.10 - '@smithy/shared-ini-file-loader': 2.2.5 - '@smithy/smithy-client': 2.1.16 - '@smithy/types': 2.6.0 - '@smithy/url-parser': 2.0.14 - '@smithy/util-base64': 2.0.1 - '@smithy/util-body-length-browser': 2.0.0 - '@smithy/util-body-length-node': 2.1.0 - '@smithy/util-defaults-mode-browser': 2.0.20 - '@smithy/util-defaults-mode-node': 2.0.26 - '@smithy/util-endpoints': 1.0.5 - '@smithy/util-retry': 2.0.7 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/token-providers@3.839.0': dependencies: '@aws-sdk/core': 3.839.0 @@ -18206,11 +17164,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/types@3.451.0': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/types@3.821.0': dependencies: '@smithy/types': 4.3.1 @@ -18235,12 +17188,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/util-endpoints': 1.0.5 - tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.828.0': dependencies: '@aws-sdk/types': 3.821.0 @@ -18278,21 +17225,10 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.310.0': - dependencies: - tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.893.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/types': 2.6.0 - bowser: 2.11.0 - tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 @@ -18321,13 +17257,6 @@ snapshots: bowser: 2.12.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.451.0': - dependencies: - '@aws-sdk/types': 3.451.0 - '@smithy/node-config-provider': 2.1.6 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.839.0': dependencies: '@aws-sdk/middleware-user-agent': 3.839.0 @@ -18360,10 +17289,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/util-utf8-browser@3.259.0': - dependencies: - tslib: 2.8.1 - '@aws-sdk/xml-builder@3.821.0': dependencies: '@smithy/types': 4.9.0 @@ -18827,16 +17752,6 @@ snapshots: '@codemirror/view': 6.7.2 '@lezer/common': 1.3.0 - '@codemirror/lang-javascript@6.1.2': - dependencies: - '@codemirror/autocomplete': 6.4.0(@codemirror/language@6.3.2)(@codemirror/state@6.2.0)(@codemirror/view@6.7.2)(@lezer/common@1.3.0) - '@codemirror/language': 6.3.2 - '@codemirror/lint': 6.4.2 - '@codemirror/state': 6.2.0 - '@codemirror/view': 6.7.2 - '@lezer/common': 1.3.0 - '@lezer/javascript': 1.4.1 - '@codemirror/lang-json@6.0.1': dependencies: '@codemirror/language': 6.3.2 @@ -18964,22 +17879,38 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} - '@electric-sql/client@0.4.0': - optionalDependencies: - '@rollup/rollup-darwin-arm64': 4.53.2 - '@electric-sql/client@1.0.14': dependencies: '@microsoft/fetch-event-source': 2.0.1 optionalDependencies: '@rollup/rollup-darwin-arm64': 4.53.2 - '@electric-sql/react@0.3.5(react@18.3.1)': + '@emnapi/core@1.11.0': dependencies: - '@electric-sql/client': 0.4.0 - use-sync-external-store: 1.2.2(react@18.3.1) - optionalDependencies: - react: 18.3.1 + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true '@emotion/hash@0.9.0': {} @@ -19672,47 +18603,13 @@ snapshots: '@grpc/proto-loader': 0.7.13 '@js-sdsl/ordered-map': 4.4.2 - '@grpc/grpc-js@1.14.3': - dependencies: - '@grpc/proto-loader': 0.8.0 - '@js-sdsl/ordered-map': 4.4.2 - - '@grpc/proto-loader@0.7.13': + '@grpc/proto-loader@0.7.13': dependencies: lodash.camelcase: 4.3.0 long: 5.2.3 protobufjs: 7.6.1 yargs: 17.7.2 - '@grpc/proto-loader@0.8.0': - dependencies: - lodash.camelcase: 4.3.0 - long: 5.3.2 - protobufjs: 7.6.1 - yargs: 17.7.2 - - '@hapi/boom@10.0.1': - dependencies: - '@hapi/hoek': 11.0.2 - - '@hapi/bourne@3.0.0': {} - - '@hapi/hoek@10.0.1': {} - - '@hapi/hoek@11.0.2': {} - - '@hapi/hoek@9.3.0': {} - - '@hapi/topo@5.1.0': - dependencies: - '@hapi/hoek': 9.3.0 - - '@hapi/wreck@18.0.1': - dependencies: - '@hapi/boom': 10.0.1 - '@hapi/bourne': 3.0.0 - '@hapi/hoek': 11.0.2 - '@hcaptcha/loader@2.0.0': {} '@hcaptcha/react-hcaptcha@1.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -19924,11 +18821,6 @@ snapshots: dependencies: '@lezer/common': 1.3.0 - '@lezer/javascript@1.4.1': - dependencies: - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 - '@lezer/json@1.0.0': dependencies: '@lezer/highlight': 1.2.3 @@ -20045,6 +18937,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + '@nodable/entities@2.1.0': {} '@nodelib/fs.scandir@2.1.5': @@ -20279,12 +19185,6 @@ snapshots: '@opentelemetry/api@1.9.1': {} - '@opentelemetry/configuration@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - yaml: 2.9.0 - '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -20303,16 +19203,6 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/exporter-logs-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@grpc/grpc-js': 1.14.3 - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -20322,29 +19212,6 @@ snapshots: '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.218.0 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - - '@opentelemetry/exporter-metrics-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@grpc/grpc-js': 1.14.3 - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -20364,25 +19231,6 @@ snapshots: '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - - '@opentelemetry/exporter-trace-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@grpc/grpc-js': 1.14.3 - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -20392,23 +19240,6 @@ snapshots: '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - - '@opentelemetry/exporter-zipkin@2.7.1(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/host-metrics@0.38.3(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -20679,14 +19510,6 @@ snapshots: '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@grpc/grpc-js': 1.14.3 - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -20697,16 +19520,6 @@ snapshots: '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - - '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/redis-common@0.36.2': {} '@opentelemetry/resource-detector-aws@2.14.0(@opentelemetry/api@1.9.1)': @@ -20742,37 +19555,6 @@ snapshots: '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-node@0.218.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.218.0 - '@opentelemetry/configuration': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1)(supports-color@10.0.0) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - transitivePeerDependencies: - - supports-color - '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -20809,6 +19591,133 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@oxc-parser/binding-android-arm-eabi@0.137.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.137.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.137.0': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.137.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.137.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.137.0': + optional: true + + '@oxc-project/types@0.137.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.21.3': + optional: true + + '@oxc-resolver/binding-android-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.21.3': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.21.3': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.21.3': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.21.3': + optional: true + '@oxfmt/binding-android-arm-eabi@0.54.0': optional: true @@ -21027,10 +19936,6 @@ snapshots: '@protobufjs/utf8@1.1.1': {} - '@radix-ui/number@1.0.0': - dependencies: - '@babel/runtime': 7.28.4 - '@radix-ui/number@1.0.1': dependencies: '@babel/runtime': 7.28.4 @@ -21313,13 +20218,6 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 - '@radix-ui/react-label@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.20.7 - '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@radix-ui/react-popover@1.0.5(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.20.7 @@ -21378,16 +20276,6 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.2.7 - '@radix-ui/react-portal@1.1.9(@types/react-dom@18.2.7)(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.7)(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.2.69)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.2.69 - '@types/react-dom': 18.2.7 - '@radix-ui/react-presence@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -21495,35 +20383,6 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.2.7 - '@radix-ui/react-select@1.2.1(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.20.7 - '@radix-ui/number': 1.0.0 - '@radix-ui/primitive': 1.0.0 - '@radix-ui/react-collection': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) - '@radix-ui/react-context': 1.0.0(react@18.3.1) - '@radix-ui/react-direction': 1.0.0(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.0.0(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.0.0(react@18.3.1) - '@radix-ui/react-popper': 1.1.1(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.1(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.0(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) - '@radix-ui/react-use-previous': 1.0.0(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - aria-hidden: 1.2.3 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.5(@types/react@18.2.69)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - '@radix-ui/react-slider@1.1.2(@types/react-dom@18.2.7)(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.22.5 @@ -21688,11 +20547,6 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 - '@radix-ui/react-use-previous@1.0.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.28.4 - react: 18.3.1 - '@radix-ui/react-use-previous@1.0.1(@types/react@18.2.69)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -21731,89 +20585,13 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 - '@react-aria/breadcrumbs@3.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-aria/datepicker@3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/link': 3.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/breadcrumbs': 3.7.2(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/button@3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/toggle': 3.7.0(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/calendar@3.5.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@internationalized/date': 3.12.1 - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/live-announcer': 3.3.1 - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/calendar': 3.4.3(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/calendar': 3.4.3(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/checkbox@3.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/form': 3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/toggle': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/checkbox': 3.6.1(react@18.3.1) - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/toggle': 3.7.0(react@18.3.1) - '@react-types/checkbox': 3.6.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/combobox@3.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/listbox': 3.11.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/live-announcer': 3.3.1 - '@react-aria/menu': 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/overlays': 3.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/textfield': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/combobox': 3.8.1(react@18.3.1) - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/combobox': 3.10.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/datepicker@3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@internationalized/date': 3.5.1 - '@internationalized/number': 3.5.0 - '@internationalized/string': 3.2.0 - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/form': 3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@internationalized/date': 3.5.1 + '@internationalized/number': 3.5.0 + '@internationalized/string': 3.2.0 + '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/form': 3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -21830,32 +20608,6 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@react-aria/dialog@3.5.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/overlays': 3.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/dialog': 3.5.7(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/dnd@3.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@internationalized/string': 3.2.8 - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/live-announcer': 3.3.1 - '@react-aria/overlays': 3.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/dnd': 3.2.7(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@react-aria/focus@3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -21878,374 +20630,59 @@ snapshots: transitivePeerDependencies: - react-dom - '@react-aria/grid@3.8.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/live-announcer': 3.3.1 - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/grid': 3.8.4(react@18.3.1) - '@react-stately/selection': 3.14.2(react@18.3.1) - '@react-stately/virtualizer': 3.6.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/checkbox': 3.6.0(react@18.3.1) - '@react-types/grid': 3.2.3(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/gridlist@3.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/grid': 3.8.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/list': 3.10.2(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@react-aria/i18n@3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@internationalized/date': 3.12.1 '@internationalized/message': 3.1.1 '@internationalized/number': 3.6.6 '@internationalized/string': 3.2.8 - '@react-aria/ssr': 3.9.1(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/interactions@3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/ssr': 3.9.1(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/label@3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/link@3.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/link': 3.5.2(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/listbox@3.11.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/list': 3.10.2(react@18.3.1) - '@react-types/listbox': 3.4.6(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/live-announcer@3.3.1': - dependencies: - '@swc/helpers': 0.5.15 - - '@react-aria/menu@3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/overlays': 3.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/menu': 3.6.0(react@18.3.1) - '@react-stately/tree': 3.7.5(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/menu': 3.9.6(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/meter@3.4.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/progress': 3.4.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/meter': 3.3.6(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/numberfield@3.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/spinbutton': 3.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/textfield': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/numberfield': 3.8.0(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/numberfield': 3.7.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/overlays@3.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/ssr': 3.9.1(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/visually-hidden': 3.8.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/overlays': 3.6.4(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/overlays': 3.8.4(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/progress@3.4.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/progress': 3.5.1(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/radio@3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/form': 3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/radio': 3.10.1(react@18.3.1) - '@react-types/radio': 3.7.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/searchfield@3.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/textfield': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/searchfield': 3.5.0(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/searchfield': 3.5.2(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/select@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/form': 3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/listbox': 3.11.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/menu': 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/visually-hidden': 3.8.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/select': 3.6.1(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/select': 3.9.1(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/selection@3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/selection': 3.14.2(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/separator@3.3.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/slider@3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/slider': 3.5.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/slider': 3.7.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/spinbutton@3.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/live-announcer': 3.3.1 - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/ssr@3.9.1(react@18.3.1)': - dependencies: - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-aria/switch@3.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/toggle': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/toggle': 3.7.0(react@18.3.1) - '@react-types/switch': 3.5.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-aria/table@3.13.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/grid': 3.8.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/live-announcer': 3.3.1 - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/visually-hidden': 3.8.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/flags': 3.0.0 - '@react-stately/table': 3.11.4(react@18.3.1) - '@react-stately/virtualizer': 3.6.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/checkbox': 3.6.0(react@18.3.1) - '@react-types/grid': 3.2.3(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/table': 3.9.2(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/tabs@3.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/tabs': 3.6.3(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/tabs': 3.3.4(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/tag@3.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/gridlist': 3.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/list': 3.10.2(react@18.3.1) - '@react-types/button': 3.9.1(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@react-aria/textfield@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/form': 3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/ssr': 3.9.1(react@18.3.1) '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/textfield': 3.9.0(react@18.3.1) '@swc/helpers': 0.5.15 react: 18.3.1 transitivePeerDependencies: - react-dom - '@react-aria/toggle@3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-aria/interactions@3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/ssr': 3.9.1(react@18.3.1) '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/toggle': 3.7.0(react@18.3.1) - '@react-types/checkbox': 3.6.0(react@18.3.1) + '@react-types/shared': 3.34.0(react@18.3.1) '@swc/helpers': 0.5.15 react: 18.3.1 transitivePeerDependencies: - react-dom - '@react-aria/tooltip@3.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-aria/label@3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-stately/tooltip': 3.4.6(react@18.3.1) '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/tooltip': 3.4.6(react@18.3.1) '@swc/helpers': 0.5.15 react: 18.3.1 transitivePeerDependencies: - react-dom + '@react-aria/live-announcer@3.3.1': + dependencies: + '@swc/helpers': 0.5.15 + + '@react-aria/spinbutton@3.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/live-announcer': 3.3.1 + '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-types/button': 3.9.1(react@18.3.1) + '@react-types/shared': 3.34.0(react@18.3.1) + '@swc/helpers': 0.5.15 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@react-aria/ssr@3.9.1(react@18.3.1)': + dependencies: + '@swc/helpers': 0.5.15 + react: 18.3.1 + '@react-aria/utils@3.23.0(react@18.3.1)': dependencies: '@react-aria/ssr': 3.9.1(react@18.3.1) @@ -22263,16 +20700,6 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-stately: 3.46.0(react@18.3.1) - '@react-aria/visually-hidden@3.8.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - '@react-email/body@0.3.0(react@18.3.1)': dependencies: react: 18.3.1 @@ -22411,49 +20838,6 @@ snapshots: dependencies: react: 18.3.1 - '@react-stately/calendar@3.4.3(react@18.3.1)': - dependencies: - '@internationalized/date': 3.12.1 - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/calendar': 3.4.3(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/checkbox@3.6.1(react@18.3.1)': - dependencies: - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/checkbox': 3.6.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/collections@3.10.4(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/combobox@3.8.1(react@18.3.1)': - dependencies: - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/list': 3.10.2(react@18.3.1) - '@react-stately/overlays': 3.6.4(react@18.3.1) - '@react-stately/select': 3.6.1(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/combobox': 3.10.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/data@3.11.0(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - '@react-stately/datepicker@3.9.1(react@18.3.1)': dependencies: '@internationalized/date': 3.5.1 @@ -22466,58 +20850,12 @@ snapshots: '@swc/helpers': 0.5.2 react: 18.3.1 - '@react-stately/dnd@3.2.7(react@18.3.1)': - dependencies: - '@react-stately/selection': 3.14.2(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/flags@3.0.0': - dependencies: - '@swc/helpers': 0.4.14 - '@react-stately/form@3.0.0(react@18.3.1)': dependencies: '@react-types/shared': 3.34.0(react@18.3.1) '@swc/helpers': 0.5.15 react: 18.3.1 - '@react-stately/grid@3.8.4(react@18.3.1)': - dependencies: - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/selection': 3.14.2(react@18.3.1) - '@react-types/grid': 3.2.3(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/list@3.10.2(react@18.3.1)': - dependencies: - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/selection': 3.14.2(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/menu@3.6.0(react@18.3.1)': - dependencies: - '@react-stately/overlays': 3.6.4(react@18.3.1) - '@react-types/menu': 3.9.6(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/numberfield@3.8.0(react@18.3.1)': - dependencies: - '@internationalized/number': 3.6.6 - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/numberfield': 3.7.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - '@react-stately/overlays@3.6.4(react@18.3.1)': dependencies: '@react-stately/utils': 3.9.0(react@18.3.1) @@ -22525,242 +20863,47 @@ snapshots: '@swc/helpers': 0.5.15 react: 18.3.1 - '@react-stately/radio@3.10.1(react@18.3.1)': - dependencies: - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/radio': 3.7.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/searchfield@3.5.0(react@18.3.1)': - dependencies: - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/searchfield': 3.5.2(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/select@3.6.1(react@18.3.1)': - dependencies: - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/list': 3.10.2(react@18.3.1) - '@react-stately/overlays': 3.6.4(react@18.3.1) - '@react-types/select': 3.9.1(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/selection@3.14.2(react@18.3.1)': - dependencies: - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/slider@3.5.0(react@18.3.1)': - dependencies: - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/slider': 3.7.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/table@3.11.4(react@18.3.1)': - dependencies: - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/flags': 3.0.0 - '@react-stately/grid': 3.8.4(react@18.3.1) - '@react-stately/selection': 3.14.2(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/grid': 3.2.3(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/table': 3.9.2(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/tabs@3.6.3(react@18.3.1)': - dependencies: - '@react-stately/list': 3.10.2(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/tabs': 3.3.4(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/toggle@3.7.0(react@18.3.1)': - dependencies: - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/checkbox': 3.6.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/tooltip@3.4.6(react@18.3.1)': - dependencies: - '@react-stately/overlays': 3.6.4(react@18.3.1) - '@react-types/tooltip': 3.4.6(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - - '@react-stately/tree@3.7.5(react@18.3.1)': - dependencies: - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/selection': 3.14.2(react@18.3.1) - '@react-stately/utils': 3.9.0(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - '@react-stately/utils@3.9.0(react@18.3.1)': dependencies: '@swc/helpers': 0.5.15 react: 18.3.1 - '@react-stately/virtualizer@3.6.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-aria/utils': 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - '@swc/helpers': 0.5.15 - react: 18.3.1 - transitivePeerDependencies: - - react-dom - - '@react-types/breadcrumbs@3.7.2(react@18.3.1)': - dependencies: - '@react-types/link': 3.5.2(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - '@react-types/button@3.9.1(react@18.3.1)': dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/calendar@3.4.3(react@18.3.1)': - dependencies: - '@internationalized/date': 3.12.1 - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/checkbox@3.6.0(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/combobox@3.10.0(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/datepicker@3.7.1(react@18.3.1)': - dependencies: - '@internationalized/date': 3.5.1 - '@react-types/calendar': 3.4.3(react@18.3.1) - '@react-types/overlays': 3.8.4(react@18.3.1) - '@react-types/shared': 3.22.0(react@18.3.1) - react: 18.3.1 - - '@react-types/dialog@3.5.7(react@18.3.1)': - dependencies: - '@react-types/overlays': 3.8.4(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/grid@3.2.3(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/link@3.5.2(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/listbox@3.4.6(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/menu@3.9.6(react@18.3.1)': - dependencies: - '@react-types/overlays': 3.8.4(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/meter@3.3.6(react@18.3.1)': - dependencies: - '@react-types/progress': 3.5.1(react@18.3.1) - react: 18.3.1 - - '@react-types/numberfield@3.7.0(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/overlays@3.8.4(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/progress@3.5.1(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/radio@3.7.0(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/searchfield@3.5.2(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - '@react-types/textfield': 3.9.0(react@18.3.1) - react: 18.3.1 - - '@react-types/select@3.9.1(react@18.3.1)': - dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) - react: 18.3.1 - - '@react-types/shared@3.22.0(react@18.3.1)': - dependencies: - react: 18.3.1 - - '@react-types/shared@3.34.0(react@18.3.1)': - dependencies: + '@react-types/shared': 3.34.0(react@18.3.1) react: 18.3.1 - '@react-types/slider@3.7.0(react@18.3.1)': + '@react-types/calendar@3.4.3(react@18.3.1)': dependencies: + '@internationalized/date': 3.12.1 '@react-types/shared': 3.34.0(react@18.3.1) react: 18.3.1 - '@react-types/switch@3.5.0(react@18.3.1)': + '@react-types/datepicker@3.7.1(react@18.3.1)': dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) + '@internationalized/date': 3.5.1 + '@react-types/calendar': 3.4.3(react@18.3.1) + '@react-types/overlays': 3.8.4(react@18.3.1) + '@react-types/shared': 3.22.0(react@18.3.1) react: 18.3.1 - '@react-types/table@3.9.2(react@18.3.1)': + '@react-types/dialog@3.5.7(react@18.3.1)': dependencies: - '@react-types/grid': 3.2.3(react@18.3.1) + '@react-types/overlays': 3.8.4(react@18.3.1) '@react-types/shared': 3.34.0(react@18.3.1) react: 18.3.1 - '@react-types/tabs@3.3.4(react@18.3.1)': + '@react-types/overlays@3.8.4(react@18.3.1)': dependencies: '@react-types/shared': 3.34.0(react@18.3.1) react: 18.3.1 - '@react-types/textfield@3.9.0(react@18.3.1)': + '@react-types/shared@3.22.0(react@18.3.1)': dependencies: - '@react-types/shared': 3.34.0(react@18.3.1) react: 18.3.1 - '@react-types/tooltip@3.4.6(react@18.3.1)': + '@react-types/shared@3.34.0(react@18.3.1)': dependencies: - '@react-types/overlays': 3.8.4(react@18.3.1) - '@react-types/shared': 3.34.0(react@18.3.1) react: 18.3.1 '@remix-run/changelog-github@0.0.5(encoding@0.1.13)': @@ -22899,6 +21042,7 @@ snapshots: transitivePeerDependencies: - supports-color - typescript + optional: true '@remix-run/server-runtime@2.17.5(typescript@5.5.4)': dependencies: @@ -22924,11 +21068,6 @@ snapshots: transitivePeerDependencies: - react-dom - '@remix-run/v1-meta@0.1.3(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4))(@remix-run/server-runtime@2.17.5(typescript@5.5.4))': - dependencies: - '@remix-run/react': 2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) - '@remix-run/server-runtime': 2.17.5(typescript@5.5.4) - '@remix-run/web-blob@3.1.0': dependencies: '@remix-run/web-stream': 1.1.0 @@ -23243,14 +21382,6 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@sideway/address@4.1.4': - dependencies: - '@hapi/hoek': 9.3.0 - - '@sideway/formula@3.0.1': {} - - '@sideway/pinpoint@2.0.0': {} - '@sinclair/typebox@0.34.38': {} '@sindresorhus/is@0.14.0': {} @@ -23283,11 +21414,6 @@ snapshots: - debug - supports-color - '@smithy/abort-controller@2.0.14': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/abort-controller@4.0.4': dependencies: '@smithy/types': 4.9.0 @@ -23307,14 +21433,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/config-resolver@2.0.19': - dependencies: - '@smithy/node-config-provider': 2.1.6 - '@smithy/types': 2.6.0 - '@smithy/util-config-provider': 2.0.0 - '@smithy/util-middleware': 2.0.7 - tslib: 2.8.1 - '@smithy/config-resolver@4.1.4': dependencies: '@smithy/node-config-provider': 4.1.3 @@ -23370,14 +21488,6 @@ snapshots: '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/credential-provider-imds@2.1.2': - dependencies: - '@smithy/node-config-provider': 2.1.6 - '@smithy/property-provider': 2.0.15 - '@smithy/types': 2.6.0 - '@smithy/url-parser': 2.0.14 - tslib: 2.8.1 - '@smithy/credential-provider-imds@4.0.6': dependencies: '@smithy/node-config-provider': 4.3.5 @@ -23394,13 +21504,6 @@ snapshots: '@smithy/url-parser': 4.2.5 tslib: 2.8.1 - '@smithy/eventstream-codec@2.0.14': - dependencies: - '@aws-crypto/crc32': 3.0.0 - '@smithy/types': 2.6.0 - '@smithy/util-hex-encoding': 2.0.0 - tslib: 2.8.1 - '@smithy/eventstream-codec@4.2.5': dependencies: '@aws-crypto/crc32': 5.2.0 @@ -23431,14 +21534,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/fetch-http-handler@2.2.7': - dependencies: - '@smithy/protocol-http': 3.0.10 - '@smithy/querystring-builder': 2.0.14 - '@smithy/types': 2.6.0 - '@smithy/util-base64': 2.0.1 - tslib: 2.8.1 - '@smithy/fetch-http-handler@5.0.4': dependencies: '@smithy/protocol-http': 5.1.2 @@ -23462,13 +21557,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/hash-node@2.0.16': - dependencies: - '@smithy/types': 2.6.0 - '@smithy/util-buffer-from': 2.0.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - '@smithy/hash-node@4.0.4': dependencies: '@smithy/types': 4.3.1 @@ -23489,11 +21577,6 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/invalid-dependency@2.0.14': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/invalid-dependency@4.0.4': dependencies: '@smithy/types': 4.3.1 @@ -23512,24 +21595,12 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/md5-js@2.0.16': - dependencies: - '@smithy/types': 2.6.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - '@smithy/md5-js@4.2.5': dependencies: '@smithy/types': 4.9.0 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/middleware-content-length@2.0.16': - dependencies: - '@smithy/protocol-http': 3.0.10 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/middleware-content-length@4.0.4': dependencies: '@smithy/protocol-http': 5.1.2 @@ -23542,16 +21613,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/middleware-endpoint@2.2.1': - dependencies: - '@smithy/middleware-serde': 2.0.14 - '@smithy/node-config-provider': 2.1.6 - '@smithy/shared-ini-file-loader': 2.2.5 - '@smithy/types': 2.6.0 - '@smithy/url-parser': 2.0.14 - '@smithy/util-middleware': 2.0.7 - tslib: 2.8.1 - '@smithy/middleware-endpoint@4.1.13': dependencies: '@smithy/core': 3.6.0 @@ -23585,17 +21646,6 @@ snapshots: '@smithy/util-middleware': 4.2.5 tslib: 2.8.1 - '@smithy/middleware-retry@2.0.21': - dependencies: - '@smithy/node-config-provider': 2.1.6 - '@smithy/protocol-http': 3.0.10 - '@smithy/service-error-classification': 2.0.7 - '@smithy/types': 2.6.0 - '@smithy/util-middleware': 2.0.7 - '@smithy/util-retry': 2.0.7 - tslib: 2.8.1 - uuid: 8.3.2 - '@smithy/middleware-retry@4.1.14': dependencies: '@smithy/node-config-provider': 4.1.3 @@ -23632,11 +21682,6 @@ snapshots: '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/middleware-serde@2.0.14': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/middleware-serde@4.0.8': dependencies: '@smithy/protocol-http': 5.1.2 @@ -23655,11 +21700,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/middleware-stack@2.0.8': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/middleware-stack@4.0.4': dependencies: '@smithy/types': 4.3.1 @@ -23670,13 +21710,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/node-config-provider@2.1.6': - dependencies: - '@smithy/property-provider': 2.0.15 - '@smithy/shared-ini-file-loader': 2.2.5 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/node-config-provider@4.1.3': dependencies: '@smithy/property-provider': 4.0.4 @@ -23691,14 +21724,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/node-http-handler@2.1.10': - dependencies: - '@smithy/abort-controller': 2.0.14 - '@smithy/protocol-http': 3.0.10 - '@smithy/querystring-builder': 2.0.14 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/node-http-handler@4.0.6': dependencies: '@smithy/abort-controller': 4.0.4 @@ -23715,11 +21740,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/property-provider@2.0.15': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/property-provider@4.0.4': dependencies: '@smithy/types': 4.9.0 @@ -23730,11 +21750,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/protocol-http@3.0.10': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/protocol-http@5.1.2': dependencies: '@smithy/types': 4.3.1 @@ -23745,12 +21760,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/querystring-builder@2.0.14': - dependencies: - '@smithy/types': 2.6.0 - '@smithy/util-uri-escape': 2.0.0 - tslib: 2.8.1 - '@smithy/querystring-builder@4.0.4': dependencies: '@smithy/types': 4.9.0 @@ -23763,11 +21772,6 @@ snapshots: '@smithy/util-uri-escape': 4.2.0 tslib: 2.8.1 - '@smithy/querystring-parser@2.0.14': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/querystring-parser@4.0.4': dependencies: '@smithy/types': 4.9.0 @@ -23778,10 +21782,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/service-error-classification@2.0.7': - dependencies: - '@smithy/types': 2.6.0 - '@smithy/service-error-classification@4.0.6': dependencies: '@smithy/types': 4.9.0 @@ -23790,11 +21790,6 @@ snapshots: dependencies: '@smithy/types': 4.9.0 - '@smithy/shared-ini-file-loader@2.2.5': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/shared-ini-file-loader@4.0.4': dependencies: '@smithy/types': 4.9.0 @@ -23805,17 +21800,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/signature-v4@2.0.16': - dependencies: - '@smithy/eventstream-codec': 2.0.14 - '@smithy/is-array-buffer': 2.2.0 - '@smithy/types': 2.6.0 - '@smithy/util-hex-encoding': 2.0.0 - '@smithy/util-middleware': 2.0.7 - '@smithy/util-uri-escape': 2.0.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - '@smithy/signature-v4@5.1.2': dependencies: '@smithy/is-array-buffer': 4.2.0 @@ -23838,13 +21822,6 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/smithy-client@2.1.16': - dependencies: - '@smithy/middleware-stack': 2.0.8 - '@smithy/types': 2.6.0 - '@smithy/util-stream': 2.0.21 - tslib: 2.8.1 - '@smithy/smithy-client@4.4.5': dependencies: '@smithy/core': 3.6.0 @@ -23875,10 +21852,6 @@ snapshots: '@smithy/util-stream': 4.5.6 tslib: 2.8.1 - '@smithy/types@2.6.0': - dependencies: - tslib: 2.8.1 - '@smithy/types@4.3.1': dependencies: tslib: 2.8.1 @@ -23887,12 +21860,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/url-parser@2.0.14': - dependencies: - '@smithy/querystring-parser': 2.0.14 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/url-parser@4.0.4': dependencies: '@smithy/querystring-parser': 4.0.4 @@ -23905,11 +21872,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/util-base64@2.0.1': - dependencies: - '@smithy/util-buffer-from': 2.0.0 - tslib: 2.8.1 - '@smithy/util-base64@4.0.0': dependencies: '@smithy/util-buffer-from': 4.0.0 @@ -23922,10 +21884,6 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/util-body-length-browser@2.0.0': - dependencies: - tslib: 2.8.1 - '@smithy/util-body-length-browser@4.0.0': dependencies: tslib: 2.8.1 @@ -23934,10 +21892,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-body-length-node@2.1.0': - dependencies: - tslib: 2.8.1 - '@smithy/util-body-length-node@4.0.0': dependencies: tslib: 2.8.1 @@ -23946,11 +21900,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-buffer-from@2.0.0': - dependencies: - '@smithy/is-array-buffer': 2.2.0 - tslib: 2.8.1 - '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 @@ -23966,10 +21915,6 @@ snapshots: '@smithy/is-array-buffer': 4.2.0 tslib: 2.8.1 - '@smithy/util-config-provider@2.0.0': - dependencies: - tslib: 2.8.1 - '@smithy/util-config-provider@4.0.0': dependencies: tslib: 2.8.1 @@ -23978,14 +21923,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@2.0.20': - dependencies: - '@smithy/property-provider': 2.0.15 - '@smithy/smithy-client': 2.1.16 - '@smithy/types': 2.6.0 - bowser: 2.11.0 - tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.0.21': dependencies: '@smithy/property-provider': 4.0.4 @@ -24008,16 +21945,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@2.0.26': - dependencies: - '@smithy/config-resolver': 2.0.19 - '@smithy/credential-provider-imds': 2.1.2 - '@smithy/node-config-provider': 2.1.6 - '@smithy/property-provider': 2.0.15 - '@smithy/smithy-client': 2.1.16 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.0.21': dependencies: '@smithy/config-resolver': 4.1.4 @@ -24048,12 +21975,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/util-endpoints@1.0.5': - dependencies: - '@smithy/node-config-provider': 2.1.6 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/util-endpoints@3.0.6': dependencies: '@smithy/node-config-provider': 4.1.3 @@ -24066,19 +21987,10 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/util-hex-encoding@2.0.0': - dependencies: - tslib: 2.8.1 - '@smithy/util-hex-encoding@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-middleware@2.0.7': - dependencies: - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/util-middleware@4.0.4': dependencies: '@smithy/types': 4.3.1 @@ -24089,12 +22001,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/util-retry@2.0.7': - dependencies: - '@smithy/service-error-classification': 2.0.7 - '@smithy/types': 2.6.0 - tslib: 2.8.1 - '@smithy/util-retry@4.0.6': dependencies: '@smithy/service-error-classification': 4.0.6 @@ -24107,17 +22013,6 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/util-stream@2.0.21': - dependencies: - '@smithy/fetch-http-handler': 2.2.7 - '@smithy/node-http-handler': 2.1.10 - '@smithy/types': 2.6.0 - '@smithy/util-base64': 2.0.1 - '@smithy/util-buffer-from': 2.2.0 - '@smithy/util-hex-encoding': 2.0.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - '@smithy/util-stream@4.2.2': dependencies: '@smithy/fetch-http-handler': 5.3.6 @@ -24140,10 +22035,6 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/util-uri-escape@2.0.0': - dependencies: - tslib: 2.8.1 - '@smithy/util-uri-escape@4.0.0': dependencies: tslib: 2.8.1 @@ -24152,11 +22043,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-utf8@2.0.2': - dependencies: - '@smithy/util-buffer-from': 2.0.0 - tslib: 2.8.1 - '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 @@ -24479,6 +22365,11 @@ snapshots: dependencies: zod: 3.25.76 + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/acorn@4.0.6': dependencies: '@types/estree': 1.0.9 @@ -24824,12 +22715,6 @@ snapshots: pg-protocol: 1.10.3 pg-types: 2.2.0 - '@types/pg@8.6.6': - dependencies: - '@types/node': 22.20.0 - pg-protocol: 1.6.1 - pg-types: 2.2.0 - '@types/polka@0.5.7': dependencies: '@types/express': 4.17.15 @@ -24845,10 +22730,6 @@ snapshots: '@types/range-parser@1.2.4': {} - '@types/react-collapse@5.0.4': - dependencies: - '@types/react': 18.2.69 - '@types/react-dom@18.2.7': dependencies: '@types/react': 18.2.69 @@ -24910,8 +22791,6 @@ snapshots: '@types/shimmer@1.2.0': {} - '@types/simple-oauth2@5.0.4': {} - '@types/slug@5.0.3': {} '@types/source-map-support@0.5.10': @@ -25231,21 +23110,6 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@whatwg-node/events@0.1.1': {} - - '@whatwg-node/fetch@0.9.14': - dependencies: - '@whatwg-node/node-fetch': 0.5.0 - urlpattern-polyfill: 9.0.0 - - '@whatwg-node/node-fetch@0.5.0': - dependencies: - '@whatwg-node/events': 0.1.1 - busboy: 1.6.0 - fast-querystring: 1.1.2 - fast-url-parser: 1.1.3 - tslib: 2.8.1 - '@window-splitter/interface@1.1.3': dependencies: '@window-splitter/state': 1.1.3(patch_hash=ecf02927f78361c14d8f8347604fd355ba36f2fc4f9ba9a08cd63adc101b7327) @@ -25793,10 +23657,6 @@ snapshots: esbuild: 0.25.1 load-tsconfig: 0.2.5 - busboy@1.6.0: - dependencies: - streamsearch: 1.1.0 - byline@5.0.0: {} bytes@3.0.0: {} @@ -25828,7 +23688,7 @@ snapshots: dotenv: 16.6.1 exsolve: 1.0.7 giget: 2.0.0 - jiti: 2.6.1 + jiti: 2.7.0 ohash: 2.0.11 pathe: 2.0.3 perfect-debounce: 1.0.0 @@ -26137,6 +23997,7 @@ snapshots: vary: 1.1.2 transitivePeerDependencies: - supports-color + optional: true compute-cosine-similarity@1.1.0: dependencies: @@ -27498,8 +25359,6 @@ snapshots: eventsource-parser@3.0.0: {} - eventsource-parser@3.0.3: {} - eventsource-parser@3.0.6: {} eventsource-parser@3.1.0: {} @@ -27508,10 +25367,6 @@ snapshots: dependencies: eventsource-parser: 3.0.0 - eventsource@4.0.0: - dependencies: - eventsource-parser: 3.0.3 - evt@2.4.13: dependencies: minimal-polyfills: 2.2.2 @@ -27734,10 +25589,6 @@ snapshots: fast-uri@3.1.2: {} - fast-url-parser@1.1.3: - dependencies: - punycode: 1.4.1 - fast-xml-builder@1.2.0: dependencies: path-expression-matcher: 1.5.0 @@ -27788,6 +25639,10 @@ snapshots: dependencies: format: 0.2.2 + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + fdir@6.2.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -27910,6 +25765,10 @@ snapshots: format@0.2.2: {} + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + formdata-node@4.4.1: dependencies: node-domexception: 1.0.0 @@ -28051,6 +25910,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + get-tsconfig@4.7.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -28742,14 +26605,6 @@ snapshots: jiti@2.7.0: {} - joi@17.7.0: - dependencies: - '@hapi/hoek': 9.3.0 - '@hapi/topo': 5.1.0 - '@sideway/address': 4.1.4 - '@sideway/formula': 3.0.1 - '@sideway/pinpoint': 2.0.0 - jose@5.4.0: {} jose@6.0.8: {} @@ -28841,8 +26696,6 @@ snapshots: '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) jsep: 1.4.0 - jsonpointer@5.0.1: {} - jsonwebtoken@9.0.2: dependencies: jws: 3.2.3 @@ -28887,6 +26740,22 @@ snapshots: kleur@4.1.5: {} + knip@6.25.0: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + formatly: 0.3.0 + get-tsconfig: 4.14.0 + jiti: 2.7.0 + oxc-parser: 0.137.0 + oxc-resolver: 11.21.3 + picomatch: 4.0.4 + smol-toml: 1.7.0 + strip-json-comments: 5.0.3 + tinyglobby: 0.2.17 + unbash: 4.0.2 + yaml: 2.9.0 + zod: 4.4.3 + kolorist@1.8.0: {} langium@4.2.2: @@ -28898,8 +26767,6 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - layerr@2.0.1: {} - layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -30075,6 +27942,7 @@ snapshots: on-headers: 1.1.0 transitivePeerDependencies: - supports-color + optional: true mri@1.2.0: {} @@ -30130,7 +27998,8 @@ snapshots: negotiator@0.6.3: {} - negotiator@0.6.4: {} + negotiator@0.6.4: + optional: true negotiator@1.0.0: {} @@ -30339,7 +28208,8 @@ snapshots: on-headers@1.0.2: {} - on-headers@1.1.0: {} + on-headers@1.1.0: + optional: true once@1.4.0: dependencies: @@ -30421,6 +28291,53 @@ snapshots: outdent@0.8.0: {} + oxc-parser@0.137.0: + dependencies: + '@oxc-project/types': 0.137.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.137.0 + '@oxc-parser/binding-android-arm64': 0.137.0 + '@oxc-parser/binding-darwin-arm64': 0.137.0 + '@oxc-parser/binding-darwin-x64': 0.137.0 + '@oxc-parser/binding-freebsd-x64': 0.137.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.137.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.137.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.137.0 + '@oxc-parser/binding-linux-arm64-musl': 0.137.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.137.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.137.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.137.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.137.0 + '@oxc-parser/binding-linux-x64-gnu': 0.137.0 + '@oxc-parser/binding-linux-x64-musl': 0.137.0 + '@oxc-parser/binding-openharmony-arm64': 0.137.0 + '@oxc-parser/binding-wasm32-wasi': 0.137.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.137.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.137.0 + '@oxc-parser/binding-win32-x64-msvc': 0.137.0 + + oxc-resolver@11.21.3: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.21.3 + '@oxc-resolver/binding-android-arm64': 11.21.3 + '@oxc-resolver/binding-darwin-arm64': 11.21.3 + '@oxc-resolver/binding-darwin-x64': 11.21.3 + '@oxc-resolver/binding-freebsd-x64': 11.21.3 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.21.3 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.21.3 + '@oxc-resolver/binding-linux-arm64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-arm64-musl': 11.21.3 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-riscv64-musl': 11.21.3 + '@oxc-resolver/binding-linux-s390x-gnu': 11.21.3 + '@oxc-resolver/binding-linux-x64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-x64-musl': 11.21.3 + '@oxc-resolver/binding-openharmony-arm64': 11.21.3 + '@oxc-resolver/binding-wasm32-wasi': 11.21.3 + '@oxc-resolver/binding-win32-arm64-msvc': 11.21.3 + '@oxc-resolver/binding-win32-x64-msvc': 11.21.3 + oxfmt@0.54.0: dependencies: tinypool: 2.1.0 @@ -30691,8 +28608,6 @@ snapshots: pg-protocol@1.10.3: {} - pg-protocol@1.6.1: {} - pg-protocol@1.9.5: {} pg-types@2.2.0: @@ -31114,8 +29029,6 @@ snapshots: inherits: 2.0.4 pump: 2.0.1 - punycode@1.4.1: {} - punycode@2.2.0: {} pure-rand@6.1.0: {} @@ -31182,48 +29095,6 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-aria@3.31.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@internationalized/string': 3.2.0 - '@react-aria/breadcrumbs': 3.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/button': 3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/calendar': 3.5.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/checkbox': 3.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/combobox': 3.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/datepicker': 3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/dialog': 3.5.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/dnd': 3.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/focus': 3.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/gridlist': 3.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/i18n': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/interactions': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/label': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/link': 3.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/listbox': 3.11.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/menu': 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/meter': 3.4.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/numberfield': 3.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/overlays': 3.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/progress': 3.4.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/radio': 3.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/searchfield': 3.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/select': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/selection': 3.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/separator': 3.3.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/slider': 3.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/ssr': 3.9.1(react@18.3.1) - '@react-aria/switch': 3.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/table': 3.13.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/tabs': 3.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/tag': 3.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/textfield': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/tooltip': 3.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/utils': 3.23.0(react@18.3.1) - '@react-aria/visually-hidden': 3.8.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-types/shared': 3.22.0(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-aria@3.48.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@internationalized/date': 3.12.1 @@ -31238,10 +29109,6 @@ snapshots: react-stately: 3.46.0(react@18.3.1) use-sync-external-store: 1.6.0(react@18.3.1) - react-collapse@5.1.1(react@18.3.1): - dependencies: - react: 18.3.1 - react-day-picker@9.13.0(react@18.3.1): dependencies: '@date-fns/tz': 1.4.1 @@ -31367,11 +29234,6 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 - react-resizable-panels@2.0.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-resizable@3.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: prop-types: 15.8.1 @@ -31399,33 +29261,6 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-stately@3.29.1(react@18.3.1): - dependencies: - '@react-stately/calendar': 3.4.3(react@18.3.1) - '@react-stately/checkbox': 3.6.1(react@18.3.1) - '@react-stately/collections': 3.10.4(react@18.3.1) - '@react-stately/combobox': 3.8.1(react@18.3.1) - '@react-stately/data': 3.11.0(react@18.3.1) - '@react-stately/datepicker': 3.9.1(react@18.3.1) - '@react-stately/dnd': 3.2.7(react@18.3.1) - '@react-stately/form': 3.0.0(react@18.3.1) - '@react-stately/list': 3.10.2(react@18.3.1) - '@react-stately/menu': 3.6.0(react@18.3.1) - '@react-stately/numberfield': 3.8.0(react@18.3.1) - '@react-stately/overlays': 3.6.4(react@18.3.1) - '@react-stately/radio': 3.10.1(react@18.3.1) - '@react-stately/searchfield': 3.5.0(react@18.3.1) - '@react-stately/select': 3.6.1(react@18.3.1) - '@react-stately/selection': 3.14.2(react@18.3.1) - '@react-stately/slider': 3.5.0(react@18.3.1) - '@react-stately/table': 3.11.4(react@18.3.1) - '@react-stately/tabs': 3.6.3(react@18.3.1) - '@react-stately/toggle': 3.7.0(react@18.3.1) - '@react-stately/tooltip': 3.4.6(react@18.3.1) - '@react-stately/tree': 3.7.5(react@18.3.1) - '@react-types/shared': 3.22.0(react@18.3.1) - react: 18.3.1 - react-stately@3.46.0(react@18.3.1): dependencies: '@internationalized/date': 3.12.1 @@ -32202,15 +30037,6 @@ snapshots: simple-concat: 1.0.1 optional: true - simple-oauth2@5.0.0: - dependencies: - '@hapi/hoek': 10.0.1 - '@hapi/wreck': 18.0.1 - debug: 4.3.4 - joi: 17.7.0 - transitivePeerDependencies: - - supports-color - simplur@3.0.1: {} sisteransi@1.0.5: {} @@ -32240,6 +30066,8 @@ snapshots: wcwidth: 1.0.1 yargs: 15.4.1 + smol-toml@1.7.0: {} + socket.io-adapter@2.5.4(bufferutil@4.0.9): dependencies: debug: 4.3.7(supports-color@10.0.0) @@ -32357,13 +30185,6 @@ snapshots: argparse: 2.0.1 nearley: 2.20.1 - sqs-consumer@7.5.0(@aws-sdk/client-sqs@3.454.0): - dependencies: - '@aws-sdk/client-sqs': 3.454.0 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - ssh-remote-port-forward@1.0.4: dependencies: '@types/ssh2': 0.5.52 @@ -32447,8 +30268,6 @@ snapshots: transitivePeerDependencies: - supports-color - streamsearch@1.1.0: {} - streamx@2.22.0: dependencies: fast-fifo: 1.3.2 @@ -32544,6 +30363,8 @@ snapshots: strip-json-comments@2.0.1: {} + strip-json-comments@5.0.3: {} + strnum@1.0.5: {} strnum@2.2.3: {} @@ -32842,6 +30663,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinyglobby@0.2.2: dependencies: fdir: 6.2.0(picomatch@4.0.4) @@ -32976,8 +30802,6 @@ snapshots: typescript: 5.5.4 walk-up-path: 4.0.0 - tslib@1.14.1: {} - tslib@2.4.1: {} tslib@2.5.0: {} @@ -33205,9 +31029,7 @@ snapshots: ulid@2.3.0: {} - ulidx@2.2.1: - dependencies: - layerr: 2.0.1 + unbash@4.0.2: {} unbox-primitive@1.0.2: dependencies: @@ -33346,8 +31168,6 @@ snapshots: dependencies: prepend-http: 2.0.0 - urlpattern-polyfill@9.0.0: {} - use-callback-ref@1.3.3(@types/react@18.2.69)(react@18.3.1): dependencies: react: 18.3.1 @@ -33953,4 +31773,6 @@ snapshots: zod@3.25.76: {} + zod@4.4.3: {} + zwitch@2.0.4: {}