diff --git a/.oxfmtrc.json b/.oxfmtrc.json index b41db4dda9e..71809f50ef4 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -12,7 +12,6 @@ "sortPackageJson": false, "ignorePatterns": [ "node_modules", - "apps/webapp", ".env", ".env.local", "pnpm-lock.yaml", diff --git a/.oxlintrc.json b/.oxlintrc.json index 76175b9eb54..99babf86a17 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -7,8 +7,7 @@ "**/*.d.ts", "**/seed.js", "**/seedCloud.ts", - "**/populate.js", - "apps/webapp/**" + "**/populate.js" ], // All rules below are disabled because they currently fire on the existing // codebase (1748 warnings across these 20 rules as of this commit). Disabled diff --git a/apps/webapp/app/assets/icons/AIPenIcon.tsx b/apps/webapp/app/assets/icons/AIPenIcon.tsx index 65f6fdb2f38..146edd0e6d2 100644 --- a/apps/webapp/app/assets/icons/AIPenIcon.tsx +++ b/apps/webapp/app/assets/icons/AIPenIcon.tsx @@ -19,13 +19,7 @@ export function AIPenIcon({ className }: { className?: string }) { stroke="currentColor" strokeWidth="2" /> - + ); } diff --git a/apps/webapp/app/assets/icons/AiProviderIcons.tsx b/apps/webapp/app/assets/icons/AiProviderIcons.tsx index 2be3fe38ed7..418cdeff569 100644 --- a/apps/webapp/app/assets/icons/AiProviderIcons.tsx +++ b/apps/webapp/app/assets/icons/AiProviderIcons.tsx @@ -147,7 +147,12 @@ export function CerebrasIcon({ className }: IconProps) { export function MistralIcon({ className }: IconProps) { return ( - + @@ -174,4 +179,3 @@ export function AzureIcon({ className }: IconProps) { ); } - diff --git a/apps/webapp/app/assets/icons/AttemptIcon.tsx b/apps/webapp/app/assets/icons/AttemptIcon.tsx index de2a886b9d5..4b6c7f03698 100644 --- a/apps/webapp/app/assets/icons/AttemptIcon.tsx +++ b/apps/webapp/app/assets/icons/AttemptIcon.tsx @@ -8,22 +8,8 @@ export function AttemptIcon({ className }: { className?: string }) { fill="none" xmlns="http://www.w3.org/2000/svg" > - - + + - + ); } diff --git a/apps/webapp/app/assets/icons/FunctionIcon.tsx b/apps/webapp/app/assets/icons/FunctionIcon.tsx index 2defe9c2f8d..11b630ac2fa 100644 --- a/apps/webapp/app/assets/icons/FunctionIcon.tsx +++ b/apps/webapp/app/assets/icons/FunctionIcon.tsx @@ -8,15 +8,7 @@ export function FunctionIcon({ className }: { className?: string }) { fill="none" xmlns="http://www.w3.org/2000/svg" > - + - + - + setHovered(true)} onMouseLeave={() => setHovered(false)} > - + - + + diff --git a/apps/webapp/app/assets/icons/StreamsIcon.tsx b/apps/webapp/app/assets/icons/StreamsIcon.tsx index 73cc480f4d4..8deb7a19c9e 100644 --- a/apps/webapp/app/assets/icons/StreamsIcon.tsx +++ b/apps/webapp/app/assets/icons/StreamsIcon.tsx @@ -1,10 +1,24 @@ export function StreamsIcon({ className }: { className?: string }) { return ( - - - - - + + + + + ); } - diff --git a/apps/webapp/app/assets/icons/TextSquareIcon.tsx b/apps/webapp/app/assets/icons/TextSquareIcon.tsx index d25faf6d0ba..cffb0aa9d2e 100644 --- a/apps/webapp/app/assets/icons/TextSquareIcon.tsx +++ b/apps/webapp/app/assets/icons/TextSquareIcon.tsx @@ -9,10 +9,42 @@ export function TextSquareIcon({ className }: { className?: string }) { xmlns="http://www.w3.org/2000/svg" > - - - - + + + + ); } diff --git a/apps/webapp/app/clientBeforeFirstRender.ts b/apps/webapp/app/clientBeforeFirstRender.ts index 3275c54423a..5b5b6404221 100644 --- a/apps/webapp/app/clientBeforeFirstRender.ts +++ b/apps/webapp/app/clientBeforeFirstRender.ts @@ -22,10 +22,7 @@ function cleanupLegacyResizablePanelStorage() { const toRemove: string[] = []; for (let i = 0; i < window.localStorage.length; i++) { const key = window.localStorage.key(i); - if ( - key && - (key.startsWith("panel-group-react-aria") || key === "panel-run-parent-v2") - ) { + if (key && (key.startsWith("panel-group-react-aria") || key === "panel-run-parent-v2")) { toRemove.push(key); } } diff --git a/apps/webapp/app/components/AskAI.tsx b/apps/webapp/app/components/AskAI.tsx index 814d4649c8f..bf76654cced 100644 --- a/apps/webapp/app/components/AskAI.tsx +++ b/apps/webapp/app/components/AskAI.tsx @@ -134,11 +134,7 @@ function AskAIProvider({ websiteId, isCollapsed = false }: AskAIProviderProps) { - + Ask AI diff --git a/apps/webapp/app/components/BlankStatePanels.tsx b/apps/webapp/app/components/BlankStatePanels.tsx index fc926266fd9..7b5bdc63109 100644 --- a/apps/webapp/app/components/BlankStatePanels.tsx +++ b/apps/webapp/app/components/BlankStatePanels.tsx @@ -208,17 +208,17 @@ export function SessionsNone() { } > - A session is a stateful execution of an agent, with two-way streaming and durable - compute. A single session can have multiple runs associated with it, so one conversation - can span many task triggers. The input stream carries incoming user messages, and the - output stream carries everything the agent produces, including AI generation parts (text, - reasoning, tool calls, etc.) and any custom data parts your task emits. + A session is a stateful execution of an agent, with two-way streaming and durable compute. A + single session can have multiple runs associated with it, so one conversation can span many + task triggers. The input stream carries incoming user messages, and the output stream + carries everything the agent produces, including AI generation parts (text, reasoning, tool + calls, etc.) and any custom data parts your task emits. The easiest way to create one is to trigger a chat.agent task, which is built on sessions and handles the chat turn loop for you. You can also call{" "} - sessions.start() directly for non-chat patterns like agent - inboxes, approval flows, or server-to-server streaming. + sessions.start() directly for non-chat patterns like agent inboxes, + approval flows, or server-to-server streaming. ); diff --git a/apps/webapp/app/components/DevPresence.tsx b/apps/webapp/app/components/DevPresence.tsx index 7c67d568904..27d96954758 100644 --- a/apps/webapp/app/components/DevPresence.tsx +++ b/apps/webapp/app/components/DevPresence.tsx @@ -154,8 +154,8 @@ export function DevPresencePanel({ isConnected }: { isConnected: boolean | undef {isConnected === undefined ? "Checking connection..." : isConnected - ? "Your dev server is connected" - : "Your dev server is not connected"} + ? "Your dev server is connected" + : "Your dev server is not connected"}
@@ -169,8 +169,8 @@ export function DevPresencePanel({ isConnected }: { isConnected: boolean | undef {isConnected === undefined ? "Checking connection..." : isConnected - ? "Your local dev server is connected to Trigger.dev" - : "Your local dev server is not connected to Trigger.dev"} + ? "Your local dev server is connected to Trigger.dev" + : "Your local dev server is not connected to Trigger.dev"}
{isConnected ? null : ( diff --git a/apps/webapp/app/components/LoginPageLayout.tsx b/apps/webapp/app/components/LoginPageLayout.tsx index 3e42cd6894f..323faf4ea26 100644 --- a/apps/webapp/app/components/LoginPageLayout.tsx +++ b/apps/webapp/app/components/LoginPageLayout.tsx @@ -63,8 +63,8 @@ export function LoginPageLayout({ children }: { children: React.ReactNode }) {
{children}
- Having login issues? Email us{" "} - or ask us in Discord + Having login issues? Email us or{" "} + ask us in Discord diff --git a/apps/webapp/app/components/SetupCommands.tsx b/apps/webapp/app/components/SetupCommands.tsx index accb2f65a8f..d219b0be721 100644 --- a/apps/webapp/app/components/SetupCommands.tsx +++ b/apps/webapp/app/components/SetupCommands.tsx @@ -209,7 +209,10 @@ export function TriggerLoginStepV3({ title }: TabsProps) { ); } -export function TriggerDeployStep({ title, environment }: TabsProps & { environment: { type: string } }) { +export function TriggerDeployStep({ + title, + environment, +}: TabsProps & { environment: { type: string } }) { const triggerCliTag = useTriggerCliTag(); const { activePackageManager, setActivePackageManager } = usePackageManager(); diff --git a/apps/webapp/app/components/Shortcuts.tsx b/apps/webapp/app/components/Shortcuts.tsx index 4702f37239c..63b5fddf715 100644 --- a/apps/webapp/app/components/Shortcuts.tsx +++ b/apps/webapp/app/components/Shortcuts.tsx @@ -4,13 +4,7 @@ import { useShortcutKeys } from "~/hooks/useShortcutKeys"; import { Button } from "./primitives/Buttons"; import { Header3 } from "./primitives/Headers"; import { Paragraph } from "./primitives/Paragraph"; -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, - SheetTrigger -} from "./primitives/SheetV3"; +import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "./primitives/SheetV3"; import { ShortcutKey } from "./primitives/ShortcutKey"; export function Shortcuts() { @@ -83,7 +77,7 @@ function ShortcutContent() { - + diff --git a/apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.server.ts b/apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.server.ts index 4855c4c2465..7de475bda8e 100644 --- a/apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.server.ts +++ b/apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.server.ts @@ -40,9 +40,7 @@ export const apiRateLimitDomain: RateLimitDomain = { }, }; -export function resolveEffectiveApiRateLimit( - override: unknown -): EffectiveRateLimit { +export function resolveEffectiveApiRateLimit(override: unknown): EffectiveRateLimit { return resolveEffectiveRateLimit(override, apiRateLimitDomain); } diff --git a/apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.tsx b/apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.tsx index b27956f4360..6b13bac8b4f 100644 --- a/apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.tsx +++ b/apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.tsx @@ -1,17 +1,8 @@ -import { - RateLimitSection, - type RateLimitWrapperProps, -} from "./RateLimitSection"; +import { RateLimitSection, type RateLimitWrapperProps } from "./RateLimitSection"; export const API_RATE_LIMIT_INTENT = "set-rate-limit"; export const API_RATE_LIMIT_SAVED_VALUE = "rate-limit"; export function ApiRateLimitSection(props: RateLimitWrapperProps) { - return ( - - ); + return ; } diff --git a/apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.server.ts b/apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.server.ts index 83a368094a9..3891e4fc40c 100644 --- a/apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.server.ts +++ b/apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.server.ts @@ -40,9 +40,7 @@ export const batchRateLimitDomain: RateLimitDomain = { }, }; -export function resolveEffectiveBatchRateLimit( - override: unknown -): EffectiveRateLimit { +export function resolveEffectiveBatchRateLimit(override: unknown): EffectiveRateLimit { return resolveEffectiveRateLimit(override, batchRateLimitDomain); } diff --git a/apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.tsx b/apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.tsx index 0e52124d290..64ee6a05d41 100644 --- a/apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.tsx +++ b/apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.tsx @@ -1,17 +1,8 @@ -import { - RateLimitSection, - type RateLimitWrapperProps, -} from "./RateLimitSection"; +import { RateLimitSection, type RateLimitWrapperProps } from "./RateLimitSection"; export const BATCH_RATE_LIMIT_INTENT = "set-batch-rate-limit"; export const BATCH_RATE_LIMIT_SAVED_VALUE = "batch-rate-limit"; export function BatchRateLimitSection(props: RateLimitWrapperProps) { - return ( - - ); + return ; } diff --git a/apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx b/apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx index bf8ecf83161..8aa4b5c93c9 100644 --- a/apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx +++ b/apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx @@ -68,9 +68,7 @@ export function MaxProjectsSection({ Limit - - {maximumProjectCount.toLocaleString()} - + {maximumProjectCount.toLocaleString()} ) : ( @@ -89,11 +87,7 @@ export function MaxProjectsSection({ {fieldError("maximumProjectCount")}
-
{isLoading ? ( diff --git a/apps/webapp/app/components/code/QueryResultsChart.tsx b/apps/webapp/app/components/code/QueryResultsChart.tsx index 2c3f1c9f2bf..97013c192a4 100644 --- a/apps/webapp/app/components/code/QueryResultsChart.tsx +++ b/apps/webapp/app/components/code/QueryResultsChart.tsx @@ -872,10 +872,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({ ); // Create value formatter for tooltips and legend based on column format - const tooltipValueFormatter = useMemo( - () => createValueFormatter(yAxisFormat), - [yAxisFormat] - ); + const tooltipValueFormatter = useMemo(() => createValueFormatter(yAxisFormat), [yAxisFormat]); // Check if the group-by column has a runStatus customRenderType const groupByIsRunStatus = useMemo(() => { @@ -1181,9 +1178,7 @@ function createYAxisFormatter( if (format === "bytes" || format === "decimalBytes") { const divisor = format === "bytes" ? 1024 : 1000; const units = - format === "bytes" - ? ["B", "KiB", "MiB", "GiB", "TiB"] - : ["B", "KB", "MB", "GB", "TB"]; + format === "bytes" ? ["B", "KiB", "MiB", "GiB", "TiB"] : ["B", "KB", "MB", "GB", "TB"]; return (value: number): string => { if (value === 0) return "0 B"; // Use consistent unit for all ticks based on max value @@ -1205,8 +1200,7 @@ function createYAxisFormatter( } if (format === "durationSeconds") { - return (value: number): string => - formatDurationMilliseconds(value * 1000, { style: "short" }); + return (value: number): string => formatDurationMilliseconds(value * 1000, { style: "short" }); } if (format === "durationNs") { diff --git a/apps/webapp/app/components/code/TSQLResultsTable.tsx b/apps/webapp/app/components/code/TSQLResultsTable.tsx index 73ca07180bf..8e10f8e6faa 100644 --- a/apps/webapp/app/components/code/TSQLResultsTable.tsx +++ b/apps/webapp/app/components/code/TSQLResultsTable.tsx @@ -186,10 +186,10 @@ const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { cellValue === null ? "NULL" : cellValue === undefined - ? "" - : typeof cellValue === "object" - ? JSON.stringify(cellValue) - : String(cellValue); + ? "" + : typeof cellValue === "object" + ? JSON.stringify(cellValue) + : String(cellValue); // Build searchable strings - formatted value (if we have column metadata) const formattedValue = meta?.outputColumn @@ -569,12 +569,8 @@ function CellValue({ if (typeof value === "string") { const spanId = row?.["span_id"]; const runPath = v3RunPathFromFriendlyId(value); - const href = typeof spanId === "string" && spanId - ? `${runPath}?span=${spanId}` - : runPath; - const tooltip = typeof spanId === "string" && spanId - ? "Jump to span" - : "Jump to run"; + const href = typeof spanId === "string" && spanId ? `${runPath}?span=${spanId}` : runPath; + const tooltip = typeof spanId === "string" && spanId ? "Jump to span" : "Jump to run"; return ( hiddenColumns?.length ? columns.filter((col) => !hiddenColumns.includes(col.name)) : columns, + () => + hiddenColumns?.length ? columns.filter((col) => !hiddenColumns.includes(col.name)) : columns, [columns, hiddenColumns] ); const columnDefs = useMemo[]>( diff --git a/apps/webapp/app/components/code/codeMirrorTheme.ts b/apps/webapp/app/components/code/codeMirrorTheme.ts index 2faed6eaa54..c9dd2f12e86 100644 --- a/apps/webapp/app/components/code/codeMirrorTheme.ts +++ b/apps/webapp/app/components/code/codeMirrorTheme.ts @@ -67,10 +67,9 @@ export function darkTheme(): Extension { }, ".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor }, - "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": - { - backgroundColor: selection, - }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { + backgroundColor: selection, + }, ".cm-panels": { backgroundColor: darkBackground, color: ivory }, ".cm-panels.cm-panels-top": { borderBottom: "2px solid black" }, @@ -167,20 +166,14 @@ export function darkTheme(): Extension { backgroundColor: scrollbarBg, }, }, - { dark: true }, + { dark: true } ); /// The highlighting style for code in the JSON Hero theme. const jsonHeroHighlightStyle = HighlightStyle.define([ { tag: tags.keyword, color: violet }, { - tag: [ - tags.name, - tags.deleted, - tags.character, - tags.propertyName, - tags.macroName, - ], + tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: lilac, }, { tag: [tags.function(tags.variableName), tags.labelName], color: malibu }, diff --git a/apps/webapp/app/components/code/tsql/index.ts b/apps/webapp/app/components/code/tsql/index.ts index bda244e30fa..71c543161d8 100644 --- a/apps/webapp/app/components/code/tsql/index.ts +++ b/apps/webapp/app/components/code/tsql/index.ts @@ -2,5 +2,9 @@ // Provides syntax highlighting, autocompletion, and linting for TSQL queries export { createTSQLCompletion } from "./tsqlCompletion"; -export { createTSQLLinter, isValidTSQLQuery, getTSQLError, type TSQLLinterConfig } from "./tsqlLinter"; - +export { + createTSQLLinter, + isValidTSQLQuery, + getTSQLError, + type TSQLLinterConfig, +} from "./tsqlLinter"; diff --git a/apps/webapp/app/components/code/tsql/tsqlCompletion.ts b/apps/webapp/app/components/code/tsql/tsqlCompletion.ts index b53c551a4d3..03d75f179d3 100644 --- a/apps/webapp/app/components/code/tsql/tsqlCompletion.ts +++ b/apps/webapp/app/components/code/tsql/tsqlCompletion.ts @@ -92,8 +92,8 @@ function createFunctionCompletions(): Completion[] { meta.maxArgs === 0 ? "()" : meta.minArgs === meta.maxArgs - ? `(${meta.minArgs} args)` - : `(${meta.minArgs}${meta.maxArgs ? `-${meta.maxArgs}` : "+"} args)`; + ? `(${meta.minArgs} args)` + : `(${meta.minArgs}${meta.maxArgs ? `-${meta.maxArgs}` : "+"} args)`; functions.push({ label: name, @@ -111,8 +111,8 @@ function createFunctionCompletions(): Completion[] { meta.maxArgs === 0 ? "()" : meta.minArgs === meta.maxArgs - ? `(${meta.minArgs} args)` - : `(${meta.minArgs}${meta.maxArgs ? `-${meta.maxArgs}` : "+"} args)`; + ? `(${meta.minArgs} args)` + : `(${meta.minArgs}${meta.maxArgs ? `-${meta.maxArgs}` : "+"} args)`; functions.push({ label: name, diff --git a/apps/webapp/app/components/code/tsql/tsqlLinter.test.ts b/apps/webapp/app/components/code/tsql/tsqlLinter.test.ts index 8acc81d66f0..8ecc21a1658 100644 --- a/apps/webapp/app/components/code/tsql/tsqlLinter.test.ts +++ b/apps/webapp/app/components/code/tsql/tsqlLinter.test.ts @@ -28,9 +28,7 @@ describe("tsqlLinter", () => { true ); expect( - isValidTSQLQuery( - "SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id" - ) + isValidTSQLQuery("SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id") ).toBe(true); }); @@ -76,4 +74,3 @@ describe("tsqlLinter", () => { }); }); }); - diff --git a/apps/webapp/app/components/code/tsql/tsqlLinter.ts b/apps/webapp/app/components/code/tsql/tsqlLinter.ts index 72794e3e9ab..42f5288fb29 100644 --- a/apps/webapp/app/components/code/tsql/tsqlLinter.ts +++ b/apps/webapp/app/components/code/tsql/tsqlLinter.ts @@ -31,11 +31,7 @@ function parseErrorPosition(message: string): { line: number; column: number } | /** * Convert line/column to a document position */ -function positionToOffset( - doc: string, - line: number, - column: number -): number { +function positionToOffset(doc: string, line: number, column: number): number { const lines = doc.split("\n"); // line is 1-indexed @@ -210,4 +206,3 @@ export function getTSQLError(query: string): string | null { return "Unknown error"; } } - diff --git a/apps/webapp/app/components/dashboard-agent/RunDiagnosisCard.tsx b/apps/webapp/app/components/dashboard-agent/RunDiagnosisCard.tsx index a0a96ecb5e2..2fef1f3deb7 100644 --- a/apps/webapp/app/components/dashboard-agent/RunDiagnosisCard.tsx +++ b/apps/webapp/app/components/dashboard-agent/RunDiagnosisCard.tsx @@ -96,7 +96,14 @@ function DiagnosisActions({ actions }: { actions: NonNullable {actions.map((action, i) => { if (action.kind === "view_run" && /^run_[a-z0-9]+$/i.test(action.target)) { - return ; + return ( + + ); } if (action.kind === "docs") { const safeUrl = toSafeUrl(action.target); diff --git a/apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx b/apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx index 32ed778f877..8a823ee78c1 100644 --- a/apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx +++ b/apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx @@ -135,12 +135,7 @@ export function ConfigureErrorAlerts({ /> - +
diff --git a/apps/webapp/app/components/integrations/VercelBuildSettings.tsx b/apps/webapp/app/components/integrations/VercelBuildSettings.tsx index d8e9f3fe3f8..8449d342f53 100644 --- a/apps/webapp/app/components/integrations/VercelBuildSettings.tsx +++ b/apps/webapp/app/components/integrations/VercelBuildSettings.tsx @@ -113,9 +113,7 @@ export function BuildSettingsFields({
); if (disabled && disabledReason) { - return ( - - ); + return ; } return row; })} @@ -140,9 +138,7 @@ export function BuildSettingsFields({ disabled={!enabledSlugs.some((s) => pullEnvVarsBeforeBuild.includes(s))} onCheckedChange={(checked) => { onDiscoverEnvVarsChange( - checked - ? enabledSlugs.filter((s) => pullEnvVarsBeforeBuild.includes(s)) - : [] + checked ? enabledSlugs.filter((s) => pullEnvVarsBeforeBuild.includes(s)) : [] ); }} /> @@ -185,9 +181,7 @@ export function BuildSettingsFields({
); if (disabled && disabledReason) { - return ( - - ); + return ; } return row; })} @@ -208,10 +202,12 @@ export function BuildSettingsFields({ When enabled, production deployments wait for Vercel deployment to complete before - promoting the Trigger.dev deployment. This will disable the "Auto-assign Custom - Production Domains" option in your Vercel project settings to perform staged - deployments.{" "} - + promoting the Trigger.dev deployment. This will disable the "Auto-assign Custom Production + Domains" option in your Vercel project settings to perform staged deployments.{" "} + Learn more . @@ -225,9 +221,8 @@ export function BuildSettingsFields({ )} {!currentTriggerVersion && currentTriggerVersionFetchFailed && ( - Couldn't read{" "} - TRIGGER_VERSION from Vercel — - check the Vercel dashboard to confirm the production pin. + Couldn't read TRIGGER_VERSION from + Vercel — check the Vercel dashboard to confirm the production pin. )} @@ -244,9 +239,9 @@ export function BuildSettingsFields({ /> - When enabled, the integration automatically promotes the Vercel deployment after - the Trigger.dev build completes. Turn off to manually promote from your Vercel - dashboard — Trigger.dev will then promote automatically once you do. + When enabled, the integration automatically promotes the Vercel deployment after the + Trigger.dev build completes. Turn off to manually promote from your Vercel dashboard — + Trigger.dev will then promote automatically once you do. )} diff --git a/apps/webapp/app/components/integrations/VercelLogo.tsx b/apps/webapp/app/components/integrations/VercelLogo.tsx index 7ddf039abfd..856b74ebada 100644 --- a/apps/webapp/app/components/integrations/VercelLogo.tsx +++ b/apps/webapp/app/components/integrations/VercelLogo.tsx @@ -1,11 +1,6 @@ export function VercelLogo({ className }: { className?: string }) { return ( - + ); diff --git a/apps/webapp/app/components/integrations/VercelOnboardingModal.tsx b/apps/webapp/app/components/integrations/VercelOnboardingModal.tsx index 21734c5c038..7ae12b20d45 100644 --- a/apps/webapp/app/components/integrations/VercelOnboardingModal.tsx +++ b/apps/webapp/app/components/integrations/VercelOnboardingModal.tsx @@ -4,11 +4,7 @@ import { ChevronDownIcon, ChevronUpIcon, } from "@heroicons/react/20/solid"; -import { - useFetcher, - useNavigation, - useSearchParams, -} from "@remix-run/react"; +import { useFetcher, useNavigation, useSearchParams } from "@remix-run/react"; import { useTypedFetcher } from "remix-typedjson"; import { Dialog, DialogContent, DialogHeader } from "~/components/primitives/Dialog"; import { Button, LinkButton } from "~/components/primitives/Buttons"; @@ -31,9 +27,7 @@ import { import { VercelLogo } from "~/components/integrations/VercelLogo"; import { BuildSettingsFields } from "~/components/integrations/VercelBuildSettings"; import { OctoKitty } from "~/components/GitHubLoginButton"; -import { - ConnectGitHubRepoModal, -} from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github"; +import { ConnectGitHubRepoModal } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github"; import { type SyncEnvVarsMapping, type EnvSlug, @@ -44,7 +38,12 @@ import { } from "~/v3/vercel/vercelProjectIntegrationSchema"; import { type VercelCustomEnvironment } from "~/models/vercelIntegration.server"; import { type VercelOnboardingData } from "~/presenters/v3/VercelSettingsPresenter.server"; -import { vercelAppInstallPath, v3ProjectSettingsIntegrationsPath, githubAppInstallPath, vercelResourcePath } from "~/utils/pathBuilder"; +import { + vercelAppInstallPath, + v3ProjectSettingsIntegrationsPath, + githubAppInstallPath, + vercelResourcePath, +} from "~/utils/pathBuilder"; import type { loader } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel"; import { useEffect, useState, useCallback, useRef } from "react"; import { usePostHogTracking } from "~/hooks/usePostHog"; @@ -73,9 +72,7 @@ function formatVercelTargets(targets: string[]): string { staging: "Staging", }; - return targets - .map((t) => targetLabels[t.toLowerCase()] || t) - .join(", "); + return targets.map((t) => targetLabels[t.toLowerCase()] || t).join(", "); } type OnboardingState = @@ -153,7 +150,8 @@ export function VercelOnboardingModal({ } // For marketplace origin, skip env-mapping step and go directly to env-var-sync if (!fromMarketplaceContext) { - const customEnvs = (onboardingData?.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment; + const customEnvs = + (onboardingData?.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment; if (customEnvs) { return "env-mapping"; } @@ -218,14 +216,18 @@ export function VercelOnboardingModal({ environmentId: string; displayName: string; } | null>(null); - const availableEnvSlugsForOnboarding = getAvailableEnvSlugs(hasStagingEnvironment, hasPreviewEnvironment); - const availableEnvSlugsForOnboardingBuildSettings = getAvailableEnvSlugsForBuildSettings(hasStagingEnvironment, hasPreviewEnvironment); + const availableEnvSlugsForOnboarding = getAvailableEnvSlugs( + hasStagingEnvironment, + hasPreviewEnvironment + ); + const availableEnvSlugsForOnboardingBuildSettings = getAvailableEnvSlugsForBuildSettings( + hasStagingEnvironment, + hasPreviewEnvironment + ); const [pullEnvVarsBeforeBuild, setPullEnvVarsBeforeBuild] = useState( () => availableEnvSlugsForOnboardingBuildSettings ); - const [atomicBuilds, setAtomicBuilds] = useState( - () => ["prod"] - ); + const [atomicBuilds, setAtomicBuilds] = useState(() => ["prod"]); const [discoverEnvVars, setDiscoverEnvVars] = useState( () => availableEnvSlugsForOnboardingBuildSettings ); @@ -309,7 +311,13 @@ export function VercelOnboardingModal({ }, 100); } } - }, [isOpen, fromMarketplaceContext, nextUrl, isOnboardingComplete, isGitHubConnectedForOnboarding]); + }, [ + isOpen, + fromMarketplaceContext, + nextUrl, + isOnboardingComplete, + isGitHubConnectedForOnboarding, + ]); useEffect(() => { if (!isOpen) { @@ -336,7 +344,6 @@ export function VercelOnboardingModal({ } switch (state) { - case "loading-projects": loadingStateRef.current = state; if (onDataReload) { @@ -371,19 +378,33 @@ export function VercelOnboardingModal({ }, [isOpen, state, onboardingData?.authInvalid, vercelStagingEnvironment, onDataReload, onClose]); useEffect(() => { - if (!onboardingData?.authInvalid && state === "loading-projects" && onboardingData?.availableProjects !== undefined) { + if ( + !onboardingData?.authInvalid && + state === "loading-projects" && + onboardingData?.availableProjects !== undefined + ) { setState("project-selection"); } }, [state, onboardingData?.availableProjects, onboardingData?.authInvalid]); useEffect(() => { - if (!onboardingData?.authInvalid && state === "loading-env-vars" && onboardingData?.environmentVariables) { + if ( + !onboardingData?.authInvalid && + state === "loading-env-vars" && + onboardingData?.environmentVariables + ) { setState("env-var-sync"); } }, [state, onboardingData?.environmentVariables, onboardingData?.authInvalid]); useEffect(() => { - if (state === "project-selection" && fetcher.data && "success" in fetcher.data && fetcher.data.success && fetcher.state === "idle") { + if ( + state === "project-selection" && + fetcher.data && + "success" in fetcher.data && + fetcher.data.success && + fetcher.state === "idle" + ) { trackOnboarding("vercel onboarding project selected", { vercel_project_name: selectedVercelProject?.name, }); @@ -394,12 +415,20 @@ export function VercelOnboardingModal({ } else if (fetcher.data && "error" in fetcher.data && typeof fetcher.data.error === "string") { setProjectSelectionError(fetcher.data.error); } - }, [state, fetcher.data, fetcher.state, onDataReload, trackOnboarding, selectedVercelProject?.name]); + }, [ + state, + fetcher.data, + fetcher.state, + onDataReload, + trackOnboarding, + selectedVercelProject?.name, + ]); // For marketplace origin, skip env-mapping step useEffect(() => { if (state === "loading-env-mapping" && onboardingData) { - const hasCustomEnvs = (onboardingData.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment; + const hasCustomEnvs = + (onboardingData.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment; if (hasCustomEnvs && !fromMarketplaceContext) { setState("env-mapping"); } else { @@ -410,14 +439,13 @@ export function VercelOnboardingModal({ const secretEnvVars = envVars.filter((v) => v.isSecret); const syncableEnvVars = envVars.filter((v) => !v.isSecret); - const enabledEnvVars = syncableEnvVars.filter( - (v) => shouldSyncEnvVarForAnyEnvironment(syncEnvVarsMapping, v.key) + const enabledEnvVars = syncableEnvVars.filter((v) => + shouldSyncEnvVarForAnyEnvironment(syncEnvVarsMapping, v.key) ); const overlappingEnvVarsCount = enabledEnvVars.filter((v) => existingVars[v.key]).length; - const isSubmitting = - navigation.state === "submitting" || navigation.state === "loading"; + const isSubmitting = navigation.state === "submitting" || navigation.state === "loading"; const actionUrl = vercelResourcePath(organizationSlug, projectSlug, environmentSlug); @@ -529,7 +557,6 @@ export function VercelOnboardingModal({ method: "post", action: actionUrl, }); - }, [vercelStagingEnvironment, envMappingFetcher, actionUrl, trackOnboarding]); const handleBuildSettingsNext = useCallback(() => { @@ -541,7 +568,10 @@ export function VercelOnboardingModal({ const formData = new FormData(); formData.append("action", "complete-onboarding"); - formData.append("vercelStagingEnvironment", vercelStagingEnvironment ? JSON.stringify(vercelStagingEnvironment) : ""); + formData.append( + "vercelStagingEnvironment", + vercelStagingEnvironment ? JSON.stringify(vercelStagingEnvironment) : "" + ); formData.append("pullEnvVarsBeforeBuild", JSON.stringify(pullEnvVarsBeforeBuild)); formData.append("atomicBuilds", JSON.stringify(atomicBuilds)); formData.append("discoverEnvVars", JSON.stringify(discoverEnvVars)); @@ -572,24 +602,52 @@ export function VercelOnboardingModal({ github_app_installed: gitHubAppInstallations.length > 0, }); } - }, [vercelStagingEnvironment, pullEnvVarsBeforeBuild, atomicBuilds, discoverEnvVars, syncEnvVarsMapping, nextUrl, fromMarketplaceContext, isGitHubConnectedForOnboarding, completeOnboardingFetcher, actionUrl, trackOnboarding, capture, organizationSlug, projectSlug, gitHubAppInstallations.length]); - - const handleFinishOnboarding = useCallback((e: React.FormEvent) => { - e.preventDefault(); - const form = e.currentTarget; - const formData = new FormData(form); - completeOnboardingFetcher.submit(formData, { - method: "post", - action: actionUrl, - }); - }, [completeOnboardingFetcher, actionUrl]); + }, [ + vercelStagingEnvironment, + pullEnvVarsBeforeBuild, + atomicBuilds, + discoverEnvVars, + syncEnvVarsMapping, + nextUrl, + fromMarketplaceContext, + isGitHubConnectedForOnboarding, + completeOnboardingFetcher, + actionUrl, + trackOnboarding, + capture, + organizationSlug, + projectSlug, + gitHubAppInstallations.length, + ]); + + const handleFinishOnboarding = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + const form = e.currentTarget; + const formData = new FormData(form); + completeOnboardingFetcher.submit(formData, { + method: "post", + action: actionUrl, + }); + }, + [completeOnboardingFetcher, actionUrl] + ); useEffect(() => { - if (completeOnboardingFetcher.data && typeof completeOnboardingFetcher.data === "object" && "success" in completeOnboardingFetcher.data && completeOnboardingFetcher.data.success && completeOnboardingFetcher.state === "idle") { + if ( + completeOnboardingFetcher.data && + typeof completeOnboardingFetcher.data === "object" && + "success" in completeOnboardingFetcher.data && + completeOnboardingFetcher.data.success && + completeOnboardingFetcher.state === "idle" + ) { if (state === "github-connection") { return; } - if ("redirectTo" in completeOnboardingFetcher.data && typeof completeOnboardingFetcher.data.redirectTo === "string") { + if ( + "redirectTo" in completeOnboardingFetcher.data && + typeof completeOnboardingFetcher.data.redirectTo === "string" + ) { const validRedirect = safeRedirectUrl(completeOnboardingFetcher.data.redirectTo); if (validRedirect) { window.location.href = validRedirect; @@ -632,7 +690,13 @@ export function VercelOnboardingModal({ }, [state, organizationSlug, projectSlug]); useEffect(() => { - if (envMappingFetcher.data && typeof envMappingFetcher.data === "object" && "success" in envMappingFetcher.data && envMappingFetcher.data.success && envMappingFetcher.state === "idle") { + if ( + envMappingFetcher.data && + typeof envMappingFetcher.data === "object" && + "success" in envMappingFetcher.data && + envMappingFetcher.data.success && + envMappingFetcher.state === "idle" + ) { setState("loading-env-vars"); } }, [envMappingFetcher.data, envMappingFetcher.state]); @@ -644,9 +708,7 @@ export function VercelOnboardingModal({ if (customEnvironments.length === 1) { selectedEnv = customEnvironments[0]; } else { - const stagingEnv = customEnvironments.find( - (env) => env.slug.toLowerCase() === "staging" - ); + const stagingEnv = customEnvironments.find((env) => env.slug.toLowerCase() === "staging"); selectedEnv = stagingEnv ?? customEnvironments[0]; } @@ -673,14 +735,17 @@ export function VercelOnboardingModal({ if (isLoadingState) { return ( - { - if (!open && !fromMarketplaceContext) { - if (state as string !== "completed") { - trackOnboarding("vercel onboarding abandoned"); + { + if (!open && !fromMarketplaceContext) { + if ((state as string) !== "completed") { + trackOnboarding("vercel onboarding abandoned"); + } + onClose(); } - onClose(); - } - }}> + }} + > e.preventDefault()}>
@@ -704,18 +769,23 @@ export function VercelOnboardingModal({ const disabledEnvSlugsForBuildSettings = hasStagingEnvironment && !vercelStagingEnvironment - ? ({ stg: "Map a custom Vercel environment to Staging to enable this" } as Partial>) + ? ({ stg: "Map a custom Vercel environment to Staging to enable this" } as Partial< + Record + >) : undefined; return ( - { - if (!open && !fromMarketplaceContext) { - if (state !== "completed") { - trackOnboarding("vercel onboarding abandoned"); + { + if (!open && !fromMarketplaceContext) { + if (state !== "completed") { + trackOnboarding("vercel onboarding abandoned"); + } + onClose(); } - onClose(); - } - }}> + }} + > e.preventDefault()}>
@@ -729,8 +799,8 @@ export function VercelOnboardingModal({
Select Vercel Project - Choose which Vercel project to connect with this Trigger.dev project. - Your API keys will be automatically synced to Vercel. + Choose which Vercel project to connect with this Trigger.dev project. Your API keys + will be automatically synced to Vercel. {availableProjects.length === 0 ? ( @@ -763,13 +833,14 @@ export function VercelOnboardingModal({ )} - {projectSelectionError && ( - {projectSelectionError} - )} + {projectSelectionError && {projectSelectionError}} - Once connected, your TRIGGER_SECRET_KEY will be - automatically synced to Vercel for each environment. + Once connected, your{" "} + + TRIGGER_SECRET_KEY + {" "} + will be automatically synced to Vercel for each environment. } cancelButton={ - } @@ -811,11 +879,13 @@ export function VercelOnboardingModal({ Map Vercel Environment to Staging Select which custom Vercel environment should map to Trigger.dev's Staging - environment. Production and Preview environments are mapped automatically. - If you skip this step, the{" "} - TRIGGER_SECRET_KEY{" "} - will not be installed for the staging environment in Vercel. You can configure this later in - project settings. + environment. Production and Preview environments are mapped automatically. If you + skip this step, the{" "} + + TRIGGER_SECRET_KEY + {" "} + will not be installed for the staging environment in Vercel. You can configure this + later in project settings. (() => ({ value }), [value]); @@ -193,11 +193,7 @@ const ClientTabsContent = React.forwardRef< )); diff --git a/apps/webapp/app/components/primitives/CopyTextLink.tsx b/apps/webapp/app/components/primitives/CopyTextLink.tsx index 33818fa6077..2c117af9b86 100644 --- a/apps/webapp/app/components/primitives/CopyTextLink.tsx +++ b/apps/webapp/app/components/primitives/CopyTextLink.tsx @@ -16,18 +16,12 @@ export function CopyTextLink({ value, className }: CopyTextLinkProps) { onClick={copy} className={cn( "inline-flex cursor-pointer items-center gap-1 text-xs transition-colors", - copied - ? "text-success" - : "text-text-dimmed hover:text-text-bright", + copied ? "text-success" : "text-text-dimmed hover:text-text-bright", className )} > {copied ? "Copied" : "Copy"} - {copied ? ( - - ) : ( - - )} + {copied ? : } ); } diff --git a/apps/webapp/app/components/primitives/DateTime.tsx b/apps/webapp/app/components/primitives/DateTime.tsx index 4dae92731af..41c51cdd74d 100644 --- a/apps/webapp/app/components/primitives/DateTime.tsx +++ b/apps/webapp/app/components/primitives/DateTime.tsx @@ -275,10 +275,10 @@ const DateTimeAccurateInner = ({ return hideDate ? formatTimeOnly(realDate, displayTimeZone, locales, hour12) : realPrevDate - ? isSameDay(realDate, realPrevDate) - ? formatTimeOnly(realDate, displayTimeZone, locales, hour12) - : formatDateTimeAccurate(realDate, displayTimeZone, locales, hour12) - : formatDateTimeAccurate(realDate, displayTimeZone, locales, hour12); + ? isSameDay(realDate, realPrevDate) + ? formatTimeOnly(realDate, displayTimeZone, locales, hour12) + : formatDateTimeAccurate(realDate, displayTimeZone, locales, hour12) + : formatDateTimeAccurate(realDate, displayTimeZone, locales, hour12); }, [realDate, displayTimeZone, locales, hour12, hideDate, previousDate]); if (!showTooltip) diff --git a/apps/webapp/app/components/primitives/Dialog.tsx b/apps/webapp/app/components/primitives/Dialog.tsx index 48fc59cfa78..bf485bbf1a2 100644 --- a/apps/webapp/app/components/primitives/Dialog.tsx +++ b/apps/webapp/app/components/primitives/Dialog.tsx @@ -122,5 +122,5 @@ export { DialogTitle, DialogDescription, DialogPortal, - DialogOverlay + DialogOverlay, }; diff --git a/apps/webapp/app/components/primitives/InputNumberStepper.tsx b/apps/webapp/app/components/primitives/InputNumberStepper.tsx index f4aafd5cae1..b434bb1d30a 100644 --- a/apps/webapp/app/components/primitives/InputNumberStepper.tsx +++ b/apps/webapp/app/components/primitives/InputNumberStepper.tsx @@ -67,7 +67,7 @@ export function InputNumberStepper({ const isMaxDisabled = max !== undefined && !Number.isNaN(numericValue) && numericValue >= max; function clamp(val: number): number { - if (Number.isNaN(val)) return typeof value === "number" ? value : min ?? 0; + if (Number.isNaN(val)) return typeof value === "number" ? value : (min ?? 0); let next = val; if (min !== undefined) next = Math.max(min, next); if (max !== undefined) next = Math.min(max, next); diff --git a/apps/webapp/app/components/primitives/MiddleTruncate.tsx b/apps/webapp/app/components/primitives/MiddleTruncate.tsx index c116205aed9..45915c2521d 100644 --- a/apps/webapp/app/components/primitives/MiddleTruncate.tsx +++ b/apps/webapp/app/components/primitives/MiddleTruncate.tsx @@ -139,16 +139,9 @@ export function MiddleTruncate({ text, className }: MiddleTruncateProps) { }, [calculateTruncation]); const content = ( - + {/* Hidden span for measuring text width */} -
); } - diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx index 2e4305d4237..d9a95560951 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx @@ -80,19 +80,22 @@ const schema = z.object({ if (i === "true") return true; return false; }, z.boolean()), - environmentIds: z.preprocess((i) => { - if (typeof i === "string") return [i]; - - if (Array.isArray(i)) { - const ids = i.filter((v) => typeof v === "string" && v !== ""); - if (ids.length === 0) { - return; + environmentIds: z.preprocess( + (i) => { + if (typeof i === "string") return [i]; + + if (Array.isArray(i)) { + const ids = i.filter((v) => typeof v === "string" && v !== ""); + if (ids.length === 0) { + return; + } + return ids; } - return ids; - } - return; - }, z.array(z.string(), { required_error: "At least one environment is required" })), + return; + }, + z.array(z.string(), { required_error: "At least one environment is required" }) + ), variables: z.preprocess((i) => { if (!Array.isArray(i)) { return []; @@ -222,7 +225,9 @@ export default function Page() { // TODO for no we only support branch-specific env vars for Preview environments // Mostly to keep the UX for setting consistent env-vars across Dev/Staging/Prod easier - const previewBranches = environments.filter((env) => env.type === "PREVIEW" && env.parentEnvironmentId !== null); + const previewBranches = environments.filter( + (env) => env.type === "PREVIEW" && env.parentEnvironmentId !== null + ); const nonBranchEnvironments = environments.filter((env) => env.parentEnvironmentId === null); const selectedEnvironments = environments.filter((env) => selectedEnvironmentIds.has(env.id)); const previewIsSelected = selectedEnvironments.some((env) => env.type === "PREVIEW"); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx index a84e445539d..62a14ec22f8 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx @@ -2,10 +2,7 @@ import { parse } from "@conform-to/zod"; import { BellAlertIcon } from "@heroicons/react/20/solid"; import { type MetaFunction, useFetcher, useRevalidator } from "@remix-run/react"; import { type ActionFunctionArgs, json, type LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { - IconAlarmSnooze as IconAlarmSnoozeBase, - IconCircleDotted, -} from "@tabler/icons-react"; +import { IconAlarmSnooze as IconAlarmSnoozeBase, IconCircleDotted } from "@tabler/icons-react"; import { ErrorId } from "@trigger.dev/core/v3/isomorphic"; import { isPast } from "date-fns"; import { AnimatePresence, motion } from "framer-motion"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.limits/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.limits/route.tsx index 782d2eef8d4..e27b11b7545 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.limits/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.limits/route.tsx @@ -335,9 +335,7 @@ function RateLimitsSection({ /> - {showSelfServe ? ( - Upgrade - ) : null} + {showSelfServe ? Upgrade : null} @@ -557,9 +555,7 @@ function QuotasSection({ Limit Current Source - {showSelfServe ? ( - Upgrade - ) : null} + {showSelfServe ? Upgrade : null} @@ -738,9 +734,7 @@ function FeaturesSection({ Feature Status - {showSelfServe ? ( - Upgrade - ) : null} + {showSelfServe ? Upgrade : null} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs/route.tsx index c913623ebab..f0ecd687a46 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs/route.tsx @@ -137,7 +137,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { const plan = await getCurrentPlan(project.organizationId); const retentionLimitDays = plan?.v3Subscription?.plan?.limits.logRetentionDays.number ?? 30; - const logsClickhouse = await clickhouseFactory.getClickhouseForOrganization(project.organizationId, "logs"); + const logsClickhouse = await clickhouseFactory.getClickhouseForOrganization( + project.organizationId, + "logs" + ); const presenter = new LogsListPresenter($replica, logsClickhouse); const listPromise = presenter diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models.$modelId/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models.$modelId/route.tsx index f4248aa64b6..94b4bfc3144 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models.$modelId/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models.$modelId/route.tsx @@ -33,11 +33,7 @@ import { requireUserId } from "~/services/session.server"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { useEnvironment } from "~/hooks/useEnvironment"; -import { - EnvironmentParamSchema, - v3ModelComparePath, - v3ModelsPath, -} from "~/utils/pathBuilder"; +import { EnvironmentParamSchema, v3ModelComparePath, v3ModelsPath } from "~/utils/pathBuilder"; import { formatModelPrice, formatTokenCount, @@ -68,7 +64,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { throw new Response("Environment not found", { status: 404 }); } - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(project.organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + project.organizationId, + "standard" + ); const presenter = new ModelRegistryPresenter(clickhouse); const model = await presenter.getModelDetail(modelId); @@ -101,12 +100,36 @@ function escapeTSQL(value: string): string { return value.replace(/'/g, "''"); } -function bignumberConfig(column: string, opts?: { aggregation?: "sum" | "avg" | "first"; suffix?: string; abbreviate?: boolean }): QueryWidgetConfig { - return { type: "bignumber", column, aggregation: opts?.aggregation ?? "sum", abbreviate: opts?.abbreviate ?? false, suffix: opts?.suffix }; +function bignumberConfig( + column: string, + opts?: { aggregation?: "sum" | "avg" | "first"; suffix?: string; abbreviate?: boolean } +): QueryWidgetConfig { + return { + type: "bignumber", + column, + aggregation: opts?.aggregation ?? "sum", + abbreviate: opts?.abbreviate ?? false, + suffix: opts?.suffix, + }; } -function chartConfig(opts: { chartType: "bar" | "line"; xAxisColumn: string; yAxisColumns: string[]; aggregation?: "sum" | "avg" }): QueryWidgetConfig { - return { type: "chart", chartType: opts.chartType, xAxisColumn: opts.xAxisColumn, yAxisColumns: opts.yAxisColumns, groupByColumn: null, stacked: false, sortByColumn: null, sortDirection: "asc", aggregation: opts.aggregation ?? "sum" }; +function chartConfig(opts: { + chartType: "bar" | "line"; + xAxisColumn: string; + yAxisColumns: string[]; + aggregation?: "sum" | "avg"; +}): QueryWidgetConfig { + return { + type: "chart", + chartType: opts.chartType, + xAxisColumn: opts.xAxisColumn, + yAxisColumns: opts.yAxisColumns, + groupByColumn: null, + stacked: false, + sortByColumn: null, + sortDirection: "asc", + aggregation: opts.aggregation ?? "sum", + }; } type Tab = "overview" | "global" | "usage"; @@ -251,8 +274,8 @@ function CostEstimator({
- Input: {formatModelCost(inputCost)} ({formatTokenCount(inputTokens * numCalls)}{" "} - tokens x {formatModelPrice(inputPrice)}/1M) + Input: {formatModelCost(inputCost)} ({formatTokenCount(inputTokens * numCalls)} tokens + x {formatModelPrice(inputPrice)}/1M)
Output: {formatModelCost(outputCost)} ({formatTokenCount(outputTokens * numCalls)}{" "} @@ -300,17 +323,13 @@ function OverviewTab({ {model.contextWindow && ( Context Window - - {formatTokenCount(model.contextWindow)} tokens - + {formatTokenCount(model.contextWindow)} tokens )} {model.maxOutputTokens && ( Max Output - - {formatTokenCount(model.maxOutputTokens)} tokens - + {formatTokenCount(model.maxOutputTokens)} tokens )} {model.features.length > 0 && ( @@ -342,25 +361,18 @@ function OverviewTab({ Input - - {formatModelPrice(model.inputPrice)} / 1M tokens - + {formatModelPrice(model.inputPrice)} / 1M tokens Output - - {formatModelPrice(model.outputPrice)} / 1M tokens - + {formatModelPrice(model.outputPrice)} / 1M tokens {model.pricingTiers.length > 1 && (

All pricing tiers

{model.pricingTiers.map((tier) => ( -
+
{tier.name} {tier.isDefault && ( @@ -462,7 +474,12 @@ function GlobalMetricsTab({ widgetKey={`${modelName}-ttfc-time`} title="TTFC over time" query={`SELECT timeBucket(), round(quantilesMerge(0.5)(ttfc_quantiles)[1], 0) AS ttfc_p50, round(quantilesMerge(0.9)(ttfc_quantiles)[1], 0) AS ttfc_p90 FROM llm_models WHERE response_model = '${escapeTSQL(modelName)}' GROUP BY timeBucket ORDER BY timeBucket`} - config={chartConfig({ chartType: "line", xAxisColumn: "timebucket", yAxisColumns: ["ttfc_p50", "ttfc_p90"], aggregation: "avg" })} + config={chartConfig({ + chartType: "line", + xAxisColumn: "timebucket", + yAxisColumns: ["ttfc_p50", "ttfc_p90"], + aggregation: "avg", + })} {...widgetProps} />
@@ -546,7 +563,11 @@ function YourUsageTab({ widgetKey={`${modelName}-user-cost-time`} title="Cost over time" query={`SELECT timeBucket(), sum(total_cost) AS cost FROM llm_metrics WHERE response_model = '${escapeTSQL(modelName)}' GROUP BY timeBucket ORDER BY timeBucket`} - config={chartConfig({ chartType: "bar", xAxisColumn: "timebucket", yAxisColumns: ["cost"] })} + config={chartConfig({ + chartType: "bar", + xAxisColumn: "timebucket", + yAxisColumns: ["cost"], + })} {...widgetProps} />
@@ -555,7 +576,11 @@ function YourUsageTab({ widgetKey={`${modelName}-user-tokens-time`} title="Tokens over time" query={`SELECT timeBucket(), sum(input_tokens) AS input_tokens, sum(output_tokens) AS output_tokens FROM llm_metrics WHERE response_model = '${escapeTSQL(modelName)}' GROUP BY timeBucket ORDER BY timeBucket`} - config={chartConfig({ chartType: "bar", xAxisColumn: "timebucket", yAxisColumns: ["input_tokens", "output_tokens"] })} + config={chartConfig({ + chartType: "bar", + xAxisColumn: "timebucket", + yAxisColumns: ["input_tokens", "output_tokens"], + })} {...widgetProps} />
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models._index/route.tsx index 2b86eb1cd45..328fb9c44ea 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models._index/route.tsx @@ -91,7 +91,11 @@ import { requireUserId } from "~/services/session.server"; import { useEnvironment } from "~/hooks/useEnvironment"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; -import { EnvironmentParamSchema, v3BuiltInDashboardPath, v3ModelComparePath } from "~/utils/pathBuilder"; +import { + EnvironmentParamSchema, + v3BuiltInDashboardPath, + v3ModelComparePath, +} from "~/utils/pathBuilder"; import { formatModelPrice, formatTokenCount, @@ -126,7 +130,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { throw new Response("Environment not found", { status: 404 }); } - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(project.organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + project.organizationId, + "standard" + ); const presenter = new ModelRegistryPresenter(clickhouse); const catalog = await presenter.getModelCatalog(); @@ -191,10 +198,7 @@ export function shouldRevalidate({ params.sort(); return params.toString(); }; - if ( - currentUrl.pathname === nextUrl.pathname && - normalize(currentUrl) === normalize(nextUrl) - ) { + if (currentUrl.pathname === nextUrl.pathname && normalize(currentUrl) === normalize(nextUrl)) { return false; } return defaultShouldRevalidate; @@ -1047,7 +1051,7 @@ function DetailYourUsageTab({ projectId, environmentId, scope: "environment" as const, - period: range.from && range.to ? null : range.period ?? "7d", + period: range.from && range.to ? null : (range.period ?? "7d"), from: range.from ?? null, to: range.to ?? null, }; @@ -1241,7 +1245,7 @@ function YourModelsTab({ projectId, environmentId, scope: "environment" as const, - period: from && to ? null : period ?? "7d", + period: from && to ? null : (period ?? "7d"), from, to, }; @@ -1254,7 +1258,11 @@ function YourModelsTab({ widgetKey="your-models-cost-time" title="Cost over time" query={`SELECT timeBucket(), sum(total_cost) AS cost FROM llm_metrics GROUP BY timeBucket ORDER BY timeBucket`} - config={chartConfig({ chartType: "bar", xAxisColumn: "timebucket", yAxisColumns: ["cost"] })} + config={chartConfig({ + chartType: "bar", + xAxisColumn: "timebucket", + yAxisColumns: ["cost"], + })} {...widgetProps} />
@@ -1277,7 +1285,11 @@ function YourModelsTab({ widgetKey="your-models-calls-over-time" title="Calls over time" query={`SELECT timeBucket(), count() AS calls FROM llm_metrics GROUP BY timeBucket ORDER BY timeBucket`} - config={chartConfig({ chartType: "bar", xAxisColumn: "timebucket", yAxisColumns: ["calls"] })} + config={chartConfig({ + chartType: "bar", + xAxisColumn: "timebucket", + yAxisColumns: ["calls"], + })} {...widgetProps} />
@@ -1287,8 +1299,8 @@ function YourModelsTab({ {usage.length === 0 ? (

- No model usage in this environment yet. Models you call from your tasks will appear here - with usage metrics. + No model usage in this environment yet. Models you call from your tasks will appear + here with usage metrics.

diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/ExamplesContent.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/ExamplesContent.tsx index f0c3c1d616f..05b4f4d9b62 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/ExamplesContent.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/ExamplesContent.tsx @@ -120,7 +120,8 @@ LIMIT 100`, }, { title: "LLM cost by model (past 7d)", - description: "Total cost, input tokens, and output tokens grouped by model over the last 7 days.", + description: + "Total cost, input tokens, and output tokens grouped by model over the last 7 days.", query: `SELECT response_model, SUM(total_cost) AS total_cost, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TRQLGuideContent.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TRQLGuideContent.tsx index 35acfd266fc..bfe7bf08f29 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TRQLGuideContent.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TRQLGuideContent.tsx @@ -1206,4 +1206,3 @@ ORDER BY run_count DESC`,
); } - diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TableSchemaContent.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TableSchemaContent.tsx index 41c17e6d3f7..e8ce98f1d49 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TableSchemaContent.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TableSchemaContent.tsx @@ -77,4 +77,3 @@ export function TableSchemaContent() { ); } - diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx index b6afe46be4f..9696e5f684b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx @@ -315,8 +315,8 @@ export default function Page() { environment.running === environment.concurrencyLimit * environment.burstFactor ? "limit" : environment.running > environment.concurrencyLimit - ? "burst" - : "within"; + ? "burst" + : "within"; const limitClassName = limitStatus === "burst" ? "text-warning" : limitStatus === "limit" ? "text-error" : undefined; @@ -441,9 +441,7 @@ export default function Page() { @@ -676,7 +674,6 @@ export default function Page() { )} - ) : (
@@ -907,7 +904,7 @@ function QueueOverrideConcurrencyButton({ const isLoading = Boolean( navigation.formData?.get("action") === "queue-override" || - navigation.formData?.get("action") === "queue-remove-override" + navigation.formData?.get("action") === "queue-remove-override" ); return ( diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.regions/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.regions/route.tsx index 2d754309a3d..d006a853e5e 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.regions/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.regions/route.tsx @@ -362,88 +362,88 @@ function SetDefaultDialog({
- - Are you sure you want to set {newDefaultRegion.name} as your new default region? - + + Are you sure you want to set {newDefaultRegion.name} as your new default region? + -
-
-
- Current default -
-
- {currentDefaultRegion?.name ?? "–"} -
-
- - {currentDefaultRegion?.cloudProvider ? ( - <> - - {cloudProviderTitle(currentDefaultRegion.cloudProvider)} - - ) : ( - "–" - )} - -
-
- - {currentDefaultRegion?.location ? ( - - ) : null} - {currentDefaultRegion?.description ?? "–"} - +
+
+
+ Current default +
+
+ {currentDefaultRegion?.name ?? "–"} +
+
+ + {currentDefaultRegion?.cloudProvider ? ( + <> + + {cloudProviderTitle(currentDefaultRegion.cloudProvider)} + + ) : ( + "–" + )} + +
+
+ + {currentDefaultRegion?.location ? ( + + ) : null} + {currentDefaultRegion?.description ?? "–"} + +
-
- {/* Middle column with arrow */} -
-
- + {/* Middle column with arrow */} +
+
+ +
-
- {/* Right column */} -
-
- New default -
-
- {newDefaultRegion.name} -
-
- - {newDefaultRegion.cloudProvider ? ( - <> - - {cloudProviderTitle(newDefaultRegion.cloudProvider)} - - ) : ( - "–" - )} - -
-
- - {newDefaultRegion.location ? ( - - ) : null} - {newDefaultRegion.description ?? "–"} - + {/* Right column */} +
+
+ New default +
+
+ {newDefaultRegion.name} +
+
+ + {newDefaultRegion.cloudProvider ? ( + <> + + {cloudProviderTitle(newDefaultRegion.cloudProvider)} + + ) : ( + "–" + )} + +
+
+ + {newDefaultRegion.location ? ( + + ) : null} + {newDefaultRegion.description ?? "–"} + +
-
- - Runs triggered from now on will execute in "{newDefaultRegion.name}", unless you{" "} - override when triggering. - + + Runs triggered from now on will execute in "{newDefaultRegion.name}", unless you{" "} + override when triggering. +
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index de23b935cd6..462c5ef293a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -1269,8 +1269,8 @@ function TimelineView({ index === 0 ? "ml-1" : index === tickCount - 1 - ? "-ml-1 -translate-x-full" - : "-translate-x-1/2" + ? "-ml-1 -translate-x-full" + : "-translate-x-1/2" )} > {formatDurationMilliseconds(ms, { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/shouldRevalidateRunsList.ts b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/shouldRevalidateRunsList.ts index 69e0ce6b637..e8c1f19144a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/shouldRevalidateRunsList.ts +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/shouldRevalidateRunsList.ts @@ -26,9 +26,7 @@ export function searchParamsEqualIgnoringBulkInspectorUiState( current: URLSearchParams, next: URLSearchParams ) { - return ( - canonicalRunsListDataSearchParams(current) === canonicalRunsListDataSearchParams(next) - ); + return canonicalRunsListDataSearchParams(current) === canonicalRunsListDataSearchParams(next); } /** True when navigation should show the runs table loading state (excludes bulk-inspector UI toggles). */ diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/useRunsLiveReload.ts b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/useRunsLiveReload.ts index 3ba1c972479..a8d26ac350b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/useRunsLiveReload.ts +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/useRunsLiveReload.ts @@ -91,7 +91,9 @@ function useNewRunsDetection({ isLoading: boolean; }) { const pollTickRef = useRef(0); - const [knownNewestRunMs, setKnownNewestRunMs] = useState(() => maxCreatedAtMs(runs) ?? Date.now()); + const [knownNewestRunMs, setKnownNewestRunMs] = useState( + () => maxCreatedAtMs(runs) ?? Date.now() + ); const [newRunsCount, setNewRunsCount] = useState(0); const shouldPollForNewRuns = hasAnyRuns && !isLoading && newRunsCount < 100; @@ -209,8 +211,7 @@ export function useRunsLiveReload({ const hasActiveRuns = activeRunIdsParam.length > 0; const runsResourcesBasePath = useMemo( - () => - `/resources/orgs/${organizationSlug}/projects/${projectSlug}/env/${environmentSlug}/runs`, + () => `/resources/orgs/${organizationSlug}/projects/${projectSlug}/env/${environmentSlug}/runs`, [organizationSlug, projectSlug, environmentSlug] ); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx index b9bd387159b..7d07cb92872 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx @@ -121,8 +121,8 @@ export default function Page() { session.closedAt != null ? "CLOSED" : session.expiresAt != null && new Date(session.expiresAt).getTime() < Date.now() - ? "EXPIRED" - : "ACTIVE"; + ? "EXPIRED" + : "ACTIVE"; const displayId = session.externalId ?? session.friendlyId; const sessionsPath = v3SessionsPath(organization, project, environment); @@ -423,8 +423,8 @@ function RawConversationView({ totalChunks === 0 ? "cursor-not-allowed opacity-50" : copied - ? "text-success hover:cursor-pointer" - : "text-text-dimmed hover:cursor-pointer hover:text-text-bright" + ? "text-success hover:cursor-pointer" + : "text-text-dimmed hover:cursor-pointer hover:text-text-bright" )} > {copied ? : } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx index d66bdb0e0da..e56162731da 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx @@ -18,10 +18,7 @@ import { InputGroup } from "~/components/primitives/InputGroup"; import { Label } from "~/components/primitives/Label"; import { SpinnerWhite } from "~/components/primitives/Spinner"; import { useProject } from "~/hooks/useProject"; -import { - redirectWithErrorMessage, - redirectWithSuccessMessage, -} from "~/models/message.server"; +import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; import { ProjectSettingsService } from "~/services/projectSettings.server"; import { logger } from "~/services/logger.server"; import { requireUserId } from "~/services/session.server"; @@ -66,7 +63,10 @@ export const action: ActionFunction = async ({ request, params }) => { const userId = await requireUserId(request); const { organizationSlug, projectParam } = params; if (!organizationSlug || !projectParam) { - return json({ errors: { body: "organizationSlug and projectParam are required" } }, { status: 400 }); + return json( + { errors: { body: "organizationSlug and projectParam are required" } }, + { status: 400 } + ); } const formData = await request.formData(); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx index cc85dbb4acc..1777d50e197 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx @@ -7,7 +7,11 @@ import * as Property from "~/components/primitives/PropertyTable"; import { AdminDebugTooltip } from "~/components/admin/debugTooltip"; import { useProject } from "~/hooks/useProject"; import { requireUserId } from "~/services/session.server"; -import { EnvironmentParamSchema, v3ProjectSettingsGeneralPath, v3ProjectSettingsIntegrationsPath } from "~/utils/pathBuilder"; +import { + EnvironmentParamSchema, + v3ProjectSettingsGeneralPath, + v3ProjectSettingsIntegrationsPath, +} from "~/utils/pathBuilder"; export const meta: MetaFunction = () => { return [ diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.dashboard/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.dashboard/route.tsx index 65363a3ef11..2d9ac6cd850 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.dashboard/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.dashboard/route.tsx @@ -115,66 +115,66 @@ export default function TasksDashboardPage() {
Tasks overview
- }> - - {(s) => ( - - )} - - - }> - - {(s) => ( - - )} - - - }> - - {(s) => ( - - )} - - + }> + + {(s) => ( + + )} + + + }> + + {(s) => ( + + )} + + + }> + + {(s) => ( + + )} + +
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.scheduled.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.scheduled.$taskParam/route.tsx index d82ebea7a5d..548bf6bf586 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.scheduled.$taskParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.scheduled.$taskParam/route.tsx @@ -149,7 +149,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { from: time.from, to: time.to, }) - .catch(() => ({ data: [], statuses: [] } satisfies TaskActivity)); + .catch(() => ({ data: [], statuses: [] }) satisfies TaskActivity); const pageRaw = parseFiniteInt(url.searchParams.get("page")); const schedulesPage = pageRaw !== undefined && pageRaw > 0 ? pageRaw : 1; @@ -653,8 +653,7 @@ function ScheduleSheet({ // Only show the loading spinner when we actually lack good data — // background reloads (e.g. after enable/disable) keep the inspector // visible with its current values until the fresh data arrives. - const isDetailLoading = - isStaleSchedule || (!!openScheduleId && detailFetcher.data === undefined); + const isDetailLoading = isStaleSchedule || (!!openScheduleId && detailFetcher.data === undefined); // Distinct from loading: the loader has resolved and the schedule is // genuinely gone (returned `null`, e.g. deleted externally). const isScheduleMissing = diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx index a6608488e41..1e3d7500589 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx @@ -104,7 +104,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { from: time.from, to: time.to, }) - .catch(() => ({ data: [], statuses: [] } satisfies TaskActivity)); + .catch(() => ({ data: [], statuses: [] }) satisfies TaskActivity); const runList = new NextRunListPresenter($replica, clickhouse) .call(project.organizationId, environment.id, { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/AIPayloadTabContent.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/AIPayloadTabContent.tsx index 0e0e0feaba5..c5ec45b4efb 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/AIPayloadTabContent.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/AIPayloadTabContent.tsx @@ -310,10 +310,10 @@ export function AIPayloadTabContent({ {isLoading ? "AI is thinking…" : lastResult === "success" - ? "Payload generated" - : lastResult === "error" - ? "Generation failed" - : "AI response"} + ? "Payload generated" + : lastResult === "error" + ? "Generation failed" + : "AI response"}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx index 00dcb37f4e5..77cd3bf63a5 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx @@ -1012,11 +1012,7 @@ function ScheduledTaskForm({ }); return ( -
+
-
- - {task.taskIdentifier} -
-
- - - - setTimestampValue(val)} - granularity="second" - showNowButton - variant="small" - utc - /> - - This is the timestamp of the CRON, it will come through to your run in the payload. - - {timestamp.error} - - - - - setLastTimestampValue(val)} - granularity="second" - showNowButton - showClearButton - variant="small" - utc - /> - - This is the timestamp of the previous run. You can use this in your code to find new - data since the previous run. - - {lastTimestamp.error} - - - - - - The Timestamp and Last timestamp are in UTC so this just changes the timezone string - that comes through in the payload. - - {timezone.error} - - - - setExternalIdValue(e.target.value)} - variant="small" - /> +
+ + {task.taskIdentifier} +
+
+ + + + setTimestampValue(val)} + granularity="second" + showNowButton + variant="small" + utc + /> + + This is the timestamp of the CRON, it will come through to your run in the + payload. + + {timestamp.error} + + + + + setLastTimestampValue(val)} + granularity="second" + showNowButton + showClearButton + variant="small" + utc + /> + + This is the timestamp of the previous run. You can use this in your code to find + new data since the previous run. + + {lastTimestamp.error} + + + + + + The Timestamp and Last timestamp are in UTC so this just changes the timezone + string that comes through in the payload. + + {timezone.error} + + + + setExternalIdValue(e.target.value)} + variant="small" + /> + + Optionally, you can specify your own IDs (like a user ID) and then use it inside + the run function of your task.{" "} + Read the docs. + + {externalId.error} + +
- Optionally, you can specify your own IDs (like a user ID) and then use it inside the - run function of your task.{" "} - Read the docs. + Options enable you to control the execution behavior of your task.{" "} + Read the docs. - {externalId.error} - -
- - Options enable you to control the execution behavior of your task.{" "} - Read the docs. - - - - - Overrides the machine preset. - {machine.error} - - - - - {disableVersionSelection ? ( - Only the latest version is available in the development environment. - ) : ( - Runs task on a specific version. - )} - {version.error} - - {regionItems.length > 1 && ( - - )} - - - {allowArbitraryQueues ? ( - setQueueValue(e.target.value)} - /> - ) : ( + + + {disableVersionSelection ? ( + Only the latest version is available in the development environment. + ) : ( + Runs task on a specific version. + )} + {version.error} + + {regionItems.length > 1 && ( + + + {/* Our Select primitive uses Ariakit under the hood, which treats + value={undefined} as uncontrolled, keeping stale internal state when + switching environments. The key forces a remount so it reinitializes + with the correct defaultValue. */} + + {isDev ? ( + Region is not available in the development environment. + ) : ( + Overrides the region for this run. + )} + {region.error} + )} - Assign run to a specific queue. - {queue.error} - - - - - Add tags to easily filter runs. - {tags.error} - - - - - setMaxAttemptsValue(e.target.value ? parseInt(e.target.value) : undefined) - } - onKeyDown={(e) => { - // only allow entering integers > 1 - if (["-", "+", ".", "e", "E"].includes(e.key)) { - e.preventDefault(); - } - }} - onBlur={(e) => { - const value = parseInt(e.target.value); - if (value < 1 && e.target.value !== "") { - e.target.value = "1"; + + + {allowArbitraryQueues ? ( + setQueueValue(e.target.value)} + /> + ) : ( + + )} + Assign run to a specific queue. + {queue.error} + + + + + Add tags to easily filter runs. + {tags.error} + + + + + setMaxAttemptsValue(e.target.value ? parseInt(e.target.value) : undefined) } - }} - /> - Retries failed runs up to the specified number of attempts. - {maxAttempts.error} - - - - - Overrides the maximum compute time limit for the run. - {maxDurationSeconds.error} - - - - - {idempotencyKey.error} - - Specify an idempotency key to ensure that a task is only triggered once with the - same key. - - - - - - Keys expire after 30 days by default. - - {idempotencyKeyTTLSeconds.error} - - - - - setConcurrencyKeyValue(e.target.value)} - /> - - Limits concurrency by creating a separate queue for each value of the key. - - {concurrencyKey.error} - - - - - Sets the priority of the run. Higher values mean higher priority. - {prioritySeconds.error} - - - - - Expires the run if it hasn't started within the TTL. - {ttlSeconds.error} - -
+ onKeyDown={(e) => { + // only allow entering integers > 1 + if (["-", "+", ".", "e", "E"].includes(e.key)) { + e.preventDefault(); + } + }} + onBlur={(e) => { + const value = parseInt(e.target.value); + if (value < 1 && e.target.value !== "") { + e.target.value = "1"; + } + }} + /> + Retries failed runs up to the specified number of attempts. + {maxAttempts.error} +
+ + + + Overrides the maximum compute time limit for the run. + {maxDurationSeconds.error} + + + + + {idempotencyKey.error} + + Specify an idempotency key to ensure that a task is only triggered once with the + same key. + + + + + + Keys expire after 30 days by default. + + {idempotencyKeyTTLSeconds.error} + + + + + setConcurrencyKeyValue(e.target.value)} + /> + + Limits concurrency by creating a separate queue for each value of the key. + + {concurrencyKey.error} + + + + + Sets the priority of the run. Higher values mean higher priority. + {prioritySeconds.error} + + + + + Expires the run if it hasn't started within the TTL. + {ttlSeconds.error} + +
{/* Toolbar overlay — same grid cell, sits above scrolling form. Outer diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx index 54102b86b54..7f762fd0303 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx @@ -233,7 +233,6 @@ export default function Page() { )} -
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx index 0eb496880a4..74ab939119b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx @@ -51,11 +51,7 @@ import { logger } from "~/services/logger.server"; import { requireUser, requireUserId } from "~/services/session.server"; import { cn } from "~/utils/cn"; import { extractDomain, faviconUrl as buildFaviconUrl } from "~/utils/favicon"; -import { - OrganizationParamsSchema, - organizationSettingsPath, - rootPath, -} from "~/utils/pathBuilder"; +import { OrganizationParamsSchema, organizationSettingsPath, rootPath } from "~/utils/pathBuilder"; export const meta: MetaFunction = () => { return [ @@ -426,7 +422,7 @@ function LogoForm({ const navigation = useNavigation(); const avatar = navigation.formData - ? avatarFromFormData(navigation.formData) ?? organization.avatar + ? (avatarFromFormData(navigation.formData) ?? organization.avatar) : organization.avatar; const hex = @@ -559,8 +555,7 @@ function LogoForm({ "border-charcoal-775 hover:border-charcoal-600" )} style={{ - borderColor: - avatar.type === "icon" && avatar.name === name ? hex : undefined, + borderColor: avatar.type === "icon" && avatar.name === name ? hex : undefined, }} > - + - + {avatar.type === "icon" && } {defaultAvatarColors.map((color) => (
- +
@@ -649,11 +629,7 @@ function ActiveConnectionState({ Auto-create memberships for first-time SSO sign-ins from your verified domains.
- +
@@ -661,23 +637,20 @@ function ActiveConnectionState({ Default role for JIT provisioned users - Role assigned to new users created via JIT provisioning. Owner is reserved - and cannot be granted automatically. + Role assigned to new users created via JIT provisioning. Owner is reserved and cannot + be granted automatically.
value={draftJitRoleId} setValue={(v) => onChangeJitRole(v === NULL_ROLE_VALUE ? null : v)} - items={[ - { id: NULL_ROLE_VALUE, name: "None", description: "" }, - ...jitRoles, - ]} + items={[{ id: NULL_ROLE_VALUE, name: "None", description: "" }, ...jitRoles]} variant="tertiary/small" dropdownIcon text={(v) => v === NULL_ROLE_VALUE ? "None" - : jitRoles.find((r) => r.id === v)?.name ?? "Select a role" + : (jitRoles.find((r) => r.id === v)?.name ?? "Select a role") } > {(items) => @@ -706,11 +679,7 @@ function ActiveConnectionState({ > Open admin portal -
@@ -719,20 +688,14 @@ function ActiveConnectionState({ ); } -function PortalLinkDialog({ - url, - onClose, -}: { - url: string | null; - onClose: () => void; -}) { +function PortalLinkDialog({ url, onClose }: { url: string | null; onClose: () => void }) { return ( (open ? undefined : onClose())}> Admin portal link - This link is active for 5 minutes — copy it and share it with your IT contact via - whatever channel you prefer. + This link is active for 5 minutes — copy it and share it with your IT contact via whatever + channel you prefer.
{url ?? ""} @@ -788,13 +751,13 @@ function EnforceConfirmDialog({ Enable SSO enforcement for {orgTitle}? - Once enabled, users whose email domain matches your verified domains will be - redirected to your identity provider to sign in. They will no longer be able to use - magic link, GitHub, or Google via that domain. + Once enabled, users whose email domain matches your verified domains will be redirected to + your identity provider to sign in. They will no longer be able to use magic link, GitHub, + or Google via that domain.

- Users with non-matching emails (e.g. contractors with personal emails) will continue - to use existing methods. + Users with non-matching emails (e.g. contractors with personal emails) will continue to + use existing methods.
- ) + )) ) : triggerButton ? ( cloneElement(triggerButton, { disabled: true, tooltip: noBillingTooltip }) ) : ( diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx index 1ad36854dcf..b80af0568e1 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx @@ -89,8 +89,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { firstDayOfNextMonth.setUTCDate(1); firstDayOfNextMonth.setUTCHours(0, 0, 0, 0); - const shouldLoadRegions = - !!projectParam && !!environment && environment.type !== "DEVELOPMENT"; + const shouldLoadRegions = !!projectParam && !!environment && environment.type !== "DEVELOPMENT"; const [plan, usage, customDashboards, regions] = await Promise.all([ getCurrentPlan(organization.id), @@ -124,14 +123,14 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { const dashboardLimit = typeof metricDashboardsLimitValue === "number" ? metricDashboardsLimitValue - : metricDashboardsLimitValue?.number ?? 3; + : (metricDashboardsLimitValue?.number ?? 3); // Derive widget-per-dashboard limit from plan, fallback to 16 const metricWidgetsLimitValue = plan?.v3Subscription?.plan?.limits?.metricWidgetsPerDashboard; const widgetLimitPerDashboard = typeof metricWidgetsLimitValue === "number" ? metricWidgetsLimitValue - : metricWidgetsLimitValue?.number ?? 16; + : (metricWidgetsLimitValue?.number ?? 16); // Compute widget counts per dashboard from layout JSON const customDashboardsWithWidgetCount = customDashboards.map((d) => { 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 39cde0cf2d0..a7581c9747d 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 @@ -361,7 +361,10 @@ export default function Page() { return ( - +
} @@ -452,9 +455,7 @@ export default function Page() { shuffledGoals.indexOf(v) + 1) - )} + value={JSON.stringify(selectedGoals.map((v) => shuffledGoals.indexOf(v) + 1))} /> - + } title="Create an Organization" diff --git a/apps/webapp/app/routes/account.tokens/route.tsx b/apps/webapp/app/routes/account.tokens/route.tsx index d2ef3b5f822..b7919adc898 100644 --- a/apps/webapp/app/routes/account.tokens/route.tsx +++ b/apps/webapp/app/routes/account.tokens/route.tsx @@ -95,9 +95,7 @@ async function loadSystemRolesForUser(userId: string) { // anything else would be a noisy create-time failure (or, with a // permissive fallback, a token bound to a role this org isn't // allowed to issue). - const availableIds = new Set( - (systemRoles ?? []).filter((r) => r.available).map((r) => r.id) - ); + const availableIds = new Set((systemRoles ?? []).filter((r) => r.available).map((r) => r.id)); const roles = allRoles.filter((r) => r.isSystem && availableIds.has(r.id)); return { diff --git a/apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts b/apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts index 7e5f559a4be..e5c51f9e1ae 100644 --- a/apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts +++ b/apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts @@ -87,7 +87,19 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ error: "Invalid request body", details: parsed.error.issues }, { status: 400 }); } - const { modelName, matchPattern, startDate, pricingTiers, provider, description, contextWindow, maxOutputTokens, capabilities, isHidden, pricingUnit } = parsed.data; + const { + modelName, + matchPattern, + startDate, + pricingTiers, + provider, + description, + contextWindow, + maxOutputTokens, + capabilities, + isHidden, + pricingUnit, + } = parsed.data; // Validate regex if provided — strip (?i) POSIX flag since our registry handles it if (matchPattern) { diff --git a/apps/webapp/app/routes/admin.api.v1.llm-models.ts b/apps/webapp/app/routes/admin.api.v1.llm-models.ts index 6753f37dcff..1d8136ca37e 100644 --- a/apps/webapp/app/routes/admin.api.v1.llm-models.ts +++ b/apps/webapp/app/routes/admin.api.v1.llm-models.ts @@ -81,7 +81,20 @@ export async function action({ request }: ActionFunctionArgs) { return json({ error: "Invalid request body", details: parsed.error.issues }, { status: 400 }); } - const { modelName, matchPattern, startDate, source, pricingTiers, provider, description, contextWindow, maxOutputTokens, capabilities, isHidden, pricingUnit } = parsed.data; + const { + modelName, + matchPattern, + startDate, + source, + pricingTiers, + provider, + description, + contextWindow, + maxOutputTokens, + capabilities, + isHidden, + pricingUnit, + } = parsed.data; // Validate regex pattern — strip (?i) POSIX flag since our registry handles it try { diff --git a/apps/webapp/app/routes/admin.api.v1.revoked-api-keys.$revokedApiKeyId.ts b/apps/webapp/app/routes/admin.api.v1.revoked-api-keys.$revokedApiKeyId.ts index 847828d981f..61eb5a69bee 100644 --- a/apps/webapp/app/routes/admin.api.v1.revoked-api-keys.$revokedApiKeyId.ts +++ b/apps/webapp/app/routes/admin.api.v1.revoked-api-keys.$revokedApiKeyId.ts @@ -20,7 +20,10 @@ export async function action({ request, params }: ActionFunctionArgs) { const parsedBody = RequestBodySchema.safeParse(rawBody); if (!parsedBody.success) { - return json({ error: "Invalid request body", issues: parsedBody.error.issues }, { status: 400 }); + return json( + { error: "Invalid request body", issues: parsedBody.error.issues }, + { status: 400 } + ); } const existing = await prisma.revokedApiKey.findFirst({ diff --git a/apps/webapp/app/routes/admin.api.v1.workers.ts b/apps/webapp/app/routes/admin.api.v1.workers.ts index caa36e5217b..ffebea96d04 100644 --- a/apps/webapp/app/routes/admin.api.v1.workers.ts +++ b/apps/webapp/app/routes/admin.api.v1.workers.ts @@ -1,8 +1,4 @@ -import { - type ActionFunctionArgs, - type LoaderFunctionArgs, - json, -} from "@remix-run/server-runtime"; +import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { tryCatch } from "@trigger.dev/core"; import { type Project, WorkerInstanceGroupType, WorkloadType } from "@trigger.dev/database"; import { z } from "zod"; @@ -174,9 +170,7 @@ export async function action({ request }: ActionFunctionArgs) { } } -async function createWorkerGroup( - options: Parameters[0] -) { +async function createWorkerGroup(options: Parameters[0]) { const service = new WorkerGroupService(); return await service.createWorkerGroup(options); } diff --git a/apps/webapp/app/routes/admin.api.v2.orgs.$organizationId.feature-flags.ts b/apps/webapp/app/routes/admin.api.v2.orgs.$organizationId.feature-flags.ts index 6081febb526..ea0dd757c25 100644 --- a/apps/webapp/app/routes/admin.api.v2.orgs.$organizationId.feature-flags.ts +++ b/apps/webapp/app/routes/admin.api.v2.orgs.$organizationId.feature-flags.ts @@ -5,7 +5,11 @@ import { z } from "zod"; import { prisma } from "~/db.server"; import { requireUser } from "~/services/session.server"; import { flags as getGlobalFlags } from "~/v3/featureFlags.server"; -import { FEATURE_FLAG, validatePartialFeatureFlags, getAllFlagControlTypes } from "~/v3/featureFlags"; +import { + FEATURE_FLAG, + validatePartialFeatureFlags, + getAllFlagControlTypes, +} from "~/v3/featureFlags"; import { featuresForRequest } from "~/features.server"; // Session-auth route for the admin feature flags dialog. diff --git a/apps/webapp/app/routes/admin.back-office._index.tsx b/apps/webapp/app/routes/admin.back-office._index.tsx index e2226aebb4a..df4d9737609 100644 --- a/apps/webapp/app/routes/admin.back-office._index.tsx +++ b/apps/webapp/app/routes/admin.back-office._index.tsx @@ -4,20 +4,17 @@ import { Header2 } from "~/components/primitives/Headers"; import { Paragraph } from "~/components/primitives/Paragraph"; import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder"; -export const loader = dashboardLoader( - { authorization: { requireSuper: true } }, - async () => { - return typedjson({}); - } -); +export const loader = dashboardLoader({ authorization: { requireSuper: true } }, async () => { + return typedjson({}); +}); export default function BackOfficeIndex() { return (
Back office - Back-office actions are applied to a single organization. Pick an org from the - Organizations tab to open its detail page. + Back-office actions are applied to a single organization. Pick an org from the Organizations + tab to open its detail page. Pick an organization diff --git a/apps/webapp/app/routes/admin.back-office.orgs.$orgId.tsx b/apps/webapp/app/routes/admin.back-office.orgs.$orgId.tsx index 627c8bd5297..23ffeb51bf6 100644 --- a/apps/webapp/app/routes/admin.back-office.orgs.$orgId.tsx +++ b/apps/webapp/app/routes/admin.back-office.orgs.$orgId.tsx @@ -1,12 +1,7 @@ import { useNavigation, useSearchParams } from "@remix-run/react"; import { useEffect } from "react"; import { z } from "zod"; -import { - redirect, - typedjson, - useTypedActionData, - useTypedLoaderData, -} from "remix-typedjson"; +import { redirect, typedjson, useTypedActionData, useTypedLoaderData } from "remix-typedjson"; import { API_RATE_LIMIT_INTENT, API_RATE_LIMIT_SAVED_VALUE, @@ -81,67 +76,61 @@ export const action = dashboardAction( const formData = await request.formData(); const intent = formData.get("intent"); - if (intent === MAX_PROJECTS_INTENT) { - const result = await handleMaxProjectsAction(formData, orgId, user.id); - if (!result.ok) { - return typedjson( - { section: MAX_PROJECTS_SAVED_VALUE, errors: result.errors }, - { status: 400 } + if (intent === MAX_PROJECTS_INTENT) { + const result = await handleMaxProjectsAction(formData, orgId, user.id); + if (!result.ok) { + return typedjson( + { section: MAX_PROJECTS_SAVED_VALUE, errors: result.errors }, + { status: 400 } + ); + } + return redirect( + `/admin/back-office/orgs/${orgId}?${SAVED_QUERY_KEY}=${MAX_PROJECTS_SAVED_VALUE}` ); } - return redirect( - `/admin/back-office/orgs/${orgId}?${SAVED_QUERY_KEY}=${MAX_PROJECTS_SAVED_VALUE}` - ); - } - if (intent === API_RATE_LIMIT_INTENT) { - const result = await handleApiRateLimitAction(formData, orgId, user.id); - if (!result.ok) { - return typedjson( - { section: API_RATE_LIMIT_SAVED_VALUE, errors: result.errors }, - { status: 400 } + if (intent === API_RATE_LIMIT_INTENT) { + const result = await handleApiRateLimitAction(formData, orgId, user.id); + if (!result.ok) { + return typedjson( + { section: API_RATE_LIMIT_SAVED_VALUE, errors: result.errors }, + { status: 400 } + ); + } + return redirect( + `/admin/back-office/orgs/${orgId}?${SAVED_QUERY_KEY}=${API_RATE_LIMIT_SAVED_VALUE}` ); } - return redirect( - `/admin/back-office/orgs/${orgId}?${SAVED_QUERY_KEY}=${API_RATE_LIMIT_SAVED_VALUE}` - ); - } - if (intent === BATCH_RATE_LIMIT_INTENT) { - const result = await handleBatchRateLimitAction(formData, orgId, user.id); - if (!result.ok) { - return typedjson( - { section: BATCH_RATE_LIMIT_SAVED_VALUE, errors: result.errors }, - { status: 400 } + if (intent === BATCH_RATE_LIMIT_INTENT) { + const result = await handleBatchRateLimitAction(formData, orgId, user.id); + if (!result.ok) { + return typedjson( + { section: BATCH_RATE_LIMIT_SAVED_VALUE, errors: result.errors }, + { status: 400 } + ); + } + return redirect( + `/admin/back-office/orgs/${orgId}?${SAVED_QUERY_KEY}=${BATCH_RATE_LIMIT_SAVED_VALUE}` ); } - return redirect( - `/admin/back-office/orgs/${orgId}?${SAVED_QUERY_KEY}=${BATCH_RATE_LIMIT_SAVED_VALUE}` - ); - } - return typedjson( - { section: null, errors: { intent: ["Unknown intent."] } }, - { status: 400 } - ); + return typedjson({ section: null, errors: { intent: ["Unknown intent."] } }, { status: 400 }); } ); export default function BackOfficeOrgPage() { - const { org, apiEffective, batchEffective } = - useTypedLoaderData(); + const { org, apiEffective, batchEffective } = useTypedLoaderData(); const actionData = useTypedActionData(); const navigation = useNavigation(); const submittingIntent = navigation.formData?.get("intent"); - const isSubmittingApi = - navigation.state !== "idle" && submittingIntent === API_RATE_LIMIT_INTENT; + const isSubmittingApi = navigation.state !== "idle" && submittingIntent === API_RATE_LIMIT_INTENT; const isSubmittingBatch = navigation.state !== "idle" && submittingIntent === BATCH_RATE_LIMIT_INTENT; const isSubmittingMaxProjects = navigation.state !== "idle" && submittingIntent === MAX_PROJECTS_INTENT; - const errorSection = - actionData && "section" in actionData ? actionData.section : null; + const errorSection = actionData && "section" in actionData ? actionData.section : null; const errors = actionData && "errors" in actionData ? (actionData.errors as Record) @@ -152,8 +141,7 @@ export default function BackOfficeOrgPage() { // If the action just returned errors for the same section, hide the // "Saved." banner so it doesn't render alongside field errors. Suppressing // here propagates to every read site (auto-dismiss + JSX comparisons). - const savedSection = - errors && errorSection === savedSectionRaw ? null : savedSectionRaw; + const savedSection = errors && errorSection === savedSectionRaw ? null : savedSectionRaw; // Auto-dismiss the "saved" banner after a few seconds. useEffect(() => { diff --git a/apps/webapp/app/routes/admin.back-office.tsx b/apps/webapp/app/routes/admin.back-office.tsx index 3ec9e99b2ca..bac76f76ba5 100644 --- a/apps/webapp/app/routes/admin.back-office.tsx +++ b/apps/webapp/app/routes/admin.back-office.tsx @@ -2,12 +2,9 @@ import { Outlet } from "@remix-run/react"; import { typedjson } from "remix-typedjson"; import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder"; -export const loader = dashboardLoader( - { authorization: { requireSuper: true } }, - async () => { - return typedjson({}); - } -); +export const loader = dashboardLoader({ authorization: { requireSuper: true } }, async () => { + return typedjson({}); +}); export default function BackOfficeLayout() { return ( diff --git a/apps/webapp/app/routes/admin.concurrency.tsx b/apps/webapp/app/routes/admin.concurrency.tsx index 630bc100b0b..f91f02bd617 100644 --- a/apps/webapp/app/routes/admin.concurrency.tsx +++ b/apps/webapp/app/routes/admin.concurrency.tsx @@ -6,14 +6,11 @@ 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 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(); diff --git a/apps/webapp/app/routes/admin.data-stores.tsx b/apps/webapp/app/routes/admin.data-stores.tsx index 8dafeb9b2fe..5e698c941f5 100644 --- a/apps/webapp/app/routes/admin.data-stores.tsx +++ b/apps/webapp/app/routes/admin.data-stores.tsx @@ -337,7 +337,13 @@ function EditButton({ name, organizationIds }: { name: string; organizationIds:
- +
@@ -357,7 +363,12 @@ function EditButton({ name, organizationIds }: { name: string; organizationIds: {fetcher.data?.error &&

{fetcher.data.error}

} - {testResult !== undefined && testResult !== null && (
- Testing: {testResult.modelString} + Testing:{" "} + {testResult.modelString} {testResult.match ? (
@@ -258,9 +256,7 @@ export default function AdminLlmModelsRoute() {
) : ( -
- No match found — this model has no pricing data -
+
No match found — this model has no pricing data
)}
)} @@ -337,7 +333,10 @@ export default function AdminLlmModelsRoute() { {otherPrices.length > 0 ? ( - p.usageType).join(", ")}> + p.usageType).join(", ")} + > +{otherPrices.length} more ) : ( diff --git a/apps/webapp/app/routes/admin.llm-models.missing.$model.tsx b/apps/webapp/app/routes/admin.llm-models.missing.$model.tsx index 3c63ce09fc4..56dc3f4ca46 100644 --- a/apps/webapp/app/routes/admin.llm-models.missing.$model.tsx +++ b/apps/webapp/app/routes/admin.llm-models.missing.$model.tsx @@ -108,7 +108,9 @@ export default function AdminMissingModelDetailRoute() { > {t.key} - {t.min === t.max ? t.min.toLocaleString() : `${t.min.toLocaleString()}-${t.max.toLocaleString()}`} + {t.min === t.max + ? t.min.toLocaleString() + : `${t.min.toLocaleString()}-${t.max.toLocaleString()}`} ))} @@ -123,7 +125,8 @@ export default function AdminMissingModelDetailRoute() { {providerCosts.length > 0 && (
- Provider-reported cost data found in {providerCosts.length} span{providerCosts.length !== 1 ? "s" : ""} + Provider-reported cost data found in {providerCosts.length} span + {providerCosts.length !== 1 ? "s" : ""}
{providerCosts.map((c, i) => ( @@ -143,7 +146,9 @@ export default function AdminMissingModelDetailRoute() {
input: {providerCosts[0].estimatedInputPrice.toExponential(4)} - output: {(providerCosts[0].estimatedOutputPrice ?? 0).toExponential(4)} + + output: {(providerCosts[0].estimatedOutputPrice ?? 0).toExponential(4)} +
Cross-reference with the provider's pricing page before using these estimates. @@ -183,10 +188,7 @@ export default function AdminMissingModelDetailRoute() { } return ( -
+
- + ; }; -const PRICING_UNITS = ["tokens", "characters", "images", "minutes", "requests", "free", "not_findable"]; +const PRICING_UNITS = [ + "tokens", + "characters", + "images", + "minutes", + "requests", + "free", + "not_findable", +]; const COMMON_USAGE_TYPES = [ "input", @@ -495,9 +524,7 @@ function TierEditor({ variant="tertiary/small" onClick={() => { const key = - newUsageType === "__custom" - ? prompt("Usage type name:") ?? "" - : newUsageType; + newUsageType === "__custom" ? (prompt("Usage type name:") ?? "") : newUsageType; if (key) { onChange({ ...tier, prices: { ...tier.prices, [key]: 0 } }); setNewUsageType(""); diff --git a/apps/webapp/app/routes/admin.notifications.tsx b/apps/webapp/app/routes/admin.notifications.tsx index 60b74d104cd..d9bab14809b 100644 --- a/apps/webapp/app/routes/admin.notifications.tsx +++ b/apps/webapp/app/routes/admin.notifications.tsx @@ -229,8 +229,8 @@ async function handleCreateAction(formData: FormData, userId: string, isPreview: startsAt: isPreview ? new Date().toISOString() : fields.startsAt - ? new Date(fields.startsAt + "Z").toISOString() - : new Date().toISOString(), + ? new Date(fields.startsAt + "Z").toISOString() + : new Date().toISOString(), endsAt: isPreview ? new Date(Date.now() + 60 * 60 * 1000).toISOString() : new Date(fields.endsAt + "Z").toISOString(), @@ -1107,10 +1107,7 @@ function NotificationForm({ > {(items) => items.map((item) => ( - + {item === DISCOVERY_MATCH_NONE ? DISCOVERY_MATCH_LABEL : item} )) diff --git a/apps/webapp/app/routes/admin.orgs.tsx b/apps/webapp/app/routes/admin.orgs.tsx index 8441d4d19da..51cd9552325 100644 --- a/apps/webapp/app/routes/admin.orgs.tsx +++ b/apps/webapp/app/routes/admin.orgs.tsx @@ -118,19 +118,13 @@ export default function AdminDashboardRoute() { {org.deletedAt ? "☠️" : ""} - + Open
- typedjson({ user }) +export const loader = dashboardLoader({ authorization: { requireSuper: true } }, async ({ user }) => + typedjson({ user }) ); export default function Page() { diff --git a/apps/webapp/app/routes/api.v1.auth.user-actor-token.ts b/apps/webapp/app/routes/api.v1.auth.user-actor-token.ts index 50318172163..d46743d0550 100644 --- a/apps/webapp/app/routes/api.v1.auth.user-actor-token.ts +++ b/apps/webapp/app/routes/api.v1.auth.user-actor-token.ts @@ -49,8 +49,7 @@ export async function action({ request }: ActionFunctionArgs) { if (tokenRole) { return json( { - error: - "Cannot mint a user-actor token from a role-restricted personal access token", + error: "Cannot mint a user-actor token from a role-restricted personal access token", }, { status: 403 } ); diff --git a/apps/webapp/app/routes/api.v1.batches.$batchId.ts b/apps/webapp/app/routes/api.v1.batches.$batchId.ts index 5c0c65baf57..b485dc86054 100644 --- a/apps/webapp/app/routes/api.v1.batches.$batchId.ts +++ b/apps/webapp/app/routes/api.v1.batches.$batchId.ts @@ -33,11 +33,7 @@ export const loader = createLoaderApiRoute( // SDK-issued tokens in the wild — a `read:runs` JWT still passes // batch retrieval. Per-id `read:batch:` and type-level // `read:batch` still grant via the first element. - resource: (batch) => - anyResource([ - { type: "batch", id: batch.friendlyId }, - { type: "runs" }, - ]), + resource: (batch) => anyResource([{ type: "batch", id: batch.friendlyId }, { type: "runs" }]), }, }, async ({ resource: batch }) => { diff --git a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.generate-registry-credentials.ts b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.generate-registry-credentials.ts index 89aaad4301e..78488bfeeb5 100644 --- a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.generate-registry-credentials.ts +++ b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.generate-registry-credentials.ts @@ -48,56 +48,64 @@ export async function action({ request, params }: ActionFunctionArgs) { const deploymentService = new DeploymentService(); - return await deploymentService.generateRegistryCredentials(authenticatedEnv, deploymentId).match( - (result) => { - return json( - { - username: result.username, - password: result.password, - expiresAt: result.expiresAt.toISOString(), - repositoryUri: result.repositoryUri, - } satisfies GenerateRegistryCredentialsResponseBody, - { status: 200 } - ); - }, - (error) => { - switch (error.type) { - case "deployment_not_found": - return json({ error: "Deployment not found" }, { status: 404 }); - case "deployment_has_no_image_reference": - logger.error( - "Failed to generate registry credentials: deployment_has_no_image_reference", - { deploymentId } - ); - return json({ error: "Deployment has no image reference" }, { status: 409 }); - case "deployment_is_already_final": - return json( - { error: "Failed to generate registry credentials: deployment_is_already_final" }, - { status: 409 } - ); - case "missing_registry_credentials": - logger.error("Failed to generate registry credentials: missing_registry_credentials", { - deploymentId, - }); - return json({ error: "Missing registry credentials" }, { status: 409 }); - case "registry_not_supported": - logger.error("Failed to generate registry credentials: registry_not_supported", { - deploymentId, - }); - return json({ error: "Registry not supported" }, { status: 409 }); - case "registry_region_not_supported": - logger.error("Failed to generate registry credentials: registry_region_not_supported", { - deploymentId, - }); - return json({ error: "Registry region not supported" }, { status: 409 }); - case "other": - default: - error.type satisfies "other"; - logger.error("Failed to generate registry credentials", { error: error.cause }); - return json({ error: "Internal server error" }, { status: 500 }); + return await deploymentService + .generateRegistryCredentials(authenticatedEnv, deploymentId) + .match( + (result) => { + return json( + { + username: result.username, + password: result.password, + expiresAt: result.expiresAt.toISOString(), + repositoryUri: result.repositoryUri, + } satisfies GenerateRegistryCredentialsResponseBody, + { status: 200 } + ); + }, + (error) => { + switch (error.type) { + case "deployment_not_found": + return json({ error: "Deployment not found" }, { status: 404 }); + case "deployment_has_no_image_reference": + logger.error( + "Failed to generate registry credentials: deployment_has_no_image_reference", + { deploymentId } + ); + return json({ error: "Deployment has no image reference" }, { status: 409 }); + case "deployment_is_already_final": + return json( + { error: "Failed to generate registry credentials: deployment_is_already_final" }, + { status: 409 } + ); + case "missing_registry_credentials": + logger.error( + "Failed to generate registry credentials: missing_registry_credentials", + { + deploymentId, + } + ); + return json({ error: "Missing registry credentials" }, { status: 409 }); + case "registry_not_supported": + logger.error("Failed to generate registry credentials: registry_not_supported", { + deploymentId, + }); + return json({ error: "Registry not supported" }, { status: 409 }); + case "registry_region_not_supported": + logger.error( + "Failed to generate registry credentials: registry_region_not_supported", + { + deploymentId, + } + ); + return json({ error: "Registry region not supported" }, { status: 409 }); + case "other": + default: + error.type satisfies "other"; + logger.error("Failed to generate registry credentials", { error: error.cause }); + return json({ error: "Internal server error" }, { status: 500 }); + } } - } - ); + ); } catch (error) { if (error instanceof Response) throw error; logger.error("Failed to generate registry credentials", { error }); diff --git a/apps/webapp/app/routes/api.v1.idempotencyKeys.$key.reset.ts b/apps/webapp/app/routes/api.v1.idempotencyKeys.$key.reset.ts index f9c5ac0b68c..feec6dd6554 100644 --- a/apps/webapp/app/routes/api.v1.idempotencyKeys.$key.reset.ts +++ b/apps/webapp/app/routes/api.v1.idempotencyKeys.$key.reset.ts @@ -40,11 +40,13 @@ export const { action } = createActionApiRoute( } logger.error("Failed to reset idempotency key via API", { - error: error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error), + error: + error instanceof Error + ? { name: error.name, message: error.message, stack: error.stack } + : String(error), }); return json({ error: "Internal Server Error" }, { status: 500 }); } - } ); diff --git a/apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts b/apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts index 6fc85d69cc3..0dbde448258 100644 --- a/apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts +++ b/apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts @@ -33,18 +33,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); if (!authenticationResult) { - return apiCors( - request, - json({ error: "Invalid or Missing Access Token" }, { status: 401 }) - ); + return apiCors(request, json({ error: "Invalid or Missing Access Token" }, { status: 401 })); } const parsedParams = ParamsSchema.safeParse(params); if (!parsedParams.success) { - return apiCors( - request, - json({ error: "Invalid parameters" }, { status: 400 }) - ); + return apiCors(request, json({ error: "Invalid parameters" }, { status: 400 })); } const { organizationSlug, projectParam } = parsedParams.data; @@ -93,17 +87,11 @@ export async function loader({ request, params }: LoaderFunctionArgs) { projectParam, }); - return apiCors( - request, - json({ error: "Internal server error" }, { status: 500 }) - ); + return apiCors(request, json({ error: "Internal server error" }, { status: 500 })); } if (result.value.type === "not_found") { - return apiCors( - request, - json({ error: "Project not found" }, { status: 404 }) - ); + return apiCors(request, json({ error: "Project not found" }, { status: 404 })); } const { project, integration } = result.value; @@ -150,4 +138,3 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return apiCors(request, json({ error: "Internal Server Error" }, { status: 500 })); } } - diff --git a/apps/webapp/app/routes/api.v1.plain.customer-cards.ts b/apps/webapp/app/routes/api.v1.plain.customer-cards.ts index fe7845c5409..ad869c7a4f7 100644 --- a/apps/webapp/app/routes/api.v1.plain.customer-cards.ts +++ b/apps/webapp/app/routes/api.v1.plain.customer-cards.ts @@ -16,13 +16,10 @@ const PlainCustomerCardRequestSchema = z.object({ email: z.string().optional(), externalId: z.string().optional(), }) - .refine( - (data) => data.email || data.externalId, - { - message: "Either customer.email or customer.externalId must be provided", - path: ["customer"], - } - ), + .refine((data) => data.email || data.externalId, { + message: "Either customer.email or customer.externalId must be provided", + path: ["customer"], + }), thread: z .object({ id: z.string(), @@ -30,7 +27,10 @@ const PlainCustomerCardRequestSchema = z.object({ .optional(), }); -function sanitizeHeaders(request: Request, skipHeaders?: string[]): Partial> { +function sanitizeHeaders( + request: Request, + skipHeaders?: string[] +): Partial> { const authHeaderName = (env.PLAIN_CUSTOMER_CARDS_HEADERS || "Authorization").toLowerCase(); const defaultSkipHeaders = skipHeaders || [authHeaderName, "cookie"]; const sanitizedHeaders: Partial> = {}; @@ -112,7 +112,6 @@ export async function action({ request }: ActionFunctionArgs) { } try { - const { customer, cardKeys } = parsed.data; const userInclude = { @@ -137,8 +136,8 @@ export async function action({ request }: ActionFunctionArgs) { const where = customer.externalId ? { id: customer.externalId } : customer.email - ? { email: customer.email } - : null; + ? { email: customer.email } + : null; const user = where ? await prisma.user.findFirst({ where, include: userInclude }) : null; @@ -166,7 +165,7 @@ export async function action({ request }: ActionFunctionArgs) { cards.push({ key: accountDetailsKey, - timeToLiveSeconds: 15, + timeToLiveSeconds: 15, components: [ uiComponent.container({ content: [ @@ -283,10 +282,7 @@ export async function action({ request }: ActionFunctionArgs) { } const orgComponents = user.orgMemberships.flatMap( - ( - membership: (typeof user.orgMemberships)[0], - index: number - ) => { + (membership: (typeof user.orgMemberships)[0], index: number) => { const org = membership.organization; const projectCount = org.projects.length; @@ -375,11 +371,9 @@ export async function action({ request }: ActionFunctionArgs) { break; } - const projectComponents = allProjects.slice(0, 10).flatMap( - ( - project: typeof allProjects[0] & { orgSlug: string }, - index: number - ) => { + const projectComponents = allProjects + .slice(0, 10) + .flatMap((project: (typeof allProjects)[0] & { orgSlug: string }, index: number) => { return [ ...(index > 0 ? [uiComponent.divider({ spacingSize: "M" })] : []), uiComponent.text({ @@ -403,8 +397,7 @@ export async function action({ request }: ActionFunctionArgs) { ], }), ]; - } - ); + }); cards.push({ key: "projects", diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts index 7f52d9a5235..90bebf0afc0 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts @@ -28,7 +28,10 @@ const RequestBodySchema = z.object({ export async function action({ request, params }: ActionFunctionArgs) { try { - const bearer = request.headers.get("Authorization")?.replace(/^Bearer /, "").trim(); + const bearer = request.headers + .get("Authorization") + ?.replace(/^Bearer /, "") + .trim(); const isUat = !!bearer && isUserActorToken(bearer); // A delegated user-actor token authenticates as its user, like a PAT. We diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.repo.snapshot.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.repo.snapshot.ts index 6b4b0518d14..6215b9c8e31 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.repo.snapshot.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.repo.snapshot.ts @@ -27,7 +27,10 @@ const ParamsSchema = z.object({ export async function loader({ request, params }: LoaderFunctionArgs) { try { - const bearer = request.headers.get("Authorization")?.replace(/^Bearer /, "").trim(); + const bearer = request.headers + .get("Authorization") + ?.replace(/^Bearer /, "") + .trim(); let authenticationResult: AuthenticationResult | undefined; if (bearer && isUserActorToken(bearer)) { const claims = await verifyUserActorToken($env.SESSION_SECRET, bearer); diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts index e9977211ec1..d99e43c239f 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts @@ -30,7 +30,10 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // on the user's behalf. Identity-only, same as the PAT path below — there's // no ability check on this route, so the cap isn't enforced here (matches // PAT behavior). - const bearer = request.headers.get("Authorization")?.replace(/^Bearer /, "").trim(); + const bearer = request.headers + .get("Authorization") + ?.replace(/^Bearer /, "") + .trim(); let authenticationResult: AuthenticationResult | undefined; if (bearer && isUserActorToken(bearer)) { const claims = await verifyUserActorToken($env.SESSION_SECRET, bearer); @@ -67,63 +70,63 @@ export async function loader({ request, params }: LoaderFunctionArgs) { ); const currentWorker = await findCurrentWorkerFromEnvironment( - { - id: runtimeEnv.id, - type: runtimeEnv.type, - }, - $replica, - params.tagName - ); + { + id: runtimeEnv.id, + type: runtimeEnv.type, + }, + $replica, + params.tagName + ); - if (!currentWorker) { - return json({ error: "Worker not found" }, { status: 404 }); - } + if (!currentWorker) { + return json({ error: "Worker not found" }, { status: 404 }); + } - const tasks = await $replica.backgroundWorkerTask.findMany({ - where: { - workerId: currentWorker.id, - }, - select: { - friendlyId: true, - slug: true, - filePath: true, - triggerSource: true, - createdAt: true, - payloadSchema: true, - }, - orderBy: { - slug: "asc", - }, - }); + const tasks = await $replica.backgroundWorkerTask.findMany({ + where: { + workerId: currentWorker.id, + }, + select: { + friendlyId: true, + slug: true, + filePath: true, + triggerSource: true, + createdAt: true, + payloadSchema: true, + }, + orderBy: { + slug: "asc", + }, + }); - const urls = { - runs: `${$env.APP_ORIGIN}${v3RunsPath( - { slug: runtimeEnv.organization.slug }, - { slug: runtimeEnv.project.slug }, - { slug: runtimeEnv.slug }, - { versions: [currentWorker.version] } - )}`, - }; + const urls = { + runs: `${$env.APP_ORIGIN}${v3RunsPath( + { slug: runtimeEnv.organization.slug }, + { slug: runtimeEnv.project.slug }, + { slug: runtimeEnv.slug }, + { versions: [currentWorker.version] } + )}`, + }; - // Prepare the response object - const response: GetWorkerByTagResponse = { - worker: { - id: currentWorker.friendlyId, - version: currentWorker.version, - engine: currentWorker.engine, - sdkVersion: currentWorker.sdkVersion, - cliVersion: currentWorker.cliVersion, - tasks: tasks.map((task) => ({ - id: task.friendlyId, - slug: task.slug, - filePath: task.filePath, - triggerSource: task.triggerSource, - createdAt: task.createdAt, - payloadSchema: task.payloadSchema, - })), - }, - urls, - }; + // Prepare the response object + const response: GetWorkerByTagResponse = { + worker: { + id: currentWorker.friendlyId, + version: currentWorker.version, + engine: currentWorker.engine, + sdkVersion: currentWorker.sdkVersion, + cliVersion: currentWorker.cliVersion, + tasks: tasks.map((task) => ({ + id: task.friendlyId, + slug: task.slug, + filePath: task.filePath, + triggerSource: task.triggerSource, + createdAt: task.createdAt, + payloadSchema: task.payloadSchema, + })), + }, + urls, + }; return json(response); } catch (error) { diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.background-workers.$envSlug.$version.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.background-workers.$envSlug.$version.ts index e09bc510d91..7a2bb5b7e68 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.background-workers.$envSlug.$version.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.background-workers.$envSlug.$version.ts @@ -61,21 +61,21 @@ export async function loader({ params, request }: LoaderFunctionArgs) { } return json({ - id: backgroundWorker.friendlyId, - version: backgroundWorker.version, - cliVersion: backgroundWorker.cliVersion, - sdkVersion: backgroundWorker.sdkVersion, - contentHash: backgroundWorker.contentHash, - createdAt: backgroundWorker.createdAt, - updatedAt: backgroundWorker.updatedAt, - tasks: backgroundWorker.tasks.map((task) => ({ - id: task.slug, - exportName: task.exportName ?? "@deprecated", - filePath: task.filePath, - source: task.triggerSource, - retryConfig: task.retryConfig, - queueConfig: task.queueConfig, - })), + id: backgroundWorker.friendlyId, + version: backgroundWorker.version, + cliVersion: backgroundWorker.cliVersion, + sdkVersion: backgroundWorker.sdkVersion, + contentHash: backgroundWorker.contentHash, + createdAt: backgroundWorker.createdAt, + updatedAt: backgroundWorker.updatedAt, + tasks: backgroundWorker.tasks.map((task) => ({ + id: task.slug, + exportName: task.exportName ?? "@deprecated", + filePath: task.filePath, + source: task.triggerSource, + retryConfig: task.retryConfig, + queueConfig: task.queueConfig, + })), files: backgroundWorker.files.map((file) => ({ id: file.friendlyId, filePath: file.filePath, diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.branches.archive.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.branches.archive.ts index ffc82e39b53..1cc565f95ec 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.branches.archive.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.branches.archive.ts @@ -65,15 +65,15 @@ export async function action({ request, params }: ActionFunctionArgs) { authenticationResult.type === "organizationAccessToken" ? { id: authenticationResult.result.organizationId } : { - members: { - some: { - userId: authenticationResult.result.userId, + members: { + some: { + userId: authenticationResult.result.userId, + }, }, }, - }, // Dev branches are per-org-member: only the owner may archive their own. ...(authenticationResult.type !== "organizationAccessToken" && - environmentType === "DEVELOPMENT" + environmentType === "DEVELOPMENT" ? { orgMember: { userId: authenticationResult.result.userId } } : {}), project: { @@ -96,7 +96,10 @@ export async function action({ request, params }: ActionFunctionArgs) { activeEnvironments.length > 1 ) { return json( - { error: "Branch name is ambiguous for development environments. Use a personal access token scoped to the branch owner." }, + { + error: + "Branch name is ambiguous for development environments. Use a personal access token scoped to the branch owner.", + }, { status: 409 } ); } diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.environments.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.environments.ts index 5940bcdda0f..dd4ce9e2ea0 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.environments.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.environments.ts @@ -59,14 +59,16 @@ export const loader = createLoaderPATApiRoute( }, }); - const result: GetProjectEnvironmentsResponseBody = sortEnvironments(environments).map((env) => ({ - id: env.id, - slug: env.slug, - type: env.type, - isBranchableEnvironment: isBranchableEnvironment(env), - branchName: env.branchName, - paused: env.paused, - })); + const result: GetProjectEnvironmentsResponseBody = sortEnvironments(environments).map( + (env) => ({ + id: env.id, + slug: env.slug, + type: env.type, + isBranchableEnvironment: isBranchableEnvironment(env), + branchName: env.branchName, + paused: env.paused, + }) + ); return json(result); } diff --git a/apps/webapp/app/routes/api.v1.prompts.$slug.override.ts b/apps/webapp/app/routes/api.v1.prompts.$slug.override.ts index 2a00ceac15c..f31ade052ea 100644 --- a/apps/webapp/app/routes/api.v1.prompts.$slug.override.ts +++ b/apps/webapp/app/routes/api.v1.prompts.$slug.override.ts @@ -22,7 +22,10 @@ const UpdateBody = z.object({ commitMessage: z.string().optional(), }); -async function findPrompt(slug: string, authentication: { environment: { projectId: string; id: string } }) { +async function findPrompt( + slug: string, + authentication: { environment: { projectId: string; id: string } } +) { return prisma.prompt.findUnique({ where: { projectId_runtimeEnvironmentId_slug: { diff --git a/apps/webapp/app/routes/api.v1.prompts.$slug.ts b/apps/webapp/app/routes/api.v1.prompts.$slug.ts index 3d8de30c25c..316701581e5 100644 --- a/apps/webapp/app/routes/api.v1.prompts.$slug.ts +++ b/apps/webapp/app/routes/api.v1.prompts.$slug.ts @@ -52,7 +52,10 @@ export const loader = createLoaderApiRoute( return json({ error: "Prompt not found" }, { status: 404 }); } - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(prompt.project.organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + prompt.project.organizationId, + "standard" + ); const presenter = new PromptPresenter(clickhouse); const version = await presenter.resolveVersion(prompt.id, { version: searchParams.version, @@ -123,7 +126,10 @@ const { action } = createActionApiRoute( return json({ error: "Prompt not found" }, { status: 404 }); } - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(authentication.environment.organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + authentication.environment.organizationId, + "standard" + ); const presenter = new PromptPresenter(clickhouse); const version = await presenter.resolveVersion(prompt.id, { version: body.version, @@ -156,21 +162,15 @@ const { action } = createActionApiRoute( export { action }; -function compileTemplate( - template: string, - variables: Record -): string { - let result = template.replace( - /\{\{#(\w+)\}\}([\s\S]*?)\{\{\/\1\}\}/g, - (_match, key, content) => { - const value = variables[key]; - return value - ? content.replace(/\{\{(\w+)\}\}/g, (_m: string, k: string) => { - return String(variables[k] ?? ""); - }) - : ""; - } - ); +function compileTemplate(template: string, variables: Record): string { + let result = template.replace(/\{\{#(\w+)\}\}([\s\S]*?)\{\{\/\1\}\}/g, (_match, key, content) => { + const value = variables[key]; + return value + ? content.replace(/\{\{(\w+)\}\}/g, (_m: string, k: string) => { + return String(variables[k] ?? ""); + }) + : ""; + }); result = result.replace(/\{\{\s*(\w+)\s*\}\}/g, (_match, key) => { const value = variables[key]; diff --git a/apps/webapp/app/routes/api.v1.prompts.$slug.versions.ts b/apps/webapp/app/routes/api.v1.prompts.$slug.versions.ts index 6ef8a014f9c..d8c64834aa7 100644 --- a/apps/webapp/app/routes/api.v1.prompts.$slug.versions.ts +++ b/apps/webapp/app/routes/api.v1.prompts.$slug.versions.ts @@ -42,7 +42,10 @@ export const loader = createLoaderApiRoute( return json({ error: "Prompt not found" }, { status: 404 }); } - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(prompt.project.organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + prompt.project.organizationId, + "standard" + ); const presenter = new PromptPresenter(clickhouse); const versions = await presenter.listVersions(prompt.id); diff --git a/apps/webapp/app/routes/api.v1.prompts._index.ts b/apps/webapp/app/routes/api.v1.prompts._index.ts index 8ba10c660de..87939ea879b 100644 --- a/apps/webapp/app/routes/api.v1.prompts._index.ts +++ b/apps/webapp/app/routes/api.v1.prompts._index.ts @@ -14,7 +14,10 @@ export const loader = createLoaderApiRoute( }, }, async ({ authentication }) => { - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(authentication.environment.organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + authentication.environment.organizationId, + "standard" + ); const presenter = new PromptPresenter(clickhouse); const prompts = await presenter.listPrompts( authentication.environment.projectId, diff --git a/apps/webapp/app/routes/api.v1.query.ts b/apps/webapp/app/routes/api.v1.query.ts index 3fb6b04ec78..d729e9e9e23 100644 --- a/apps/webapp/app/routes/api.v1.query.ts +++ b/apps/webapp/app/routes/api.v1.query.ts @@ -1,10 +1,7 @@ import { json } from "@remix-run/server-runtime"; import { QueryError } from "@internal/clickhouse"; import { z } from "zod"; -import { - createActionApiRoute, - everyResource, -} from "~/services/routeBuilders/apiBuilder.server"; +import { createActionApiRoute, everyResource } from "~/services/routeBuilders/apiBuilder.server"; import { executeQuery, type QueryScope } from "~/services/queryService.server"; import { logger } from "~/services/logger.server"; import { rowsToCSV } from "~/utils/dataExport"; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.events.ts b/apps/webapp/app/routes/api.v1.runs.$runId.events.ts index 42468f67604..43b0340877d 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.events.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.events.ts @@ -1,10 +1,7 @@ import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server"; -import { - anyResource, - createLoaderApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { ApiRetrieveRunPresenter } from "~/presenters/v3/ApiRetrieveRunPresenter.server"; import { getEventRepositoryForStore } from "~/v3/eventRepository/index.server"; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.metadata.ts b/apps/webapp/app/routes/api.v1.runs.$runId.metadata.ts index 7ec10835c78..58cf572f44d 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.metadata.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.metadata.ts @@ -190,9 +190,7 @@ const { action } = createActionApiRoute( // owns the full request shape including parent/root operations, // metadataVersion CAS, batching, validation — none of which the // buffer side needs to reimplement. - const [pgError, pgResult] = await tryCatch( - updateMetadataService.call(runId, body, env) - ); + const [pgError, pgResult] = await tryCatch(updateMetadataService.call(runId, body, env)); if (pgError) { if (pgError instanceof ServiceValidationError) { return json({ error: pgError.message }, { status: pgError.status ?? 422 }); @@ -280,13 +278,9 @@ const { action } = createActionApiRoute( routeOperationsToRun( bufferOutcome.parentTaskRunFriendlyId ?? runId, body.parentOperations, - env, - ), - routeOperationsToRun( - bufferOutcome.rootTaskRunFriendlyId ?? runId, - body.rootOperations, - env, + env ), + routeOperationsToRun(bufferOutcome.rootTaskRunFriendlyId ?? runId, body.rootOperations, env), ]); // Wire-shape parity with the PG branch. `UpdateMetadataService.call` diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.spans.$spanId.ts b/apps/webapp/app/routes/api.v1.runs.$runId.spans.$spanId.ts index 061199f33e9..5f4c35c1556 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.spans.$spanId.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.spans.$spanId.ts @@ -3,10 +3,7 @@ import { BatchId } from "@trigger.dev/core/v3/isomorphic"; import { z } from "zod"; import { $replica } from "~/db.server"; import { extractAISpanData } from "~/components/runs/v3/ai"; -import { - anyResource, - createLoaderApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { getEventRepositoryForStore } from "~/v3/eventRepository/index.server"; import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server"; import { findRunByIdWithMollifierFallback } from "~/v3/mollifier/readFallback.server"; @@ -26,13 +23,13 @@ const ParamsSchema = z.object({ // same 200 contract they'd get for a freshly-triggered run. type ResolvedRun = | { source: "pg"; run: Awaited> & {} } - | { source: "buffer"; run: NonNullable>> }; + | { + source: "buffer"; + run: NonNullable>>; + }; async function findPgRun(runId: string, environmentId: string) { - return runStore.findRun( - { friendlyId: runId, runtimeEnvironmentId: environmentId }, - $replica - ); + return runStore.findRun({ friendlyId: runId, runtimeEnvironmentId: environmentId }, $replica); } export const loader = createLoaderApiRoute( diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tags.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tags.ts index c3a99fcec4e..be717efa355 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tags.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tags.ts @@ -85,7 +85,12 @@ export async function action({ request, params }: ActionFunctionArgs) { if (newTags.length === 0) { return json({ message: "No new tags to add" }, { status: 200 }); } - const updated = await runStore.pushTags(taskRun.id, newTags, { runtimeEnvironmentId: env.id }, prisma); + const updated = await runStore.pushTags( + taskRun.id, + newTags, + { runtimeEnvironmentId: env.id }, + prisma + ); // Publish a run-changed record with the NEW tag set so tag feeds reindex // (no-op unless enabled). updatedAt is the read-your-writes watermark. publishChangeRecord({ @@ -114,19 +119,13 @@ export async function action({ request, params }: ActionFunctionArgs) { const newTagsCount = existing ? nonEmptyTags.filter((t) => !existing.includes(t)).length : nonEmptyTags.length; - return json( - { message: `Successfully set ${newTagsCount} new tags.` }, - { status: 200 } - ); + return json({ message: `Successfully set ${newTagsCount} new tags.` }, { status: 200 }); }, // Buffer rejected the append because it would exceed the cap. We // don't know the exact deduped overflow count here (the Lua does), // so report the limit rather than a precise "trying to set N". rejectedResponse: () => - json( - { error: `Runs can only have ${MAX_TAGS_PER_RUN} tags.` }, - { status: 422 } - ), + json({ error: `Runs can only have ${MAX_TAGS_PER_RUN} tags.` }, { status: 422 }), abortSignal: getRequestAbortSignal(), }); diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.trace.ts b/apps/webapp/app/routes/api.v1.runs.$runId.trace.ts index f1aa4d58967..82c356630d0 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.trace.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.trace.ts @@ -2,10 +2,7 @@ import { json } from "@remix-run/server-runtime"; import { BatchId } from "@trigger.dev/core/v3/isomorphic"; import { z } from "zod"; import { $replica } from "~/db.server"; -import { - anyResource, - createLoaderApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { getEventRepositoryForStore } from "~/v3/eventRepository/index.server"; import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server"; import { findRunByIdWithMollifierFallback } from "~/v3/mollifier/readFallback.server"; @@ -24,13 +21,13 @@ const ParamsSchema = z.object({ // pass-through control case in scripts/mollifier-api-parity.sh). type ResolvedRun = | { source: "pg"; run: Awaited> & {} } - | { source: "buffer"; run: NonNullable>> }; + | { + source: "buffer"; + run: NonNullable>>; + }; async function findPgRun(runId: string, environmentId: string) { - return runStore.findRun( - { friendlyId: runId, runtimeEnvironmentId: environmentId }, - $replica - ); + return runStore.findRun({ friendlyId: runId, runtimeEnvironmentId: environmentId }, $replica); } export const loader = createLoaderApiRoute( diff --git a/apps/webapp/app/routes/api.v1.runs.$runParam.replay.ts b/apps/webapp/app/routes/api.v1.runs.$runParam.replay.ts index 4b238869d3a..df684946b18 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runParam.replay.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runParam.replay.ts @@ -125,8 +125,7 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ error: "Run not found" }, { status: 404 }); } - const triggerSource = - sanitizeTriggerSource(request.headers.get("x-trigger-source")) ?? "api"; + const triggerSource = sanitizeTriggerSource(request.headers.get("x-trigger-source")) ?? "api"; const service = new ReplayTaskRunService(); const newRun = await service.call(taskRun, { triggerSource }); diff --git a/apps/webapp/app/routes/api.v1.runs.$runParam.reschedule.ts b/apps/webapp/app/routes/api.v1.runs.$runParam.reschedule.ts index cbdd9807d8b..dbc521a7424 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runParam.reschedule.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runParam.reschedule.ts @@ -91,10 +91,7 @@ export async function action({ request, params }: ActionFunctionArgs) { const snapshotDelayUntil = typeof snapshot.delayUntil === "string" ? snapshot.delayUntil : undefined; if (!snapshotDelayUntil) { - return json( - { error: "Cannot reschedule a run that is not delayed" }, - { status: 422 }, - ); + return json({ error: "Cannot reschedule a run that is not delayed" }, { status: 422 }); } } diff --git a/apps/webapp/app/routes/api.v1.runs.ts b/apps/webapp/app/routes/api.v1.runs.ts index 4cbd689f627..21b7b57857b 100644 --- a/apps/webapp/app/routes/api.v1.runs.ts +++ b/apps/webapp/app/routes/api.v1.runs.ts @@ -4,10 +4,7 @@ import { ApiRunListSearchParams, } from "~/presenters/v3/ApiRunListPresenter.server"; import { logger } from "~/services/logger.server"; -import { - anyResource, - createLoaderApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; export const loader = createLoaderApiRoute( { diff --git a/apps/webapp/app/routes/api.v1.schedules.$scheduleId.ts b/apps/webapp/app/routes/api.v1.schedules.$scheduleId.ts index 8f19194a5d4..79ad6091cda 100644 --- a/apps/webapp/app/routes/api.v1.schedules.$scheduleId.ts +++ b/apps/webapp/app/routes/api.v1.schedules.$scheduleId.ts @@ -55,7 +55,9 @@ export async function action({ request, params }: ActionFunctionArgs) { // Check if it's a Prisma error if (error instanceof Prisma.PrismaClientKnownRequestError) { return json( - { error: error.code === "P2025" ? "Schedule not found" : clientSafeErrorMessage(error) }, + { + error: error.code === "P2025" ? "Schedule not found" : clientSafeErrorMessage(error), + }, { status: error.code === "P2025" ? 404 : 422 } ); } else { diff --git a/apps/webapp/app/routes/api.v1.sessions.$session.close.ts b/apps/webapp/app/routes/api.v1.sessions.$session.close.ts index 15c2e8dc6bd..b52a58c8c3f 100644 --- a/apps/webapp/app/routes/api.v1.sessions.$session.close.ts +++ b/apps/webapp/app/routes/api.v1.sessions.$session.close.ts @@ -1,8 +1,5 @@ import { json } from "@remix-run/server-runtime"; -import { - CloseSessionRequestBody, - type RetrieveSessionResponseBody, -} from "@trigger.dev/core/v3"; +import { CloseSessionRequestBody, type RetrieveSessionResponseBody } from "@trigger.dev/core/v3"; import { z } from "zod"; import { $replica, prisma } from "~/db.server"; import { @@ -42,9 +39,7 @@ const { action, loader } = createActionApiRoute( // Idempotent: if already closed, return the current row without clobbering // the original closedAt / closedReason. if (existing.closedAt) { - return json( - await serializeSessionWithFriendlyRunId(existing) - ); + return json(await serializeSessionWithFriendlyRunId(existing)); } // `closedAt: null` on the where clause makes the update conditional at @@ -62,16 +57,12 @@ const { action, loader } = createActionApiRoute( if (count === 0) { const final = await prisma.session.findFirst({ where: { id: existing.id } }); if (!final) return json({ error: "Session not found" }, { status: 404 }); - return json( - await serializeSessionWithFriendlyRunId(final) - ); + return json(await serializeSessionWithFriendlyRunId(final)); } const updated = await prisma.session.findFirst({ where: { id: existing.id } }); if (!updated) return json({ error: "Session not found" }, { status: 404 }); - return json( - await serializeSessionWithFriendlyRunId(updated) - ); + return json(await serializeSessionWithFriendlyRunId(updated)); } ); diff --git a/apps/webapp/app/routes/api.v1.sessions.$session.end-and-continue.ts b/apps/webapp/app/routes/api.v1.sessions.$session.end-and-continue.ts index cc1a6d4f9fc..35770928c2d 100644 --- a/apps/webapp/app/routes/api.v1.sessions.$session.end-and-continue.ts +++ b/apps/webapp/app/routes/api.v1.sessions.$session.end-and-continue.ts @@ -8,10 +8,7 @@ import { $replica, prisma } from "~/db.server"; import { logger } from "~/services/logger.server"; import { swapSessionRun } from "~/services/realtime/sessionRunManager.server"; import { resolveSessionByIdOrExternalId } from "~/services/realtime/sessions.server"; -import { - anyResource, - createActionApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { runStore } from "~/v3/runStore.server"; const ParamsSchema = z.object({ @@ -67,17 +64,11 @@ const { action, loader } = createActionApiRoute( } if (session.closedAt) { - return json( - { error: "Cannot end-and-continue a closed session" }, - { status: 400 } - ); + return json({ error: "Cannot end-and-continue a closed session" }, { status: 400 }); } if (session.expiresAt && session.expiresAt.getTime() < Date.now()) { - return json( - { error: "Cannot end-and-continue an expired session" }, - { status: 400 } - ); + return json({ error: "Cannot end-and-continue an expired session" }, { status: 400 }); } // The wire `callingRunId` is a friendlyId (that's what the agent diff --git a/apps/webapp/app/routes/api.v1.sessions.$session.ts b/apps/webapp/app/routes/api.v1.sessions.$session.ts index 58208de35e3..6a92db2793a 100644 --- a/apps/webapp/app/routes/api.v1.sessions.$session.ts +++ b/apps/webapp/app/routes/api.v1.sessions.$session.ts @@ -1,8 +1,5 @@ import { json } from "@remix-run/server-runtime"; -import { - type RetrieveSessionResponseBody, - UpdateSessionRequestBody, -} from "@trigger.dev/core/v3"; +import { type RetrieveSessionResponseBody, UpdateSessionRequestBody } from "@trigger.dev/core/v3"; import { Prisma } from "@trigger.dev/database"; import { z } from "zod"; import { $replica, prisma } from "~/db.server"; @@ -44,9 +41,7 @@ export const loader = createLoaderApiRoute( }, }, async ({ resource: session }) => { - return json( - await serializeSessionWithFriendlyRunId(session) - ); + return json(await serializeSessionWithFriendlyRunId(session)); } ); @@ -79,10 +74,7 @@ const { action } = createActionApiRoute( // scope all derive from it. Re-keying a session would orphan its // streams (the chat goes silent) and invalidate the PAT's scope, so // reject any change. Same-value PATCHes stay idempotent. - if ( - body.externalId !== undefined && - body.externalId !== existing.externalId - ) { + if (body.externalId !== undefined && body.externalId !== existing.externalId) { return json( { error: @@ -109,9 +101,7 @@ const { action } = createActionApiRoute( }, }); - return json( - await serializeSessionWithFriendlyRunId(updated) - ); + return json(await serializeSessionWithFriendlyRunId(updated)); } catch (error) { // A duplicate externalId in the same environment violates the // `(runtimeEnvironmentId, externalId)` unique constraint. Surface that diff --git a/apps/webapp/app/routes/api.v1.sessions.$sessionId.snapshot-url.ts b/apps/webapp/app/routes/api.v1.sessions.$sessionId.snapshot-url.ts index 80140f05d20..ba70f08daae 100644 --- a/apps/webapp/app/routes/api.v1.sessions.$sessionId.snapshot-url.ts +++ b/apps/webapp/app/routes/api.v1.sessions.$sessionId.snapshot-url.ts @@ -18,8 +18,10 @@ const routeConfig = { params: ParamsSchema, allowJWT: true, corsStrategy: "all" as const, - findResource: async (params: z.infer, auth: { environment: { id: string } }) => - resolveSessionByIdOrExternalId($replica, auth.environment.id, params.sessionId), + findResource: async ( + params: z.infer, + auth: { environment: { id: string } } + ) => resolveSessionByIdOrExternalId($replica, auth.environment.id, params.sessionId), }; // Authorize against the union of the URL form, friendlyId, and externalId — @@ -75,19 +77,20 @@ export const loader = createLoaderApiRoute( }, }, async ({ authentication, resource: session }) => { - if (!session) { - return json({ error: "Session not found" }, { status: 404 }); - } + if (!session) { + return json({ error: "Session not found" }, { status: 404 }); + } - const signed = await generatePresignedUrl( - authentication.environment.project.externalRef, - authentication.environment.slug, - chatSnapshotStorageKey(session), - "GET" - ); - if (!signed.success) { - return json({ error: `Failed to generate presigned URL: ${signed.error}` }, { status: 500 }); - } + const signed = await generatePresignedUrl( + authentication.environment.project.externalRef, + authentication.environment.slug, + chatSnapshotStorageKey(session), + "GET" + ); + if (!signed.success) { + return json({ error: `Failed to generate presigned URL: ${signed.error}` }, { status: 500 }); + } - return json({ presignedUrl: signed.url }); -}); + return json({ presignedUrl: signed.url }); + } +); diff --git a/apps/webapp/app/routes/api.v1.sessions.ts b/apps/webapp/app/routes/api.v1.sessions.ts index ec8c171fc20..1a10e6c6f02 100644 --- a/apps/webapp/app/routes/api.v1.sessions.ts +++ b/apps/webapp/app/routes/api.v1.sessions.ts @@ -158,10 +158,7 @@ const { action } = createActionApiRoute( // via the JWT ability's wildcard branches. action: "write", resource: (_params, _searchParams, _headers, body) => - anyResource([ - { type: "tasks", id: body.taskIdentifier }, - { type: "sessions" }, - ]), + anyResource([{ type: "tasks", id: body.taskIdentifier }, { type: "sessions" }]), }, corsStrategy: "all", }, @@ -280,10 +277,7 @@ const { action } = createActionApiRoute( // the externalId; otherwise the friendlyId. Mirrors the // canonical addressing key used server-side. const addressingKey = session.externalId ?? session.friendlyId; - const publicAccessToken = await mintSessionToken( - authentication.environment, - addressingKey - ); + const publicAccessToken = await mintSessionToken(authentication.environment, addressingKey); const sessionItem: SessionItem = { ...serializeSession(session), diff --git a/apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts b/apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts index eb9e5d974e4..2b3f308dfa2 100644 --- a/apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts +++ b/apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts @@ -128,7 +128,9 @@ const { action, loader } = createActionApiRoute( realtimeStreamsVersion: determineRealtimeStreamsVersion( realtimeStreamsVersion ?? undefined ), - triggerSource: isFromWorker ? "sdk" : sanitizeTriggerSource(triggerSourceHeader) ?? "api", + triggerSource: isFromWorker + ? "sdk" + : (sanitizeTriggerSource(triggerSourceHeader) ?? "api"), triggerAction: "trigger", }, engineVersion ?? undefined diff --git a/apps/webapp/app/routes/api.v1.tasks.batch.ts b/apps/webapp/app/routes/api.v1.tasks.batch.ts index 16b3ef16062..2c33e1b4a05 100644 --- a/apps/webapp/app/routes/api.v1.tasks.batch.ts +++ b/apps/webapp/app/routes/api.v1.tasks.batch.ts @@ -7,10 +7,7 @@ import { import { env } from "~/env.server"; import { AuthenticatedEnvironment, getOneTimeUseToken } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; -import { - createActionApiRoute, - everyResource, -} from "~/services/routeBuilders/apiBuilder.server"; +import { createActionApiRoute, everyResource } from "~/services/routeBuilders/apiBuilder.server"; import { resolveIdempotencyKeyTTL } from "~/utils/idempotencyKeys.server"; import { ServiceValidationError } from "~/v3/services/baseService.server"; import { @@ -125,7 +122,7 @@ const { action, loader } = createActionApiRoute( realtimeStreamsVersion: determineRealtimeStreamsVersion( realtimeStreamsVersion ?? undefined ), - triggerSource: isFromWorker ? "sdk" : sanitizeTriggerSource(triggerSourceHeader) ?? "api", + triggerSource: isFromWorker ? "sdk" : (sanitizeTriggerSource(triggerSourceHeader) ?? "api"), triggerAction: "trigger", }); diff --git a/apps/webapp/app/routes/api.v2.batches.$batchId.ts b/apps/webapp/app/routes/api.v2.batches.$batchId.ts index a4a3027cfb7..deb3b788229 100644 --- a/apps/webapp/app/routes/api.v2.batches.$batchId.ts +++ b/apps/webapp/app/routes/api.v2.batches.$batchId.ts @@ -27,11 +27,7 @@ export const loader = createLoaderApiRoute( action: "read", // See sibling note in api.v1.batches.$batchId.ts — `{type: "runs"}` // preserves pre-RBAC `read:runs` superScope access for batch reads. - resource: (batch) => - anyResource([ - { type: "batch", id: batch.friendlyId }, - { type: "runs" }, - ]), + resource: (batch) => anyResource([{ type: "batch", id: batch.friendlyId }, { type: "runs" }]), }, }, async ({ resource: batch }) => { diff --git a/apps/webapp/app/routes/api.v2.tasks.batch.ts b/apps/webapp/app/routes/api.v2.tasks.batch.ts index 974b1ed2962..4845bcf473a 100644 --- a/apps/webapp/app/routes/api.v2.tasks.batch.ts +++ b/apps/webapp/app/routes/api.v2.tasks.batch.ts @@ -9,10 +9,7 @@ import { env } from "~/env.server"; import { RunEngineBatchTriggerService } from "~/runEngine/services/batchTrigger.server"; import { AuthenticatedEnvironment, getOneTimeUseToken } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; -import { - createActionApiRoute, - everyResource, -} from "~/services/routeBuilders/apiBuilder.server"; +import { createActionApiRoute, everyResource } from "~/services/routeBuilders/apiBuilder.server"; import { handleRequestIdempotency, saveRequestIdempotency, @@ -140,7 +137,7 @@ const { action, loader } = createActionApiRoute( realtimeStreamsVersion: determineRealtimeStreamsVersion( realtimeStreamsVersion ?? undefined ), - triggerSource: isFromWorker ? "sdk" : sanitizeTriggerSource(triggerSourceHeader) ?? "api", + triggerSource: isFromWorker ? "sdk" : (sanitizeTriggerSource(triggerSourceHeader) ?? "api"), triggerAction: "trigger", }); diff --git a/apps/webapp/app/routes/api.v3.batches.ts b/apps/webapp/app/routes/api.v3.batches.ts index 8787ae47017..a42706432dd 100644 --- a/apps/webapp/app/routes/api.v3.batches.ts +++ b/apps/webapp/app/routes/api.v3.batches.ts @@ -132,7 +132,7 @@ const { action, loader } = createActionApiRoute( realtimeStreamsVersion: determineRealtimeStreamsVersion( realtimeStreamsVersion ?? undefined ), - triggerSource: isFromWorker ? "sdk" : sanitizeTriggerSource(triggerSourceHeader) ?? "api", + triggerSource: isFromWorker ? "sdk" : (sanitizeTriggerSource(triggerSourceHeader) ?? "api"), }); const $responseHeaders = await responseHeaders( diff --git a/apps/webapp/app/routes/api.v3.runs.$runId.ts b/apps/webapp/app/routes/api.v3.runs.$runId.ts index 00ea7102580..5debce70356 100644 --- a/apps/webapp/app/routes/api.v3.runs.$runId.ts +++ b/apps/webapp/app/routes/api.v3.runs.$runId.ts @@ -1,10 +1,7 @@ import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { ApiRetrieveRunPresenter } from "~/presenters/v3/ApiRetrieveRunPresenter.server"; -import { - anyResource, - createLoaderApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; const ParamsSchema = z.object({ runId: z.string(), diff --git a/apps/webapp/app/routes/auth.google.callback.tsx b/apps/webapp/app/routes/auth.google.callback.tsx index 932186b511b..593b9d40fb8 100644 --- a/apps/webapp/app/routes/auth.google.callback.tsx +++ b/apps/webapp/app/routes/auth.google.callback.tsx @@ -95,4 +95,3 @@ export let loader: LoaderFunction = async ({ request }) => { return redirect(redirectTo, { headers }); }; - diff --git a/apps/webapp/app/routes/auth.google.ts b/apps/webapp/app/routes/auth.google.ts index 125fd8ec9d8..95fb4ff7b58 100644 --- a/apps/webapp/app/routes/auth.google.ts +++ b/apps/webapp/app/routes/auth.google.ts @@ -35,4 +35,3 @@ export const redirectCookie = createCookie("google-redirect-to", { sameSite: "lax", secure: env.NODE_ENV === "production", }); - diff --git a/apps/webapp/app/routes/confirm-basic-details.tsx b/apps/webapp/app/routes/confirm-basic-details.tsx index 2430af764e1..ab84f9c34f4 100644 --- a/apps/webapp/app/routes/confirm-basic-details.tsx +++ b/apps/webapp/app/routes/confirm-basic-details.tsx @@ -242,7 +242,10 @@ export default function Page() { return ( - + { +async function cancelRunsInline(runFriendlyIds: string[], environmentId: string): Promise { const runIds = runFriendlyIds.map((fid) => RunId.toId(fid)); const runs = await runStore.findRuns( diff --git a/apps/webapp/app/routes/engine.v1.dev.presence.ts b/apps/webapp/app/routes/engine.v1.dev.presence.ts index 23970ac960a..bda6466cec8 100644 --- a/apps/webapp/app/routes/engine.v1.dev.presence.ts +++ b/apps/webapp/app/routes/engine.v1.dev.presence.ts @@ -12,7 +12,6 @@ export const loader = createSSELoader({ handler: async ({ id, controller, debug, request }) => { const authentication = await authenticateApiRequestWithFailure(request); - if (!authentication.ok) { throw json({ error: "Invalid or Missing API key" }, { status: 401 }); } diff --git a/apps/webapp/app/routes/engine.v1.worker-actions.connect.ts b/apps/webapp/app/routes/engine.v1.worker-actions.connect.ts index 247984d454f..713a344d05b 100644 --- a/apps/webapp/app/routes/engine.v1.worker-actions.connect.ts +++ b/apps/webapp/app/routes/engine.v1.worker-actions.connect.ts @@ -1,5 +1,8 @@ import { json, TypedResponse } from "@remix-run/server-runtime"; -import { WorkerApiConnectRequestBody, WorkerApiConnectResponseBody } from "@trigger.dev/core/v3/workers"; +import { + WorkerApiConnectRequestBody, + WorkerApiConnectResponseBody, +} from "@trigger.dev/core/v3/workers"; import { createActionWorkerApiRoute } from "~/services/routeBuilders/apiBuilder.server"; export const action = createActionWorkerApiRoute( diff --git a/apps/webapp/app/routes/engine.v1.worker-actions.heartbeat.ts b/apps/webapp/app/routes/engine.v1.worker-actions.heartbeat.ts index 111cab6011d..92a6645a718 100644 --- a/apps/webapp/app/routes/engine.v1.worker-actions.heartbeat.ts +++ b/apps/webapp/app/routes/engine.v1.worker-actions.heartbeat.ts @@ -1,5 +1,8 @@ import { json, TypedResponse } from "@remix-run/server-runtime"; -import { WorkerApiHeartbeatResponseBody, WorkerApiHeartbeatRequestBody } from "@trigger.dev/core/v3/workers"; +import { + WorkerApiHeartbeatResponseBody, + WorkerApiHeartbeatRequestBody, +} from "@trigger.dev/core/v3/workers"; import { createActionWorkerApiRoute } from "~/services/routeBuilders/apiBuilder.server"; export const action = createActionWorkerApiRoute( diff --git a/apps/webapp/app/routes/invites.tsx b/apps/webapp/app/routes/invites.tsx index 7b234d4129b..aa28eff0e3c 100644 --- a/apps/webapp/app/routes/invites.tsx +++ b/apps/webapp/app/routes/invites.tsx @@ -101,7 +101,10 @@ export default function Page() { return ( - +
} diff --git a/apps/webapp/app/routes/login._index/route.tsx b/apps/webapp/app/routes/login._index/route.tsx index 9c7df4f9128..ca418e2d7f8 100644 --- a/apps/webapp/app/routes/login._index/route.tsx +++ b/apps/webapp/app/routes/login._index/route.tsx @@ -204,7 +204,11 @@ export default function LoginPage() {
{data.lastAuthMethod === "email" && } = { }, domain_policy: { heading: "SSO required", - body: - "Trigger.dev couldn't send a magic link because your organization requires single sign-on. Continue to your identity provider.", + body: "Trigger.dev couldn't send a magic link because your organization requires single sign-on. Continue to your identity provider.", }, oauth_blocked: { heading: "SSO required", - body: - "You can't use that provider to sign in — your organization requires SSO. Continue with your identity provider.", + body: "You can't use that provider to sign in — your organization requires SSO. Continue with your identity provider.", }, expired: { heading: "Login attempt timed out", @@ -84,7 +82,9 @@ export async function loader({ request }: LoaderFunctionArgs) { reason, email, redirectTo, - errorMessage: errorCode ? (ERROR_MESSAGES[errorCode] ?? "We couldn't complete sign-in. Try again.") : null, + errorMessage: errorCode + ? (ERROR_MESSAGES[errorCode] ?? "We couldn't complete sign-in. Try again.") + : null, }); } diff --git a/apps/webapp/app/routes/magic.tsx b/apps/webapp/app/routes/magic.tsx index c309f27dbc6..eccd889f1bd 100644 --- a/apps/webapp/app/routes/magic.tsx +++ b/apps/webapp/app/routes/magic.tsx @@ -38,9 +38,7 @@ export async function loader({ request }: LoaderFunctionArgs) { const session = await getSession(request.headers.get("cookie")); session.flash("auth:error", { message: - thrown instanceof Error - ? thrown.message - : "Your magic link is invalid or has expired.", + thrown instanceof Error ? thrown.message : "Your magic link is invalid or has expired.", }); return redirect("/login/magic", { headers: { "Set-Cookie": await commitSession(session) }, diff --git a/apps/webapp/app/routes/otel.v1.metrics.ts b/apps/webapp/app/routes/otel.v1.metrics.ts index 803dc3da260..a73442ff513 100644 --- a/apps/webapp/app/routes/otel.v1.metrics.ts +++ b/apps/webapp/app/routes/otel.v1.metrics.ts @@ -13,9 +13,7 @@ export async function action({ request }: ActionFunctionArgs) { const exporter = await otlpExporter; const body = await request.json(); - const exportResponse = await exporter.exportMetrics( - body as ExportMetricsServiceRequest - ); + const exportResponse = await exporter.exportMetrics(body as ExportMetricsServiceRequest); return json(exportResponse, { status: 200 }); } else if (contentType.startsWith("application/x-protobuf")) { diff --git a/apps/webapp/app/routes/realtime.v1.batches.$batchId.ts b/apps/webapp/app/routes/realtime.v1.batches.$batchId.ts index add50434d48..07906292d4d 100644 --- a/apps/webapp/app/routes/realtime.v1.batches.$batchId.ts +++ b/apps/webapp/app/routes/realtime.v1.batches.$batchId.ts @@ -25,11 +25,7 @@ export const loader = createLoaderApiRoute( action: "read", // See sibling note in api.v1.batches.$batchId.ts — `{type: "runs"}` // preserves pre-RBAC `read:runs` superScope access for batch reads. - resource: (batch) => - anyResource([ - { type: "batch", id: batch.friendlyId }, - { type: "runs" }, - ]), + resource: (batch) => anyResource([{ type: "batch", id: batch.friendlyId }, { type: "runs" }]), }, }, async ({ authentication, request, resource: batchRun, apiVersion }) => { diff --git a/apps/webapp/app/routes/realtime.v1.runs.$runId.ts b/apps/webapp/app/routes/realtime.v1.runs.$runId.ts index f2268989bdd..297190bc443 100644 --- a/apps/webapp/app/routes/realtime.v1.runs.$runId.ts +++ b/apps/webapp/app/routes/realtime.v1.runs.$runId.ts @@ -3,10 +3,7 @@ import { z } from "zod"; import { $replica } from "~/db.server"; import { getRequestAbortSignal } from "~/services/httpAsyncStorage.server"; import { resolveRealtimeStreamClient } from "~/services/realtime/resolveRealtimeStreamClient.server"; -import { - anyResource, - createLoaderApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { runStore } from "~/v3/runStore.server"; const ParamsSchema = z.object({ diff --git a/apps/webapp/app/routes/realtime.v1.runs.ts b/apps/webapp/app/routes/realtime.v1.runs.ts index 2e3617800fe..c0600b392bb 100644 --- a/apps/webapp/app/routes/realtime.v1.runs.ts +++ b/apps/webapp/app/routes/realtime.v1.runs.ts @@ -1,10 +1,7 @@ import { z } from "zod"; import { getRequestAbortSignal } from "~/services/httpAsyncStorage.server"; import { resolveRealtimeStreamClient } from "~/services/realtime/resolveRealtimeStreamClient.server"; -import { - anyResource, - createLoaderApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; const SearchParamsSchema = z.object({ tags: z diff --git a/apps/webapp/app/routes/realtime.v1.sessions.$session.$io.append.ts b/apps/webapp/app/routes/realtime.v1.sessions.$session.$io.append.ts index 0bdcceb7146..ddd4ab4ac54 100644 --- a/apps/webapp/app/routes/realtime.v1.sessions.$session.$io.append.ts +++ b/apps/webapp/app/routes/realtime.v1.sessions.$session.$io.append.ts @@ -15,10 +15,7 @@ import { drainSessionStreamWaitpoints, releaseSessionStreamPart, } from "~/services/sessionStreamWaitpointCache.server"; -import { - anyResource, - createActionApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { engine } from "~/v3/runEngine.server"; import { ServiceValidationError } from "~/v3/services/common.server"; @@ -82,17 +79,11 @@ const { action, loader } = createActionApiRoute( } if (session.closedAt) { - return json( - { ok: false, error: "Cannot append to a closed session" }, - { status: 400 } - ); + return json({ ok: false, error: "Cannot append to a closed session" }, { status: 400 }); } if (session.expiresAt && session.expiresAt.getTime() < Date.now()) { - return json( - { ok: false, error: "Cannot append to an expired session" }, - { status: 400 } - ); + return json({ ok: false, error: "Cannot append to an expired session" }, { status: 400 }); } // `.out` is the agent→client channel. Only PRIVATE (secret key) auth — diff --git a/apps/webapp/app/routes/realtime.v1.sessions.$session.$io.ts b/apps/webapp/app/routes/realtime.v1.sessions.$session.$io.ts index ab646217509..e846e851be8 100644 --- a/apps/webapp/app/routes/realtime.v1.sessions.$session.$io.ts +++ b/apps/webapp/app/routes/realtime.v1.sessions.$session.$io.ts @@ -98,10 +98,7 @@ const loader = createLoaderApiRoute( allowJWT: true, corsStrategy: "all", findResource: async (params, auth) => { - const row = await resolveSessionWithWriterFallback( - auth.environment.id, - params.session - ); + const row = await resolveSessionWithWriterFallback(auth.environment.id, params.session); if (!row && isSessionFriendlyIdForm(params.session)) { return undefined; // 404 — opaque friendlyId must reference a real row } diff --git a/apps/webapp/app/routes/realtime.v1.streams.$runId.$streamId.ts b/apps/webapp/app/routes/realtime.v1.streams.$runId.$streamId.ts index 81784f9bc3a..19e56e5e0aa 100644 --- a/apps/webapp/app/routes/realtime.v1.streams.$runId.$streamId.ts +++ b/apps/webapp/app/routes/realtime.v1.streams.$runId.$streamId.ts @@ -3,10 +3,7 @@ import { z } from "zod"; import { $replica } from "~/db.server"; import { getRequestAbortSignal } from "~/services/httpAsyncStorage.server"; import { getRealtimeStreamInstance } from "~/services/realtime/v1StreamsGlobal.server"; -import { - anyResource, - createLoaderApiRoute, -} from "~/services/routeBuilders/apiBuilder.server"; +import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { runStore } from "~/v3/runStore.server"; @@ -155,9 +152,15 @@ export const loader = createLoaderApiRoute( { run } ); - return realtimeStream.streamResponse(request, run.friendlyId, params.streamId, getRequestAbortSignal(), { - lastEventId, - timeoutInSeconds, - }); + return realtimeStream.streamResponse( + request, + run.friendlyId, + params.streamId, + getRequestAbortSignal(), + { + lastEventId, + timeoutInSeconds, + } + ); } ); diff --git a/apps/webapp/app/routes/realtime.v1.streams.$runId.$target.$streamId.append.ts b/apps/webapp/app/routes/realtime.v1.streams.$runId.$target.$streamId.append.ts index 7cb813a6dec..12400e8d0b0 100644 --- a/apps/webapp/app/routes/realtime.v1.streams.$runId.$target.$streamId.append.ts +++ b/apps/webapp/app/routes/realtime.v1.streams.$runId.$target.$streamId.append.ts @@ -59,8 +59,8 @@ const { action } = createActionApiRoute( params.target === "self" ? run.friendlyId : params.target === "parent" - ? run.parentTaskRun?.friendlyId - : run.rootTaskRun?.friendlyId; + ? run.parentTaskRun?.friendlyId + : run.rootTaskRun?.friendlyId; if (!targetId) { return new Response("Target not found", { status: 404 }); diff --git a/apps/webapp/app/routes/realtime.v1.streams.$runId.$target.$streamId.ts b/apps/webapp/app/routes/realtime.v1.streams.$runId.$target.$streamId.ts index c71ad48d121..01a2b550d45 100644 --- a/apps/webapp/app/routes/realtime.v1.streams.$runId.$target.$streamId.ts +++ b/apps/webapp/app/routes/realtime.v1.streams.$runId.$target.$streamId.ts @@ -54,8 +54,8 @@ const { action } = createActionApiRoute( params.target === "self" ? run : params.target === "parent" - ? run.parentTaskRun - : run.rootTaskRun; + ? run.parentTaskRun + : run.rootTaskRun; if (!targetRun?.friendlyId) { return new Response("Target not found", { status: 404 }); @@ -191,8 +191,8 @@ const loader = createLoaderApiRoute( params.target === "self" ? run : params.target === "parent" - ? run.parentTaskRun - : run.rootTaskRun; + ? run.parentTaskRun + : run.rootTaskRun; if (!targetRun?.friendlyId) { return new Response("Target not found", { status: 404 }); @@ -209,11 +209,9 @@ const loader = createLoaderApiRoute( const clientId = request.headers.get("X-Client-Id") || "default"; const streamVersion = request.headers.get("X-Stream-Version") || "v1"; - const realtimeStream = getRealtimeStreamInstance( - authentication.environment, - streamVersion, - { run: { streamBasinName: targetRun.streamBasinName ?? null } } - ); + const realtimeStream = getRealtimeStreamInstance(authentication.environment, streamVersion, { + run: { streamBasinName: targetRun.streamBasinName ?? null }, + }); const lastChunkIndex = await realtimeStream.getLastChunkIndex( targetId, diff --git a/apps/webapp/app/routes/resources.account.mfa.setup/useMfaSetup.ts b/apps/webapp/app/routes/resources.account.mfa.setup/useMfaSetup.ts index 0a69d44617f..8e1aad9ae1e 100644 --- a/apps/webapp/app/routes/resources.account.mfa.setup/useMfaSetup.ts +++ b/apps/webapp/app/routes/resources.account.mfa.setup/useMfaSetup.ts @@ -2,7 +2,7 @@ import { useReducer, useEffect } from "react"; import { useTypedFetcher } from "remix-typedjson"; import { action } from "./route"; -export type MfaPhase = 'idle' | 'enabling' | 'validating' | 'showing-recovery' | 'disabling'; +export type MfaPhase = "idle" | "enabling" | "validating" | "showing-recovery" | "disabling"; export interface MfaState { phase: MfaPhase; @@ -14,150 +14,150 @@ export interface MfaState { recoveryCodes?: string[]; error?: string; isSubmitting: boolean; - disableMethod: 'totp' | 'recovery'; + disableMethod: "totp" | "recovery"; } -export type MfaAction = - | { type: 'ENABLE_MFA' } - | { type: 'SETUP_DATA_RECEIVED'; setupData: { secret: string; otpAuthUrl: string } } - | { type: 'CANCEL_SETUP' } - | { type: 'VALIDATE_TOTP'; code: string } - | { type: 'VALIDATION_SUCCESS'; recoveryCodes: string[] } - | { type: 'VALIDATION_FAILED'; error: string; setupData: { secret: string; otpAuthUrl: string } } - | { type: 'RECOVERY_CODES_SAVED' } - | { type: 'OPEN_DISABLE_DIALOG' } - | { type: 'DISABLE_MFA' } - | { type: 'DISABLE_SUCCESS' } - | { type: 'DISABLE_FAILED'; error: string } - | { type: 'CANCEL_DISABLE' } - | { type: 'SET_DISABLE_METHOD'; method: 'totp' | 'recovery' } - | { type: 'SET_ERROR'; error: string } - | { type: 'CLEAR_ERROR' } - | { type: 'SET_SUBMITTING'; isSubmitting: boolean }; +export type MfaAction = + | { type: "ENABLE_MFA" } + | { type: "SETUP_DATA_RECEIVED"; setupData: { secret: string; otpAuthUrl: string } } + | { type: "CANCEL_SETUP" } + | { type: "VALIDATE_TOTP"; code: string } + | { type: "VALIDATION_SUCCESS"; recoveryCodes: string[] } + | { type: "VALIDATION_FAILED"; error: string; setupData: { secret: string; otpAuthUrl: string } } + | { type: "RECOVERY_CODES_SAVED" } + | { type: "OPEN_DISABLE_DIALOG" } + | { type: "DISABLE_MFA" } + | { type: "DISABLE_SUCCESS" } + | { type: "DISABLE_FAILED"; error: string } + | { type: "CANCEL_DISABLE" } + | { type: "SET_DISABLE_METHOD"; method: "totp" | "recovery" } + | { type: "SET_ERROR"; error: string } + | { type: "CLEAR_ERROR" } + | { type: "SET_SUBMITTING"; isSubmitting: boolean }; function mfaReducer(state: MfaState, action: MfaAction): MfaState { switch (action.type) { - case 'ENABLE_MFA': + case "ENABLE_MFA": return { ...state, - phase: 'enabling', + phase: "enabling", isSubmitting: true, error: undefined, }; - case 'SETUP_DATA_RECEIVED': + case "SETUP_DATA_RECEIVED": return { ...state, - phase: 'enabling', + phase: "enabling", setupData: action.setupData, error: undefined, isSubmitting: false, }; - case 'CANCEL_SETUP': + case "CANCEL_SETUP": return { ...state, - phase: 'idle', + phase: "idle", setupData: undefined, error: undefined, isSubmitting: false, }; - case 'VALIDATE_TOTP': + case "VALIDATE_TOTP": return { ...state, - phase: 'validating', + phase: "validating", isSubmitting: true, error: undefined, }; - case 'VALIDATION_SUCCESS': + case "VALIDATION_SUCCESS": return { ...state, - phase: 'showing-recovery', + phase: "showing-recovery", recoveryCodes: action.recoveryCodes, isSubmitting: false, isEnabled: true, error: undefined, }; - case 'VALIDATION_FAILED': + case "VALIDATION_FAILED": return { ...state, - phase: 'enabling', + phase: "enabling", setupData: action.setupData, error: action.error, isSubmitting: false, }; - case 'RECOVERY_CODES_SAVED': + case "RECOVERY_CODES_SAVED": return { ...state, - phase: 'idle', + phase: "idle", setupData: undefined, recoveryCodes: undefined, isSubmitting: false, }; - case 'OPEN_DISABLE_DIALOG': + case "OPEN_DISABLE_DIALOG": return { ...state, - phase: 'disabling', + phase: "disabling", error: undefined, isSubmitting: false, }; - case 'DISABLE_MFA': + case "DISABLE_MFA": return { ...state, isSubmitting: true, error: undefined, }; - case 'DISABLE_SUCCESS': + case "DISABLE_SUCCESS": return { ...state, - phase: 'idle', + phase: "idle", isEnabled: false, error: undefined, isSubmitting: false, }; - case 'DISABLE_FAILED': + case "DISABLE_FAILED": return { ...state, error: action.error, isSubmitting: false, }; - case 'CANCEL_DISABLE': + case "CANCEL_DISABLE": return { ...state, - phase: 'idle', + phase: "idle", error: undefined, isSubmitting: false, }; - case 'SET_DISABLE_METHOD': + case "SET_DISABLE_METHOD": return { ...state, disableMethod: action.method, error: undefined, }; - case 'SET_ERROR': + case "SET_ERROR": return { ...state, error: action.error, }; - case 'CLEAR_ERROR': + case "CLEAR_ERROR": return { ...state, error: undefined, }; - case 'SET_SUBMITTING': + case "SET_SUBMITTING": return { ...state, isSubmitting: action.isSubmitting, @@ -170,55 +170,55 @@ function mfaReducer(state: MfaState, action: MfaAction): MfaState { export function useMfaSetup(initialIsEnabled: boolean) { const fetcher = useTypedFetcher(); - + const [state, dispatch] = useReducer(mfaReducer, { - phase: 'idle', + phase: "idle", isEnabled: initialIsEnabled, isSubmitting: false, - disableMethod: 'totp', + disableMethod: "totp", }); // Handle fetcher responses useEffect(() => { if (fetcher.data) { const { data } = fetcher; - + switch (data.action) { - case 'enable-mfa': - dispatch({ - type: 'SETUP_DATA_RECEIVED', - setupData: { secret: data.secret, otpAuthUrl: data.otpAuthUrl } + case "enable-mfa": + dispatch({ + type: "SETUP_DATA_RECEIVED", + setupData: { secret: data.secret, otpAuthUrl: data.otpAuthUrl }, }); break; - - case 'validate-totp': + + case "validate-totp": if (data.success) { - dispatch({ - type: 'VALIDATION_SUCCESS', - recoveryCodes: data.recoveryCodes || [] + dispatch({ + type: "VALIDATION_SUCCESS", + recoveryCodes: data.recoveryCodes || [], }); } else { - dispatch({ - type: 'VALIDATION_FAILED', - error: data.error || 'Invalid code', - setupData: { secret: data.secret!, otpAuthUrl: data.otpAuthUrl! } + dispatch({ + type: "VALIDATION_FAILED", + error: data.error || "Invalid code", + setupData: { secret: data.secret!, otpAuthUrl: data.otpAuthUrl! }, }); } break; - - case 'disable-mfa': + + case "disable-mfa": if (data.success) { - dispatch({ type: 'DISABLE_SUCCESS' }); + dispatch({ type: "DISABLE_SUCCESS" }); } else { - dispatch({ - type: 'DISABLE_FAILED', - error: data.error || 'Failed to disable MFA' + dispatch({ + type: "DISABLE_FAILED", + error: data.error || "Failed to disable MFA", }); } break; - - case 'cancel-totp': - dispatch({ type: 'CANCEL_SETUP' }); + + case "cancel-totp": + dispatch({ type: "CANCEL_SETUP" }); break; } } @@ -226,68 +226,65 @@ export function useMfaSetup(initialIsEnabled: boolean) { // Handle submitting state useEffect(() => { - dispatch({ type: 'SET_SUBMITTING', isSubmitting: fetcher.state === 'submitting' }); + dispatch({ type: "SET_SUBMITTING", isSubmitting: fetcher.state === "submitting" }); }, [fetcher.state]); const actions = { enableMfa: () => { - dispatch({ type: 'ENABLE_MFA' }); + dispatch({ type: "ENABLE_MFA" }); fetcher.submit( - { action: 'enable-mfa' }, - { method: 'POST', action: '/resources/account/mfa/setup' } + { action: "enable-mfa" }, + { method: "POST", action: "/resources/account/mfa/setup" } ); }, cancelSetup: () => { - dispatch({ type: 'CANCEL_SETUP' }); + dispatch({ type: "CANCEL_SETUP" }); fetcher.submit( - { action: 'cancel-totp' }, - { method: 'POST', action: '/resources/account/mfa/setup' } + { action: "cancel-totp" }, + { method: "POST", action: "/resources/account/mfa/setup" } ); }, validateTotp: (code: string) => { - dispatch({ type: 'VALIDATE_TOTP', code }); + dispatch({ type: "VALIDATE_TOTP", code }); fetcher.submit( - { action: 'validate-totp', totpCode: code }, - { method: 'POST', action: '/resources/account/mfa/setup' } + { action: "validate-totp", totpCode: code }, + { method: "POST", action: "/resources/account/mfa/setup" } ); }, saveRecoveryCodes: () => { - dispatch({ type: 'RECOVERY_CODES_SAVED' }); + dispatch({ type: "RECOVERY_CODES_SAVED" }); fetcher.submit( - { action: 'saved-recovery-codes' }, - { method: 'POST', action: '/resources/account/mfa/setup' } + { action: "saved-recovery-codes" }, + { method: "POST", action: "/resources/account/mfa/setup" } ); }, openDisableDialog: () => { - dispatch({ type: 'OPEN_DISABLE_DIALOG' }); + dispatch({ type: "OPEN_DISABLE_DIALOG" }); }, disableMfa: (totpCode?: string, recoveryCode?: string) => { - dispatch({ type: 'DISABLE_MFA' }); - const formData: Record = { action: 'disable-mfa' }; + dispatch({ type: "DISABLE_MFA" }); + const formData: Record = { action: "disable-mfa" }; if (totpCode) formData.totpCode = totpCode; if (recoveryCode) formData.recoveryCode = recoveryCode; - - fetcher.submit( - formData, - { method: 'POST', action: '/resources/account/mfa/setup' } - ); + + fetcher.submit(formData, { method: "POST", action: "/resources/account/mfa/setup" }); }, cancelDisable: () => { - dispatch({ type: 'CANCEL_DISABLE' }); + dispatch({ type: "CANCEL_DISABLE" }); }, - setDisableMethod: (method: 'totp' | 'recovery') => { - dispatch({ type: 'SET_DISABLE_METHOD', method }); + setDisableMethod: (method: "totp" | "recovery") => { + dispatch({ type: "SET_DISABLE_METHOD", method }); }, clearError: () => { - dispatch({ type: 'CLEAR_ERROR' }); + dispatch({ type: "CLEAR_ERROR" }); }, }; @@ -295,8 +292,10 @@ export function useMfaSetup(initialIsEnabled: boolean) { state, actions, // Computed properties for easier access - isQrDialogOpen: (state.phase === 'enabling' && !!state.setupData) || (state.phase === 'showing-recovery' && !!state.recoveryCodes), + isQrDialogOpen: + (state.phase === "enabling" && !!state.setupData) || + (state.phase === "showing-recovery" && !!state.recoveryCodes), isRecoveryDialogOpen: false, // Recovery is now handled within the setup dialog - isDisableDialogOpen: state.phase === 'disabling', + isDisableDialogOpen: state.phase === "disabling", }; -} \ No newline at end of file +} diff --git a/apps/webapp/app/routes/resources.branches.archive.tsx b/apps/webapp/app/routes/resources.branches.archive.tsx index 3b7f178da16..105a21644f7 100644 --- a/apps/webapp/app/routes/resources.branches.archive.tsx +++ b/apps/webapp/app/routes/resources.branches.archive.tsx @@ -46,8 +46,8 @@ export async function action({ request }: ActionFunctionArgs) { if (result.success) { return redirectWithSuccessMessage( - result.branch.type === "DEVELOPMENT" ? - branchesDevPath(result.organization, result.project, result.branch) + result.branch.type === "DEVELOPMENT" + ? branchesDevPath(result.organization, result.project, result.branch) : branchesPath(result.organization, result.project, result.branch), request, `Branch "${result.branch.branchName}" archived` diff --git a/apps/webapp/app/routes/resources.feedback.ts b/apps/webapp/app/routes/resources.feedback.ts index 62b13b518b3..3a7e5bd34b5 100644 --- a/apps/webapp/app/routes/resources.feedback.ts +++ b/apps/webapp/app/routes/resources.feedback.ts @@ -49,10 +49,7 @@ export const feedbackTypes = { labelTypeId: "lt_01KS54WBRYKE6DY369KPK2SS4W", threadTitle: "Web app: HIPAA BAA request", }, -} as const satisfies Record< - string, - { label: string; labelTypeId?: string; threadTitle: string } ->; +} as const satisfies Record; export type FeedbackType = keyof typeof feedbackTypes; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.can-view-logs-page/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.can-view-logs-page/route.tsx index f55307c7c34..501b4a8ad35 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.can-view-logs-page/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.can-view-logs-page/route.tsx @@ -42,12 +42,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); const { organizationSlug } = OrganizationParamsSchema.parse(params); - const canViewLogsPage = user.admin || user.isImpersonating || await hasLogsPageAccess( - user.id, - user.admin, - user.isImpersonating, - organizationSlug - ); + const canViewLogsPage = + user.admin || + user.isImpersonating || + (await hasLogsPageAccess(user.id, user.admin, user.isImpersonating, organizationSlug)); return typedjson({ canViewLogsPage }); }; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts index 66f5aae80c1..c2a4bf59ffe 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts @@ -55,7 +55,14 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = user.id; const { organizationSlug, projectParam } = EnvironmentParamSchema.parse(params); - if (!(await canAccessDashboardAgent({ userId, isAdmin: user.admin, isImpersonating: user.isImpersonating, organizationSlug }))) { + if ( + !(await canAccessDashboardAgent({ + userId, + isAdmin: user.admin, + isImpersonating: user.isImpersonating, + organizationSlug, + })) + ) { return json({ error: "Not found" }, { status: 404 }); } @@ -83,7 +90,14 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { const userId = user.id; const { organizationSlug, projectParam, envParam } = EnvironmentParamSchema.parse(params); - if (!(await canAccessDashboardAgent({ userId, isAdmin: user.admin, isImpersonating: user.isImpersonating, organizationSlug }))) { + if ( + !(await canAccessDashboardAgent({ + userId, + isAdmin: user.admin, + isImpersonating: user.isImpersonating, + organizationSlug, + })) + ) { return json({ error: "Not found" }, { status: 404 }); } @@ -186,7 +200,13 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { // id). The transport falls back here to re-establish a session for an // existing chat (e.g. after its token expired), so verify ownership before // issuing one — a client-supplied chatId must belong to the caller. - if (!(await chatExists(dashboardAgentDb, { chatId, userId, organizationId: project.organizationId }))) { + if ( + !(await chatExists(dashboardAgentDb, { + chatId, + userId, + organizationId: project.organizationId, + })) + ) { return json({ error: "Chat not found" }, { status: 404 }); } let clientData: Record | undefined; @@ -215,7 +235,13 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { } // Only mint a session token for a chat the caller owns, so a client-supplied // chatId can't be used to get a token for someone else's session. - if (!(await chatExists(dashboardAgentDb, { chatId, userId, organizationId: project.organizationId }))) { + if ( + !(await chatExists(dashboardAgentDb, { + chatId, + userId, + organizationId: project.organizationId, + })) + ) { return json({ error: "Chat not found" }, { status: 404 }); } return json({ token: await mintDashboardAgentToken(chatId) }); diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx index 51bbded5932..ee3f56147df 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx @@ -6,10 +6,7 @@ import { prisma } from "~/db.server"; import { QueryWidgetConfig } from "~/components/metrics/QueryWidget"; import { findProjectBySlug } from "~/models/project.server"; import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server"; -import { - DashboardLayout, - LayoutItem, -} from "~/presenters/v3/MetricDashboardPresenter.server"; +import { DashboardLayout, LayoutItem } from "~/presenters/v3/MetricDashboardPresenter.server"; import { getCurrentPlan } from "~/services/platform.v3.server"; import { requireUserId } from "~/services/session.server"; import { EnvironmentParamSchema } from "~/utils/pathBuilder"; @@ -145,10 +142,9 @@ async function saveDashboardLayout( }); if (result.count === 0) { - throw new Response( - "Dashboard was modified by another request. Please refresh and try again.", - { status: 409 } - ); + throw new Response("Dashboard was modified by another request. Please refresh and try again.", { + status: 409, + }); } } diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.create.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.create.tsx index 1e33b3b6817..ef5ed4fa1e5 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.create.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.create.tsx @@ -29,8 +29,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { }), ]); - const metricDashboardsLimitValue = (plan?.v3Subscription?.plan?.limits as any) - ?.metricDashboards; + const metricDashboardsLimitValue = (plan?.v3Subscription?.plan?.limits as any)?.metricDashboards; const dashboardLimit = typeof metricDashboardsLimitValue === "number" ? metricDashboardsLimitValue diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs.$logId.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs.$logId.tsx index be4fdba7fe8..418cee805c1 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs.$logId.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs.$logId.tsx @@ -44,7 +44,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { const [traceId, spanId, , startTime] = parts; - const logsClickhouse = await clickhouseFactory.getClickhouseForOrganization(project.organizationId, "logs"); + const logsClickhouse = await clickhouseFactory.getClickhouseForOrganization( + project.organizationId, + "logs" + ); const presenter = new LogDetailPresenter($replica, logsClickhouse); let result; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs.ts index 38b7dd390f8..8e918ec5f2f 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs.ts @@ -4,7 +4,11 @@ import { requireUser, requireUserId } from "~/services/session.server"; import { EnvironmentParamSchema } from "~/utils/pathBuilder"; import { findProjectBySlug } from "~/models/project.server"; import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server"; -import { LogsListPresenter, type LogLevel, LogsListOptionsSchema } from "~/presenters/v3/LogsListPresenter.server"; +import { + LogsListPresenter, + type LogLevel, + LogsListOptionsSchema, +} from "~/presenters/v3/LogsListPresenter.server"; import { $replica } from "~/db.server"; import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server"; import { getCurrentPlan } from "~/services/platform.v3.server"; @@ -69,7 +73,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { retentionLimitDays, }) as any; // Validated by LogsListOptionsSchema at runtime - const logsClickhouse = await clickhouseFactory.getClickhouseForOrganization(project.organizationId, "logs"); + const logsClickhouse = await clickhouseFactory.getClickhouseForOrganization( + project.organizationId, + "logs" + ); const presenter = new LogsListPresenter($replica, logsClickhouse); const result = await presenter.call(project.organizationId, environment.id, options); diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx index 826618277af..9485f5ff4c6 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx @@ -117,7 +117,12 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { const tags = [ `chat:${chatId}`, "playground:true", - ...(tagsStr ? tagsStr.split(",").map((t) => t.trim()).filter(Boolean) : []), + ...(tagsStr + ? tagsStr + .split(",") + .map((t) => t.trim()) + .filter(Boolean) + : []), ].slice(0, 5); const triggerConfig = { @@ -262,12 +267,11 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { }); if (existing?.title === "New conversation") { - const firstUserMsg = messagesData.find( - (m: any) => m.role === "user" - ) as Record | undefined; + const firstUserMsg = messagesData.find((m: any) => m.role === "user") as + | Record + | undefined; const firstText = - firstUserMsg?.parts?.find((p: any) => p.type === "text")?.text ?? - firstUserMsg?.content; + firstUserMsg?.parts?.find((p: any) => p.type === "text")?.text ?? firstUserMsg?.content; if (firstText && typeof firstText === "string") { titleUpdate = { title: firstText.length > 60 ? firstText.slice(0, 60) + "..." : firstText, diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.realtime.v1.sessions.$session.$io.append.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.realtime.v1.sessions.$session.$io.append.ts index 038e053a8b0..0d158659314 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.realtime.v1.sessions.$session.$io.append.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.realtime.v1.sessions.$session.$io.append.ts @@ -56,27 +56,17 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ ok: false, error: "Request body too large" }, { status: 413 }); } - const session = await resolveSessionByIdOrExternalId( - $replica, - environment.id, - sessionParam - ); + const session = await resolveSessionByIdOrExternalId($replica, environment.id, sessionParam); if (!session) { return json({ ok: false, error: "Session not found" }, { status: 404 }); } if (session.closedAt) { - return json( - { ok: false, error: "Cannot append to a closed session" }, - { status: 400 } - ); + return json({ ok: false, error: "Cannot append to a closed session" }, { status: 400 }); } if (session.expiresAt && session.expiresAt.getTime() < Date.now()) { - return json( - { ok: false, error: "Cannot append to an expired session" }, - { status: 400 } - ); + return json({ ok: false, error: "Cannot append to an expired session" }, { status: 400 }); } const realtimeStream = getRealtimeStreamInstance(environment, "v2", { session }); @@ -117,10 +107,7 @@ export async function action({ request, params }: ActionFunctionArgs) { if (appendError) { if (appendError instanceof ServiceValidationError) { - return json( - { ok: false, error: appendError.message }, - { status: appendError.status ?? 422 } - ); + return json({ ok: false, error: appendError.message }, { status: appendError.status ?? 422 }); } return json({ ok: false, error: appendError.message }, { status: 500 }); } diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.realtime.v1.sessions.$session.$io.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.realtime.v1.sessions.$session.$io.ts index e54bfe35755..317bd38a7d0 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.realtime.v1.sessions.$session.$io.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.realtime.v1.sessions.$session.$io.ts @@ -41,11 +41,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return new Response("Environment not found", { status: 404 }); } - const session = await resolveSessionByIdOrExternalId( - $replica, - environment.id, - sessionParam - ); + const session = await resolveSessionByIdOrExternalId($replica, environment.id, sessionParam); if (!session) { return new Response("Session not found", { status: 404 }); diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug.generations.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug.generations.ts index e468438cdc1..98975140323 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug.generations.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug.generations.ts @@ -22,8 +22,9 @@ export type GenerationsResponse = { export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); - const { projectParam, organizationSlug, envParam, promptSlug } = - EnvironmentParamSchema.extend({ promptSlug: z.string() }).parse(params); + const { projectParam, organizationSlug, envParam, promptSlug } = EnvironmentParamSchema.extend({ + promptSlug: z.string(), + }).parse(params); const project = await findProjectBySlug(organizationSlug, projectParam, userId); if (!project) throw new Response("Project not found", { status: 404 }); @@ -59,7 +60,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { const operations = url.searchParams.getAll("operations").filter(Boolean); const providers = url.searchParams.getAll("providers").filter(Boolean); - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(project.organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + project.organizationId, + "standard" + ); const presenter = new PromptPresenter(clickhouse); const result = await presenter.listGenerations({ environmentId: environment.id, diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.sessions.$sessionId.$io.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.sessions.$sessionId.$io.ts index 3a0dfca568e..c001d66d509 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.sessions.$sessionId.$io.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.sessions.$sessionId.$io.ts @@ -66,11 +66,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return new Response("Run not found", { status: 404 }); } - const session = await resolveSessionByIdOrExternalId( - $replica, - environment.id, - sessionId - ); + const session = await resolveSessionByIdOrExternalId($replica, environment.id, sessionId); if (!session) { return new Response("Session not found", { status: 404 }); diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.streams.$runId.$streamId.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.streams.$runId.$streamId.ts index cec6c3c4e98..6c50b0f1463 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.streams.$runId.$streamId.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.streams.$runId.$streamId.ts @@ -82,14 +82,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // `request.signal` is severed by Remix's Request.clone() + Node undici GC bug // (see apps/webapp/CLAUDE.md). Use the Express res.on('close')-backed signal so // the upstream stream fetch actually aborts when the user closes the tab. - return realtimeStream.streamResponse( - request, - run.friendlyId, - streamId, - getRequestAbortSignal(), - { - lastEventId, - timeoutInSeconds, - } - ); + return realtimeStream.streamResponse(request, run.friendlyId, streamId, getRequestAbortSignal(), { + lastEventId, + timeoutInSeconds, + }); } diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx index d6d39ebbb95..450b9c69662 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx @@ -397,10 +397,7 @@ function RunBody({ className="size-5 min-h-5 min-w-5" /> {run.taskIdentifier} @@ -1315,10 +1312,10 @@ function SpanEntity({ span }: { span: Span }) { span.isCancelled ? "CANCELED" : span.isError - ? "FAILED" - : span.isPartial - ? "EXECUTING" - : "COMPLETED" + ? "FAILED" + : span.isPartial + ? "EXECUTING" + : "COMPLETED" } className="text-sm" /> @@ -1430,10 +1427,10 @@ function SpanEntity({ span }: { span: Span }) { span.isCancelled ? "CANCELED" : span.isError - ? "FAILED" - : span.isPartial - ? "EXECUTING" - : "COMPLETED" + ? "FAILED" + : span.isPartial + ? "EXECUTING" + : "COMPLETED" } className="text-sm" /> @@ -1508,8 +1505,8 @@ function SpanEntity({ span }: { span: Span }) { typeof span.properties === "string" ? span.properties : span.properties != null - ? JSON.stringify(span.properties, null, 2) - : undefined + ? JSON.stringify(span.properties, null, 2) + : undefined } startTime={span.startTime} duration={span.duration} diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.streams.$streamKey/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.streams.$streamKey/route.tsx index 24e7a73374f..ee5eda4c3a6 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.streams.$streamKey/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.streams.$streamKey/route.tsx @@ -95,9 +95,15 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { { run } ); - return realtimeStream.streamResponse(request, run.friendlyId, streamKey, getRequestAbortSignal(), { - lastEventId, - }); + return realtimeStream.streamResponse( + request, + run.friendlyId, + streamKey, + getRequestAbortSignal(), + { + lastEventId, + } + ); }; export function RealtimeStreamViewer({ @@ -338,8 +344,8 @@ export function RealtimeStreamViewer({ chunks.length === 0 ? "cursor-not-allowed opacity-50" : copied - ? "text-success hover:cursor-pointer" - : "text-text-dimmed hover:cursor-pointer hover:text-text-bright" + ? "text-success hover:cursor-pointer" + : "text-text-dimmed hover:cursor-pointer hover:text-text-bright" )} > {copied ? ( diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.live.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.live.ts index 616aa728872..44def23a85e 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.live.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.live.ts @@ -23,8 +23,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { Object.fromEntries(url.searchParams) ); - const newRunsSince = - includeNewRuns && since !== undefined ? since : undefined; + const newRunsSince = includeNewRuns && since !== undefined ? since : undefined; if (runIds.length === 0 && newRunsSince === undefined) { return { runs: [] }; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx index 1f7a2add413..157462a86d5 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx @@ -241,8 +241,8 @@ export function UpsertScheduleForm({ {schedule?.friendlyId ? "Edit schedule" : defaultTaskIdentifier - ? `New schedule for ${defaultTaskIdentifier}` - : "New schedule"} + ? `New schedule for ${defaultTaskIdentifier}` + : "New schedule"}
diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.schedules-addon.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.schedules-addon.ts index f765da3534c..8f09c6e4e16 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.schedules-addon.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.schedules-addon.ts @@ -14,10 +14,7 @@ export const PurchaseSchema = z.discriminatedUnion("action", [ }), z.object({ action: z.literal("quota-increase"), - amount: z.coerce - .number() - .int("Must be a whole number") - .min(1, "Amount must be greater than 0"), + amount: z.coerce.number().int("Must be a whole number").min(1, "Amount must be greater than 0"), }), ]); @@ -44,10 +41,9 @@ export async function action({ request, params }: ActionFunctionArgs) { ); } if (purchaseBlockReason === "managed_billing") { - return json( - { ok: false, error: "Contact us to request more schedules." } as const, - { status: 403 } - ); + return json({ ok: false, error: "Contact us to request more schedules." } as const, { + status: 403, + }); } const formData = await request.formData(); diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx index 08dcad356c9..b87c1bfc4c1 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx @@ -531,10 +531,10 @@ export function TierFree({ {subscription?.plan === undefined ? "Select plan" : subscription.plan.type === "free" - ? "Current plan" - : subscription.canceledAt !== undefined - ? "Current plan" - : "Select plan"} + ? "Current plan" + : subscription.canceledAt !== undefined + ? "Current plan" + : "Select plan"} )} @@ -657,10 +657,10 @@ export function TierHobby({ {subscription?.plan === undefined ? "Select plan" : subscription.plan.type === "free" || subscription.canceledAt !== undefined - ? `Upgrade to ${plan.title}` - : subscription.plan.code === plan.code - ? "Current plan" - : `Upgrade to ${plan.title}`} + ? `Upgrade to ${plan.title}` + : subscription.plan.code === plan.code + ? "Current plan" + : `Upgrade to ${plan.title}`} )} @@ -802,10 +802,10 @@ export function TierPro({ {subscription?.plan === undefined ? "Select plan" : subscription.plan.type === "free" || subscription.canceledAt !== undefined - ? `Upgrade to ${plan.title}` - : subscription.plan.code === plan.code - ? "Current plan" - : `Upgrade to ${plan.title}`} + ? `Upgrade to ${plan.title}` + : subscription.plan.code === plan.code + ? "Current plan" + : `Upgrade to ${plan.title}`} )}
diff --git a/apps/webapp/app/routes/resources.preferences.sidemenu.tsx b/apps/webapp/app/routes/resources.preferences.sidemenu.tsx index 13f442aa610..94db448b581 100644 --- a/apps/webapp/app/routes/resources.preferences.sidemenu.tsx +++ b/apps/webapp/app/routes/resources.preferences.sidemenu.tsx @@ -4,10 +4,7 @@ import { SideMenuSectionIdSchema, type SideMenuSectionId, } from "~/components/navigation/sideMenuTypes"; -import { - updateItemOrder, - updateSideMenuPreferences, -} from "~/services/dashboardPreferences.server"; +import { updateItemOrder, updateSideMenuPreferences } from "~/services/dashboardPreferences.server"; import { requireUser } from "~/services/session.server"; // Transforms form data string "true"/"false" to boolean, or undefined if not present diff --git a/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts b/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts index 7662a88b4d2..6b514034294 100644 --- a/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts +++ b/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts @@ -83,10 +83,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) { return new Response("Not found", { status: 404 }); } - const eventRepository = await getEventRepositoryForStore( - run.taskEventStore, - run.organizationId - ); + const eventRepository = await getEventRepositoryForStore(run.taskEventStore, run.organizationId); // Stream the trace straight from the store to the gzip response, one event at // a time, never materialising the full set or building a tree. This keeps the diff --git a/apps/webapp/app/routes/resources.runs.$runParam.ts b/apps/webapp/app/routes/resources.runs.$runParam.ts index 38e17531f6f..e85da45bbde 100644 --- a/apps/webapp/app/routes/resources.runs.$runParam.ts +++ b/apps/webapp/app/routes/resources.runs.$runParam.ts @@ -144,17 +144,17 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { finishedAttempt === null ? undefined : finishedAttempt.outputType === "application/store" - ? `/resources/packets/${run.runtimeEnvironment.id}/${finishedAttempt.output}` - : typeof finishedAttempt.output !== "undefined" && finishedAttempt.output !== null - ? await prettyPrintPacket(finishedAttempt.output, finishedAttempt.outputType ?? undefined) - : undefined; + ? `/resources/packets/${run.runtimeEnvironment.id}/${finishedAttempt.output}` + : typeof finishedAttempt.output !== "undefined" && finishedAttempt.output !== null + ? await prettyPrintPacket(finishedAttempt.output, finishedAttempt.outputType ?? undefined) + : undefined; const payload = run.payloadType === "application/store" ? `/resources/packets/${run.runtimeEnvironment.id}/${run.payload}` : typeof run.payload !== "undefined" && run.payload !== null - ? await prettyPrintPacket(run.payload, run.payloadType ?? undefined) - : undefined; + ? await prettyPrintPacket(run.payload, run.payloadType ?? undefined) + : undefined; let error: TaskRunError | undefined = undefined; if (finishedAttempt?.error) { diff --git a/apps/webapp/app/routes/resources.sessions.$sessionParam.close.ts b/apps/webapp/app/routes/resources.sessions.$sessionParam.close.ts index 27ffec56716..afc26a06e3b 100644 --- a/apps/webapp/app/routes/resources.sessions.$sessionParam.close.ts +++ b/apps/webapp/app/routes/resources.sessions.$sessionParam.close.ts @@ -49,11 +49,7 @@ export const action: ActionFunction = async ({ request, params }) => { return json(submission); } - const session = await resolveSessionByIdOrExternalId( - $replica, - environment.id, - sessionParam - ); + const session = await resolveSessionByIdOrExternalId($replica, environment.id, sessionParam); if (!session) { submission.error = { sessionParam: ["Session not found"] }; diff --git a/apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts b/apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts index 49c2dbcf783..a068f6fa7e0 100644 --- a/apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts +++ b/apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts @@ -224,7 +224,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { ? undefined : regionForDisplay(run.region, run.workerQueue), regions: regionsResult.regions, - ttlSeconds: run.ttl ? parseDuration(run.ttl, "s") ?? undefined : undefined, + ttlSeconds: run.ttl ? (parseDuration(run.ttl, "s") ?? undefined) : undefined, idempotencyKey: run.idempotencyKey, runTags: run.runTags, payload, diff --git a/apps/webapp/app/routes/storybook.agent-ui/route.tsx b/apps/webapp/app/routes/storybook.agent-ui/route.tsx index 0bd205fa608..e43015a361c 100644 --- a/apps/webapp/app/routes/storybook.agent-ui/route.tsx +++ b/apps/webapp/app/routes/storybook.agent-ui/route.tsx @@ -34,7 +34,8 @@ const fullDiagnosis: DiagnosisBlock = { reference: "error_emptyorder", }, ], - impact: "14 runs of process-order failed with this error in the last 24 hours, all in production.", + impact: + "14 runs of process-order failed with this error in the last 24 hours, all in production.", nextSteps: [ "Guard against an empty items array at the top of processOrder and return early.", "Validate the payload before triggering so empty orders never reach the task.", @@ -54,7 +55,11 @@ const externalServiceDiagnosis: DiagnosisBlock = { "The Stripe call has no timeout or retry, so a slow upstream response runs past the task's max duration.", confidence: "medium", evidence: [ - { type: "error", detail: "TimeoutError: Stripe API timed out after 30s", reference: "run_f6g7h8i9j0" }, + { + type: "error", + detail: "TimeoutError: Stripe API timed out after 30s", + reference: "run_f6g7h8i9j0", + }, { type: "deploy", detail: "First seen on version 20260620.2", reference: "20260620.2" }, ], impact: "Intermittent: 3 of the last 50 charge-payment runs timed out.", @@ -68,7 +73,8 @@ const externalServiceDiagnosis: DiagnosisBlock = { const lowConfidenceDiagnosis: DiagnosisBlock = { type: "diagnosis", runId: "run_k1l2m3n4o5", - summary: "The run crashed without a captured error, so the cause isn't conclusive from the available signals.", + summary: + "The run crashed without a captured error, so the cause isn't conclusive from the available signals.", category: "unknown", likelyCause: "The container exited without writing an error. This is consistent with an out-of-memory kill, but there's no OOM signal in the trace to confirm it.", diff --git a/apps/webapp/app/routes/storybook.animated-panel/route.tsx b/apps/webapp/app/routes/storybook.animated-panel/route.tsx index 2f136bddb19..f30be72b622 100644 --- a/apps/webapp/app/routes/storybook.animated-panel/route.tsx +++ b/apps/webapp/app/routes/storybook.animated-panel/route.tsx @@ -30,16 +30,76 @@ type DemoItem = { }; const demoItems: DemoItem[] = [ - { id: "run_a1b2c3d4", name: "Process invoices", status: "completed", duration: "2.3s", task: "invoice/process" }, - { id: "run_e5f6g7h8", name: "Send welcome email", status: "running", duration: "0.8s", task: "email/welcome" }, - { id: "run_i9j0k1l2", name: "Generate report", status: "failed", duration: "12.1s", task: "report/generate" }, - { id: "run_m3n4o5p6", name: "Sync inventory", status: "completed", duration: "5.7s", task: "inventory/sync" }, - { id: "run_q7r8s9t0", name: "Resize images", status: "queued", duration: "—", task: "image/resize" }, - { id: "run_u1v2w3x4", name: "Update search index", status: "completed", duration: "1.1s", task: "search/index" }, - { id: "run_y5z6a7b8", name: "Calculate analytics", status: "running", duration: "8.4s", task: "analytics/calc" }, - { id: "run_c9d0e1f2", name: "Deploy preview", status: "completed", duration: "34.2s", task: "deploy/preview" }, - { id: "run_g3h4i5j6", name: "Run migrations", status: "failed", duration: "0.3s", task: "db/migrate" }, - { id: "run_k7l8m9n0", name: "Notify Slack", status: "completed", duration: "0.5s", task: "notify/slack" }, + { + id: "run_a1b2c3d4", + name: "Process invoices", + status: "completed", + duration: "2.3s", + task: "invoice/process", + }, + { + id: "run_e5f6g7h8", + name: "Send welcome email", + status: "running", + duration: "0.8s", + task: "email/welcome", + }, + { + id: "run_i9j0k1l2", + name: "Generate report", + status: "failed", + duration: "12.1s", + task: "report/generate", + }, + { + id: "run_m3n4o5p6", + name: "Sync inventory", + status: "completed", + duration: "5.7s", + task: "inventory/sync", + }, + { + id: "run_q7r8s9t0", + name: "Resize images", + status: "queued", + duration: "—", + task: "image/resize", + }, + { + id: "run_u1v2w3x4", + name: "Update search index", + status: "completed", + duration: "1.1s", + task: "search/index", + }, + { + id: "run_y5z6a7b8", + name: "Calculate analytics", + status: "running", + duration: "8.4s", + task: "analytics/calc", + }, + { + id: "run_c9d0e1f2", + name: "Deploy preview", + status: "completed", + duration: "34.2s", + task: "deploy/preview", + }, + { + id: "run_g3h4i5j6", + name: "Run migrations", + status: "failed", + duration: "0.3s", + task: "db/migrate", + }, + { + id: "run_k7l8m9n0", + name: "Notify Slack", + status: "completed", + duration: "0.5s", + task: "notify/slack", + }, ]; const statusColors: Record = { @@ -144,10 +204,7 @@ export default function Story() {
- + - {/* Simple Line Chart (no zoom, but synced with date range) */} diff --git a/apps/webapp/app/routes/storybook.icons/route.tsx b/apps/webapp/app/routes/storybook.icons/route.tsx index 2b5f4c4cd3e..dee5aa3e25a 100644 --- a/apps/webapp/app/routes/storybook.icons/route.tsx +++ b/apps/webapp/app/routes/storybook.icons/route.tsx @@ -105,19 +105,12 @@ import { FlagEurope, FlagUSA } from "~/assets/icons/RegionIcons"; import { RightSideMenuIcon } from "~/assets/icons/RightSideMenuIcon"; import { RolesIcon } from "~/assets/icons/RolesIcon"; import { RunFunctionIcon } from "~/assets/icons/RunFunctionIcon"; -import { - RunsIcon, - RunsIconExtraSmall, - RunsIconSmall, -} from "~/assets/icons/RunsIcon"; +import { RunsIcon, RunsIconExtraSmall, RunsIconSmall } from "~/assets/icons/RunsIcon"; import { SaplingIcon } from "~/assets/icons/SaplingIcon"; import { ScheduleIcon } from "~/assets/icons/ScheduleIcon"; import { ShieldIcon } from "~/assets/icons/ShieldIcon"; import { ShieldLockIcon } from "~/assets/icons/ShieldLockIcon"; -import { - ShowParentIcon, - ShowParentIconSelected, -} from "~/assets/icons/ShowParentIcon"; +import { ShowParentIcon, ShowParentIconSelected } from "~/assets/icons/ShowParentIcon"; import { SideMenuRightClosedIcon } from "~/assets/icons/SideMenuRightClosed"; import { SlackIcon } from "~/assets/icons/SlackIcon"; import { SlackMonoIcon } from "~/assets/icons/SlackMonoIcon"; @@ -310,13 +303,8 @@ export default function Story() { key={icon.name} className="flex flex-col items-center gap-3 rounded-md border border-grid-bright bg-background-bright p-4 text-text-bright" > -
- {icon.render("size-6")} -
-
+
{icon.render("size-6")}
+
{icon.name}
diff --git a/apps/webapp/app/routes/storybook.timeline/route.tsx b/apps/webapp/app/routes/storybook.timeline/route.tsx index 24a1382fe51..e5606c15946 100644 --- a/apps/webapp/app/routes/storybook.timeline/route.tsx +++ b/apps/webapp/app/routes/storybook.timeline/route.tsx @@ -197,8 +197,8 @@ export default function Story() { index === 0 ? "left-0.5" : index === tickCount - 1 - ? "-right-0 -translate-x-full" - : "left-1/2 -translate-x-1/2" + ? "-right-0 -translate-x-full" + : "left-1/2 -translate-x-1/2" } > {formatDurationMilliseconds(ms, { diff --git a/apps/webapp/app/routes/vercel.callback.ts b/apps/webapp/app/routes/vercel.callback.ts index 6a188acfa3f..cbd2ee93557 100644 --- a/apps/webapp/app/routes/vercel.callback.ts +++ b/apps/webapp/app/routes/vercel.callback.ts @@ -14,7 +14,7 @@ const VercelCallbackSchema = z error: z.string().optional(), error_description: z.string().optional(), configurationId: z.string().optional(), - next: z.string().optional() + next: z.string().optional(), }) .passthrough(); diff --git a/apps/webapp/app/routes/vercel.configure.tsx b/apps/webapp/app/routes/vercel.configure.tsx index 25b9197176c..a9b4f4a151f 100644 --- a/apps/webapp/app/routes/vercel.configure.tsx +++ b/apps/webapp/app/routes/vercel.configure.tsx @@ -16,7 +16,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { await requireUserId(request); const url = new URL(request.url); const searchParams = Object.fromEntries(url.searchParams); - + const { configurationId } = SearchParamsSchema.parse(searchParams); // Find the organization integration by configurationId (installationId in integrationData) @@ -49,4 +49,4 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { // This route doesn't render anything, it just redirects export default function VercelConfigurePage() { return null; -} \ No newline at end of file +} diff --git a/apps/webapp/app/routes/vercel.connect.tsx b/apps/webapp/app/routes/vercel.connect.tsx index faf43ca35e6..6534e87d0e5 100644 --- a/apps/webapp/app/routes/vercel.connect.tsx +++ b/apps/webapp/app/routes/vercel.connect.tsx @@ -23,7 +23,7 @@ async function createOrFindVercelIntegration( projectId: string, tokenResponse: TokenResponse, configurationId: string | undefined, - origin: 'marketplace' | 'dashboard' + origin: "marketplace" | "dashboard" ): Promise { const project = await prisma.project.findUnique({ where: { id: projectId }, @@ -47,7 +47,7 @@ async function createOrFindVercelIntegration( teamId: tokenResponse.teamId ?? null, userId: tokenResponse.userId, installationId: configurationId, - raw: tokenResponse.raw + raw: tokenResponse.raw, }); } else { await VercelIntegrationRepository.createVercelOrgIntegration({ @@ -146,7 +146,13 @@ export async function loader({ request }: LoaderFunctionArgs) { ); const result = await fromPromise( - createOrFindVercelIntegration(stateData.organizationId, stateData.projectId, tokenResponse, configurationId, origin), + createOrFindVercelIntegration( + stateData.organizationId, + stateData.projectId, + tokenResponse, + configurationId, + origin + ), (error) => error ); diff --git a/apps/webapp/app/routes/vercel.onboarding.tsx b/apps/webapp/app/routes/vercel.onboarding.tsx index 8187ff9282c..94fd8f53476 100644 --- a/apps/webapp/app/routes/vercel.onboarding.tsx +++ b/apps/webapp/app/routes/vercel.onboarding.tsx @@ -151,11 +151,7 @@ export async function loader({ request }: LoaderFunctionArgs) { organizationId: params.data.organizationId, userId, }); - throw redirectWithErrorMessage( - "/", - request, - "Organization not found. Please try again." - ); + throw redirectWithErrorMessage("/", request, "Organization not found. Please try again."); } return typedjson({ @@ -216,11 +212,7 @@ export async function action({ request }: ActionFunctionArgs) { resumeParams.set("next", submission.data.next); } const resumeUrl = `/vercel/onboarding?${resumeParams.toString()}`; - const ssoRedirect = await ssoRedirectForEmail( - sessionUser.email, - "oauth_blocked", - resumeUrl - ); + const ssoRedirect = await ssoRedirectForEmail(sessionUser.email, "oauth_blocked", resumeUrl); if (ssoRedirect) { // The user is already authenticated via a non-SSO method, so a plain // redirect to `/login/sso` would be bounced straight home by that @@ -337,13 +329,12 @@ export default function VercelOnboardingPage() { return ( - + - @@ -369,7 +360,10 @@ export default function VercelOnboardingPage() { return ( - + } title="Select Organization" @@ -395,7 +389,8 @@ export default function VercelOnboardingPage() { defaultValue={data.organizations[0]?.id} text={(v) => typeof v === "string" - ? data.organizations.find((o) => o.id === v)?.title || "Choose an organization" + ? data.organizations.find((o) => o.id === v)?.title || + "Choose an organization" : "Choose an organization" } > @@ -445,7 +440,10 @@ export default function VercelOnboardingPage() { return ( - + } title="Select Project" @@ -472,7 +470,8 @@ export default function VercelOnboardingPage() { defaultValue={data.organization.projects[0]?.id} text={(v) => typeof v === "string" - ? data.organization.projects.find((p) => p.id === v)?.name || "Choose a project" + ? data.organization.projects.find((p) => p.id === v)?.name || + "Choose a project" : "Choose a project" } > diff --git a/apps/webapp/app/runEngine/concerns/batchGlobalRateLimiter.server.ts b/apps/webapp/app/runEngine/concerns/batchGlobalRateLimiter.server.ts index 9d808dd7566..3b5da88087a 100644 --- a/apps/webapp/app/runEngine/concerns/batchGlobalRateLimiter.server.ts +++ b/apps/webapp/app/runEngine/concerns/batchGlobalRateLimiter.server.ts @@ -33,4 +33,3 @@ export function createBatchGlobalRateLimiter(itemsPerSecond: number): GlobalRate }, }; } - diff --git a/apps/webapp/app/runEngine/concerns/computeMigration.server.ts b/apps/webapp/app/runEngine/concerns/computeMigration.server.ts index c885a80356d..e598cbdca72 100644 --- a/apps/webapp/app/runEngine/concerns/computeMigration.server.ts +++ b/apps/webapp/app/runEngine/concerns/computeMigration.server.ts @@ -36,10 +36,10 @@ export function isOrgMigrated({ const pct = planType === "free" - ? flags?.computeMigrationFreePercentage ?? 0 + ? (flags?.computeMigrationFreePercentage ?? 0) : planType === "paid" - ? flags?.computeMigrationPaidPercentage ?? 0 - : 0; // enterprise / undefined + ? (flags?.computeMigrationPaidPercentage ?? 0) + : 0; // enterprise / undefined return hashBucket(orgId) < pct; } @@ -67,7 +67,11 @@ export function resolveComputeMigration({ backing, envType, ...decision -}: ResolveInput): { workerQueue: string | undefined; region: string | undefined; enableFastPath: boolean } { +}: ResolveInput): { + workerQueue: string | undefined; + region: string | undefined; + enableFastPath: boolean; +} { const passthrough = { workerQueue: baseWorkerQueue, region, enableFastPath: baseEnableFastPath }; if (baseWorkerQueue === undefined) return passthrough; if (envType === "DEVELOPMENT") return passthrough; diff --git a/apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts b/apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts index 02d0ec957f2..3e049ceb37e 100644 --- a/apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts +++ b/apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts @@ -66,7 +66,7 @@ export class IdempotencyKeyConcern { environmentId: string, organizationId: string, taskIdentifier: string, - idempotencyKey: string, + idempotencyKey: string ): Promise { const buffer = getMollifierBuffer(); if (!buffer) return null; @@ -111,10 +111,7 @@ export class IdempotencyKeyConcern { // accept goes through as a fresh trigger. Mirrors what // `ResetIdempotencyKeyService` does for the explicit // reset-via-API path. - if ( - synthetic.idempotencyKeyExpiresAt && - synthetic.idempotencyKeyExpiresAt < new Date() - ) { + if (synthetic.idempotencyKeyExpiresAt && synthetic.idempotencyKeyExpiresAt < new Date()) { const buffer = getMollifierBuffer(); if (buffer) { try { @@ -178,7 +175,7 @@ export class IdempotencyKeyConcern { request.environment.id, request.environment.organizationId, request.taskId, - idempotencyKey, + idempotencyKey ); if (buffered) { return { isCached: true, run: buffered }; @@ -307,18 +304,18 @@ export class IdempotencyKeyConcern { orgId: request.environment.organizationId, taskId: request.taskId, orgFeatureFlags: - ((request.environment.organization?.featureFlags as + (request.environment.organization?.featureFlags as | Record | null - | undefined) ?? null), + | undefined) ?? null, })); if (claimEligible) { const ttlSeconds = Math.max( 1, Math.min( env.TRIGGER_MOLLIFIER_CLAIM_TTL_SECONDS, - Math.ceil((idempotencyKeyExpiresAt.getTime() - Date.now()) / 1000), - ), + Math.ceil((idempotencyKeyExpiresAt.getTime() - Date.now()) / 1000) + ) ); const outcome = await claimOrAwait({ envId: request.environment.id, @@ -348,7 +345,7 @@ export class IdempotencyKeyConcern { request.environment.id, request.environment.organizationId, request.taskId, - idempotencyKey, + idempotencyKey ); if (buffered) { return { isCached: true, run: buffered }; @@ -381,10 +378,7 @@ export class IdempotencyKeyConcern { }); } if (outcome.kind === "timed_out") { - throw new ServiceValidationError( - "Idempotency claim resolution timed out", - 503, - ); + throw new ServiceValidationError("Idempotency claim resolution timed out", 503); } if (outcome.kind === "claimed") { // Caller MUST publish/release. Signalled via the result's diff --git a/apps/webapp/app/runEngine/concerns/payloads.server.ts b/apps/webapp/app/runEngine/concerns/payloads.server.ts index 7e1efbdf302..17e52109da7 100644 --- a/apps/webapp/app/runEngine/concerns/payloads.server.ts +++ b/apps/webapp/app/runEngine/concerns/payloads.server.ts @@ -32,7 +32,13 @@ export class DefaultPayloadProcessor implements PayloadProcessor { const filename = `${request.friendlyId}/payload.json`; const [uploadError, uploadedFilename] = await tryCatch( - uploadPacketToObjectStore(filename, packet.data, packet.dataType, request.environment, env.OBJECT_STORE_DEFAULT_PROTOCOL) + uploadPacketToObjectStore( + filename, + packet.data, + packet.dataType, + request.environment, + env.OBJECT_STORE_DEFAULT_PROTOCOL + ) ); if (uploadError) { diff --git a/apps/webapp/app/runEngine/concerns/queues.server.ts b/apps/webapp/app/runEngine/concerns/queues.server.ts index 5fea4eedd89..5a4f18d1d6b 100644 --- a/apps/webapp/app/runEngine/concerns/queues.server.ts +++ b/apps/webapp/app/runEngine/concerns/queues.server.ts @@ -16,7 +16,12 @@ import { env } from "~/env.server"; import { tryCatch } from "@trigger.dev/core/v3"; import { ServiceValidationError } from "~/v3/services/common.server"; import { isInfrastructureError } from "~/utils/prismaErrors"; -import { createCache, createLRUMemoryStore, DefaultStatefulContext, Namespace } from "@internal/cache"; +import { + createCache, + createLRUMemoryStore, + DefaultStatefulContext, + Namespace, +} from "@internal/cache"; import { singleton } from "~/utils/singleton"; import type { TaskMetadataCache, TaskMetadataEntry } from "~/services/taskMetadataCache.server"; import { taskMetadataCacheInstance } from "~/services/taskMetadataCacheInstance.server"; @@ -111,7 +116,8 @@ export class DefaultQueueManager implements QueueManager { if (!specifiedQueue) { throw new ServiceValidationError( - `Specified queue '${specifiedQueueName}' not found or not associated with locked version '${lockedBackgroundWorker.version ?? "" + `Specified queue '${specifiedQueueName}' not found or not associated with locked version '${ + lockedBackgroundWorker.version ?? "" }'.` ); } @@ -151,7 +157,8 @@ export class DefaultQueueManager implements QueueManager { if (!lockedMeta) { throw new ServiceValidationError( - `Task '${request.taskId}' not found on locked version '${lockedBackgroundWorker.version ?? "" + `Task '${request.taskId}' not found on locked version '${ + lockedBackgroundWorker.version ?? "" }'.` ); } @@ -167,7 +174,8 @@ export class DefaultQueueManager implements QueueManager { version: lockedBackgroundWorker.version, }); throw new ServiceValidationError( - `Default queue configuration for task '${request.taskId}' missing on locked version '${lockedBackgroundWorker.version ?? "" + `Default queue configuration for task '${request.taskId}' missing on locked version '${ + lockedBackgroundWorker.version ?? "" }'.` ); } @@ -474,7 +482,9 @@ export class DefaultQueueManager implements QueueManager { } } -export function getMaximumSizeForEnvironment(environment: AuthenticatedEnvironment): number | undefined { +export function getMaximumSizeForEnvironment( + environment: AuthenticatedEnvironment +): number | undefined { if (environment.type === "DEVELOPMENT") { return environment.organization.maximumDevQueueSize ?? env.MAXIMUM_DEV_QUEUE_SIZE; } else { diff --git a/apps/webapp/app/runEngine/services/batchTrigger.server.ts b/apps/webapp/app/runEngine/services/batchTrigger.server.ts index 3df2dfb00f9..e4500577990 100644 --- a/apps/webapp/app/runEngine/services/batchTrigger.server.ts +++ b/apps/webapp/app/runEngine/services/batchTrigger.server.ts @@ -16,7 +16,10 @@ import { env } from "~/env.server"; import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; import { batchTriggerWorker } from "~/v3/batchTriggerWorker.server"; -import { downloadPacketFromObjectStore, uploadPacketToObjectStore } from "../../v3/objectStore.server"; +import { + downloadPacketFromObjectStore, + uploadPacketToObjectStore, +} from "../../v3/objectStore.server"; import { ServiceValidationError, WithRunEngine } from "../../v3/services/baseService.server"; import { TriggerTaskService } from "../../v3/services/triggerTask.server"; import { startActiveSpan } from "../../v3/tracer.server"; @@ -559,11 +562,14 @@ export class RunEngineBatchTriggerService extends WithRunEngine { if (!runFriendlyId) { const errorMessage = "Trigger failed for batch item (queue limit, entitlement, or validation error)"; - logger.debug("[RunEngineBatchTrigger][processBatchTaskRun] Item trigger failed, creating pre-failed run", { - batchId: batch.friendlyId, - currentIndex: workingIndex, - task: item.task, - }); + logger.debug( + "[RunEngineBatchTrigger][processBatchTaskRun] Item trigger failed, creating pre-failed run", + { + batchId: batch.friendlyId, + currentIndex: workingIndex, + task: item.task, + } + ); const failedRunId = await triggerFailedTaskService.call({ taskId: item.task, @@ -583,10 +589,13 @@ export class RunEngineBatchTriggerService extends WithRunEngine { if (failedRunId) { runFriendlyId = failedRunId; } else { - logger.error("[RunEngineBatchTrigger][processBatchTaskRun] Failed to create pre-failed run", { - batchId: batch.friendlyId, - currentIndex: workingIndex, - }); + logger.error( + "[RunEngineBatchTrigger][processBatchTaskRun] Failed to create pre-failed run", + { + batchId: batch.friendlyId, + currentIndex: workingIndex, + } + ); return { status: "ERROR", @@ -717,7 +726,12 @@ export class RunEngineBatchTriggerService extends WithRunEngine { const filename = `${pathPrefix}/payload.json`; - const uploadedFilename = await uploadPacketToObjectStore(filename, packet.data, packet.dataType, environment); + const uploadedFilename = await uploadPacketToObjectStore( + filename, + packet.data, + packet.dataType, + environment + ); return { data: uploadedFilename, diff --git a/apps/webapp/app/runEngine/services/streamBatchItems.server.ts b/apps/webapp/app/runEngine/services/streamBatchItems.server.ts index d0d13adfe6c..fd229777c10 100644 --- a/apps/webapp/app/runEngine/services/streamBatchItems.server.ts +++ b/apps/webapp/app/runEngine/services/streamBatchItems.server.ts @@ -447,9 +447,7 @@ export class StreamBatchItemsService extends WithRunEngine { if (!parseResult.success) { const rawIndex = (rawItem as { index?: unknown } | null)?.index; const where = typeof rawIndex === "number" ? `index ${rawIndex}` : "unknown index"; - throw new ServiceValidationError( - `Invalid item at ${where}: ${parseResult.error.message}` - ); + throw new ServiceValidationError(`Invalid item at ${where}: ${parseResult.error.message}`); } const item = parseResult.data; diff --git a/apps/webapp/app/runEngine/services/triggerFailedTask.server.ts b/apps/webapp/app/runEngine/services/triggerFailedTask.server.ts index 031411844b4..6e3b73220f9 100644 --- a/apps/webapp/app/runEngine/services/triggerFailedTask.server.ts +++ b/apps/webapp/app/runEngine/services/triggerFailedTask.server.ts @@ -230,8 +230,7 @@ export class TriggerFailedTaskService { logger.warn("TriggerFailedTaskService: alert enqueue failed", { taskId: request.taskId, friendlyId: failedRun.friendlyId, - error: - alertsError instanceof Error ? alertsError.message : String(alertsError), + error: alertsError instanceof Error ? alertsError.message : String(alertsError), }); } @@ -337,8 +336,7 @@ export class TriggerFailedTaskService { logger.warn("TriggerFailedTaskService.callWithoutTraceEvents: alert enqueue failed", { taskId: opts.taskId, friendlyId: failedRun.friendlyId, - error: - alertsError instanceof Error ? alertsError.message : String(alertsError), + error: alertsError instanceof Error ? alertsError.message : String(alertsError), }); } diff --git a/apps/webapp/app/runEngine/services/triggerTask.server.ts b/apps/webapp/app/runEngine/services/triggerTask.server.ts index 89a938da8bf..cee833e96e0 100644 --- a/apps/webapp/app/runEngine/services/triggerTask.server.ts +++ b/apps/webapp/app/runEngine/services/triggerTask.server.ts @@ -30,10 +30,7 @@ import type { TriggerTaskServiceResult, } from "../../v3/services/triggerTask.server"; import { clampMaxDuration } from "../../v3/utils/maxDuration"; -import { - IdempotencyKeyConcern, - type ClaimedIdempotency, -} from "../concerns/idempotencyKeys.server"; +import { IdempotencyKeyConcern, type ClaimedIdempotency } from "../concerns/idempotencyKeys.server"; import { resolveScheduledQueueSplitEnabled, workerQueueForRun, @@ -235,7 +232,7 @@ export class RunEngineTriggerTaskService { if (debounceDelayError || !debounceDelayUntil) { throw new ServiceValidationError( `Invalid debounce delay: ${body.options.debounce.delay}. ` + - `Supported formats: {number}s, {number}m, {number}h, {number}d, {number}w` + `Supported formats: {number}s, {number}m, {number}h, {number}d, {number}w` ); } } @@ -243,12 +240,12 @@ export class RunEngineTriggerTaskService { // Get parent run if specified const parentRun = body.options?.parentRunId ? await runStore.findRun( - { - id: RunId.fromFriendlyId(body.options.parentRunId), - runtimeEnvironmentId: environment.id, - }, - this.prisma - ) + { + id: RunId.fromFriendlyId(body.options.parentRunId), + runtimeEnvironmentId: environment.id, + }, + this.prisma + ) : undefined; // Validate parent run @@ -271,8 +268,11 @@ export class RunEngineTriggerTaskService { return idempotencyKeyConcernResult; } - const { idempotencyKey, idempotencyKeyExpiresAt, claim: claimResult } = - idempotencyKeyConcernResult; + const { + idempotencyKey, + idempotencyKeyExpiresAt, + claim: claimResult, + } = idempotencyKeyConcernResult; // If we own an idempotency claim, the trigger pipeline below MUST // resolve it — publish on success so waiters see our runId, @@ -290,18 +290,18 @@ export class RunEngineTriggerTaskService { const lockedToBackgroundWorker = body.options?.lockToVersion ? await this.prisma.backgroundWorker.findFirst({ - where: { - projectId: environment.projectId, - runtimeEnvironmentId: environment.id, - version: body.options?.lockToVersion, - }, - select: { - id: true, - version: true, - sdkVersion: true, - cliVersion: true, - }, - }) + where: { + projectId: environment.projectId, + runtimeEnvironmentId: environment.id, + version: body.options?.lockToVersion, + }, + select: { + id: true, + version: true, + sdkVersion: true, + cliVersion: true, + }, + }) : undefined; const { queueName, lockedQueueId, taskTtl, taskKind } = @@ -340,10 +340,10 @@ export class RunEngineTriggerTaskService { const metadataPacket = body.options?.metadata ? handleMetadataPacket( - body.options?.metadata, - body.options?.metadataType ?? "application/json", - this.metadataMaximumSize - ) + body.options?.metadata, + body.options?.metadataType ?? "application/json", + this.metadataMaximumSize + ) : undefined; const tags = ( @@ -367,8 +367,12 @@ export class RunEngineTriggerTaskService { // from the in-memory snapshots (no DB query). A cold read (registry not yet // loaded) returns undefined/[] and the resolver falls back to not-migrated. const workerGroups = workerRegionRegistry.current() ?? []; - const region = baseWorkerQueue ? regionForQueue(baseWorkerQueue, workerGroups) : undefined; - const backing = baseWorkerQueue ? backingForQueue(baseWorkerQueue, workerGroups) : undefined; + const region = baseWorkerQueue + ? regionForQueue(baseWorkerQueue, workerGroups) + : undefined; + const backing = baseWorkerQueue + ? backingForQueue(baseWorkerQueue, workerGroups) + : undefined; const migrated = resolveComputeMigration({ baseWorkerQueue, baseEnableFastPath: enableFastPath, @@ -376,7 +380,10 @@ export class RunEngineTriggerTaskService { backing, planType, orgId: environment.organization.id, - orgFeatureFlags: environment.organization.featureFlags as Record | null, + orgFeatureFlags: environment.organization.featureFlags as Record< + string, + unknown + > | null, flags: globalFlagsRegistry.current(), envType: environment.type, }); @@ -470,8 +477,10 @@ export class RunEngineTriggerTaskService { orgId: environment.organizationId, taskId, orgFeatureFlags: - (environment.organization.featureFlags as Record | null) ?? - null, + (environment.organization.featureFlags as Record< + string, + unknown + > | null) ?? null, options: { debounce: body.options?.debounce, oneTimeUseToken: options.oneTimeUseToken, @@ -629,26 +638,26 @@ export class RunEngineTriggerTaskService { onDebounced: body.options?.debounce && body.options?.resumeParentOnCompletion ? async ({ existingRun, waitpoint, debounceKey }) => { - return await this.traceEventConcern.traceDebouncedRun( - triggerRequest, - parentRun?.taskEventStore, - { - existingRun, - debounceKey, - incomplete: waitpoint.status === "PENDING", - isError: waitpoint.outputIsError, - }, - async (spanEvent) => { - const spanId = - options?.parentAsLinkType === "replay" - ? spanEvent.spanId - : spanEvent.traceparent?.spanId - ? `${spanEvent.traceparent.spanId}:${spanEvent.spanId}` - : spanEvent.spanId; - return spanId; - } - ); - } + return await this.traceEventConcern.traceDebouncedRun( + triggerRequest, + parentRun?.taskEventStore, + { + existingRun, + debounceKey, + incomplete: waitpoint.status === "PENDING", + isError: waitpoint.outputIsError, + }, + async (spanEvent) => { + const spanId = + options?.parentAsLinkType === "replay" + ? spanEvent.spanId + : spanEvent.traceparent?.spanId + ? `${spanEvent.traceparent.spanId}:${spanEvent.spanId}` + : spanEvent.spanId; + return spanId; + } + ); + } : undefined, }, this.prisma @@ -703,7 +712,7 @@ export class RunEngineTriggerTaskService { throw error; } - }, + } ); // Pipeline returned successfully — publish the claim if we held // one. Waiters polling for our key resolve to this runId. @@ -745,13 +754,23 @@ export class RunEngineTriggerTaskService { workerQueue?: string; region?: string; enableFastPath: boolean; - lockedToBackgroundWorker?: { id: string; version: string; sdkVersion: string; cliVersion: string }; + lockedToBackgroundWorker?: { + id: string; + version: string; + sdkVersion: string; + cliVersion: string; + }; delayUntil?: Date; ttl?: string; metadataPacket?: { data?: string; dataType: string }; tags: string[]; depth: number; - parentRun?: { id: string; rootTaskRunId?: string | null; queueTimestamp?: Date | null; taskEventStore?: string }; + parentRun?: { + id: string; + rootTaskRunId?: string | null; + queueTimestamp?: Date | null; + taskEventStore?: string; + }; annotations: { triggerSource: string; triggerAction: string; @@ -826,7 +845,7 @@ export class RunEngineTriggerTaskService { queueTimestamp: args.options.queueTimestamp ?? (args.parentRun && args.body.options?.resumeParentOnCompletion - ? args.parentRun.queueTimestamp ?? undefined + ? (args.parentRun.queueTimestamp ?? undefined) : undefined), scheduleId: args.options.scheduleId, scheduleInstanceId: args.options.scheduleInstanceId, diff --git a/apps/webapp/app/runEngine/types.ts b/apps/webapp/app/runEngine/types.ts index c0c5de1d2fd..339f1a36e1c 100644 --- a/apps/webapp/app/runEngine/types.ts +++ b/apps/webapp/app/runEngine/types.ts @@ -37,13 +37,13 @@ export type TriggerTaskResult = { export type QueueValidationResult = | { - ok: true; - } + ok: true; + } | { - ok: false; - maximumSize: number; - queueSize: number; - }; + ok: false; + maximumSize: number; + queueSize: number; + }; export type QueueProperties = { queueName: string; @@ -99,22 +99,22 @@ export interface ParentRunValidationParams { export type ValidationResult = | { - ok: true; - } + ok: true; + } | { - ok: false; - error: Error; - }; + ok: false; + error: Error; + }; export type EntitlementValidationResult = | { - ok: true; - plan?: ReportUsagePlan; - } + ok: true; + plan?: ReportUsagePlan; + } | { - ok: false; - error: Error; - }; + ok: false; + error: Error; + }; export interface TriggerTaskValidator { validateTags(params: TagValidationParams): ValidationResult; diff --git a/apps/webapp/app/services/admin/missingLlmModels.server.ts b/apps/webapp/app/services/admin/missingLlmModels.server.ts index 07e6160ee03..2ad5bffb520 100644 --- a/apps/webapp/app/services/admin/missingLlmModels.server.ts +++ b/apps/webapp/app/services/admin/missingLlmModels.server.ts @@ -7,9 +7,11 @@ export type MissingLlmModel = { count: number; }; -export async function getMissingLlmModels(opts: { - lookbackHours?: number; -} = {}): Promise { +export async function getMissingLlmModels( + opts: { + lookbackHours?: number; + } = {} +): Promise { const lookbackHours = opts.lookbackHours ?? 24; const since = new Date(Date.now() - lookbackHours * 60 * 60 * 1000); @@ -100,14 +102,7 @@ export async function getMissingModelSamples(opts: { const createBuilder = adminClickhouse.reader.queryBuilderFast({ name: "missingModelSamples", table: "trigger_dev.task_events_v2", - columns: [ - "span_id", - "run_id", - "message", - "attributes_text", - "duration", - "start_time", - ], + columns: ["span_id", "run_id", "message", "attributes_text", "duration", "start_time"], }); const qb = createBuilder(); diff --git a/apps/webapp/app/services/apiAuth.server.ts b/apps/webapp/app/services/apiAuth.server.ts index 8297f95aa03..8e9a37f5521 100644 --- a/apps/webapp/app/services/apiAuth.server.ts +++ b/apps/webapp/app/services/apiAuth.server.ts @@ -318,10 +318,10 @@ function getApiKeyResult(apiKey: string): { const type = isPublicApiKey(apiKey) ? "PUBLIC" : isSecretApiKey(apiKey) - ? "PRIVATE" - : isPublicJWT(apiKey) - ? "PUBLIC_JWT" - : "PRIVATE"; // Fallback to private key + ? "PRIVATE" + : isPublicJWT(apiKey) + ? "PUBLIC_JWT" + : "PRIVATE"; // Fallback to private key return { apiKey, type }; } @@ -351,7 +351,7 @@ const defaultAllowedAuthenticationMethods: AllowedAuthenticationMethods = { }; type FilteredAuthenticationResult< - T extends AllowedAuthenticationMethods = AllowedAuthenticationMethods + T extends AllowedAuthenticationMethods = AllowedAuthenticationMethods, > = | (T["personalAccessToken"] extends true ? Extract @@ -387,7 +387,7 @@ type FilteredAuthenticationResult< * ``` */ export async function authenticateRequest< - T extends AllowedAuthenticationMethods = AllowedAuthenticationMethods + T extends AllowedAuthenticationMethods = AllowedAuthenticationMethods, >( request: Request, allowedAuthenticationMethods?: T @@ -484,7 +484,10 @@ export async function authenticatedEnvironmentForAuthentication( ); } - if (auth.result.environment.slug !== slug && auth.result.environment.branchName !== resolvedBranch) { + if ( + auth.result.environment.slug !== slug && + auth.result.environment.branchName !== resolvedBranch + ) { throw json( { error: @@ -520,10 +523,10 @@ export async function authenticatedEnvironmentForAuthentication( slug: slug, ...(slug === "dev" ? { - orgMember: { - userId: user.id, - }, - } + orgMember: { + userId: user.id, + }, + } : {}), }, include: authIncludeBase, @@ -543,10 +546,10 @@ export async function authenticatedEnvironmentForAuthentication( branchName: resolvedBranch, ...(slug === "dev" ? { - orgMember: { - userId: user.id, - }, - } + orgMember: { + userId: user.id, + }, + } : {}), archivedAt: null, }, diff --git a/apps/webapp/app/services/attio.server.ts b/apps/webapp/app/services/attio.server.ts index 727a5b9ab24..c26eca8e37e 100644 --- a/apps/webapp/app/services/attio.server.ts +++ b/apps/webapp/app/services/attio.server.ts @@ -33,7 +33,11 @@ class AttioClient { constructor(private readonly apiKey: string) {} // Create-or-update by unique attribute; returns the record id. Throws on failure so the worker retries. - async #assert(object: string, matchingAttribute: string, values: Record): Promise { + async #assert( + object: string, + matchingAttribute: string, + values: Record + ): Promise { const url = `${ATTIO_API}/objects/${object}/records?matching_attribute=${matchingAttribute}`; const response = await fetch(url, { method: "PUT", @@ -43,7 +47,12 @@ class AttioClient { if (!response.ok) { const body = await response.text(); - logger.error("Attio assert failed", { object, matchingAttribute, status: response.status, body }); + logger.error("Attio assert failed", { + object, + matchingAttribute, + status: response.status, + body, + }); throw new Error(`Attio assert ${object} failed with status ${response.status}`); } @@ -105,7 +114,11 @@ export async function enqueueAttioWorkspaceSync(payload: AttioWorkspaceSync) { try { // Lazy import to avoid a circular dependency with commonWorker (which imports this module's schemas). const { commonWorker } = await import("~/v3/commonWorker.server"); - await commonWorker.enqueue({ id: `attio:workspace:${payload.orgId}`, job: "attio.syncWorkspace", payload }); + await commonWorker.enqueue({ + id: `attio:workspace:${payload.orgId}`, + job: "attio.syncWorkspace", + payload, + }); } catch (error) { logger.error("Failed to enqueue Attio workspace sync", { orgId: payload.orgId, error }); } @@ -115,7 +128,11 @@ export async function enqueueAttioUserSync(payload: AttioUserSync) { if (!attioClient) return; try { const { commonWorker } = await import("~/v3/commonWorker.server"); - await commonWorker.enqueue({ id: `attio:user:${payload.userId}`, job: "attio.syncUser", payload }); + await commonWorker.enqueue({ + id: `attio:user:${payload.userId}`, + job: "attio.syncUser", + payload, + }); } catch (error) { logger.error("Failed to enqueue Attio user sync", { userId: payload.userId, error }); } diff --git a/apps/webapp/app/services/authorizationRateLimitMiddleware.server.ts b/apps/webapp/app/services/authorizationRateLimitMiddleware.server.ts index 431a0062db8..6eb3bb08bac 100644 --- a/apps/webapp/app/services/authorizationRateLimitMiddleware.server.ts +++ b/apps/webapp/app/services/authorizationRateLimitMiddleware.server.ts @@ -138,8 +138,8 @@ export function createLimiterFromConfig(config: RateLimiterConfig): Limiter { return config.type === "fixedWindow" ? Ratelimit.fixedWindow(config.tokens, config.window) : config.type === "tokenBucket" - ? Ratelimit.tokenBucket(config.refillRate, config.interval, config.maxTokens) - : Ratelimit.slidingWindow(config.tokens, config.window); + ? Ratelimit.tokenBucket(config.refillRate, config.interval, config.maxTokens) + : Ratelimit.slidingWindow(config.tokens, config.window); } //returns an Express middleware that rate limits using the Bearer token in the Authorization header diff --git a/apps/webapp/app/services/betterstack/betterstack.server.ts b/apps/webapp/app/services/betterstack/betterstack.server.ts index 95fe2208836..0a097458e40 100644 --- a/apps/webapp/app/services/betterstack/betterstack.server.ts +++ b/apps/webapp/app/services/betterstack/betterstack.server.ts @@ -42,9 +42,7 @@ export type IncidentStatus = { title: string | null; }; -type CachedResult = - | { success: true; data: IncidentStatus } - | { success: false; error: unknown }; +type CachedResult = { success: true; data: IncidentStatus } | { success: false; error: unknown }; const ctx = new DefaultStatefulContext(); const memory = createLRUMemoryStore(100); @@ -79,10 +77,7 @@ export class BetterStackClient { return cachedResult.val; } - private async fetchIncidentStatus( - apiKey: string, - statusPageId: string - ): Promise { + private async fetchIncidentStatus(apiKey: string, statusPageId: string): Promise { const headers = { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", diff --git a/apps/webapp/app/services/dashboardAgent.server.ts b/apps/webapp/app/services/dashboardAgent.server.ts index 273b16edd30..8e8b8884e3e 100644 --- a/apps/webapp/app/services/dashboardAgent.server.ts +++ b/apps/webapp/app/services/dashboardAgent.server.ts @@ -91,7 +91,10 @@ export type DashboardAgentRepoSnapshot = { // The GitHub archive redirect URL is valid for a few minutes; cache the resolved // pointer briefly so multi-turn chats don't re-mint a token + re-resolve on every // message. Keyed by project + ref. -const repoSnapshotCache = new Map(); +const repoSnapshotCache = new Map< + string, + { snapshot: DashboardAgentRepoSnapshot; expiresAt: number } +>(); const REPO_SNAPSHOT_TTL_MS = 60_000; const REPO_SNAPSHOT_MAX_ENTRIES = 1_000; diff --git a/apps/webapp/app/services/dashboardAgentHeadStart.server.ts b/apps/webapp/app/services/dashboardAgentHeadStart.server.ts index ff9039f3efa..d30f8da7ab9 100644 --- a/apps/webapp/app/services/dashboardAgentHeadStart.server.ts +++ b/apps/webapp/app/services/dashboardAgentHeadStart.server.ts @@ -34,8 +34,7 @@ export async function startDashboardAgentHeadStart(params: { mode: "assistant" | "code"; metadata: Record; }): Promise { - const tools = - params.mode === "code" ? dashboardAgentCodeToolSchemas : dashboardAgentToolSchemas; + const tools = params.mode === "code" ? dashboardAgentCodeToolSchemas : dashboardAgentToolSchemas; const system = params.mode === "code" ? DASHBOARD_AGENT_CODE_SYSTEM_PROMPT : DASHBOARD_AGENT_SYSTEM_PROMPT; diff --git a/apps/webapp/app/services/dataStores/organizationDataStoresRegistry.server.ts b/apps/webapp/app/services/dataStores/organizationDataStoresRegistry.server.ts index 07a4f1d3592..bf495e885c1 100644 --- a/apps/webapp/app/services/dataStores/organizationDataStoresRegistry.server.ts +++ b/apps/webapp/app/services/dataStores/organizationDataStoresRegistry.server.ts @@ -149,7 +149,6 @@ export class OrganizationDataStoresRegistry { config: { version: 1, data: { secretKey } }, }, }); - } async updateDataStore({ diff --git a/apps/webapp/app/services/environmentMetricsRepository.server.ts b/apps/webapp/app/services/environmentMetricsRepository.server.ts index e06ad05cbe5..61b53a4d6ca 100644 --- a/apps/webapp/app/services/environmentMetricsRepository.server.ts +++ b/apps/webapp/app/services/environmentMetricsRepository.server.ts @@ -152,7 +152,7 @@ type TaskActivityResults = Array<{ taskIdentifier: string; status: TaskRunStatus; day: Date; - count: BigInt; + count: bigint; }>; function fillInDailyTaskActivity(activity: TaskActivityResults, days: number): DailyTaskActivity { @@ -196,7 +196,7 @@ function fillInDailyTaskActivity(activity: TaskActivityResults, days: number): D type CurrentRunningStatsResults = Array<{ taskIdentifier: string; status: TaskRunStatus; - count: BigInt; + count: bigint; }>; function fillInCurrentRunningStats( diff --git a/apps/webapp/app/services/environmentVariableApiAccess.server.ts b/apps/webapp/app/services/environmentVariableApiAccess.server.ts index 49d745ddad0..11d401125ac 100644 --- a/apps/webapp/app/services/environmentVariableApiAccess.server.ts +++ b/apps/webapp/app/services/environmentVariableApiAccess.server.ts @@ -43,7 +43,10 @@ export async function authorizePatEnvironmentAccess({ resource: EnvironmentScopedResource; action: "read" | "write"; }): Promise { - const bearer = request.headers.get("Authorization")?.replace(/^Bearer /, "").trim(); + const bearer = request.headers + .get("Authorization") + ?.replace(/^Bearer /, "") + .trim(); const isUat = !!bearer && isUserActorToken(bearer); // Machine creds (apiKey) and org tokens carry no user role to enforce. A diff --git a/apps/webapp/app/services/gitHub.server.ts b/apps/webapp/app/services/gitHub.server.ts index 4363c680502..e67895fe066 100644 --- a/apps/webapp/app/services/gitHub.server.ts +++ b/apps/webapp/app/services/gitHub.server.ts @@ -45,8 +45,8 @@ export async function linkGitHubAppInstallation( ? "login" in installation.account ? installation.account.login : "slug" in installation.account - ? installation.account.slug - : "-" + ? installation.account.slug + : "-" : "-", permissions: installation.permissions, repositorySelection, @@ -91,8 +91,8 @@ export async function updateGitHubAppInstallation(installationId: number): Promi ? "login" in installation.account ? installation.account.login : "slug" in installation.account - ? installation.account.slug - : "-" + ? installation.account.slug + : "-" : "-", permissions: installation.permissions, suspendedAt: existingInstallation?.suspendedAt, diff --git a/apps/webapp/app/services/impersonation.server.ts b/apps/webapp/app/services/impersonation.server.ts index 288fd635ae6..a4f3ee59c9f 100644 --- a/apps/webapp/app/services/impersonation.server.ts +++ b/apps/webapp/app/services/impersonation.server.ts @@ -56,18 +56,16 @@ function getImpersonationTokenSecret(): Uint8Array { } function getImpersonationTokenRedisClient(): RedisClient { - return singleton( - "impersonationTokenRedis", - () => - createRedisClient("impersonation:token", { - host: env.CACHE_REDIS_HOST, - port: env.CACHE_REDIS_PORT, - username: env.CACHE_REDIS_USERNAME, - password: env.CACHE_REDIS_PASSWORD, - tlsDisabled: env.CACHE_REDIS_TLS_DISABLED === "true", - clusterMode: env.CACHE_REDIS_CLUSTER_MODE_ENABLED === "1", - keyPrefix: "impersonation:token:", - }) + return singleton("impersonationTokenRedis", () => + createRedisClient("impersonation:token", { + host: env.CACHE_REDIS_HOST, + port: env.CACHE_REDIS_PORT, + username: env.CACHE_REDIS_USERNAME, + password: env.CACHE_REDIS_PASSWORD, + tlsDisabled: env.CACHE_REDIS_TLS_DISABLED === "true", + clusterMode: env.CACHE_REDIS_CLUSTER_MODE_ENABLED === "1", + keyPrefix: "impersonation:token:", + }) ); } @@ -134,9 +132,12 @@ export async function validateAndConsumeImpersonationToken( return undefined; } } catch (redisError) { - logger.warn("Redis unavailable for impersonation token tracking, relying on JWT expiry only", { - error: redisError instanceof Error ? redisError.message : String(redisError), - }); + logger.warn( + "Redis unavailable for impersonation token tracking, relying on JWT expiry only", + { + error: redisError instanceof Error ? redisError.message : String(redisError), + } + ); } } diff --git a/apps/webapp/app/services/loadProjectEnvironmentFromRequest.server.ts b/apps/webapp/app/services/loadProjectEnvironmentFromRequest.server.ts index 76c39caba67..f317a4c58c8 100644 --- a/apps/webapp/app/services/loadProjectEnvironmentFromRequest.server.ts +++ b/apps/webapp/app/services/loadProjectEnvironmentFromRequest.server.ts @@ -4,10 +4,7 @@ import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server"; import { requireUserId } from "~/services/session.server"; import { EnvironmentParamSchema } from "~/utils/pathBuilder"; -export async function loadProjectEnvironmentFromRequest( - request: Request, - params: Params -) { +export async function loadProjectEnvironmentFromRequest(request: Request, params: Params) { const userId = await requireUserId(request); const { organizationSlug, projectParam, envParam } = EnvironmentParamSchema.parse(params); diff --git a/apps/webapp/app/services/org.server.ts b/apps/webapp/app/services/org.server.ts index 75c1467ab24..ebf7e7a7654 100644 --- a/apps/webapp/app/services/org.server.ts +++ b/apps/webapp/app/services/org.server.ts @@ -3,7 +3,7 @@ import { requireUserId } from "./session.server"; export async function requireOrganization(request: Request, organizationSlug: string) { const userId = await requireUserId(request); - + const organization = await prisma.organization.findFirst({ where: { slug: organizationSlug, diff --git a/apps/webapp/app/services/personalAccessToken.server.ts b/apps/webapp/app/services/personalAccessToken.server.ts index acd3959911c..a0821d6602e 100644 --- a/apps/webapp/app/services/personalAccessToken.server.ts +++ b/apps/webapp/app/services/personalAccessToken.server.ts @@ -131,10 +131,7 @@ export async function updateLastAccessedAtIfStale( tokenId: string, lastAccessedAt: Date | null ): Promise { - if ( - lastAccessedAt && - Date.now() - lastAccessedAt.getTime() <= PAT_LAST_ACCESSED_THROTTLE_MS - ) { + if (lastAccessedAt && Date.now() - lastAccessedAt.getTime() <= PAT_LAST_ACCESSED_THROTTLE_MS) { return; // fresh — no roundtrip } await prisma.personalAccessToken.updateMany({ diff --git a/apps/webapp/app/services/platform.v3.server.ts b/apps/webapp/app/services/platform.v3.server.ts index fdc709fb1b1..9d102101547 100644 --- a/apps/webapp/app/services/platform.v3.server.ts +++ b/apps/webapp/app/services/platform.v3.server.ts @@ -62,8 +62,7 @@ const platformClientMeter = metrics.getMeter("trigger.dev/platform-client"); const platformClientFailuresCounter = platformClientMeter.createCounter( "platform_client.failures_total", { - description: - "Failures returned or thrown by @trigger.dev/platform billing client calls", + description: "Failures returned or thrown by @trigger.dev/platform billing client calls", } ); @@ -71,7 +70,6 @@ function recordPlatformFailure(fn: string, kind: "caught" | "no_success") { platformClientFailuresCounter.add(1, { function: fn, kind }); } - function initializePlatformCache() { const ctx = new DefaultStatefulContext(); const memory = createLRUMemoryStore(1000); diff --git a/apps/webapp/app/services/platformNotifications.server.ts b/apps/webapp/app/services/platformNotifications.server.ts index 6425b7bdb61..19faa038215 100644 --- a/apps/webapp/app/services/platformNotifications.server.ts +++ b/apps/webapp/app/services/platformNotifications.server.ts @@ -1,7 +1,10 @@ import { z } from "zod"; import { errAsync, fromPromise, type ResultAsync } from "neverthrow"; import { prisma } from "~/db.server"; -import { type PlatformNotificationScope, type PlatformNotificationSurface } from "@trigger.dev/database"; +import { + type PlatformNotificationScope, + type PlatformNotificationSurface, +} from "@trigger.dev/database"; import { incrementCliRequestCounter } from "./platformNotificationCounter.server"; // --- Payload schema (spec v1) --- @@ -65,9 +68,7 @@ export async function getAdminNotificationsList({ pageSize?: number; hideInactive?: boolean; }) { - const where = hideInactive - ? { archivedAt: null, endsAt: { gt: new Date() } } - : {}; + const where = hideInactive ? { archivedAt: null, endsAt: { gt: new Date() } } : {}; const [notifications, total] = await Promise.all([ prisma.platformNotification.findMany({ @@ -114,7 +115,9 @@ export async function getAdminNotificationsList({ payloadDescription: parsed.success ? parsed.data.data.description : null, payloadActionUrl: parsed.success ? parsed.data.data.actionUrl : null, payloadImage: parsed.success ? parsed.data.data.image : null, - payloadDismissOnAction: parsed.success ? (parsed.data.data.dismissOnAction ?? false) : false, + payloadDismissOnAction: parsed.success + ? (parsed.data.data.dismissOnAction ?? false) + : false, payloadDiscovery: parsed.success ? (parsed.data.data.discovery ?? null) : null, cliMaxShowCount: n.cliMaxShowCount, cliMaxDaysAfterFirstSeen: n.cliMaxDaysAfterFirstSeen, @@ -514,8 +517,7 @@ export async function getNextCliNotification({ // If this display reaches cliMaxShowCount, also set cliDismissedAt now // so it's recorded immediately rather than waiting for a future request. const reachedMaxShows = - n.cliMaxShowCount !== null && - ((interaction?.showCount ?? 0) + 1) >= n.cliMaxShowCount; + n.cliMaxShowCount !== null && (interaction?.showCount ?? 0) + 1 >= n.cliMaxShowCount; const updated = await prisma.platformNotificationInteraction.upsert({ where: { notificationId_userId: { notificationId: n.id, userId } }, @@ -699,9 +701,7 @@ export const UpdatePlatformNotificationSchema = z validateEndsAt(data, ctx); }); -type CreateError = - | { type: "validation"; issues: z.ZodIssue[] } - | { type: "db"; message: string }; +type CreateError = { type: "validation"; issues: z.ZodIssue[] } | { type: "db"; message: string }; export function createPlatformNotification( input: CreatePlatformNotificationInput @@ -765,7 +765,8 @@ export function updatePlatformNotification( startsAt: data.startsAt, endsAt: data.endsAt, priority: data.priority, - cliMaxDaysAfterFirstSeen: data.surface === "CLI" ? (data.cliMaxDaysAfterFirstSeen ?? null) : null, + cliMaxDaysAfterFirstSeen: + data.surface === "CLI" ? (data.cliMaxDaysAfterFirstSeen ?? null) : null, cliMaxShowCount: data.surface === "CLI" ? (data.cliMaxShowCount ?? null) : null, cliShowEvery: data.surface === "CLI" ? (data.cliShowEvery ?? null) : null, }, diff --git a/apps/webapp/app/services/queryConcurrencyLimiter.server.ts b/apps/webapp/app/services/queryConcurrencyLimiter.server.ts index b623f018b1c..a25b9545104 100644 --- a/apps/webapp/app/services/queryConcurrencyLimiter.server.ts +++ b/apps/webapp/app/services/queryConcurrencyLimiter.server.ts @@ -26,4 +26,3 @@ export const DEFAULT_ORG_CONCURRENCY_LIMIT = env.QUERY_DEFAULT_ORG_CONCURRENCY_L /** Global concurrency limit from environment */ export const GLOBAL_CONCURRENCY_LIMIT = env.QUERY_GLOBAL_CONCURRENCY_LIMIT; - diff --git a/apps/webapp/app/services/queryService.server.ts b/apps/webapp/app/services/queryService.server.ts index 4a576c2bf34..57b877ed876 100644 --- a/apps/webapp/app/services/queryService.server.ts +++ b/apps/webapp/app/services/queryService.server.ts @@ -224,8 +224,7 @@ export async function executeQuery( scope === "project" || scope === "environment" ? { op: "eq", value: projectId } : undefined, - environment_id: - scope === "environment" ? { op: "eq", value: environmentId } : undefined, + environment_id: scope === "environment" ? { op: "eq", value: environmentId } : undefined, }), [timeColumn]: { op: "gte", value: maxQueryPeriodDate }, // Optional filters for tasks and queues @@ -246,8 +245,7 @@ export async function executeQuery( : undefined, operation_id: operations && operations.length > 0 ? { op: "in", values: operations } : undefined, - gen_ai_system: - providers && providers.length > 0 ? { op: "in", values: providers } : undefined, + gen_ai_system: providers && providers.length > 0 ? { op: "in", values: providers } : undefined, } satisfies Record; // Compute the effective time range for timeBucket() interval calculation @@ -275,7 +273,10 @@ export async function executeQuery( environment: Object.fromEntries(environments.map((e) => [e.id, e.slug])), }; - const queryClickhouse = await clickhouseFactory.getClickhouseForOrganization(organizationId, "query"); + const queryClickhouse = await clickhouseFactory.getClickhouseForOrganization( + organizationId, + "query" + ); const result = await executeTSQL(queryClickhouse.reader, { ...baseOptions, schema: z.record(z.any()), diff --git a/apps/webapp/app/services/realtime/duration.server.ts b/apps/webapp/app/services/realtime/duration.server.ts index c6aab9eb9df..b69c63ee746 100644 --- a/apps/webapp/app/services/realtime/duration.server.ts +++ b/apps/webapp/app/services/realtime/duration.server.ts @@ -28,20 +28,19 @@ export function parseDuration(input: string): number { } const value = parseInt(match[1]!, 10); const unit = match[2]!; - const multiplier = - /^s/.test(unit) - ? 1 - : /^m(?:in|ins|inute|inutes)?$/.test(unit) - ? 60 - : /^h/.test(unit) - ? 3600 - : /^d/.test(unit) - ? 86400 - : /^w/.test(unit) - ? 604800 - : /^y/.test(unit) - ? 31_536_000 - : NaN; + const multiplier = /^s/.test(unit) + ? 1 + : /^m(?:in|ins|inute|inutes)?$/.test(unit) + ? 60 + : /^h/.test(unit) + ? 3600 + : /^d/.test(unit) + ? 86400 + : /^w/.test(unit) + ? 604800 + : /^y/.test(unit) + ? 31_536_000 + : NaN; if (!Number.isFinite(multiplier)) { throw new Error(`Invalid duration unit: ${unit}`); } diff --git a/apps/webapp/app/services/realtime/electricStreamProtocol.server.ts b/apps/webapp/app/services/realtime/electricStreamProtocol.server.ts index efe711a7273..fc3a3285af9 100644 --- a/apps/webapp/app/services/realtime/electricStreamProtocol.server.ts +++ b/apps/webapp/app/services/realtime/electricStreamProtocol.server.ts @@ -185,7 +185,9 @@ export function buildElectricSchemaHeader(skipColumns: string[] = []): string { if (skip.has(column.name)) { continue; } - schema[column.name] = column.dims ? { type: column.type, dims: column.dims } : { type: column.type }; + schema[column.name] = column.dims + ? { type: column.type, dims: column.dims } + : { type: column.type }; } return JSON.stringify(schema); diff --git a/apps/webapp/app/services/realtime/envChangeRouter.server.ts b/apps/webapp/app/services/realtime/envChangeRouter.server.ts index 6c5969e02c6..95e25b7d0c0 100644 --- a/apps/webapp/app/services/realtime/envChangeRouter.server.ts +++ b/apps/webapp/app/services/realtime/envChangeRouter.server.ts @@ -588,9 +588,7 @@ export class EnvChangeRouter { staleRunIds.clear(); } const echoHorizonMs = gate.maxDelayMs * 10; - const newestWatermarkMs = Math.max( - ...staleRecords.map((record) => record.updatedAtMs ?? 0) - ); + const newestWatermarkMs = Math.max(...staleRecords.map((record) => record.updatedAtMs ?? 0)); const withinEchoHorizon = Date.now() - newestWatermarkMs < echoHorizonMs; if (attempt < gate.staleRetries || withinEchoHorizon) { const retryDelayMs = Math.max( @@ -685,7 +683,10 @@ export class EnvChangeRouter { /** Authoritative re-check for tag feeds: the hydrated row carries ALL the filter's tags * (Electric's `runTags @> ARRAY[...]` semantics) and its createdAt is within the window. */ #tagRowMatches(row: RealtimeRunRow, filter: Extract): boolean { - if (filter.createdAtFloorMs !== undefined && row.createdAt.getTime() < filter.createdAtFloorMs) { + if ( + filter.createdAtFloorMs !== undefined && + row.createdAt.getTime() < filter.createdAtFloorMs + ) { return false; } const rowTags = row.runTags ?? []; diff --git a/apps/webapp/app/services/realtime/nativeRealtimeClient.server.ts b/apps/webapp/app/services/realtime/nativeRealtimeClient.server.ts index 00e50ed9fcc..8b49e52cfdc 100644 --- a/apps/webapp/app/services/realtime/nativeRealtimeClient.server.ts +++ b/apps/webapp/app/services/realtime/nativeRealtimeClient.server.ts @@ -26,11 +26,7 @@ import { type SerializedRowChange, } from "./electricStreamProtocol.server"; import { BoundedTtlCache } from "./boundedTtlCache"; -import { - type EnvChangeRouter, - type FeedFilter, - type MatchedRow, -} from "./envChangeRouter.server"; +import { type EnvChangeRouter, type FeedFilter, type MatchedRow } from "./envChangeRouter.server"; import { type RunHydrator, type RunListResolver } from "./runReader.server"; import { type RealtimeConcurrencyLimiter } from "./realtimeConcurrencyLimiter.server"; import { InMemoryReplayCursorStore, type ReplayCursorStore } from "./replayCursorStore.server"; @@ -391,7 +387,9 @@ export class NativeRealtimeClient implements RealtimeStreamClient { existingHandle?: string ): Response { const body = buildSnapshotBody(row, skipColumns); - const offset = row ? encodeOffset(row.updatedAt.getTime(), this.#nextSeq()) : encodeOffset(0, 0); + const offset = row + ? encodeOffset(row.updatedAt.getTime(), this.#nextSeq()) + : encodeOffset(0, 0); return this.#buildResponse(body, apiVersion, clientVersion, { offset, handle: existingHandle ?? this.#mintHandle(runId), @@ -568,14 +566,22 @@ export class NativeRealtimeClient implements RealtimeStreamClient { let prevSeen = this.#workingSetCache.get(workingSetKey); const markPollEnd = () => this.#replayCursors.set(workingSetKey, Date.now()); - const emitFromSerialized = (changes: SerializedRowChange[], maxUpdatedAt: number): Response => { + const emitFromSerialized = ( + changes: SerializedRowChange[], + maxUpdatedAt: number + ): Response => { const seq = this.#nextSeq(); markPollEnd(); - return this.#buildResponse(buildRowsBodyFromSerialized(changes), apiVersion, clientVersion, { - offset: encodeOffset(maxUpdatedAt, seq), - handle, - cursor: String(seq), - }); + return this.#buildResponse( + buildRowsBodyFromSerialized(changes), + apiVersion, + clientVersion, + { + offset: encodeOffset(maxUpdatedAt, seq), + handle, + cursor: String(seq), + } + ); }; const emitFromRows = (changes: RowChange[], maxUpdatedAt: number): Response => { const seq = this.#nextSeq(); @@ -853,7 +859,8 @@ export class NativeRealtimeClient implements RealtimeStreamClient { * same projected columns, so cached rows always match the requesting feed. */ #runSetCacheKey(environmentId: string, filter: RunSetFilter, skipColumns: string[]): string { // JSON-encode the arrays (not a join) so a tag containing the separator can't collide with a different filter. - const tags = filter.tags && filter.tags.length > 0 ? JSON.stringify([...filter.tags].sort()) : ""; + const tags = + filter.tags && filter.tags.length > 0 ? JSON.stringify([...filter.tags].sort()) : ""; const cols = skipColumns.length > 0 ? JSON.stringify([...skipColumns].sort()) : ""; const maxListResults = this.options.maxListResults ?? DEFAULT_MAX_LIST_RESULTS; return `${environmentId}|${tags}|${filter.batchId ?? ""}|${ @@ -1040,7 +1047,8 @@ export class NativeRealtimeClient implements RealtimeStreamClient { } #resolveSkipColumns(url: URL, requestOptions?: RealtimeRequestOptions): string[] { - const raw = requestOptions?.skipColumns ?? url.searchParams.get("skipColumns")?.split(",") ?? []; + const raw = + requestOptions?.skipColumns ?? url.searchParams.get("skipColumns")?.split(",") ?? []; return raw.map((c) => c.trim()).filter((c) => c !== "" && !RESERVED_COLUMNS.includes(c)); } } diff --git a/apps/webapp/app/services/realtime/realtimeConcurrencyLimiter.server.ts b/apps/webapp/app/services/realtime/realtimeConcurrencyLimiter.server.ts index 1b6fdb3b0b4..414ffec9f79 100644 --- a/apps/webapp/app/services/realtime/realtimeConcurrencyLimiter.server.ts +++ b/apps/webapp/app/services/realtime/realtimeConcurrencyLimiter.server.ts @@ -27,7 +27,11 @@ export class RealtimeConcurrencyLimiter { this.#registerCommands(); } - async incrementAndCheck(environmentId: string, requestId: string, limit: number): Promise { + async incrementAndCheck( + environmentId: string, + requestId: string, + limit: number + ): Promise { const key = this.#getKey(environmentId); const now = Date.now(); diff --git a/apps/webapp/app/services/realtime/resolveRealtimeStreamClient.server.ts b/apps/webapp/app/services/realtime/resolveRealtimeStreamClient.server.ts index 69ca81cf2cc..4b2207fe190 100644 --- a/apps/webapp/app/services/realtime/resolveRealtimeStreamClient.server.ts +++ b/apps/webapp/app/services/realtime/resolveRealtimeStreamClient.server.ts @@ -37,7 +37,7 @@ export async function resolveRealtimeStreamClient( // The authenticated environment already carries the org's feature flags; pass them // through so a cache miss doesn't need an extra organization read. const orgFeatureFlags = environment.organization - ? environment.organization.featureFlags ?? {} + ? (environment.organization.featureFlags ?? {}) : undefined; switch (await getRealtimeBackend(environment.organizationId, orgFeatureFlags)) { diff --git a/apps/webapp/app/services/realtime/runChangeNotifierInstance.server.ts b/apps/webapp/app/services/realtime/runChangeNotifierInstance.server.ts index 7300d081bfb..2e032bd1599 100644 --- a/apps/webapp/app/services/realtime/runChangeNotifierInstance.server.ts +++ b/apps/webapp/app/services/realtime/runChangeNotifierInstance.server.ts @@ -14,7 +14,8 @@ function initializeRunChangeNotifier(): RunChangeNotifier { const clusterMode = env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_CLUSTER_MODE_ENABLED === "1"; // Sharded pub/sub only works against a cluster; classic pub/sub there would // broadcast every message to every node, so this is what actually shards load. - const shardedPubSub = clusterMode && env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_SHARDED_ENABLED === "1"; + const shardedPubSub = + clusterMode && env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_SHARDED_ENABLED === "1"; const meter = getMeter("realtime-notifier"); diff --git a/apps/webapp/app/services/realtime/runReader.server.ts b/apps/webapp/app/services/realtime/runReader.server.ts index 98ce4dc35ff..952280e7749 100644 --- a/apps/webapp/app/services/realtime/runReader.server.ts +++ b/apps/webapp/app/services/realtime/runReader.server.ts @@ -1,4 +1,8 @@ -import { type Prisma, type PrismaClient, type PrismaClientOrTransaction } from "@trigger.dev/database"; +import { + type Prisma, + type PrismaClient, + type PrismaClientOrTransaction, +} from "@trigger.dev/database"; import type { RunStore } from "@internal/run-store"; import { BoundedTtlCache } from "./boundedTtlCache"; import { RESERVED_COLUMNS, type RealtimeRunRow } from "./electricStreamProtocol.server"; diff --git a/apps/webapp/app/services/realtime/s2realtimeStreams.server.ts b/apps/webapp/app/services/realtime/s2realtimeStreams.server.ts index b91f47c6b3c..22d2ead23fb 100644 --- a/apps/webapp/app/services/realtime/s2realtimeStreams.server.ts +++ b/apps/webapp/app/services/realtime/s2realtimeStreams.server.ts @@ -440,18 +440,15 @@ export class S2RealtimeStreams implements StreamResponder, StreamIngestor { let res: Response; try { - res = await fetch( - `${this.baseUrl}/streams/${encodeURIComponent(s2Stream)}/records?${qs}`, - { - method: "GET", - headers: { - Authorization: `Bearer ${this.token}`, - Accept: "application/json", - "S2-Format": "raw", - "S2-Basin": this.basin, - }, - } - ); + res = await fetch(`${this.baseUrl}/streams/${encodeURIComponent(s2Stream)}/records?${qs}`, { + method: "GET", + headers: { + Authorization: `Bearer ${this.token}`, + Accept: "application/json", + "S2-Format": "raw", + "S2-Basin": this.basin, + }, + }); } catch (err) { this.logger.warn("S2 peek last record: fetch failed", { err, stream: s2Stream }); return false; @@ -569,9 +566,7 @@ export class S2RealtimeStreams implements StreamResponder, StreamIngestor { return (await res.json()) as S2AppendAck; } const text = await res.text().catch(() => ""); - const httpError = new Error( - `S2 append failed: ${res.status} ${res.statusText} ${text}` - ); + const httpError = new Error(`S2 append failed: ${res.status} ${res.statusText} ${text}`); if (res.status >= 400 && res.status < 500) { // 4xx — caller-side problem (auth, malformed body, closed stream). // Retrying won't help. diff --git a/apps/webapp/app/services/realtime/sessionRunManager.server.ts b/apps/webapp/app/services/realtime/sessionRunManager.server.ts index b227f382c7b..341680b32e3 100644 --- a/apps/webapp/app/services/realtime/sessionRunManager.server.ts +++ b/apps/webapp/app/services/realtime/sessionRunManager.server.ts @@ -51,12 +51,7 @@ type EnsureRunForSessionParams = { */ session: Pick< Session, - | "id" - | "friendlyId" - | "taskIdentifier" - | "triggerConfig" - | "currentRunId" - | "currentRunVersion" + "id" | "friendlyId" | "taskIdentifier" | "triggerConfig" | "currentRunId" | "currentRunVersion" >; environment: AuthenticatedEnvironment; reason: EnsureRunReason; @@ -335,12 +330,7 @@ type SwapSessionRunParams = { */ session: Pick< Session, - | "id" - | "friendlyId" - | "taskIdentifier" - | "triggerConfig" - | "currentRunId" - | "currentRunVersion" + "id" | "friendlyId" | "taskIdentifier" | "triggerConfig" | "currentRunId" | "currentRunVersion" >; /** * The run requesting the swap. Optimistic claim requires @@ -382,9 +372,7 @@ export type SwapSessionRunResult = { * a parallel append-time probe that already swapped to a different * run wins the race and `swapped: false` is surfaced. */ -export async function swapSessionRun( - params: SwapSessionRunParams -): Promise { +export async function swapSessionRun(params: SwapSessionRunParams): Promise { const { session, callingRunId, environment, reason, payloadOverrides } = params; // `callingRunId` is the internal cuid (`Session.currentRunId` stores @@ -515,11 +503,7 @@ async function getRunStatusAndFriendlyId( * acceptable degraded behavior. */ async function resolveRunFriendlyId(runId: string): Promise { - const row = await runStore.findRun( - { id: runId }, - { select: { friendlyId: true } }, - $replica - ); + const row = await runStore.findRun({ id: runId }, { select: { friendlyId: true } }, $replica); return row?.friendlyId ?? runId; } diff --git a/apps/webapp/app/services/realtime/sessions.server.ts b/apps/webapp/app/services/realtime/sessions.server.ts index a523111b5b2..a7129830e71 100644 --- a/apps/webapp/app/services/realtime/sessions.server.ts +++ b/apps/webapp/app/services/realtime/sessions.server.ts @@ -75,10 +75,7 @@ export function isSessionFriendlyIdForm(value: string): boolean { * Friendlyid-form callers without a matching row are rejected by * the route handler before this is reached. */ -export function canonicalSessionAddressingKey( - row: Session | null, - paramSession: string -): string { +export function canonicalSessionAddressingKey(row: Session | null, paramSession: string): string { if (row) { return row.externalId ?? row.friendlyId; } @@ -126,9 +123,7 @@ export function serializeSession(session: Session): SessionItem { * `$replica` — a TaskRun's `friendlyId` is immutable so replica lag is * harmless, and serializing on the writer would just add hot-path load. */ -export async function serializeSessionWithFriendlyRunId( - session: Session -): Promise { +export async function serializeSessionWithFriendlyRunId(session: Session): Promise { const base = serializeSession(session); if (!session.currentRunId) return base; @@ -155,7 +150,9 @@ export async function serializeSessionsWithFriendlyRunIds( sessions: Session[], scope: { projectId: string; runtimeEnvironmentId: string } ): Promise { - const runIds = [...new Set(sessions.map((s) => s.currentRunId).filter((id): id is string => !!id))]; + const runIds = [ + ...new Set(sessions.map((s) => s.currentRunId).filter((id): id is string => !!id)), + ]; // `currentRunId` is a plain string pointer (no FK), so scope the lookup to // the caller's tenant — a stale value must not resolve a run in another env. @@ -177,7 +174,7 @@ export async function serializeSessionsWithFriendlyRunIds( return sessions.map((session) => ({ ...serializeSession(session), currentRunId: session.currentRunId - ? friendlyIdByRunId.get(session.currentRunId) ?? null + ? (friendlyIdByRunId.get(session.currentRunId) ?? null) : null, })); } diff --git a/apps/webapp/app/services/realtime/shadowRealtimeClientInstance.server.ts b/apps/webapp/app/services/realtime/shadowRealtimeClientInstance.server.ts index 35333f9639b..7453b107837 100644 --- a/apps/webapp/app/services/realtime/shadowRealtimeClientInstance.server.ts +++ b/apps/webapp/app/services/realtime/shadowRealtimeClientInstance.server.ts @@ -37,7 +37,11 @@ function initializeShadowRealtimeClient(): ShadowRealtimeClient { onOutcome: (outcome) => { const { feed } = outcome; if (outcome.serializationMatched) { - compares.add(outcome.serializationMatched, { feed, kind: "serialization", result: "match" }); + compares.add(outcome.serializationMatched, { + feed, + kind: "serialization", + result: "match", + }); } if (outcome.serializationDiverged) { compares.add(outcome.serializationDiverged, { diff --git a/apps/webapp/app/services/realtime/streamBasinProvisioner.server.ts b/apps/webapp/app/services/realtime/streamBasinProvisioner.server.ts index e29aeb168fb..3cb30ba6a27 100644 --- a/apps/webapp/app/services/realtime/streamBasinProvisioner.server.ts +++ b/apps/webapp/app/services/realtime/streamBasinProvisioner.server.ts @@ -85,10 +85,7 @@ export async function provisionBasinForOrg( return { kind: "provisioned", basin, retention }; } -export async function reconfigureBasinForOrg( - orgId: string, - retention: string -): Promise { +export async function reconfigureBasinForOrg(orgId: string, retention: string): Promise { if (!isPerOrgBasinsEnabled()) return; const accessToken = env.REALTIME_STREAMS_S2_ACCESS_TOKEN; @@ -121,10 +118,7 @@ type EnsureResult = // Idempotent: provisions if the org has no basin, PATCHes retention if // it does. The single entrypoint the cloud billing app drives — both // for the live plan-change path and the bulk backfill. -export async function ensureBasinForOrg( - orgId: string, - retention: string -): Promise { +export async function ensureBasinForOrg(orgId: string, retention: string): Promise { if (!isPerOrgBasinsEnabled()) { return { kind: "skipped", reason: "feature-disabled" }; } @@ -136,9 +130,7 @@ export async function ensureBasinForOrg( if (!org) return { kind: "skipped", reason: "org-not-found" }; if (!org.streamBasinName) { - const result = await provisionBasinForOrg( - { id: org.id, streamBasinName: null, retention } - ); + const result = await provisionBasinForOrg({ id: org.id, streamBasinName: null, retention }); if (result.kind === "provisioned") { return { kind: "provisioned", basin: result.basin, retention: result.retention }; } @@ -245,4 +237,3 @@ async function s2ReconfigureBasin(name: string, opts: ReconfigureBasinOptions): const text = await res.text().catch(() => ""); throw new Error(`S2 reconfigureBasin failed: ${res.status} ${res.statusText} ${text}`); } - diff --git a/apps/webapp/app/services/realtime/types.ts b/apps/webapp/app/services/realtime/types.ts index f09507997a2..3121f0e18df 100644 --- a/apps/webapp/app/services/realtime/types.ts +++ b/apps/webapp/app/services/realtime/types.ts @@ -30,11 +30,7 @@ export interface StreamIngestor { getLastChunkIndex(runId: string, streamId: string, clientId: string): Promise; - readRecords( - runId: string, - streamId: string, - afterSeqNum?: number - ): Promise; + readRecords(runId: string, streamId: string, afterSeqNum?: number): Promise; } export type StreamResponseOptions = { diff --git a/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts b/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts index 593c916f7c9..49c909f7f7b 100644 --- a/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts +++ b/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts @@ -20,10 +20,7 @@ import { API_VERSIONS, getApiVersion } from "~/api/versions"; import { WORKER_HEADERS } from "@trigger.dev/core/v3/runEngineWorker"; import { ServiceValidationError } from "~/v3/services/common.server"; import { EngineServiceValidationError } from "@internal/run-engine"; -import { - tenantContext, - tenantContextFromAuthEnvironment, -} from "~/services/tenantContext.server"; +import { tenantContext, tenantContextFromAuthEnvironment } from "~/services/tenantContext.server"; // Client aborts and service-level validation errors aren't bugs — they're // expected at API boundaries. Log them at `warn` so they stay in stdout @@ -148,11 +145,7 @@ function isEveryResource(value: unknown): value is EveryResourceAuth { type AuthResource = RbacResource | AnyResourceAuth | EveryResourceAuth; -function checkAuth( - ability: RbacAbility, - action: string, - resource: AuthResource -): boolean { +function checkAuth(ability: RbacAbility, action: string, resource: AuthResource): boolean { if (isEveryResource(resource)) { // Empty array via [].every() is vacuously true — would let any token // pass auth. Routes building everyResource() from request bodies @@ -176,7 +169,7 @@ type ApiKeyRouteBuilderOptions< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, - TResource = never + TResource = never, > = { params?: TParamsSchema; searchParams?: TSearchParamsSchema; @@ -218,7 +211,7 @@ type ApiKeyHandlerFunction< TParamsSchema extends AnyZodSchema | undefined, TSearchParamsSchema extends AnyZodSchema | undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, - TResource = never + TResource = never, > = (args: { params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion ? z.infer @@ -241,7 +234,7 @@ export function createLoaderApiRoute< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, - TResource = never + TResource = never, >( options: ApiKeyRouteBuilderOptions, handler: ApiKeyHandlerFunction @@ -404,7 +397,7 @@ export function createLoaderApiRoute< type PATRouteBuilderOptions< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, - THeadersSchema extends AnyZodSchema | undefined = undefined + THeadersSchema extends AnyZodSchema | undefined = undefined, > = { params?: TParamsSchema; searchParams?: TSearchParamsSchema; @@ -446,7 +439,7 @@ type PATRouteBuilderOptions< type PATHandlerFunction< TParamsSchema extends AnyZodSchema | undefined, TSearchParamsSchema extends AnyZodSchema | undefined, - THeadersSchema extends AnyZodSchema | undefined = undefined + THeadersSchema extends AnyZodSchema | undefined = undefined, > = (args: { params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion ? z.infer @@ -468,7 +461,7 @@ type PATHandlerFunction< export function createLoaderPATApiRoute< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, - THeadersSchema extends AnyZodSchema | undefined = undefined + THeadersSchema extends AnyZodSchema | undefined = undefined, >( options: PATRouteBuilderOptions, handler: PATHandlerFunction @@ -560,7 +553,10 @@ export function createLoaderPATApiRoute< let authenticationResult: PersonalAccessTokenAuthenticationResult; let ability: RbacAbility; - const bearer = request.headers.get("Authorization")?.replace(/^Bearer /, "").trim(); + const bearer = request.headers + .get("Authorization") + ?.replace(/^Bearer /, "") + .trim(); if (bearer && isUserActorToken(bearer)) { // A user-actor token validates + computes the cap-and-floor ability // in one call, same shape as a PAT. @@ -648,7 +644,7 @@ type ApiKeyActionRouteBuilderOptions< TSearchParamsSchema extends AnyZodSchema | undefined = undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, TBodySchema extends AnyZodSchema | undefined = undefined, - TResource = never + TResource = never, > = { params?: TParamsSchema; searchParams?: TSearchParamsSchema; @@ -701,7 +697,7 @@ type ApiKeyActionHandlerFunction< TSearchParamsSchema extends AnyZodSchema | undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, TBodySchema extends AnyZodSchema | undefined = undefined, - TResource = never + TResource = never, > = (args: { params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion ? z.infer @@ -727,7 +723,7 @@ export function createActionApiRoute< TSearchParamsSchema extends AnyZodSchema | undefined = undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, TBodySchema extends AnyZodSchema | undefined = undefined, - TResource = never + TResource = never, >( options: ApiKeyActionRouteBuilderOptions< TParamsSchema, @@ -1002,7 +998,7 @@ type MethodConfig = { type MultiMethodApiRouteOptions< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, - THeadersSchema extends AnyZodSchema | undefined = undefined + THeadersSchema extends AnyZodSchema | undefined = undefined, > = { params?: TParamsSchema; searchParams?: TSearchParamsSchema; @@ -1027,7 +1023,7 @@ type MultiMethodApiRouteOptions< export function createMultiMethodApiRoute< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, - THeadersSchema extends AnyZodSchema | undefined = undefined + THeadersSchema extends AnyZodSchema | undefined = undefined, >(options: MultiMethodApiRouteOptions) { const { params: paramsSchema, @@ -1243,7 +1239,7 @@ async function wrapResponse( type WorkerLoaderRouteBuilderOptions< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, - THeadersSchema extends AnyZodSchema | undefined = undefined + THeadersSchema extends AnyZodSchema | undefined = undefined, > = { params?: TParamsSchema; searchParams?: TSearchParamsSchema; @@ -1253,7 +1249,7 @@ type WorkerLoaderRouteBuilderOptions< type WorkerLoaderHandlerFunction< TParamsSchema extends AnyZodSchema | undefined, TSearchParamsSchema extends AnyZodSchema | undefined, - THeadersSchema extends AnyZodSchema | undefined = undefined + THeadersSchema extends AnyZodSchema | undefined = undefined, > = (args: { params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion ? z.infer @@ -1274,7 +1270,7 @@ type WorkerLoaderHandlerFunction< export function createLoaderWorkerApiRoute< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, - THeadersSchema extends AnyZodSchema | undefined = undefined + THeadersSchema extends AnyZodSchema | undefined = undefined, >( options: WorkerLoaderRouteBuilderOptions, handler: WorkerLoaderHandlerFunction @@ -1360,7 +1356,7 @@ type WorkerActionRouteBuilderOptions< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, - TBodySchema extends AnyZodSchema | undefined = undefined + TBodySchema extends AnyZodSchema | undefined = undefined, > = { params?: TParamsSchema; searchParams?: TSearchParamsSchema; @@ -1373,7 +1369,7 @@ type WorkerActionHandlerFunction< TParamsSchema extends AnyZodSchema | undefined, TSearchParamsSchema extends AnyZodSchema | undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, - TBodySchema extends AnyZodSchema | undefined = undefined + TBodySchema extends AnyZodSchema | undefined = undefined, > = (args: { params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion ? z.infer @@ -1398,7 +1394,7 @@ export function createActionWorkerApiRoute< TParamsSchema extends AnyZodSchema | undefined = undefined, TSearchParamsSchema extends AnyZodSchema | undefined = undefined, THeadersSchema extends AnyZodSchema | undefined = undefined, - TBodySchema extends AnyZodSchema | undefined = undefined + TBodySchema extends AnyZodSchema | undefined = undefined, >( options: WorkerActionRouteBuilderOptions< TParamsSchema, diff --git a/apps/webapp/app/services/routeBuilders/dashboardBuilder.ts b/apps/webapp/app/services/routeBuilders/dashboardBuilder.ts index b009c218425..a4603895915 100644 --- a/apps/webapp/app/services/routeBuilders/dashboardBuilder.ts +++ b/apps/webapp/app/services/routeBuilders/dashboardBuilder.ts @@ -83,7 +83,7 @@ export function dashboardLoader< TParams extends AnyZodSchema | undefined = undefined, TSearchParams extends AnyZodSchema | undefined = undefined, TContext extends AuthScope = AuthScope, - TReturn extends Response = Response + TReturn extends Response = Response, >( options: DashboardLoaderOptions, handler: (args: DashboardLoaderHandlerArgs) => Promise @@ -109,7 +109,7 @@ export function dashboardLoader< export type DashboardActionOptions< TParams, TSearchParams, - TContext extends AuthScope + TContext extends AuthScope, > = DashboardLoaderOptions; export type DashboardActionHandlerArgs = @@ -119,7 +119,7 @@ export function dashboardAction< TParams extends AnyZodSchema | undefined = undefined, TSearchParams extends AnyZodSchema | undefined = undefined, TContext extends AuthScope = AuthScope, - TReturn extends Response = Response + TReturn extends Response = Response, >( options: DashboardActionOptions, handler: (args: DashboardActionHandlerArgs) => Promise diff --git a/apps/webapp/app/services/runsReplicationService.server.ts b/apps/webapp/app/services/runsReplicationService.server.ts index 31d8a3844cf..f76a3c7b83d 100644 --- a/apps/webapp/app/services/runsReplicationService.server.ts +++ b/apps/webapp/app/services/runsReplicationService.server.ts @@ -101,7 +101,7 @@ type TaskRunInsert = { export type RunsReplicationServiceEvents = { message: [{ lsn: string; message: PgoutputMessage; service: RunsReplicationService }]; batchFlushed: [ - { flushId: string; taskRunInserts: TaskRunInsertArray[]; payloadInserts: PayloadInsertArray[] } + { flushId: string; taskRunInserts: TaskRunInsertArray[]; payloadInserts: PayloadInsertArray[] }, ]; }; diff --git a/apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts b/apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts index d32652a0b3b..9bf67314779 100644 --- a/apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts +++ b/apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts @@ -260,7 +260,7 @@ export class ClickHouseRunsRepository implements IRunsRepository { environmentId: options.environmentId, }); - const periodMs = options.period ? parseDuration(options.period) ?? undefined : undefined; + const periodMs = options.period ? (parseDuration(options.period) ?? undefined) : undefined; if (periodMs) { queryBuilder.where("created_at >= fromUnixTimestamp64Milli({period: Int64})", { period: new Date(Date.now() - periodMs).getTime(), @@ -405,9 +405,7 @@ function applyRunFiltersToQueryBuilder( if (options.taskKinds && options.taskKinds.length > 0) { const includesStandard = options.taskKinds.includes("STANDARD"); // Include empty string when filtering for STANDARD (default value for pre-existing runs) - const effectiveKinds = includesStandard - ? [...options.taskKinds, ""] - : options.taskKinds; + const effectiveKinds = includesStandard ? [...options.taskKinds, ""] : options.taskKinds; if (effectiveKinds.length === 1) { queryBuilder.where("task_kind = {taskKind: String}", { diff --git a/apps/webapp/app/services/runsRepository/runsRepository.server.ts b/apps/webapp/app/services/runsRepository/runsRepository.server.ts index d40e5cf8c0a..a4543a26dfb 100644 --- a/apps/webapp/app/services/runsRepository/runsRepository.server.ts +++ b/apps/webapp/app/services/runsRepository/runsRepository.server.ts @@ -274,7 +274,7 @@ export async function convertRunListInputOptionsToFilterRunsOptions( from: options.from, to: options.to, }); - convertedOptions.period = time.period ? parseDuration(time.period) ?? undefined : undefined; + convertedOptions.period = time.period ? (parseDuration(time.period) ?? undefined) : undefined; // Batch friendlyId to id if (options.batchId && options.batchId.startsWith("batch_")) { diff --git a/apps/webapp/app/services/secrets/secretStore.server.ts b/apps/webapp/app/services/secrets/secretStore.server.ts index 58af8e86f76..10405f975ab 100644 --- a/apps/webapp/app/services/secrets/secretStore.server.ts +++ b/apps/webapp/app/services/secrets/secretStore.server.ts @@ -219,7 +219,7 @@ class PrismaSecretStore implements SecretStoreProvider { export function getSecretStore< K extends SecretStoreOptions, - TOptions extends ProviderInitializationOptions[K] + TOptions extends ProviderInitializationOptions[K], >(provider: K, options?: TOptions): SecretStore { switch (provider) { case "DATABASE": { diff --git a/apps/webapp/app/services/sessionStreamWaitpointCache.server.ts b/apps/webapp/app/services/sessionStreamWaitpointCache.server.ts index 31121678282..7b53042d8d3 100644 --- a/apps/webapp/app/services/sessionStreamWaitpointCache.server.ts +++ b/apps/webapp/app/services/sessionStreamWaitpointCache.server.ts @@ -87,13 +87,7 @@ export async function addSessionStreamWaitpoint( try { const key = buildKey(environmentId, addressingKey, io); - await redis.eval( - ADD_WAITPOINT_SCRIPT, - 1, - key, - waitpointId, - String(ttlMs ?? DEFAULT_TTL_MS) - ); + await redis.eval(ADD_WAITPOINT_SCRIPT, 1, key, waitpointId, String(ttlMs ?? DEFAULT_TTL_MS)); } catch (error) { logger.error("Failed to set session stream waitpoint cache", { environmentId, diff --git a/apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts b/apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts index b66faf4d3e0..10086c52f36 100644 --- a/apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts +++ b/apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts @@ -56,7 +56,7 @@ export class ClickHouseSessionsRepository implements ISessionsRepository { const direction = options.page.direction ?? "forward"; switch (direction) { case "forward": { - previousCursor = options.page.cursor ? sessionIds.at(0) ?? null : null; + previousCursor = options.page.cursor ? (sessionIds.at(0) ?? null) : null; if (hasMore) { nextCursor = sessionIds[options.page.size - 1]; } @@ -155,7 +155,7 @@ export class ClickHouseSessionsRepository implements ISessionsRepository { environmentId: options.environmentId, }); - const periodMs = options.period ? parseDuration(options.period) ?? undefined : undefined; + const periodMs = options.period ? (parseDuration(options.period) ?? undefined) : undefined; if (periodMs) { queryBuilder.where("created_at >= fromUnixTimestamp64Milli({period: Int64})", { period: new Date(Date.now() - periodMs).getTime(), @@ -221,9 +221,7 @@ function applySessionFiltersToQueryBuilder( if (options.statuses && options.statuses.length > 0) { const conditions: string[] = []; if (options.statuses.includes("ACTIVE")) { - conditions.push( - "(closed_at IS NULL AND (expires_at IS NULL OR expires_at > now64(3)))" - ); + conditions.push("(closed_at IS NULL AND (expires_at IS NULL OR expires_at > now64(3)))"); } if (options.statuses.includes("CLOSED")) { conditions.push("closed_at IS NOT NULL"); diff --git a/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts b/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts index 5a808051035..4c15d0423b0 100644 --- a/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts +++ b/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts @@ -202,6 +202,6 @@ export function convertSessionListInputOptionsToFilterOptions( ): FilterSessionsOptions { return { ...options, - period: options.period ? parseDuration(options.period) ?? undefined : undefined, + period: options.period ? (parseDuration(options.period) ?? undefined) : undefined, }; } diff --git a/apps/webapp/app/services/signals.server.ts b/apps/webapp/app/services/signals.server.ts index 308f16fdeaa..b20df4ebdf5 100644 --- a/apps/webapp/app/services/signals.server.ts +++ b/apps/webapp/app/services/signals.server.ts @@ -6,13 +6,13 @@ export type SignalsEvents = { { time: Date; signal: NodeJS.Signals; - } + }, ]; SIGINT: [ { time: Date; signal: NodeJS.Signals; - } + }, ]; }; diff --git a/apps/webapp/app/services/ssoSessionRevalidation.server.ts b/apps/webapp/app/services/ssoSessionRevalidation.server.ts index 2d582dac1a6..a8f2cb15c09 100644 --- a/apps/webapp/app/services/ssoSessionRevalidation.server.ts +++ b/apps/webapp/app/services/ssoSessionRevalidation.server.ts @@ -77,7 +77,9 @@ export async function revalidateSsoSession( // `sso.revalidation.timeout` warn for alerting. const timeoutMs = env.SSO_SESSION_REVALIDATION_TIMEOUT_MS; let timer: ReturnType | undefined; - let result: Awaited> | typeof REVALIDATION_TIMEOUT; + let result: + | Awaited> + | typeof REVALIDATION_TIMEOUT; try { result = await Promise.race([ // ResultAsync is a PromiseLike; Promise.resolve unwraps it to a Result. diff --git a/apps/webapp/app/services/taskIdentifierRegistry.server.ts b/apps/webapp/app/services/taskIdentifierRegistry.server.ts index 074c9b4c3b6..6a1842b5232 100644 --- a/apps/webapp/app/services/taskIdentifierRegistry.server.ts +++ b/apps/webapp/app/services/taskIdentifierRegistry.server.ts @@ -1,4 +1,8 @@ -import { type PrismaClient, type PrismaClientOrTransaction, TaskTriggerSource } from "@trigger.dev/database"; +import { + type PrismaClient, + type PrismaClientOrTransaction, + TaskTriggerSource, +} from "@trigger.dev/database"; import { $replica, prisma } from "~/db.server"; import { getAllTaskIdentifiers } from "~/models/task.server"; import { logger } from "./logger.server"; @@ -99,8 +103,7 @@ export async function syncTaskIdentifiers( function sortEntries(entries: TaskIdentifierEntry[]): TaskIdentifierEntry[] { return entries.sort((a, b) => { - if (a.isInLatestDeployment !== b.isInLatestDeployment) - return a.isInLatestDeployment ? -1 : 1; + if (a.isInLatestDeployment !== b.isInLatestDeployment) return a.isInLatestDeployment ? -1 : 1; return a.slug.localeCompare(b.slug); }); } diff --git a/apps/webapp/app/services/telemetry.server.ts b/apps/webapp/app/services/telemetry.server.ts index ad67871c19d..71ca0a0e66e 100644 --- a/apps/webapp/app/services/telemetry.server.ts +++ b/apps/webapp/app/services/telemetry.server.ts @@ -47,11 +47,11 @@ class Telemetry { createdAt: user.createdAt, isNewUser, }; - + if (referralSource) { properties.referralSource = referralSource; } - + this.#posthogClient.identify({ distinctId: user.id, properties, diff --git a/apps/webapp/app/services/unkey/redisCacheStore.server.ts b/apps/webapp/app/services/unkey/redisCacheStore.server.ts index 25bbb27634f..0bc88a9af9c 100644 --- a/apps/webapp/app/services/unkey/redisCacheStore.server.ts +++ b/apps/webapp/app/services/unkey/redisCacheStore.server.ts @@ -8,9 +8,10 @@ export type RedisCacheStoreConfig = { name?: string; }; -export class RedisCacheStore - implements Store -{ +export class RedisCacheStore implements Store< + TNamespace, + TValue +> { public readonly name = "redis"; private readonly redis: RedisClient; diff --git a/apps/webapp/app/services/vercelIntegration.server.ts b/apps/webapp/app/services/vercelIntegration.server.ts index 286e9974054..326d43e5839 100644 --- a/apps/webapp/app/services/vercelIntegration.server.ts +++ b/apps/webapp/app/services/vercelIntegration.server.ts @@ -44,7 +44,7 @@ export class VercelIntegrationService { } async getVercelProjectIntegration( - projectId: string, + projectId: string ): Promise { const integration = await this.#prismaClient.organizationProjectIntegration.findFirst({ where: { @@ -107,7 +107,9 @@ export class VercelIntegrationService { return integrations .map((integration) => { - const parsedData = VercelProjectIntegrationDataSchema.safeParse(integration.integrationData); + const parsedData = VercelProjectIntegrationDataSchema.safeParse( + integration.integrationData + ); if (!parsedData.success) { logger.error("Failed to parse Vercel integration data", { integrationId: integration.id, @@ -199,9 +201,7 @@ export class VercelIntegrationService { }); if (existing) { - const parsedData = VercelProjectIntegrationDataSchema.safeParse( - existing.integrationData - ); + const parsedData = VercelProjectIntegrationDataSchema.safeParse(existing.integrationData); const updated = await tx.organizationProjectIntegration.update({ where: { id: existing.id }, @@ -270,14 +270,15 @@ export class VercelIntegrationService { : { success: false, errors: [syncResultAsync.error.message] }; if (wasCreated) { - const disableResult = await VercelIntegrationRepository.getVercelClient(orgIntegration) - .andThen((client) => - VercelIntegrationRepository.disableAutoAssignCustomDomains( - client, - params.vercelProjectId, - teamId - ) - ); + const disableResult = await VercelIntegrationRepository.getVercelClient( + orgIntegration + ).andThen((client) => + VercelIntegrationRepository.disableAutoAssignCustomDomains( + client, + params.vercelProjectId, + teamId + ) + ); if (disableResult.isErr()) { logger.warn("Failed to disable autoAssignCustomDomains during project selection", { @@ -329,9 +330,8 @@ export class VercelIntegrationService { return { ...updated, parsedIntegrationData: updatedData }; } - const orgIntegration = await VercelIntegrationRepository.findVercelOrgIntegrationForProject( - projectId - ); + const orgIntegration = + await VercelIntegrationRepository.findVercelOrgIntegrationForProject(projectId); if (orgIntegration) { await this.#syncTriggerVersionToVercelProduction( @@ -377,11 +377,14 @@ export class VercelIntegrationService { }); if (removeResult.isErr()) { - logger.error("Failed to remove staging TRIGGER_SECRET_KEY from previous custom environment", { - projectId, - previousCustomEnvironmentId, - error: removeResult.error.message, - }); + logger.error( + "Failed to remove staging TRIGGER_SECRET_KEY from previous custom environment", + { + projectId, + previousCustomEnvironmentId, + error: removeResult.error.message, + } + ); } } @@ -534,7 +537,12 @@ export class VercelIntegrationService { return null; } - const syncEnvVarsMapping = params.syncEnvVarsMapping ?? { "dev":{}, "stg":{}, "prod":{}, "preview":{} }; + const syncEnvVarsMapping = params.syncEnvVarsMapping ?? { + dev: {}, + stg: {}, + prod: {}, + preview: {}, + }; const updatedData: VercelProjectIntegrationData = { ...existing.parsedIntegrationData, config: { @@ -557,9 +565,8 @@ export class VercelIntegrationService { }, }); - const orgIntegration = await VercelIntegrationRepository.findVercelOrgIntegrationForProject( - projectId - ); + const orgIntegration = + await VercelIntegrationRepository.findVercelOrgIntegrationForProject(projectId); if (orgIntegration) { const teamId = await VercelIntegrationRepository.getTeamIdFromIntegration(orgIntegration); @@ -702,7 +709,10 @@ export class VercelIntegrationService { logger.error("Failed to sync TRIGGER_VERSION to Vercel production", { projectId, vercelProjectId, - error: createResult.error instanceof Error ? createResult.error.message : String(createResult.error), + error: + createResult.error instanceof Error + ? createResult.error.message + : String(createResult.error), }); return; } @@ -826,4 +836,3 @@ export class VercelIntegrationService { return true; } } - diff --git a/apps/webapp/app/utils.ts b/apps/webapp/app/utils.ts index 2f8cdf7e50c..680c2a0c9f2 100644 --- a/apps/webapp/app/utils.ts +++ b/apps/webapp/app/utils.ts @@ -83,10 +83,13 @@ export function useMatchesData(id: string | string[], debug: boolean = false): U const paths = Array.isArray(id) ? id : [id]; // Get the first matching route - const route = paths.reduce((acc, path) => { - if (acc) return acc; - return matchingRoutes.find((route) => route.id === path); - }, undefined as UIMatch | undefined); + const route = paths.reduce( + (acc, path) => { + if (acc) return acc; + return matchingRoutes.find((route) => route.id === path); + }, + undefined as UIMatch | undefined + ); return route; } diff --git a/apps/webapp/app/utils/branchableEnvironment.ts b/apps/webapp/app/utils/branchableEnvironment.ts index 53ec64bc5f0..997042c316c 100644 --- a/apps/webapp/app/utils/branchableEnvironment.ts +++ b/apps/webapp/app/utils/branchableEnvironment.ts @@ -6,10 +6,7 @@ type BranchableEnvironmentInput = { isBranchableEnvironment: boolean; }; -export type BranchableEnvironmentType = Extract< - RuntimeEnvironmentType, - "PREVIEW" | "DEVELOPMENT" ->; +export type BranchableEnvironmentType = Extract; /** * The wire/form token for a branchable environment kind, as sent by the CLI and @@ -21,8 +18,10 @@ export function toBranchableEnvironmentType( env: BranchableEnvironmentToken ): BranchableEnvironmentType { switch (env) { - case "preview": return "PREVIEW"; - case "development": return "DEVELOPMENT"; + case "preview": + return "PREVIEW"; + case "development": + return "DEVELOPMENT"; } } diff --git a/apps/webapp/app/utils/dataExport.ts b/apps/webapp/app/utils/dataExport.ts index be7cde397b8..c58d11a7e5f 100644 --- a/apps/webapp/app/utils/dataExport.ts +++ b/apps/webapp/app/utils/dataExport.ts @@ -33,7 +33,10 @@ function escapeCSVValue(value: unknown): string { * @param columns - Column metadata describing the result columns * @returns CSV string with header row and data rows */ -export function rowsToCSV(rows: Record[], columns: OutputColumnMetadata[]): string { +export function rowsToCSV( + rows: Record[], + columns: OutputColumnMetadata[] +): string { if (columns.length === 0) { return ""; } @@ -44,7 +47,9 @@ export function rowsToCSV(rows: Record[], columns: OutputColumn const headerRow = columnNames.map(escapeCSVValue).join(","); // Data rows - const dataRows = rows.map((row) => columnNames.map((name) => escapeCSVValue(row[name])).join(",")); + const dataRows = rows.map((row) => + columnNames.map((name) => escapeCSVValue(row[name])).join(",") + ); return [headerRow, ...dataRows].join("\n"); } @@ -75,5 +80,3 @@ export function downloadFile(content: string, filename: string, mimeType: string a.click(); URL.revokeObjectURL(url); } - - diff --git a/apps/webapp/app/utils/logUtils.ts b/apps/webapp/app/utils/logUtils.ts index 71ec44534b0..b6f3681d0d4 100644 --- a/apps/webapp/app/utils/logUtils.ts +++ b/apps/webapp/app/utils/logUtils.ts @@ -54,9 +54,7 @@ export function highlightSearchText( parts.push(text.substring(lastIndex, match.index)); } // Add highlighted match - parts.push( - createElement("span", { key: `match-${matchCount}`, style }, match[0]) - ); + parts.push(createElement("span", { key: `match-${matchCount}`, style }, match[0])); lastIndex = regex.lastIndex; matchCount++; } diff --git a/apps/webapp/app/utils/longPollingFetch.ts b/apps/webapp/app/utils/longPollingFetch.ts index cb5f97693b7..708734dcb41 100644 --- a/apps/webapp/app/utils/longPollingFetch.ts +++ b/apps/webapp/app/utils/longPollingFetch.ts @@ -52,7 +52,9 @@ export async function longPollingFetch( // ReadableStream stays open and undici keeps buffering chunks into memory // until the upstream times out (see H1 isolation test — ~44 KB retained // per unconsumed-body fetch in RSS). - try { await upstream?.body?.cancel(); } catch {} + try { + await upstream?.body?.cancel(); + } catch {} // AbortError is the expected path when downstream disconnects with a // propagated signal — treat as a clean client-close, not a server error. diff --git a/apps/webapp/app/utils/pathBuilder.ts b/apps/webapp/app/utils/pathBuilder.ts index 3b65fde1395..ca167e2163f 100644 --- a/apps/webapp/app/utils/pathBuilder.ts +++ b/apps/webapp/app/utils/pathBuilder.ts @@ -733,7 +733,6 @@ export function branchesDevPath( return `${v3EnvironmentPath(organization, project, environment)}/dev-branches`; } - export function concurrencyPath( organization: OrgForPath, project: ProjectForPath, diff --git a/apps/webapp/app/utils/plain.server.ts b/apps/webapp/app/utils/plain.server.ts index ee205374e17..72b20588432 100644 --- a/apps/webapp/app/utils/plain.server.ts +++ b/apps/webapp/app/utils/plain.server.ts @@ -10,14 +10,7 @@ type Input = { labelTypeIds?: string[]; }; -export async function sendToPlain({ - userId, - email, - name, - title, - components, - labelTypeIds, -}: Input) { +export async function sendToPlain({ userId, email, name, title, components, labelTypeIds }: Input) { if (!env.PLAIN_API_KEY) { return; } diff --git a/apps/webapp/app/utils/prismaErrors.ts b/apps/webapp/app/utils/prismaErrors.ts index ddbdb3cef64..6c128b259d7 100644 --- a/apps/webapp/app/utils/prismaErrors.ts +++ b/apps/webapp/app/utils/prismaErrors.ts @@ -117,7 +117,11 @@ export function logTransactionInfrastructureError( error: unknown, log: ErrorLogger = logger ): boolean { - if (!isInfrastructureError(error) || isPrismaKnownError(error) || infraErrorAlreadyLogged(error)) { + if ( + !isInfrastructureError(error) || + isPrismaKnownError(error) || + infraErrorAlreadyLogged(error) + ) { return false; } diff --git a/apps/webapp/app/utils/reloadingRegistry.server.ts b/apps/webapp/app/utils/reloadingRegistry.server.ts index 65a65850128..81eb6723457 100644 --- a/apps/webapp/app/utils/reloadingRegistry.server.ts +++ b/apps/webapp/app/utils/reloadingRegistry.server.ts @@ -55,7 +55,9 @@ export type ReloadingRegistryOptions = { * contract as the datastore / LLM-pricing registries. Interval-only: no pub/sub * (a follow-up if sub-second propagation is ever needed). */ -export function createReloadingRegistry(opts: ReloadingRegistryOptions): ReloadingRegistry { +export function createReloadingRegistry( + opts: ReloadingRegistryOptions +): ReloadingRegistry { let snapshot: T | undefined; let loaded = false; let loadSeq = 0; diff --git a/apps/webapp/app/utils/searchParams.ts b/apps/webapp/app/utils/searchParams.ts index bc2d7d1e47e..739b284c2a1 100644 --- a/apps/webapp/app/utils/searchParams.ts +++ b/apps/webapp/app/utils/searchParams.ts @@ -54,7 +54,10 @@ export function objectToSearchParams( } class SearchParams { - constructor(private params: TParams, readonly schema: ZodType) {} + constructor( + private params: TParams, + readonly schema: ZodType + ) {} get(key: keyof TParams) { return this.params[key]; diff --git a/apps/webapp/app/utils/timeGranularity.ts b/apps/webapp/app/utils/timeGranularity.ts index 0d8592d27bf..0c8ecc41ac8 100644 --- a/apps/webapp/app/utils/timeGranularity.ts +++ b/apps/webapp/app/utils/timeGranularity.ts @@ -1,15 +1,13 @@ import { z } from "zod"; import parseDuration from "parse-duration"; -const DurationString = z - .string() - .refine( - (val) => { - const ms = parseDuration(val); - return ms !== null && ms > 0; - }, - (val) => ({ message: `Invalid or non-positive duration string: "${val}"` }) - ); +const DurationString = z.string().refine( + (val) => { + const ms = parseDuration(val); + return ms !== null && ms > 0; + }, + (val) => ({ message: `Invalid or non-positive duration string: "${val}"` }) +); const BracketSchema = z.object({ max: z.union([z.literal("Infinity"), DurationString]), diff --git a/apps/webapp/app/v3/accountsWebhookWorker.server.ts b/apps/webapp/app/v3/accountsWebhookWorker.server.ts index 0caf5c591f3..0c56968102f 100644 --- a/apps/webapp/app/v3/accountsWebhookWorker.server.ts +++ b/apps/webapp/app/v3/accountsWebhookWorker.server.ts @@ -79,9 +79,7 @@ function initializeWorker() { // Only poll on worker-role instances (same gate as commonWorker) and // only when the feature is enabled (no plugin loaded otherwise). if (env.COMMON_WORKER_ENABLED === "true" && env.SSO_ENABLED) { - logger.debug( - `👨‍🏭 Starting accounts webhook worker at host ${env.COMMON_WORKER_REDIS_HOST}` - ); + logger.debug(`👨‍🏭 Starting accounts webhook worker at host ${env.COMMON_WORKER_REDIS_HOST}`); worker.start(); } diff --git a/apps/webapp/app/v3/dynamicFlushScheduler.server.ts b/apps/webapp/app/v3/dynamicFlushScheduler.server.ts index afa3e133222..ddf11c2103f 100644 --- a/apps/webapp/app/v3/dynamicFlushScheduler.server.ts +++ b/apps/webapp/app/v3/dynamicFlushScheduler.server.ts @@ -420,4 +420,4 @@ export class DynamicFlushScheduler { await new Promise((resolve) => setTimeout(resolve, 100)); } } -} \ No newline at end of file +} diff --git a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts index 3bd9d90356d..39a8025213f 100644 --- a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts +++ b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts @@ -197,8 +197,7 @@ export class EnvironmentVariablesRepository implements Repository { existingSecret && existingSecret.secret === variable.value && existingValueRecord && - (options.isSecret === undefined || - existingValueRecord.isSecret === options.isSecret); + (options.isSecret === undefined || existingValueRecord.isSecret === options.isSecret); if (canSkip) { continue; } diff --git a/apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts b/apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts index 1e091944e6a..79fe5a4f9d5 100644 --- a/apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts @@ -889,7 +889,7 @@ export class ClickhouseEventRepository implements IEventRepository { const traceId = options.spanParentAsLink ? generateTraceId() - : propagatedContext?.traceparent?.traceId ?? generateTraceId(); + : (propagatedContext?.traceparent?.traceId ?? generateTraceId()); const parentId = options.spanParentAsLink ? undefined : propagatedContext?.traceparent?.spanId; const spanId = options.spanIdSeed ? generateDeterministicSpanId(traceId, options.spanIdSeed) diff --git a/apps/webapp/app/v3/eventRepository/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository/eventRepository.server.ts index 268c955fd25..46ca8cbe485 100644 --- a/apps/webapp/app/v3/eventRepository/eventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository/eventRepository.server.ts @@ -500,8 +500,8 @@ export class EventRepository implements IEventRepository { const isError = isCancelled ? false : typeof overrides?.isError === "boolean" - ? overrides.isError - : event.isError; + ? overrides.isError + : event.isError; const span = { id: event.spanId, @@ -632,8 +632,8 @@ export class EventRepository implements IEventRepository { const isError = isCancelled ? false : typeof overrides?.isError === "boolean" - ? overrides.isError - : event.isError; + ? overrides.isError + : event.isError; const properties = event.properties ? removePrivateProperties(event.properties as Attributes) @@ -700,7 +700,7 @@ export class EventRepository implements IEventRepository { { includeDebugLogs: options?.includeDebugLogs } )) { const properties = event.properties - ? removePrivateProperties(event.properties as Attributes) ?? {} + ? (removePrivateProperties(event.properties as Attributes) ?? {}) : {}; yield { @@ -932,8 +932,8 @@ export class EventRepository implements IEventRepository { const isError = isCancelled ? false : typeof overrides?.isError === "boolean" - ? overrides.isError - : event.isError; + ? overrides.isError + : event.isError; const span = { id: event.spanId, @@ -1199,7 +1199,7 @@ export class EventRepository implements IEventRepository { const traceId = options.spanParentAsLink ? generateTraceId() - : propagatedContext?.traceparent?.traceId ?? generateTraceId(); + : (propagatedContext?.traceparent?.traceId ?? generateTraceId()); const parentId = options.spanParentAsLink ? undefined : propagatedContext?.traceparent?.spanId; const tracestate = options.spanParentAsLink ? undefined : propagatedContext?.tracestate; const spanId = options.spanIdSeed diff --git a/apps/webapp/app/v3/eventRepository/index.server.ts b/apps/webapp/app/v3/eventRepository/index.server.ts index c59be0f3f57..ca0fb85f00b 100644 --- a/apps/webapp/app/v3/eventRepository/index.server.ts +++ b/apps/webapp/app/v3/eventRepository/index.server.ts @@ -29,10 +29,7 @@ export type EventStoreType = (typeof EVENT_STORE_TYPES)[keyof typeof EVENT_STORE * directly and gate startup on `clickhouseFactory.isReady()`. Everything else * should use {@link getEventRepositoryForStore}, the async variant below. */ -function resolveEventRepositoryForStore( - store: string, - organizationId: string -): IEventRepository { +function resolveEventRepositoryForStore(store: string, organizationId: string): IEventRepository { if (store === EVENT_STORE_TYPES.CLICKHOUSE || store === EVENT_STORE_TYPES.CLICKHOUSE_V2) { return clickhouseFactory.getEventRepositoryForOrganizationSync(store, organizationId) .repository; diff --git a/apps/webapp/app/v3/eventRepository/traceExport.server.ts b/apps/webapp/app/v3/eventRepository/traceExport.server.ts index d1e9f5b6f0c..c0a736c60b0 100644 --- a/apps/webapp/app/v3/eventRepository/traceExport.server.ts +++ b/apps/webapp/app/v3/eventRepository/traceExport.server.ts @@ -105,7 +105,8 @@ const logFormat: TraceExportFormat = { const level = event.level.padEnd(5); const errMsg = errorMessage(event); const status = event.isError ? (errMsg ? ` [ERROR: ${errMsg}]` : " [ERROR]") : ""; - const duration = event.durationNs > 0 ? ` (${formatDurationNanoseconds(event.durationNs)})` : ""; + const duration = + event.durationNs > 0 ? ` (${formatDurationNanoseconds(event.durationNs)})` : ""; let out = `${time} ${level} [${lineage(event)}] ${event.message}${status}${duration}\n`; if (hasProperties(event.propertiesText)) { diff --git a/apps/webapp/app/v3/legacyRunEngineWorker.server.ts b/apps/webapp/app/v3/legacyRunEngineWorker.server.ts index 7a381063bdc..7e4458b0175 100644 --- a/apps/webapp/app/v3/legacyRunEngineWorker.server.ts +++ b/apps/webapp/app/v3/legacyRunEngineWorker.server.ts @@ -5,10 +5,7 @@ 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 { completeBatchTaskRunItemV3, tryCompleteBatchV3 } from "./services/batchTriggerV3.server"; import { prisma } from "~/db.server"; import { marqs } from "./marqs/index.server"; diff --git a/apps/webapp/app/v3/marqs/asyncWorker.server.ts b/apps/webapp/app/v3/marqs/asyncWorker.server.ts index 016662e1d5f..64171f36654 100644 --- a/apps/webapp/app/v3/marqs/asyncWorker.server.ts +++ b/apps/webapp/app/v3/marqs/asyncWorker.server.ts @@ -2,7 +2,10 @@ export class AsyncWorker { private running = false; private timeout?: NodeJS.Timeout; - constructor(private readonly fn: () => Promise, private readonly interval: number) {} + constructor( + private readonly fn: () => Promise, + private readonly interval: number + ) {} start() { if (this.running) { diff --git a/apps/webapp/app/v3/marqs/devQueueConsumer.server.ts b/apps/webapp/app/v3/marqs/devQueueConsumer.server.ts index 3143e40f0de..3b6c1f4448e 100644 --- a/apps/webapp/app/v3/marqs/devQueueConsumer.server.ts +++ b/apps/webapp/app/v3/marqs/devQueueConsumer.server.ts @@ -398,8 +398,8 @@ export class DevQueueConsumer { } const backgroundWorker = existingTaskRun.lockedToVersionId - ? this._deprecatedWorkers.get(existingTaskRun.lockedToVersionId) ?? - this._backgroundWorkers.get(existingTaskRun.lockedToVersionId) + ? (this._deprecatedWorkers.get(existingTaskRun.lockedToVersionId) ?? + this._backgroundWorkers.get(existingTaskRun.lockedToVersionId)) : this.#getLatestBackgroundWorker(); if (!backgroundWorker) { diff --git a/apps/webapp/app/v3/marqs/fairDequeuingStrategy.server.ts b/apps/webapp/app/v3/marqs/fairDequeuingStrategy.server.ts index e9205cd000e..1e88a5348bc 100644 --- a/apps/webapp/app/v3/marqs/fairDequeuingStrategy.server.ts +++ b/apps/webapp/app/v3/marqs/fairDequeuingStrategy.server.ts @@ -222,13 +222,16 @@ export class FairDequeuingStrategy implements MarQSFairDequeueStrategy { } #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>); + 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]) { @@ -391,12 +394,15 @@ export class FairDequeuingStrategy implements MarQSFairDequeueStrategy { 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); + 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, @@ -427,13 +433,16 @@ export class FairDequeuingStrategy implements MarQSFairDequeueStrategy { #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); + 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]) => { diff --git a/apps/webapp/app/v3/marqs/index.server.ts b/apps/webapp/app/v3/marqs/index.server.ts index 5348f228ae1..c1beced9ae5 100644 --- a/apps/webapp/app/v3/marqs/index.server.ts +++ b/apps/webapp/app/v3/marqs/index.server.ts @@ -325,8 +325,8 @@ export class MarQS { typeof timestamp === "undefined" ? Date.now() : typeof timestamp === "number" - ? timestamp - : timestamp.getTime(); + ? timestamp + : timestamp.getTime(); const messagePayload: MessagePayload = { version: "1", diff --git a/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts b/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts index 518b64666d4..19d50faac3e 100644 --- a/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts +++ b/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts @@ -620,11 +620,11 @@ export class SharedQueueConsumer { return existingTaskRun.lockedById ? await getWorkerDeploymentFromWorkerTask(existingTaskRun.lockedById) : existingTaskRun.lockedToVersionId - ? await getWorkerDeploymentFromWorker(existingTaskRun.lockedToVersionId) - : await findCurrentWorkerDeployment({ - environmentId: existingTaskRun.runtimeEnvironmentId, - type: "V1", - }); + ? await getWorkerDeploymentFromWorker(existingTaskRun.lockedToVersionId) + : await findCurrentWorkerDeployment({ + environmentId: existingTaskRun.runtimeEnvironmentId, + type: "V1", + }); }); const worker = deployment?.worker; diff --git a/apps/webapp/app/v3/mollifier/applyMetadataMutation.server.ts b/apps/webapp/app/v3/mollifier/applyMetadataMutation.server.ts index bef87afa376..25fe6d73d67 100644 --- a/apps/webapp/app/v3/mollifier/applyMetadataMutation.server.ts +++ b/apps/webapp/app/v3/mollifier/applyMetadataMutation.server.ts @@ -81,10 +81,7 @@ export async function applyMetadataMutationToBufferedRun(input: { if (!entry) return { kind: "not_found" }; // Env+org check: an entry from a different env is treated as a // miss (not 403) so existence in other envs doesn't leak. - if ( - entry.envId !== input.environmentId || - entry.orgId !== input.organizationId - ) { + if (entry.envId !== input.environmentId || entry.orgId !== input.organizationId) { return { kind: "not_found" }; } if (entry.status !== "QUEUED" || entry.materialised) { @@ -146,7 +143,7 @@ export async function applyMetadataMutationToBufferedRun(input: { // metadata; ignore `body.metadata` to match PG behaviour. metadataObject = applyMetadataOperations( parseSnapshotMetadata(), - input.body.operations, + input.body.operations ).newMetadata; } else if (input.body.metadata !== undefined) { // No operations — full replace. diff --git a/apps/webapp/app/v3/mollifier/mollifierDrainer.server.ts b/apps/webapp/app/v3/mollifier/mollifierDrainer.server.ts index c520847ce3c..dacefaac71f 100644 --- a/apps/webapp/app/v3/mollifier/mollifierDrainer.server.ts +++ b/apps/webapp/app/v3/mollifier/mollifierDrainer.server.ts @@ -41,7 +41,7 @@ function initializeMollifierDrainer(): MollifierDrainer { // to nothing). Crashing surfaces the misconfig immediately rather // than silently leaving entries un-drained. throw new MollifierConfigurationError( - "MollifierDrainer initialised without a buffer — env vars inconsistent", + "MollifierDrainer initialised without a buffer — env vars inconsistent" ); } @@ -65,7 +65,7 @@ function initializeMollifierDrainer(): MollifierDrainer { env.GRACEFUL_SHUTDOWN_TIMEOUT - shutdownMarginMs ) { throw new MollifierConfigurationError( - `TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS (${env.TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS}) must be at least ${shutdownMarginMs}ms below GRACEFUL_SHUTDOWN_TIMEOUT (${env.GRACEFUL_SHUTDOWN_TIMEOUT}); otherwise the primary's hard exit shadows the drainer's deadline.`, + `TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS (${env.TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS}) must be at least ${shutdownMarginMs}ms below GRACEFUL_SHUTDOWN_TIMEOUT (${env.GRACEFUL_SHUTDOWN_TIMEOUT}); otherwise the primary's hard exit shadows the drainer's deadline.` ); } diff --git a/apps/webapp/app/v3/mollifier/mollifierDrainerHandler.server.ts b/apps/webapp/app/v3/mollifier/mollifierDrainerHandler.server.ts index 6e829baa575..fd9daa5d615 100644 --- a/apps/webapp/app/v3/mollifier/mollifierDrainerHandler.server.ts +++ b/apps/webapp/app/v3/mollifier/mollifierDrainerHandler.server.ts @@ -89,7 +89,7 @@ export function createDrainerHandler(deps: { cancelReason, emitRunCancelledEvent: false, }, - deps.prisma, + deps.prisma ); } catch (err) { // createCancelledRun throws a conflict when the normal trigger @@ -118,8 +118,10 @@ export function createDrainerHandler(deps: { if (isRetryablePgError(err)) { throw err; } - span.setAttribute("mollifier.cancel_terminal_failure_reason", - err instanceof Error ? err.message : String(err)); + span.setAttribute( + "mollifier.cancel_terminal_failure_reason", + err instanceof Error ? err.message : String(err) + ); try { const wrote = await writeMollifierTerminalFailureRow(deps, { friendlyId: input.runId, @@ -141,9 +143,7 @@ export function createDrainerHandler(deps: { } span.setAttribute("mollifier.cancel_conflict", true); const friendlyId = - typeof input.payload.friendlyId === "string" - ? input.payload.friendlyId - : input.runId; + typeof input.payload.friendlyId === "string" ? input.payload.friendlyId : input.runId; await deps.engine.cancelRun({ runId: RunId.fromFriendlyId(friendlyId), completedAt: new Date(cancelledAtStr), @@ -295,7 +295,7 @@ export function createDrainerHandler(deps: { // logic can own the decision. async function writeMollifierTerminalFailureRow( deps: { engine: RunEngine; prisma: PrismaClientOrTransaction }, - args: { friendlyId: string; snapshot: Record; reason: string }, + args: { friendlyId: string; snapshot: Record; reason: string } ) { const { snapshot } = args; const env = snapshot.environment as @@ -334,8 +334,7 @@ async function writeMollifierTerminalFailureRow( }, parentTaskRunId: typeof snapshot.parentTaskRunId === "string" ? snapshot.parentTaskRunId : undefined, - rootTaskRunId: - typeof snapshot.rootTaskRunId === "string" ? snapshot.rootTaskRunId : undefined, + rootTaskRunId: typeof snapshot.rootTaskRunId === "string" ? snapshot.rootTaskRunId : undefined, depth: typeof snapshot.depth === "number" ? snapshot.depth : 0, resumeParentOnCompletion: snapshot.resumeParentOnCompletion === true, batch, @@ -344,8 +343,7 @@ async function writeMollifierTerminalFailureRow( taskEventStore: typeof snapshot.taskEventStore === "string" ? snapshot.taskEventStore : undefined, queue: typeof snapshot.queue === "string" ? snapshot.queue : undefined, - lockedQueueId: - typeof snapshot.lockedQueueId === "string" ? snapshot.lockedQueueId : undefined, + lockedQueueId: typeof snapshot.lockedQueueId === "string" ? snapshot.lockedQueueId : undefined, emitRunFailedEvent: false, }); // Alerts side of `runFailed` — the engine emit was suppressed above diff --git a/apps/webapp/app/v3/mollifier/mollifierDrainingGauge.server.ts b/apps/webapp/app/v3/mollifier/mollifierDrainingGauge.server.ts index eda8f45ebf1..85a2acd65cb 100644 --- a/apps/webapp/app/v3/mollifier/mollifierDrainingGauge.server.ts +++ b/apps/webapp/app/v3/mollifier/mollifierDrainingGauge.server.ts @@ -19,10 +19,12 @@ let intervalHandle: ReturnType | null = null; // // Idempotent: a second call is a no-op (Remix dev hot-reload re-runs // the bootstrap; the existing interval keeps ticking). -export function startMollifierDrainingGauge(opts: { - intervalMs?: number; - getBuffer?: typeof getMollifierBuffer; -} = {}): void { +export function startMollifierDrainingGauge( + opts: { + intervalMs?: number; + getBuffer?: typeof getMollifierBuffer; + } = {} +): void { if (intervalHandle !== null) return; const intervalMs = opts.intervalMs ?? POLL_INTERVAL_MS; diff --git a/apps/webapp/app/v3/mollifier/mollifierGate.server.ts b/apps/webapp/app/v3/mollifier/mollifierGate.server.ts index 461faa8d3b0..8e237b8e76e 100644 --- a/apps/webapp/app/v3/mollifier/mollifierGate.server.ts +++ b/apps/webapp/app/v3/mollifier/mollifierGate.server.ts @@ -73,14 +73,8 @@ export type GateDependencies = { isShadowModeOn: () => boolean; resolveOrgFlag: (inputs: GateInputs) => Promise; evaluator: TripEvaluator; - logShadow: ( - inputs: GateInputs, - decision: Extract, - ) => void; - logMollified: ( - inputs: GateInputs, - decision: Extract, - ) => void; + logShadow: (inputs: GateInputs, decision: Extract) => void; + logMollified: (inputs: GateInputs, decision: Extract) => void; recordDecision: (outcome: DecisionOutcome, opts: RecordDecisionOptions) => void; }; @@ -99,7 +93,7 @@ const defaultEvaluator = createRealTripEvaluator({ function logDivertDecision( message: "mollifier.would_mollify" | "mollifier.mollified", inputs: GateInputs, - decision: Extract, + decision: Extract ): void { logger.debug(message, { envId: inputs.envId, @@ -140,16 +134,14 @@ export const defaultGateDependencies: GateDependencies = { isShadowModeOn: () => env.TRIGGER_MOLLIFIER_SHADOW_MODE === "1", resolveOrgFlag: resolveMollifierFlag, evaluator: defaultEvaluator, - logShadow: (inputs, decision) => - logDivertDecision("mollifier.would_mollify", inputs, decision), - logMollified: (inputs, decision) => - logDivertDecision("mollifier.mollified", inputs, decision), + logShadow: (inputs, decision) => logDivertDecision("mollifier.would_mollify", inputs, decision), + logMollified: (inputs, decision) => logDivertDecision("mollifier.mollified", inputs, decision), recordDecision, }; export async function evaluateGate( inputs: GateInputs, - deps: Partial = {}, + deps: Partial = {} ): Promise { const d = { ...defaultGateDependencies, ...deps }; diff --git a/apps/webapp/app/v3/mollifier/mollifierMollify.server.ts b/apps/webapp/app/v3/mollifier/mollifierMollify.server.ts index a8b0b151115..6ebcf4a2487 100644 --- a/apps/webapp/app/v3/mollifier/mollifierMollify.server.ts +++ b/apps/webapp/app/v3/mollifier/mollifierMollify.server.ts @@ -35,8 +35,7 @@ export type MollifySyntheticResult = { const NOTICE: MollifyNotice = { code: "mollifier.queued", - message: - "Trigger accepted into burst buffer. Consider batchTrigger for fan-outs of 100+.", + message: "Trigger accepted into burst buffer. Consider batchTrigger for fan-outs of 100+.", docs: "https://trigger.dev/docs/management/tasks/batch-trigger", }; diff --git a/apps/webapp/app/v3/mollifier/mollifierStaleSweep.server.ts b/apps/webapp/app/v3/mollifier/mollifierStaleSweep.server.ts index d135824032c..c2c2a2d03db 100644 --- a/apps/webapp/app/v3/mollifier/mollifierStaleSweep.server.ts +++ b/apps/webapp/app/v3/mollifier/mollifierStaleSweep.server.ts @@ -1,7 +1,10 @@ import type { MollifierBuffer } from "@trigger.dev/redis-worker"; import { logger as defaultLogger } from "~/services/logger.server"; import { getMollifierBuffer } from "./mollifierBuffer.server"; -import { MollifierStaleSweepState, type StaleSweepStateStore } from "./mollifierStaleSweepState.server"; +import { + MollifierStaleSweepState, + type StaleSweepStateStore, +} from "./mollifierStaleSweepState.server"; import { recordStaleEntry as defaultRecordStaleEntry, reportStaleEntrySnapshot as defaultReportStaleEntrySnapshot, @@ -81,12 +84,11 @@ export type StaleSweepResult = { // every tick) does not bound work. export async function runStaleSweepOnce( config: StaleSweepConfig, - deps: StaleSweepDeps, + deps: StaleSweepDeps ): Promise { const getBuffer = deps.getBuffer ?? getMollifierBuffer; const recordStale = deps.recordStaleEntry ?? defaultRecordStaleEntry; - const reportSnapshot = - deps.reportStaleEntrySnapshot ?? defaultReportStaleEntrySnapshot; + const reportSnapshot = deps.reportStaleEntrySnapshot ?? defaultReportStaleEntrySnapshot; const log = deps.logger ?? defaultLogger; const now = (deps.now ?? Date.now)(); const maxEntries = config.maxEntriesPerEnv ?? DEFAULT_MAX_ENTRIES_PER_ENV; @@ -114,10 +116,7 @@ export async function runStaleSweepOnce( await deps.state.rebuildOrgList(orgs); } - const { orgs: slice, total } = await deps.state.readOrgListSlice( - cursor, - maxOrgsPerPass, - ); + const { orgs: slice, total } = await deps.state.readOrgListSlice(cursor, maxOrgsPerPass); let envsScanned = 0; let entriesScanned = 0; @@ -194,7 +193,7 @@ export type StaleSweepIntervalHandle = { // overlapping sweeps that all log the same stale entries). export function startStaleSweepInterval( config: StaleSweepConfig & { intervalMs: number }, - deps: StaleSweepDeps, + deps: StaleSweepDeps ): StaleSweepIntervalHandle { let stopped = false; let inFlight = false; diff --git a/apps/webapp/app/v3/mollifier/mollifierStaleSweepState.server.ts b/apps/webapp/app/v3/mollifier/mollifierStaleSweepState.server.ts index a6f3ff3e4a9..9be3c171ce2 100644 --- a/apps/webapp/app/v3/mollifier/mollifierStaleSweepState.server.ts +++ b/apps/webapp/app/v3/mollifier/mollifierStaleSweepState.server.ts @@ -70,7 +70,7 @@ export class MollifierStaleSweepState implements StaleSweepStateStore { onError: (error) => { this.logger.error("MollifierStaleSweepState redis client error:", { error }); }, - }, + } ); } @@ -97,10 +97,7 @@ export class MollifierStaleSweepState implements StaleSweepStateStore { await pipeline.exec(); } - async readOrgListSlice( - start: number, - count: number, - ): Promise<{ orgs: string[]; total: number }> { + async readOrgListSlice(start: number, count: number): Promise<{ orgs: string[]; total: number }> { const pipeline = this.redis.pipeline(); pipeline.lrange(ORG_LIST_KEY, start, start + count - 1); pipeline.llen(ORG_LIST_KEY); diff --git a/apps/webapp/app/v3/mollifier/mollifierTelemetry.server.ts b/apps/webapp/app/v3/mollifier/mollifierTelemetry.server.ts index 4bd2c45eb54..6310ad9d51f 100644 --- a/apps/webapp/app/v3/mollifier/mollifierTelemetry.server.ts +++ b/apps/webapp/app/v3/mollifier/mollifierTelemetry.server.ts @@ -30,7 +30,7 @@ export type RecordDecisionOptions = { // unit-testable without standing up an OTel meter. export function decisionLabels( outcome: DecisionOutcome, - opts: RecordDecisionOptions, + opts: RecordDecisionOptions ): Record { return { outcome, @@ -54,7 +54,7 @@ export const realtimeBufferedSubscriptionsCounter = meter.createCounter( { description: "Realtime subscriptions opened against a runId that exists only in the mollifier buffer", - }, + } ); // No `envId` attribute — `envId` is a banned high-cardinality metric @@ -72,13 +72,9 @@ export function recordRealtimeBufferedSubscription(): void { // single stuck entry observed by N sweep ticks adds N to the counter, // so `rate()` over an alerting window reflects (entries × ticks), not // "entries that are stale right now". -export const staleEntriesCounter = meter.createCounter( - "mollifier.stale_entries", - { - description: - "Mollifier buffer entries whose dwell exceeds the stale threshold (per sweep pass)", - }, -); +export const staleEntriesCounter = meter.createCounter("mollifier.stale_entries", { + description: "Mollifier buffer entries whose dwell exceeds the stale threshold (per sweep pass)", +}); // No `envId` attribute — see comment above. export function recordStaleEntry(): void { @@ -90,13 +86,10 @@ export function recordStaleEntry(): void { // the gauge drops back to 0 when the drainer catches up instead of // staying latched. Recommended alert: // mollifier_stale_entries_current > 0 for 5m -export const staleEntriesGauge = meter.createObservableGauge( - "mollifier.stale_entries.current", - { - description: - "Buffer entries whose dwell exceeds the stale threshold, as observed by the latest sweep pass", - }, -); +export const staleEntriesGauge = meter.createObservableGauge("mollifier.stale_entries.current", { + description: + "Buffer entries whose dwell exceeds the stale threshold, as observed by the latest sweep pass", +}); let latestStaleTotal = 0; @@ -115,7 +108,7 @@ meter.addBatchObservableCallback( (result) => { result.observe(staleEntriesGauge, latestStaleTotal); }, - [staleEntriesGauge], + [staleEntriesGauge] ); // Observability gauge for entries currently in DRAINING state — popped @@ -130,13 +123,10 @@ meter.addBatchObservableCallback( // // No `envId` attribute — same high-cardinality constraint as the other // mollifier gauges. The per-entry hash carries env/org for drill-down. -export const drainingCountGauge = meter.createObservableGauge( - "mollifier.draining.current", - { - description: - "Mollifier buffer entries currently in DRAINING state (popped but not yet acked/failed/requeued)", - }, -); +export const drainingCountGauge = meter.createObservableGauge("mollifier.draining.current", { + description: + "Mollifier buffer entries currently in DRAINING state (popped but not yet acked/failed/requeued)", +}); let latestDrainingCount = 0; @@ -148,7 +138,7 @@ meter.addBatchObservableCallback( (result) => { result.observe(drainingCountGauge, latestDrainingCount); }, - [drainingCountGauge], + [drainingCountGauge] ); // Electric SQL's shape-stream protocol adds a `handle=` query param on diff --git a/apps/webapp/app/v3/mollifier/mutateWithFallback.server.ts b/apps/webapp/app/v3/mollifier/mutateWithFallback.server.ts index 6ed2d9a1e0d..8460fbe541a 100644 --- a/apps/webapp/app/v3/mollifier/mutateWithFallback.server.ts +++ b/apps/webapp/app/v3/mollifier/mutateWithFallback.server.ts @@ -37,18 +37,14 @@ export type MutateWithFallbackInput = { // matching the PG path, without an extra Redis round-trip. // `bufferEntry` is `null` in the rare race where the entry didn't // exist at pre-check time but appeared before `mutateSnapshot`. - synthesisedResponse: (ctx: { - bufferEntry: BufferEntry | null; - }) => TResponse | Promise; + synthesisedResponse: (ctx: { bufferEntry: BufferEntry | null }) => TResponse | Promise; // Called when the buffer rejected the patch as invalid (e.g. an // `append_tags` patch carrying `maxTags` would exceed the cap). Required // only by callers that send a rejectable patch; the helper throws if the // buffer reports a rejection and no builder was supplied. Receives the // same `bufferEntry` context as `synthesisedResponse` so a rejection // message can reference the prior state if useful. - rejectedResponse?: (ctx: { - bufferEntry: BufferEntry | null; - }) => TResponse | Promise; + rejectedResponse?: (ctx: { bufferEntry: BufferEntry | null }) => TResponse | Promise; abortSignal?: AbortSignal; // Override defaults for tests. safetyNetMs?: number; @@ -78,7 +74,7 @@ export type MutateWithFallbackOutcome = // this helper never throws Response objects so it remains route-agnostic // and unit-testable in isolation. export async function mutateWithFallback( - input: MutateWithFallbackInput, + input: MutateWithFallbackInput ): Promise> { const replica = input.prismaReplica ?? $replica; const writer = input.prismaWriter ?? prisma; @@ -122,8 +118,7 @@ export async function mutateWithFallback( const entryForAuth = await buffer.getEntry(input.runId); if ( entryForAuth && - (entryForAuth.envId !== input.environmentId || - entryForAuth.orgId !== input.organizationId) + (entryForAuth.envId !== input.environmentId || entryForAuth.orgId !== input.organizationId) ) { // Hide existence on env mismatch: return not_found, same shape as // a true miss, rather than 403 which would leak that the runId @@ -132,10 +127,7 @@ export async function mutateWithFallback( } // Path 2 — buffer snapshot mutation. - const result: MutateSnapshotResult = await buffer.mutateSnapshot( - input.runId, - input.bufferPatch, - ); + const result: MutateSnapshotResult = await buffer.mutateSnapshot(input.runId, input.bufferPatch); if (result === "applied_to_snapshot") { return { @@ -150,7 +142,7 @@ export async function mutateWithFallback( // caller sent a rejectable patch without handling the rejection. if (!input.rejectedResponse) { throw new Error( - "mutateWithFallback: buffer returned 'limit_exceeded' but no rejectedResponse was provided", + "mutateWithFallback: buffer returned 'limit_exceeded' but no rejectedResponse was provided" ); } return { @@ -227,7 +219,7 @@ export async function mutateWithFallback( async function findRunInPg( client: PrismaClientOrTransaction | PrismaReplicaClient, friendlyId: string, - environmentId: string, + environmentId: string ): Promise { return runStore.findRun({ friendlyId, runtimeEnvironmentId: environmentId }, client); } diff --git a/apps/webapp/app/v3/mollifier/readFallback.server.ts b/apps/webapp/app/v3/mollifier/readFallback.server.ts index d684741219f..188face643f 100644 --- a/apps/webapp/app/v3/mollifier/readFallback.server.ts +++ b/apps/webapp/app/v3/mollifier/readFallback.server.ts @@ -116,7 +116,9 @@ function asString(value: unknown): string | undefined { } function asStringArray(value: unknown): string[] { - return Array.isArray(value) && value.every((v) => typeof v === "string") ? (value as string[]) : []; + return Array.isArray(value) && value.every((v) => typeof v === "string") + ? (value as string[]) + : []; } function asDate(value: unknown): Date | undefined { @@ -137,7 +139,7 @@ function internalRunIdToFriendlyId(internalId: string | undefined): string | und export async function findRunByIdWithMollifierFallback( input: ReadFallbackInput, - deps: ReadFallbackDeps = {}, + deps: ReadFallbackDeps = {} ): Promise { const buffer = (deps.getBuffer ?? getMollifierBuffer)(); if (!buffer) return null; @@ -164,7 +166,7 @@ export async function findRunByIdWithMollifierFallback( // instead of the customer-supplied key — diverging from how // materialised runs render the same field. const idempotencyKeyOptionsParsed = IdempotencyKeyOptionsSchema.safeParse( - snapshot.idempotencyKeyOptions, + snapshot.idempotencyKeyOptions ); const idempotencyKeyOptions = idempotencyKeyOptionsParsed.success ? idempotencyKeyOptionsParsed.data @@ -219,8 +221,7 @@ export async function findRunByIdWithMollifierFallback( spanId: asString(snapshot.spanId), parentSpanId: asString(snapshot.parentSpanId), - runtimeEnvironmentId: - asString(environment?.id) ?? entry.envId, + runtimeEnvironmentId: asString(environment?.id) ?? entry.envId, engine: "V2", workerQueue: asString(snapshot.workerQueue), region: asString(snapshot.region), @@ -249,12 +250,8 @@ export async function findRunByIdWithMollifierFallback( // not the friendlyIds the SyntheticRun contract expects. Convert // internal → friendly here so consumers don't have to special-case // the buffered path. - parentTaskRunFriendlyId: internalRunIdToFriendlyId( - asString(snapshot.parentTaskRunId) - ), - rootTaskRunFriendlyId: internalRunIdToFriendlyId( - asString(snapshot.rootTaskRunId) - ), + parentTaskRunFriendlyId: internalRunIdToFriendlyId(asString(snapshot.parentTaskRunId)), + rootTaskRunFriendlyId: internalRunIdToFriendlyId(asString(snapshot.rootTaskRunId)), error: entry.lastError, }; diff --git a/apps/webapp/app/v3/mollifier/resolveRunForMutation.server.ts b/apps/webapp/app/v3/mollifier/resolveRunForMutation.server.ts index fc251469c8c..258c0da2117 100644 --- a/apps/webapp/app/v3/mollifier/resolveRunForMutation.server.ts +++ b/apps/webapp/app/v3/mollifier/resolveRunForMutation.server.ts @@ -45,11 +45,7 @@ export async function resolveRunForMutation(input: { if (buffer) { const entry = await buffer.getEntry(input.runParam); - if ( - entry && - entry.envId === input.environmentId && - entry.orgId === input.organizationId - ) { + if (entry && entry.envId === input.environmentId && entry.orgId === input.organizationId) { return { source: "buffer", friendlyId: input.runParam }; } } diff --git a/apps/webapp/app/v3/mollifier/syntheticRedirectInfo.server.ts b/apps/webapp/app/v3/mollifier/syntheticRedirectInfo.server.ts index e316846d708..4bc6b9f9be8 100644 --- a/apps/webapp/app/v3/mollifier/syntheticRedirectInfo.server.ts +++ b/apps/webapp/app/v3/mollifier/syntheticRedirectInfo.server.ts @@ -57,7 +57,7 @@ export async function findBufferedRunRedirectInfo( // org membership in the PG query either). skipOrgMembershipCheck?: boolean; }, - deps: FindBufferedRunRedirectInfoDeps = {}, + deps: FindBufferedRunRedirectInfoDeps = {} ): Promise { const buffer = (deps.getBuffer ?? getMollifierBuffer)(); const prismaClient = deps.prismaClient ?? prisma; diff --git a/apps/webapp/app/v3/mollifier/syntheticRunHeader.server.ts b/apps/webapp/app/v3/mollifier/syntheticRunHeader.server.ts index 9b137f87fb3..4b505fcb57e 100644 --- a/apps/webapp/app/v3/mollifier/syntheticRunHeader.server.ts +++ b/apps/webapp/app/v3/mollifier/syntheticRunHeader.server.ts @@ -46,8 +46,8 @@ export function buildSyntheticRunHeader(args: { status: isCancelled ? ("CANCELED" as const) : isFailed - ? ("SYSTEM_FAILURE" as const) - : ("PENDING" as const), + ? ("SYSTEM_FAILURE" as const) + : ("PENDING" as const), isFinished: isCancelled || isFailed, startedAt: null, // Symmetric with `buildSyntheticSpanRun` and the diff --git a/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts b/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts index ae274aac3d5..d5d682426d0 100644 --- a/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts +++ b/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts @@ -32,7 +32,11 @@ function narrowMachinePreset(value: string | undefined): SpanRun["machinePreset" // snapshot fields as inline packets. export async function buildSyntheticSpanRun(args: { run: SyntheticRun; - environment: { id: string; slug: string; type: "PRODUCTION" | "DEVELOPMENT" | "STAGING" | "PREVIEW" }; + environment: { + id: string; + slug: string; + type: "PRODUCTION" | "DEVELOPMENT" | "STAGING" | "PREVIEW"; + }; }): Promise { const { run, environment } = args; @@ -63,8 +67,8 @@ export async function buildSyntheticSpanRun(args: { const idempotencyKeyStatus: SpanRun["idempotencyKeyStatus"] = idempotencyKey ? "active" : idempotencyKeyScope - ? "inactive" - : undefined; + ? "inactive" + : undefined; const taskKind = RunAnnotations.safeParse(run.annotations).data?.taskKind; const isAgentRun = taskKind === "AGENT"; @@ -82,8 +86,8 @@ export async function buildSyntheticSpanRun(args: { const status: SpanRun["status"] = isCancelled ? "CANCELED" : isFailed - ? "SYSTEM_FAILURE" - : "PENDING"; + ? "SYSTEM_FAILURE" + : "PENDING"; // Mirror ApiRetrieveRunPresenter's STRING_ERROR synthesis so the panel // shows why a buffered run failed instead of an empty error block. @@ -97,10 +101,10 @@ export async function buildSyntheticSpanRun(args: { friendlyId: run.friendlyId, status, statusReason: isCancelled - ? run.cancelReason ?? undefined + ? (run.cancelReason ?? undefined) : isFailed - ? run.error?.message ?? undefined - : undefined, + ? (run.error?.message ?? undefined) + : undefined, createdAt: run.createdAt, startedAt: null, executedAt: null, @@ -177,7 +181,7 @@ export async function buildSyntheticSpanRun(args: { }, }, null, - 2, + 2 ), metadata, maxDurationInSeconds: getMaxDuration(run.maxDurationInSeconds), diff --git a/apps/webapp/app/v3/mollifier/syntheticTrace.server.ts b/apps/webapp/app/v3/mollifier/syntheticTrace.server.ts index 03b6e03ca1c..e36aaa19e62 100644 --- a/apps/webapp/app/v3/mollifier/syntheticTrace.server.ts +++ b/apps/webapp/app/v3/mollifier/syntheticTrace.server.ts @@ -41,9 +41,7 @@ export function buildSyntheticTraceForBufferedRun(run: SyntheticRun) { const events = tree ? flattenTree(tree).map((n) => { - const offset = millisecondsToNanoseconds( - n.data.startTime.getTime() - treeRootStartTimeMs - ); + const offset = millisecondsToNanoseconds(n.data.startTime.getTime() - treeRootStartTimeMs); // Mirror RunPresenter: raw span events stay server-side, only // timelineEvents ship to the client. const { events: spanEvents, ...data } = n.data; @@ -51,7 +49,11 @@ export function buildSyntheticTraceForBufferedRun(run: SyntheticRun) { ...n, data: { ...data, - timelineEvents: createTimelineSpanEventsFromSpanEvents(spanEvents, false, treeRootStartTimeMs), + timelineEvents: createTimelineSpanEventsFromSpanEvents( + spanEvents, + false, + treeRootStartTimeMs + ), duration: n.data.isPartial ? null : n.data.duration, offset, isRoot: n.id === spanId, diff --git a/apps/webapp/app/v3/mollifierDrainerWorker.server.ts b/apps/webapp/app/v3/mollifierDrainerWorker.server.ts index b79b9f08e5b..1ffa48f1cd7 100644 --- a/apps/webapp/app/v3/mollifierDrainerWorker.server.ts +++ b/apps/webapp/app/v3/mollifierDrainerWorker.server.ts @@ -52,7 +52,7 @@ export function initMollifierDrainerWorker( // without manipulating module-level env state. isEnabled?: () => boolean; getDrainer?: typeof getMollifierDrainer; - } = {}, + } = {} ): void { const isEnabled = opts.isEnabled ?? (() => env.TRIGGER_MOLLIFIER_DRAINER_ENABLED === "1"); const getDrainer = opts.getDrainer ?? getMollifierDrainer; diff --git a/apps/webapp/app/v3/mollifierStaleSweepWorker.server.ts b/apps/webapp/app/v3/mollifierStaleSweepWorker.server.ts index e147422bfe6..2247a6bdff1 100644 --- a/apps/webapp/app/v3/mollifierStaleSweepWorker.server.ts +++ b/apps/webapp/app/v3/mollifierStaleSweepWorker.server.ts @@ -60,7 +60,7 @@ export function initMollifierStaleSweepWorker(): void { maxEntriesPerEnv: env.TRIGGER_MOLLIFIER_STALE_SWEEP_MAX_ENTRIES_PER_ENV, maxOrgsPerPass: env.TRIGGER_MOLLIFIER_STALE_SWEEP_MAX_ORGS_PER_PASS, }, - { state }, + { state } ); // `handle.stop` is now async (it closes the Redis client). The signals diff --git a/apps/webapp/app/v3/objectStore.server.ts b/apps/webapp/app/v3/objectStore.server.ts index 16aa9d71ee4..159f2f41c6f 100644 --- a/apps/webapp/app/v3/objectStore.server.ts +++ b/apps/webapp/app/v3/objectStore.server.ts @@ -283,11 +283,7 @@ export async function downloadPacketFromObjectStore( } const { protocol, path } = parseStorageUri(packet.data); - const key = buildPacketObjectStoreKey( - environment.project.externalRef, - environment.slug, - path - ); + const key = buildPacketObjectStoreKey(environment.project.externalRef, environment.slug, path); const client = getObjectStoreClient(protocol); diff --git a/apps/webapp/app/v3/objectStoreClient.server.ts b/apps/webapp/app/v3/objectStoreClient.server.ts index 9999a40b79e..790530de352 100644 --- a/apps/webapp/app/v3/objectStoreClient.server.ts +++ b/apps/webapp/app/v3/objectStoreClient.server.ts @@ -46,7 +46,11 @@ class Aws4FetchClient implements IObjectStoreClient { return url.toString(); } - async putObject(key: string, body: ReadableStream | string, contentType: string): Promise { + async putObject( + key: string, + body: ReadableStream | string, + contentType: string + ): Promise { const objectUrl = this.buildUrl(key); const response = await this.awsClient.fetch(objectUrl, { method: "PUT", @@ -114,7 +118,11 @@ class AwsSdkClient implements IObjectStoreClient { return url.href; } - async putObject(key: string, body: ReadableStream | string, contentType: string): Promise { + async putObject( + key: string, + body: ReadableStream | string, + contentType: string + ): Promise { const s3Key = this.toS3ObjectKey(key); await this.s3Client.send( new PutObjectCommand({ diff --git a/apps/webapp/app/v3/otlpExporter.server.ts b/apps/webapp/app/v3/otlpExporter.server.ts index 975e4aed4a7..148109a0340 100644 --- a/apps/webapp/app/v3/otlpExporter.server.ts +++ b/apps/webapp/app/v3/otlpExporter.server.ts @@ -745,16 +745,16 @@ function convertKeyValueItemsToMap( map[`${prefix ? `${prefix}.` : ""}${attribute.key}`] = isStringValue(attribute.value) ? attribute.value.stringValue : isIntValue(attribute.value) - ? Number(attribute.value.intValue) - : isDoubleValue(attribute.value) - ? attribute.value.doubleValue - : isBoolValue(attribute.value) - ? attribute.value.boolValue - : isBytesValue(attribute.value) - ? binaryToHex(attribute.value.bytesValue) - : isArrayValue(attribute.value) - ? serializeArrayValue(attribute.value.arrayValue!.values) - : undefined; + ? Number(attribute.value.intValue) + : isDoubleValue(attribute.value) + ? attribute.value.doubleValue + : isBoolValue(attribute.value) + ? attribute.value.boolValue + : isBytesValue(attribute.value) + ? binaryToHex(attribute.value.bytesValue) + : isArrayValue(attribute.value) + ? serializeArrayValue(attribute.value.arrayValue!.values) + : undefined; return map; }, @@ -783,16 +783,16 @@ function convertSelectedKeyValueItemsToMap( map[`${prefix ? `${prefix}.` : ""}${attribute.key}`] = isStringValue(attribute.value) ? attribute.value.stringValue : isIntValue(attribute.value) - ? Number(attribute.value.intValue) - : isDoubleValue(attribute.value) - ? attribute.value.doubleValue - : isBoolValue(attribute.value) - ? attribute.value.boolValue - : isBytesValue(attribute.value) - ? binaryToHex(attribute.value.bytesValue) - : isArrayValue(attribute.value) - ? serializeArrayValue(attribute.value.arrayValue!.values) - : undefined; + ? Number(attribute.value.intValue) + : isDoubleValue(attribute.value) + ? attribute.value.doubleValue + : isBoolValue(attribute.value) + ? attribute.value.boolValue + : isBytesValue(attribute.value) + ? binaryToHex(attribute.value.bytesValue) + : isArrayValue(attribute.value) + ? serializeArrayValue(attribute.value.arrayValue!.values) + : undefined; return map; }, diff --git a/apps/webapp/app/v3/querySchemas.ts b/apps/webapp/app/v3/querySchemas.ts index 304664800ea..4784ad75629 100644 --- a/apps/webapp/app/v3/querySchemas.ts +++ b/apps/webapp/app/v3/querySchemas.ts @@ -198,7 +198,8 @@ export const runsSchema: TableSchema = { example: "us-east-1", }), // No whereTransform: the expression drives WHERE too, so pre-region rows still match. - expression: "multiIf(region != '', region, startsWith(worker_queue, 'cm'), NULL, worker_queue)", + expression: + "multiIf(region != '', region, startsWith(worker_queue, 'cm'), NULL, worker_queue)", }, // Timing diff --git a/apps/webapp/app/v3/runEngine.server.ts b/apps/webapp/app/v3/runEngine.server.ts index 06cb591a0b4..3f9cd603b07 100644 --- a/apps/webapp/app/v3/runEngine.server.ts +++ b/apps/webapp/app/v3/runEngine.server.ts @@ -21,8 +21,7 @@ function createRunEngine() { logLevel: env.RUN_ENGINE_WORKER_LOG_LEVEL, treatProductionExecutionStallsAsOOM: env.RUN_ENGINE_TREAT_PRODUCTION_EXECUTION_STALLS_AS_OOM === "1", - readReplicaSnapshotsSinceEnabled: - env.RUN_ENGINE_READ_REPLICA_SNAPSHOTS_SINCE_ENABLED === "1", + readReplicaSnapshotsSinceEnabled: env.RUN_ENGINE_READ_REPLICA_SNAPSHOTS_SINCE_ENABLED === "1", readReplicaSnapshotsSinceRetryDelay: { minMs: env.RUN_ENGINE_SNAPSHOTS_SINCE_REPLICA_RETRY_MIN_MS, maxMs: env.RUN_ENGINE_SNAPSHOTS_SINCE_REPLICA_RETRY_MAX_MS, diff --git a/apps/webapp/app/v3/runEngineHandlers.server.ts b/apps/webapp/app/v3/runEngineHandlers.server.ts index e2285a4fecc..a9c9df58373 100644 --- a/apps/webapp/app/v3/runEngineHandlers.server.ts +++ b/apps/webapp/app/v3/runEngineHandlers.server.ts @@ -896,7 +896,7 @@ export function setupBatchQueueCallbacks() { batchIndex: itemIndex, realtimeStreamsVersion: meta.realtimeStreamsVersion, planType: meta.planType, - triggerSource: meta.parentRunId ? "sdk" : meta.triggerSource ?? "api", + triggerSource: meta.parentRunId ? "sdk" : (meta.triggerSource ?? "api"), triggerAction: "trigger", }, "V2" diff --git a/apps/webapp/app/v3/services/adminWorker.server.ts b/apps/webapp/app/v3/services/adminWorker.server.ts index cf3dbd57c6c..6b416b45b7a 100644 --- a/apps/webapp/app/v3/services/adminWorker.server.ts +++ b/apps/webapp/app/v3/services/adminWorker.server.ts @@ -14,8 +14,7 @@ import { runsReplicationInstance } from "~/services/runsReplicationInstance.serv // initializer never fires. Assignment to globalThis is an observable side // effect the bundler must preserve. See TRI-9864. import { sessionsReplicationInstance } from "~/services/sessionsReplicationInstance.server"; -(globalThis as Record).__sessionsReplicationInstance = - sessionsReplicationInstance; +(globalThis as Record).__sessionsReplicationInstance = sessionsReplicationInstance; import { singleton } from "~/utils/singleton"; import { tracer } from "../tracer.server"; import { $replica } from "~/db.server"; diff --git a/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts b/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts index 49f464d6dc8..247efb5d48f 100644 --- a/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts +++ b/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts @@ -33,10 +33,7 @@ import { ProjectAlertWebhookProperties, } from "~/models/projectAlert.server"; import { ApiRetrieveRunPresenter } from "~/presenters/v3/ApiRetrieveRunPresenter.server"; -import { - processGitMetadata, - type GitMetaLinks, -} from "~/presenters/v3/BranchesPresenter.server"; +import { processGitMetadata, type GitMetaLinks } from "~/presenters/v3/BranchesPresenter.server"; import { DeploymentPresenter } from "~/presenters/v3/DeploymentPresenter.server"; import { sendAlertEmail } from "~/services/email.server"; import { VercelProjectIntegrationDataSchema } from "~/v3/vercel/vercelProjectIntegrationSchema"; @@ -164,9 +161,7 @@ export class DeliverAlertService extends BaseService { const deploymentMeta = alert.type === "DEPLOYMENT_SUCCESS" || alert.type === "DEPLOYMENT_FAILURE" - ? ( - await fromPromise(this.#resolveDeploymentMetadata(alert), (e) => e) - ).unwrapOr(emptyMeta) + ? (await fromPromise(this.#resolveDeploymentMetadata(alert), (e) => e)).unwrapOr(emptyMeta) : emptyMeta; try { @@ -774,7 +769,14 @@ export class DeliverAlertService extends BaseService { text: this.#wrapInCodeBlock(error.stackTrace ?? error.message), }, }, - this.#buildRunQuoteBlock(taskIdentifier, version, environment, runId, alert.project.name, timestamp), + this.#buildRunQuoteBlock( + taskIdentifier, + version, + environment, + runId, + alert.project.name, + timestamp + ), { type: "actions", elements: [ @@ -862,7 +864,13 @@ export class DeliverAlertService extends BaseService { text: this.#wrapInCodeBlock(preparedError.stack ?? preparedError.message), }, }, - this.#buildDeploymentQuoteBlock(alert, deploymentMeta, version, environment, timestamp), + this.#buildDeploymentQuoteBlock( + alert, + deploymentMeta, + version, + environment, + timestamp + ), { type: "actions", elements: [ @@ -903,7 +911,13 @@ export class DeliverAlertService extends BaseService { text: `:rocket: Deployed *${version}.${environment}* successfully`, }, }, - this.#buildDeploymentQuoteBlock(alert, deploymentMeta, version, environment, timestamp), + this.#buildDeploymentQuoteBlock( + alert, + deploymentMeta, + version, + environment, + timestamp + ), { type: "actions", elements: [ @@ -1057,9 +1071,7 @@ export class DeliverAlertService extends BaseService { } } - async #resolveDeploymentMetadata( - alert: FoundAlert - ): Promise { + async #resolveDeploymentMetadata(alert: FoundAlert): Promise { const deployment = alert.workerDeployment; if (!deployment) { return { git: null, vercelDeploymentUrl: undefined }; @@ -1078,20 +1090,19 @@ export class DeliverAlertService extends BaseService { projectId: string, deploymentId: string ): Promise { - const vercelProjectIntegration = - await this._prisma.organizationProjectIntegration.findFirst({ - where: { - projectId, + const vercelProjectIntegration = await this._prisma.organizationProjectIntegration.findFirst({ + where: { + projectId, + deletedAt: null, + organizationIntegration: { + service: "VERCEL", deletedAt: null, - organizationIntegration: { - service: "VERCEL", - deletedAt: null, - }, - }, - select: { - integrationData: true, }, - }); + }, + select: { + integrationData: true, + }, + }); if (!vercelProjectIntegration) { return undefined; @@ -1105,19 +1116,18 @@ export class DeliverAlertService extends BaseService { return undefined; } - const integrationDeployment = - await this._prisma.integrationDeployment.findFirst({ - where: { - deploymentId, - integrationName: "vercel", - }, - select: { - integrationDeploymentId: true, - }, - orderBy: { - createdAt: "desc", - }, - }); + const integrationDeployment = await this._prisma.integrationDeployment.findFirst({ + where: { + deploymentId, + integrationName: "vercel", + }, + select: { + integrationDeploymentId: true, + }, + orderBy: { + createdAt: "desc", + }, + }); if (!integrationDeployment) { return undefined; diff --git a/apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts b/apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts index 647a0b0cf39..b9c1e7ddf90 100644 --- a/apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts +++ b/apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts @@ -68,7 +68,11 @@ export class DeliverErrorGroupAlertService { return; } - const errorLink = this.#buildErrorLink(channel.project.organization, channel.project, payload.error); + const errorLink = this.#buildErrorLink( + channel.project.organization, + channel.project, + payload.error + ); try { switch (channel.type) { @@ -86,7 +90,9 @@ export class DeliverErrorGroupAlertService { } } catch (error) { if (error instanceof SkipRetryError) { - logger.warn("[DeliverErrorGroupAlert] Skipping retry", { reason: (error as Error).message }); + logger.warn("[DeliverErrorGroupAlert] Skipping retry", { + reason: (error as Error).message, + }); return; } throw error; @@ -113,7 +119,11 @@ export class DeliverErrorGroupAlertService { } async #sendEmail( - channel: { type: ProjectAlertChannelType; properties: unknown; project: { name: string; organization: { title: string } } }, + channel: { + type: ProjectAlertChannelType; + properties: unknown; + project: { name: string; organization: { title: string } }; + }, payload: ErrorAlertPayload, errorLink: string ): Promise { @@ -182,11 +192,7 @@ export class DeliverErrorGroupAlertService { return; } - const message = this.#buildErrorGroupSlackMessage( - payload, - errorLink, - channel.project.name - ); + const message = this.#buildErrorGroupSlackMessage(payload, errorLink, channel.project.name); await this.#postSlackMessage(integration, { channel: slackProperties.data.channelId, @@ -198,7 +204,14 @@ export class DeliverErrorGroupAlertService { channel: { type: ProjectAlertChannelType; properties: unknown; - project: { id: string; externalRef: string; slug: string; name: string; organizationId: string; organization: { slug: string; title: string } }; + project: { + id: string; + externalRef: string; + slug: string; + name: string; + organizationId: string; + organization: { slug: string; title: string }; + }; }, payload: ErrorAlertPayload, errorLink: string diff --git a/apps/webapp/app/v3/services/batchTriggerV3.server.ts b/apps/webapp/app/v3/services/batchTriggerV3.server.ts index c001932baad..a9d3a8f2f2f 100644 --- a/apps/webapp/app/v3/services/batchTriggerV3.server.ts +++ b/apps/webapp/app/v3/services/batchTriggerV3.server.ts @@ -335,15 +335,18 @@ export class BatchTriggerV3Service extends BaseService { } // Group items by taskIdentifier - const itemsByTask = body.items.reduce((acc, item) => { - if (!item.options?.idempotencyKey) return acc; + const itemsByTask = body.items.reduce( + (acc, item) => { + if (!item.options?.idempotencyKey) return acc; - if (!acc[item.task]) { - acc[item.task] = []; - } - acc[item.task].push(item); - return acc; - }, {} as Record); + if (!acc[item.task]) { + acc[item.task] = []; + } + acc[item.task].push(item); + return acc; + }, + {} as Record + ); logger.debug("[BatchTriggerV2][call] Grouped items by task identifier", { itemsByTask, @@ -933,7 +936,12 @@ export class BatchTriggerV3Service extends BaseService { const filename = `${pathPrefix}/payload.json`; - const uploadedFilename = await uploadPacketToObjectStore(filename, packet.data, packet.dataType, environment); + const uploadedFilename = await uploadPacketToObjectStore( + filename, + packet.data, + packet.dataType, + environment + ); return { data: uploadedFilename, diff --git a/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts b/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts index 76d550c7008..579f9a42b10 100644 --- a/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts +++ b/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts @@ -38,7 +38,10 @@ export class BulkActionService extends BaseService { const filters = await getFilters(payload, request); // Count the runs that will be affected by the bulk action - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + organizationId, + "standard" + ); const runsRepository = new RunsRepository({ clickhouse, prisma: this._replica as PrismaClient, @@ -148,7 +151,10 @@ export class BulkActionService extends BaseService { ...rawParams, }); - const clickhouse = await clickhouseFactory.getClickhouseForOrganization(group.project.organizationId, "standard"); + const clickhouse = await clickhouseFactory.getClickhouseForOrganization( + group.project.organizationId, + "standard" + ); const runsRepository = new RunsRepository({ clickhouse, prisma: this._replica as PrismaClient, diff --git a/apps/webapp/app/v3/services/completeAttempt.server.ts b/apps/webapp/app/v3/services/completeAttempt.server.ts index 22a9047c3fe..dc51150a8e1 100644 --- a/apps/webapp/app/v3/services/completeAttempt.server.ts +++ b/apps/webapp/app/v3/services/completeAttempt.server.ts @@ -563,7 +563,7 @@ export class CompleteAttemptService extends BaseService { properties: { retryAt: retryAt.toISOString(), previousMachine: oomMachine - ? taskRunAttempt.taskRun.machinePreset ?? undefined + ? (taskRunAttempt.taskRun.machinePreset ?? undefined) : undefined, nextMachine: oomMachine, }, diff --git a/apps/webapp/app/v3/services/computeTemplateCreation.server.ts b/apps/webapp/app/v3/services/computeTemplateCreation.server.ts index 8342291a327..7bc03e9d508 100644 --- a/apps/webapp/app/v3/services/computeTemplateCreation.server.ts +++ b/apps/webapp/app/v3/services/computeTemplateCreation.server.ts @@ -78,10 +78,7 @@ export class ComputeTemplateCreationService { writer?: WritableStreamDefaultWriter; }): Promise { return startActiveSpan("compute.template.create", async (span) => { - const { mode, migrated, reason } = await this.resolveMode( - options.projectId, - options.prisma - ); + const { mode, migrated, reason } = await this.resolveMode(options.projectId, options.prisma); span.setAttributes({ ...attributesFromAuthenticatedEnv(options.authenticatedEnv), diff --git a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts index 64d9786094f..b5b6207a74c 100644 --- a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts +++ b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts @@ -381,7 +381,7 @@ async function createWorkerTask( : ("STANDARD" as const); resolvedTtl = - typeof task.ttl === "number" ? stringifyDuration(task.ttl) ?? null : task.ttl ?? null; + typeof task.ttl === "number" ? (stringifyDuration(task.ttl) ?? null) : (task.ttl ?? null); await prisma.backgroundWorkerTask.create({ data: { diff --git a/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4/findOrCreateBackgroundWorker.server.ts b/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4/findOrCreateBackgroundWorker.server.ts index 7a2dcb06c34..d73ee46edf2 100644 --- a/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4/findOrCreateBackgroundWorker.server.ts +++ b/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4/findOrCreateBackgroundWorker.server.ts @@ -69,9 +69,7 @@ export async function findOrCreateBackgroundWorker( // P2002 — the CLI's retry will then hit `findFirst` and find the winner's row. // Intentionally NOT a ServiceValidationError so the caller doesn't fail-deploy // on a transient race. - if ( - isUniqueConstraintError(error, ["projectId", "runtimeEnvironmentId", "version"]) - ) { + if (isUniqueConstraintError(error, ["projectId", "runtimeEnvironmentId", "version"])) { throw new Error( "Concurrent background worker registration detected for this deployment version; please retry" ); diff --git a/apps/webapp/app/v3/services/createTaskRunAttempt.server.ts b/apps/webapp/app/v3/services/createTaskRunAttempt.server.ts index dbc4c576b75..0c0503b35e0 100644 --- a/apps/webapp/app/v3/services/createTaskRunAttempt.server.ts +++ b/apps/webapp/app/v3/services/createTaskRunAttempt.server.ts @@ -126,8 +126,8 @@ export class CreateTaskRunAttemptService extends BaseService { const nextAttemptNumber = taskRun.attempts[0] ? taskRun.attempts[0].number + 1 : startAtZero - ? 0 - : 1; + ? 0 + : 1; if (nextAttemptNumber > MAX_TASK_RUN_ATTEMPTS) { const service = new CrashTaskRunService(this._prisma); diff --git a/apps/webapp/app/v3/services/promptService.server.ts b/apps/webapp/app/v3/services/promptService.server.ts index ab2729fc235..56e6b8fa662 100644 --- a/apps/webapp/app/v3/services/promptService.server.ts +++ b/apps/webapp/app/v3/services/promptService.server.ts @@ -158,5 +158,4 @@ export class PromptService extends BaseService { WHERE "promptId" = ${promptId} AND ${label} = ANY("labels") `; } - } diff --git a/apps/webapp/app/v3/services/taskRunConcurrencyTracker.server.ts b/apps/webapp/app/v3/services/taskRunConcurrencyTracker.server.ts index 6b9907bbabe..98034761bc0 100644 --- a/apps/webapp/app/v3/services/taskRunConcurrencyTracker.server.ts +++ b/apps/webapp/app/v3/services/taskRunConcurrencyTracker.server.ts @@ -253,10 +253,13 @@ class TaskRunConcurrencyTracker implements MessageQueueSubscriber { 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); + return taskIds.reduce( + (acc, taskId, index) => { + acc[taskId] = counts[index] ?? 0; + return acc; + }, + {} as Record + ); } async environmentConcurrentRunCounts( @@ -273,14 +276,17 @@ class TaskRunConcurrencyTracker implements MessageQueueSubscriber { return Object.fromEntries(environmentIds.map((id) => [id, 0])); } - return results.reduce((acc, [err, count], index) => { - if (err) { - console.error("Error in environmentConcurrentRunCounts:", err); + 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; - } - acc[environmentIds[index]] = count as number; - return acc; - }, {} as Record); + }, + {} as Record + ); } catch (error) { logger.error("TaskRunConcurrencyTracker.environmentConcurrentRunCounts() error", { error }); return Object.fromEntries(environmentIds.map((id) => [id, 0])); diff --git a/apps/webapp/app/v3/services/triggerTaskV1.server.ts b/apps/webapp/app/v3/services/triggerTaskV1.server.ts index a2f0b9de21b..5ccee9e5c5c 100644 --- a/apps/webapp/app/v3/services/triggerTaskV1.server.ts +++ b/apps/webapp/app/v3/services/triggerTaskV1.server.ts @@ -78,7 +78,7 @@ export class TriggerTaskServiceV1 extends BaseService { const ttl = typeof body.options?.ttl === "number" ? stringifyDuration(body.options?.ttl) - : body.options?.ttl ?? (environment.type === "DEVELOPMENT" ? "10m" : undefined); + : (body.options?.ttl ?? (environment.type === "DEVELOPMENT" ? "10m" : undefined)); const existingRun = idempotencyKey ? await this._prisma.taskRun.findFirst({ @@ -352,10 +352,10 @@ export class TriggerTaskServiceV1 extends BaseService { const depth = dependentAttempt ? dependentAttempt.taskRun.depth + 1 : parentAttempt - ? parentAttempt.taskRun.depth + 1 - : dependentBatchRun?.dependentTaskAttempt - ? dependentBatchRun.dependentTaskAttempt.taskRun.depth + 1 - : 0; + ? parentAttempt.taskRun.depth + 1 + : dependentBatchRun?.dependentTaskAttempt + ? dependentBatchRun.dependentTaskAttempt.taskRun.depth + 1 + : 0; const queueTimestamp = options.queueTimestamp ?? @@ -738,7 +738,12 @@ export class TriggerTaskServiceV1 extends BaseService { const filename = `${pathPrefix}/payload.json`; - const uploadedFilename = await uploadPacketToObjectStore(filename, packet.data, packet.dataType, environment); + const uploadedFilename = await uploadPacketToObjectStore( + filename, + packet.data, + packet.dataType, + environment + ); return { data: uploadedFilename, diff --git a/apps/webapp/app/v3/services/worker/workerGroupTokenService.server.ts b/apps/webapp/app/v3/services/worker/workerGroupTokenService.server.ts index 7f52e32d5c3..da1caa866e3 100644 --- a/apps/webapp/app/v3/services/worker/workerGroupTokenService.server.ts +++ b/apps/webapp/app/v3/services/worker/workerGroupTokenService.server.ts @@ -1,4 +1,9 @@ -import { createCache, createLRUMemoryStore, DefaultStatefulContext, Namespace } from "@internal/cache"; +import { + createCache, + createLRUMemoryStore, + DefaultStatefulContext, + Namespace, +} from "@internal/cache"; import { CheckpointInput, CompleteRunAttemptResult, diff --git a/apps/webapp/app/v3/taskEventStore.server.ts b/apps/webapp/app/v3/taskEventStore.server.ts index ed580b40d0e..093f127132f 100644 --- a/apps/webapp/app/v3/taskEventStore.server.ts +++ b/apps/webapp/app/v3/taskEventStore.server.ts @@ -56,7 +56,10 @@ export function getTaskEventStore(): TaskEventStoreTable { } export class TaskEventStore { - constructor(private db: PrismaClient, private readReplica: PrismaReplicaClient) {} + constructor( + private db: PrismaClient, + private readReplica: PrismaReplicaClient + ) {} /** * Insert one record. diff --git a/apps/webapp/app/v3/utils/enrichCreatableEvents.server.ts b/apps/webapp/app/v3/utils/enrichCreatableEvents.server.ts index 024ac75a940..9173038db02 100644 --- a/apps/webapp/app/v3/utils/enrichCreatableEvents.server.ts +++ b/apps/webapp/app/v3/utils/enrichCreatableEvents.server.ts @@ -163,19 +163,22 @@ function enrichLlmMetrics(event: CreateEventInput): void { // v6 emits ai.response.finishReason (plain string); v7 (@ai-sdk/otel) emits // gen_ai.response.finish_reasons as a JSON array string (e.g. `["stop"]`). const finishReason = readFinishReason(props); - const operationId = typeof props["ai.operationId"] === "string" - ? props["ai.operationId"] - : typeof props["gen_ai.operation.name"] === "string" - ? props["gen_ai.operation.name"] - : typeof props["operation.name"] === "string" - ? props["operation.name"] - : ""; - const msToFirstChunk = typeof props["ai.response.msToFirstChunk"] === "number" - ? props["ai.response.msToFirstChunk"] - : 0; - const avgTokensPerSec = typeof props["ai.response.avgOutputTokensPerSecond"] === "number" - ? props["ai.response.avgOutputTokensPerSecond"] - : 0; + const operationId = + typeof props["ai.operationId"] === "string" + ? props["ai.operationId"] + : typeof props["gen_ai.operation.name"] === "string" + ? props["gen_ai.operation.name"] + : typeof props["operation.name"] === "string" + ? props["operation.name"] + : ""; + const msToFirstChunk = + typeof props["ai.response.msToFirstChunk"] === "number" + ? props["ai.response.msToFirstChunk"] + : 0; + const avgTokensPerSec = + typeof props["ai.response.avgOutputTokensPerSecond"] === "number" + ? props["ai.response.avgOutputTokensPerSecond"] + : 0; const costSource = cost ? "registry" : providerCost ? providerCost.source : ""; const providerCostValue = providerCost?.totalCost ?? 0; @@ -187,7 +190,10 @@ function enrichLlmMetrics(event: CreateEventInput): void { : typeof props["gen_ai.provider.name"] === "string" ? props["gen_ai.provider.name"] : "unknown", - requestModel: typeof props["gen_ai.request.model"] === "string" ? props["gen_ai.request.model"] : responseModel, + requestModel: + typeof props["gen_ai.request.model"] === "string" + ? props["gen_ai.request.model"] + : responseModel, responseModel, baseResponseModel: modelCatalog[responseModel]?.baseModelName ?? responseModel, matchedModelId: cost?.matchedModelId ?? "", @@ -195,10 +201,12 @@ function enrichLlmMetrics(event: CreateEventInput): void { finishReason, costSource, pricingTierId: cost?.pricingTierId ?? (providerCost ? `provider:${providerCost.source}` : ""), - pricingTierName: cost?.pricingTierName ?? (providerCost ? `${providerCost.source} reported` : ""), + pricingTierName: + cost?.pricingTierName ?? (providerCost ? `${providerCost.source} reported` : ""), inputTokens: usageDetails["input"] ?? 0, outputTokens: usageDetails["output"] ?? 0, - totalTokens: usageDetails["total"] ?? (usageDetails["input"] ?? 0) + (usageDetails["output"] ?? 0), + totalTokens: + usageDetails["total"] ?? (usageDetails["input"] ?? 0) + (usageDetails["output"] ?? 0), usageDetails, inputCost: cost?.inputCost ?? 0, outputCost: cost?.outputCost ?? 0, diff --git a/apps/webapp/app/v3/utils/zodPubSub.server.ts b/apps/webapp/app/v3/utils/zodPubSub.server.ts index 2c081bc368d..264be04894d 100644 --- a/apps/webapp/app/v3/utils/zodPubSub.server.ts +++ b/apps/webapp/app/v3/utils/zodPubSub.server.ts @@ -20,9 +20,9 @@ export interface ZodSubscriber stopListening(): Promise; } -class RedisZodSubscriber - implements ZodSubscriber -{ +class RedisZodSubscriber< + TMessageCatalog extends ZodMessageCatalogSchema, +> implements ZodSubscriber { private _subscriber: RedisClient; private _listeners: Map Promise> = new Map(); private _messageHandler: ZodMessageHandler; diff --git a/apps/webapp/app/v3/vercel/index.ts b/apps/webapp/app/v3/vercel/index.ts index f34f0b64c6b..39be3f97d12 100644 --- a/apps/webapp/app/v3/vercel/index.ts +++ b/apps/webapp/app/v3/vercel/index.ts @@ -13,5 +13,3 @@ export function getVercelInstallParams(request: Request) { return null; } - - diff --git a/apps/webapp/app/v3/vercel/vercelOAuthState.server.ts b/apps/webapp/app/v3/vercel/vercelOAuthState.server.ts index 31f42acc879..e6bfbb0362b 100644 --- a/apps/webapp/app/v3/vercel/vercelOAuthState.server.ts +++ b/apps/webapp/app/v3/vercel/vercelOAuthState.server.ts @@ -12,9 +12,7 @@ export const VercelOAuthStateSchema = z.object({ export type VercelOAuthState = z.infer; -export async function generateVercelOAuthState( - params: VercelOAuthState -): Promise { +export async function generateVercelOAuthState(params: VercelOAuthState): Promise { return generateJWT({ secretKey: env.ENCRYPTION_KEY, payload: params, diff --git a/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts b/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts index 95cff1a480e..1399b87fc2b 100644 --- a/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts +++ b/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts @@ -15,38 +15,47 @@ const safeJsonParse = Result.fromThrowable( * Zod transform for form fields that submit JSON-encoded arrays. * Parses the string as JSON and returns the array, or null if invalid. */ -export const jsonArrayField = z.string().optional().transform((val) => { - if (!val) return null; - return safeJsonParse(val).match( - (parsed) => (Array.isArray(parsed) ? parsed : null), - () => null - ); -}); +export const jsonArrayField = z + .string() + .optional() + .transform((val) => { + if (!val) return null; + return safeJsonParse(val).match( + (parsed) => (Array.isArray(parsed) ? parsed : null), + () => null + ); + }); /** * Zod transform for form fields that submit JSON-encoded EnvSlug arrays. * Parses the string as JSON and validates each element is a valid EnvSlug. * Invalid elements are filtered out rather than rejecting the whole array. */ -export const envSlugArrayField = z.string().optional().transform((val): EnvSlug[] | null => { - if (!val) return null; - return safeJsonParse(val).match( - (parsed) => { - if (!Array.isArray(parsed)) return null; - return parsed.filter((item): item is EnvSlug => EnvSlugSchema.safeParse(item).success); - }, - () => null - ); -}); +export const envSlugArrayField = z + .string() + .optional() + .transform((val): EnvSlug[] | null => { + if (!val) return null; + return safeJsonParse(val).match( + (parsed) => { + if (!Array.isArray(parsed)) return null; + return parsed.filter((item): item is EnvSlug => EnvSlugSchema.safeParse(item).success); + }, + () => null + ); + }); export const VercelIntegrationConfigSchema = z.object({ atomicBuilds: z.array(EnvSlugSchema).nullable().optional(), pullEnvVarsBeforeBuild: z.array(EnvSlugSchema).nullable().optional(), /** Maps a custom Vercel environment to Trigger.dev's staging environment. */ - vercelStagingEnvironment: z.object({ - environmentId: z.string(), - displayName: z.string(), - }).nullable().optional(), + vercelStagingEnvironment: z + .object({ + environmentId: z.string(), + displayName: z.string(), + }) + .nullable() + .optional(), discoverEnvVars: z.array(EnvSlugSchema).nullable().optional(), autoPromote: z.boolean().optional().default(true), }); @@ -61,7 +70,9 @@ export type TriggerEnvironmentType = z.infer; * Missing env slug = sync all vars. Missing var in env = sync by default. * Only explicitly `false` entries disable sync. */ -export const SyncEnvVarsMappingSchema = z.record(EnvSlugSchema, z.record(z.string(), z.boolean())).default({}); +export const SyncEnvVarsMappingSchema = z + .record(EnvSlugSchema, z.record(z.string(), z.boolean())) + .default({}); export type SyncEnvVarsMapping = z.infer; @@ -135,7 +146,9 @@ export function getAvailableEnvSlugsForBuildSettings( hasStagingEnvironment: boolean, hasPreviewEnvironment: boolean ): EnvSlug[] { - return getAvailableEnvSlugs(hasStagingEnvironment, hasPreviewEnvironment).filter((s) => s !== "dev"); + return getAvailableEnvSlugs(hasStagingEnvironment, hasPreviewEnvironment).filter( + (s) => s !== "dev" + ); } export function isDiscoverEnvVarsEnabledForEnvironment( diff --git a/apps/webapp/app/v3/vercel/vercelUrls.server.ts b/apps/webapp/app/v3/vercel/vercelUrls.server.ts index 957e0d2907b..9656148a116 100644 --- a/apps/webapp/app/v3/vercel/vercelUrls.server.ts +++ b/apps/webapp/app/v3/vercel/vercelUrls.server.ts @@ -12,10 +12,7 @@ export function sanitizeVercelNextUrl(url: string | undefined | null): string | try { const parsed = new URL(url); - if ( - parsed.protocol === "https:" && - /^([a-z0-9-]+\.)*vercel\.com$/i.test(parsed.hostname) - ) { + if (parsed.protocol === "https:" && /^([a-z0-9-]+\.)*vercel\.com$/i.test(parsed.hostname)) { return parsed.toString(); } } catch { diff --git a/apps/webapp/app/v3/workerRegions.server.ts b/apps/webapp/app/v3/workerRegions.server.ts index 5e86f1dd464..f682868e558 100644 --- a/apps/webapp/app/v3/workerRegions.server.ts +++ b/apps/webapp/app/v3/workerRegions.server.ts @@ -39,10 +39,7 @@ export function backingForQueue( if (!region) return undefined; const backing = groups.find( (g) => - g.workloadType === "MICROVM" && - g.region === region && - !g.hidden && - g.masterQueue !== queue + g.workloadType === "MICROVM" && g.region === region && !g.hidden && g.masterQueue !== queue ); if (!backing) return undefined; return { workerQueue: backing.masterQueue, enableFastPath: backing.enableFastPath }; diff --git a/apps/webapp/evals/aiRunFilter.eval.ts b/apps/webapp/evals/aiRunFilter.eval.ts index 28c49eceb13..265e42da249 100644 --- a/apps/webapp/evals/aiRunFilter.eval.ts +++ b/apps/webapp/evals/aiRunFilter.eval.ts @@ -232,8 +232,12 @@ evalite("AI Run Filter", { expected: JSON.stringify({ success: true, filters: { - from: new Date(new Date(Date.now() - 24*60*60*1000).toDateString() + " 14:00:00").getTime(), - to: new Date(new Date(Date.now() - 24*60*60*1000).toDateString() + " 14:59:59").getTime(), + from: new Date( + new Date(Date.now() - 24 * 60 * 60 * 1000).toDateString() + " 14:00:00" + ).getTime(), + to: new Date( + new Date(Date.now() - 24 * 60 * 60 * 1000).toDateString() + " 14:59:59" + ).getTime(), }, }), }, diff --git a/apps/webapp/seed-ai-spans.mts b/apps/webapp/seed-ai-spans.mts index 6ada9fb3285..5297171f955 100644 --- a/apps/webapp/seed-ai-spans.mts +++ b/apps/webapp/seed-ai-spans.mts @@ -3,10 +3,7 @@ import { createOrganization } from "./app/models/organization.server"; import { createProject } from "./app/models/project.server"; import { ClickHouse } from "@internal/clickhouse"; import type { TaskEventV2Input, LlmMetricsV1Input } from "@internal/clickhouse"; -import { - generateTraceId, - generateSpanId, -} from "./app/v3/eventRepository/common.server"; +import { generateTraceId, generateSpanId } from "./app/v3/eventRepository/common.server"; import { enrichCreatableEvents, setLlmPricingRegistry, @@ -24,9 +21,18 @@ const QUEUE_NAME = "task/ai-chat"; const WORKER_VERSION = "seed-ai-spans-v1"; const SEED_USER_IDS = [ - "user_alice", "user_bob", "user_carol", "user_dave", - "user_eve", "user_frank", "user_grace", "user_heidi", - "user_ivan", "user_judy", "user_karl", "user_liam", + "user_alice", + "user_bob", + "user_carol", + "user_dave", + "user_eve", + "user_frank", + "user_grace", + "user_heidi", + "user_ivan", + "user_judy", + "user_karl", + "user_liam", ]; function randomUserId(): string { @@ -80,8 +86,7 @@ function eventToClickhouseRow(event: CreateEventInput): TaskEventV2Input { // attributes const publicAttrs = removePrivateProperties(event.properties as Attributes); const unflattened = publicAttrs ? unflattenAttributes(publicAttrs) : {}; - const attributes = - unflattened && typeof unflattened === "object" ? { ...unflattened } : {}; + const attributes = unflattened && typeof unflattened === "object" ? { ...unflattened } : {}; // metadata — mirrors createEventToTaskEventV1InputMetadata const metadataObj: Record = {}; @@ -118,9 +123,7 @@ function eventToClickhouseRow(event: CreateEventInput): TaskEventV2Input { status, attributes, metadata, - expires_at: formatClickhouseDateTime( - new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) - ), + expires_at: formatClickhouseDateTime(new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)), machine_id: "", }; } @@ -386,9 +389,7 @@ Please structure your response with clear headings, use tables for comparative d const enrichedCount = enriched.filter((e) => e._llmMetrics != null).length; const totalCost = enriched.reduce((sum, e) => sum + (e._llmMetrics?.totalCost ?? 0), 0); - console.log( - `Enriched ${enrichedCount} spans with LLM cost (total: $${totalCost.toFixed(6)})` - ); + console.log(`Enriched ${enrichedCount} spans with LLM cost (total: $${totalCost.toFixed(6)})`); // 11. Insert into ClickHouse const clickhouseUrl = process.env.CLICKHOUSE_URL ?? process.env.EVENTS_CLICKHOUSE_URL; @@ -844,8 +845,7 @@ Cut (-25 bps): 10.7% "ai.response.finishReason": "tool-calls", "ai.response.id": "msg_seed_003", "ai.response.model": "claude-haiku-4-5-20251001", - "ai.response.text": - "I'll search for the latest Federal Reserve interest rate information.", + "ai.response.text": "I'll search for the latest Federal Reserve interest rate information.", "ai.response.toolCalls": JSON.stringify([ { toolCallId: "toolu_seed_001", @@ -1099,7 +1099,15 @@ Cut (-25 bps): 10.7% } } - events.push(makeEvent({ message: opts.wrapperMsg, spanId: wId, parentId: runFnId, ...wrap, properties: wrapperProps })); + events.push( + makeEvent({ + message: opts.wrapperMsg, + spanId: wId, + parentId: runFnId, + ...wrap, + properties: wrapperProps, + }) + ); cursor = wrap.startMs + 50; const doTiming = next(opts.doDurationMs); @@ -1159,29 +1167,45 @@ Cut (-25 bps): 10.7% } if (opts.extraDoProps) Object.assign(doProps, opts.extraDoProps); - events.push(makeEvent({ message: opts.doMsg, spanId: dId, parentId: wId, ...doTiming, properties: doProps })); + events.push( + makeEvent({ + message: opts.doMsg, + spanId: dId, + parentId: wId, + ...doTiming, + properties: doProps, + }) + ); return { wrapperId: wId, doId: dId }; } // Helper: add a tool call span - function addToolCall(parentId: string, name: string, args: string, result: string, durationMs = 500) { + function addToolCall( + parentId: string, + name: string, + args: string, + result: string, + durationMs = 500 + ) { const id = generateSpanId(); const timing = next(durationMs); - events.push(makeEvent({ - message: "ai.toolCall", - spanId: id, - parentId, - ...timing, - properties: { - "ai.operationId": "ai.toolCall", - "ai.toolCall.name": name, - "ai.toolCall.id": `call_${generateSpanId().slice(0, 8)}`, - "ai.toolCall.args": args, - "ai.toolCall.result": result, - "operation.name": "ai.toolCall", - }, - })); + events.push( + makeEvent({ + message: "ai.toolCall", + spanId: id, + parentId, + ...timing, + properties: { + "ai.operationId": "ai.toolCall", + "ai.toolCall.name": name, + "ai.toolCall.id": `call_${generateSpanId().slice(0, 8)}`, + "ai.toolCall.args": args, + "ai.toolCall.result": result, + "operation.name": "ai.toolCall", + }, + }) + ); return id; } @@ -1253,7 +1277,8 @@ The document primarily discusses **quarterly earnings guidance** for the technol canonicalSlug: "openai/gpt-5-mini", finalProvider: "openai", fallbacksAvailable: ["azure"], - planningReasoning: "System credentials planned for: openai, azure. Total execution order: openai(system) → azure(system)", + planningReasoning: + "System credentials planned for: openai, azure. Total execution order: openai(system) → azure(system)", modelAttemptCount: 1, }, cost: "0.000482", @@ -1293,7 +1318,12 @@ The document primarily discusses **quarterly earnings guidance** for the technol }, }, }); - addToolCall(ds.wrapperId, "classifyContent", '{"text":"Federal Reserve rate analysis"}', '{"category":"finance","confidence":0.98}'); + addToolCall( + ds.wrapperId, + "classifyContent", + '{"text":"Federal Reserve rate analysis"}', + '{"category":"finance","confidence":0.98}' + ); // ===================================================================== // 8) Gateway → Anthropic claude-haiku via gateway prefix @@ -1354,7 +1384,10 @@ The document primarily discusses **quarterly earnings guidance** for the technol finishReason: "stop", wrapperDurationMs: 1_200, doDurationMs: 1_000, - responseObject: JSON.stringify({ sentiment: "neutral", topics: ["monetary_policy", "interest_rates"] }), + responseObject: JSON.stringify({ + sentiment: "neutral", + topics: ["monetary_policy", "interest_rates"], + }), useCompletionStyle: true, providerMetadata: { gateway: { @@ -1495,16 +1528,23 @@ The next decision point will hinge on incoming data, particularly: wrapperDurationMs: 3_500, doDurationMs: 3_000, responseText: "Let me look up the latest rate decision.", - toolCallsJson: JSON.stringify([{ - toolCallId: "call_azure_001", - toolName: "lookupRate", - input: '{"source":"federal_reserve","metric":"funds_rate"}', - }]), + toolCallsJson: JSON.stringify([ + { + toolCallId: "call_azure_001", + toolName: "lookupRate", + input: '{"source":"federal_reserve","metric":"funds_rate"}', + }, + ]), providerMetadata: { azure: { responseId: "resp_seed_azure_001", serviceTier: "default" }, }, }); - addToolCall(az.wrapperId, "lookupRate", '{"source":"federal_reserve","metric":"funds_rate"}', '{"rate":"4.25-4.50%","effectiveDate":"2024-12-18"}'); + addToolCall( + az.wrapperId, + "lookupRate", + '{"source":"federal_reserve","metric":"funds_rate"}', + '{"rate":"4.25-4.50%","effectiveDate":"2024-12-18"}' + ); // ===================================================================== // 14) Perplexity → sonar-pro @@ -1605,7 +1645,8 @@ The FOMC has cited three primary factors: ### Technical Note The effective federal funds rate (\`EFFR\`) currently sits at **4.33%**, near the midpoint of the target range. The overnight reverse repo facility (ON RRP) rate is set at **4.25%**.`, - responseReasoning: "The user is asking about the current Federal Reserve interest rate. Let me provide a comprehensive answer based on the most recent FOMC decision. I should include context about the rate trajectory and forward guidance. I'll structure this with a table showing the recent rate changes and explain the pause rationale.", + responseReasoning: + "The user is asking about the current Federal Reserve interest rate. Let me provide a comprehensive answer based on the most recent FOMC decision. I should include context about the rate trajectory and forward guidance. I'll structure this with a table showing the recent rate changes and explain the pause rationale.", cacheReadTokens: 12400, cacheCreationTokens: 2800, providerMetadata: { @@ -1637,11 +1678,13 @@ The effective federal funds rate (\`EFFR\`) currently sits at **4.33%**, near th wrapperDurationMs: 6_000, doDurationMs: 5_500, responseText: "I'll search for the latest FOMC decision and rate information.", - toolCallsJson: JSON.stringify([{ - toolCallId: "call_vertex_001", - toolName: "searchFOMC", - input: '{"query":"latest FOMC decision december 2024"}', - }]), + toolCallsJson: JSON.stringify([ + { + toolCallId: "call_vertex_001", + toolName: "searchFOMC", + input: '{"query":"latest FOMC decision december 2024"}', + }, + ]), providerMetadata: { google: { usageMetadata: { @@ -1653,7 +1696,13 @@ The effective federal funds rate (\`EFFR\`) currently sits at **4.33%**, near th }, }, }); - addToolCall(vt.wrapperId, "searchFOMC", '{"query":"latest FOMC decision december 2024"}', '{"decision":"hold","rate":"4.25-4.50%","date":"2024-12-18","vote":"unanimous"}', 800); + addToolCall( + vt.wrapperId, + "searchFOMC", + '{"query":"latest FOMC decision december 2024"}', + '{"decision":"hold","rate":"4.25-4.50%","date":"2024-12-18","vote":"unanimous"}', + 800 + ); // ===================================================================== // 18) openai.responses → gpt-5.4 with reasoning tokens @@ -1704,7 +1753,8 @@ rates = { | Global growth slowdown | ↓ Downside | Medium | > *"The committee remains attentive to the risks to both sides of its dual mandate."* — Chair Powell, Dec 18 press conference`, - responseReasoning: "I need to provide accurate, up-to-date information about the Federal Reserve interest rate. The last FOMC meeting was in December 2024 where they cut rates by 25 bps after two previous cuts. Let me include the dot plot projections and a risk assessment table for a comprehensive view.", + responseReasoning: + "I need to provide accurate, up-to-date information about the Federal Reserve interest rate. The last FOMC meeting was in December 2024 where they cut rates by 25 bps after two previous cuts. Let me include the dot plot projections and a risk assessment table for a comprehensive view.", reasoningTokens: 516, providerMetadata: { openai: { diff --git a/apps/webapp/seed.mts b/apps/webapp/seed.mts index 2571f8679d7..7e67e616e94 100644 --- a/apps/webapp/seed.mts +++ b/apps/webapp/seed.mts @@ -294,9 +294,7 @@ async function ensureDefaultWorkerGroup() { console.log(`✅ Created worker instance group: ${workerGroup.name} (${workerGroup.id})`); } else { - console.log( - `✅ Worker instance group already exists: ${workerGroup.name} (${workerGroup.id})` - ); + console.log(`✅ Worker instance group already exists: ${workerGroup.name} (${workerGroup.id})`); } // Set the feature flag diff --git a/apps/webapp/test/EnvironmentVariablesPresenter.test.ts b/apps/webapp/test/EnvironmentVariablesPresenter.test.ts index b5172bd5948..ba4daea89d8 100644 --- a/apps/webapp/test/EnvironmentVariablesPresenter.test.ts +++ b/apps/webapp/test/EnvironmentVariablesPresenter.test.ts @@ -11,9 +11,7 @@ vi.mock("~/db.server", () => ({ fnOrOptions?: ((tx: unknown) => Promise) | unknown ) => { const fn = - typeof nameOrFn === "string" - ? (fnOrOptions as (tx: unknown) => Promise) - : nameOrFn; + typeof nameOrFn === "string" ? (fnOrOptions as (tx: unknown) => Promise) : nameOrFn; return prismaClient.$transaction(fn); }, @@ -31,44 +29,52 @@ import { vi.setConfig({ testTimeout: 60_000 }); describe("EnvironmentVariablesPresenter", () => { - postgresTest("keeps secret values redacted while returning non-secret values", async ({ prisma }) => { - const { user, organization, project, projectSlug } = await createTestOrgProjectWithMember(prisma); - const production = await createRuntimeEnvironment(prisma, { - projectId: project.id, - organizationId: organization.id, - type: "PRODUCTION", - }); - - const repository = new EnvironmentVariablesRepository(prisma, prisma); - - await createEnvironmentVariable(repository, project.id, { - environmentId: production.id, - key: "SECRET_VAR", - value: "super-secret", - isSecret: true, - userId: user.id, - }); - await createEnvironmentVariable(repository, project.id, { - environmentId: production.id, - key: "PLAIN_VAR", - value: "plain-value", - isSecret: false, - userId: user.id, - }); - - const result = await new EnvironmentVariablesPresenter(prisma, prisma).call({ - userId: user.id, - projectSlug, - }); - - const secretVariable = result.environmentVariables.find((variable) => variable.key === "SECRET_VAR"); - const nonSecretVariable = result.environmentVariables.find((variable) => variable.key === "PLAIN_VAR"); - - expect(secretVariable).toBeDefined(); - expect(nonSecretVariable).toBeDefined(); - expect(secretVariable!.value).toBe(""); - expect(nonSecretVariable!.value).toBe("plain-value"); - }); + postgresTest( + "keeps secret values redacted while returning non-secret values", + async ({ prisma }) => { + const { user, organization, project, projectSlug } = + await createTestOrgProjectWithMember(prisma); + const production = await createRuntimeEnvironment(prisma, { + projectId: project.id, + organizationId: organization.id, + type: "PRODUCTION", + }); + + const repository = new EnvironmentVariablesRepository(prisma, prisma); + + await createEnvironmentVariable(repository, project.id, { + environmentId: production.id, + key: "SECRET_VAR", + value: "super-secret", + isSecret: true, + userId: user.id, + }); + await createEnvironmentVariable(repository, project.id, { + environmentId: production.id, + key: "PLAIN_VAR", + value: "plain-value", + isSecret: false, + userId: user.id, + }); + + const result = await new EnvironmentVariablesPresenter(prisma, prisma).call({ + userId: user.id, + projectSlug, + }); + + const secretVariable = result.environmentVariables.find( + (variable) => variable.key === "SECRET_VAR" + ); + const nonSecretVariable = result.environmentVariables.find( + (variable) => variable.key === "PLAIN_VAR" + ); + + expect(secretVariable).toBeDefined(); + expect(nonSecretVariable).toBeDefined(); + expect(secretVariable!.value).toBe(""); + expect(nonSecretVariable!.value).toBe("plain-value"); + } + ); postgresTest( "returns values for active environments (including branch environments) and excludes archived branch environments", diff --git a/apps/webapp/test/api-auth.e2e.test.ts b/apps/webapp/test/api-auth.e2e.test.ts index 31e365d6d40..9e0cc2b55be 100644 --- a/apps/webapp/test/api-auth.e2e.test.ts +++ b/apps/webapp/test/api-auth.e2e.test.ts @@ -177,7 +177,6 @@ describe("API bearer auth — action requests", () => { }); expect(res.status).toBe(401); }); - }); describe("JWT bearer auth — action requests", () => { @@ -353,7 +352,7 @@ describe("JWT bearer auth — resource-scoped scopes", () => { // - multi-key resource callbacks (runs/tags/batch/tasks) — any key match grants access // - empty resource callbacks relying on superScopes describe("JWT bearer auth — behaviours to preserve through TRI-8719", () => { - it("custom action: type-level write:tasks scope satisfies action=\"trigger\" (auth passes)", async () => { + it('custom action: type-level write:tasks scope satisfies action="trigger" (auth passes)', async () => { const { environment } = await seedTestEnvironment(server.prisma); // Current SDK + MCP JWTs for task-trigger use type-level scope, e.g. write:tasks. // Legacy checkAuthorization passes via exact superScope match ["write:tasks", "admin"]. diff --git a/apps/webapp/test/auth-api.e2e.full.test.ts b/apps/webapp/test/auth-api.e2e.full.test.ts index e66cb1e8072..279cbeb98fc 100644 --- a/apps/webapp/test/auth-api.e2e.full.test.ts +++ b/apps/webapp/test/auth-api.e2e.full.test.ts @@ -160,8 +160,7 @@ describe("API", () => { // Plus the equivalent full matrix for input-streams which the smoke // matrix doesn't touch. describe("Resource-scoped writes — waitpoints (gap-fill)", () => { - const pathFor = (friendlyId: string) => - `/api/v1/waitpoints/tokens/${friendlyId}/complete`; + const pathFor = (friendlyId: string) => `/api/v1/waitpoints/tokens/${friendlyId}/complete`; const completeRequest = (path: string, headers: Record) => getTestServer().webapp.fetch(path, { method: "POST", @@ -941,10 +940,9 @@ describe("API", () => { }, expirationTime: "15m", }); - const res = await get( - "?filter%5BtaskIdentifier%5D=task_a%2Ctask_b", - { Authorization: `Bearer ${jwt}` } - ); + const res = await get("?filter%5BtaskIdentifier%5D=task_a%2Ctask_b", { + Authorization: `Bearer ${jwt}`, + }); // Resource array is [{type:"runs"}, {type:"tasks",id:"task_a"}, {type:"tasks",id:"task_b"}]. // The scope read:tasks:task_a matches the second element → access granted. // Handler may 500 (ClickHouse unreachable in tests) but auth passed. @@ -964,10 +962,9 @@ describe("API", () => { }, expirationTime: "15m", }); - const res = await get( - "?filter%5BtaskIdentifier%5D=task_a", - { Authorization: `Bearer ${jwt}` } - ); + const res = await get("?filter%5BtaskIdentifier%5D=task_a", { + Authorization: `Bearer ${jwt}`, + }); // Resource is [{runs}, {tasks:task_a}]. JWT scope says // read:tasks:task_z which doesn't match the runs collection // (wrong type) or the task_a element (wrong id). 403. @@ -1767,10 +1764,9 @@ describe("API", () => { payload: { pub: true, sub: seed.environment.id, scopes: ["read:batch"] }, expirationTime: "15m", }); - const res = await getTestServer().webapp.fetch( - `/api/v2/batches/${seeded.batchFriendlyId}`, - { headers: { Authorization: `Bearer ${jwt}` } } - ); + const res = await getTestServer().webapp.fetch(`/api/v2/batches/${seeded.batchFriendlyId}`, { + headers: { Authorization: `Bearer ${jwt}` }, + }); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); }); @@ -2064,14 +2060,11 @@ describe("API", () => { }); // Body must satisfy the route's schema ({ version: positive int }) // — otherwise body validation 400s before authorization runs. - const res = await server.webapp.fetch( - "/api/v1/prompts/some-slug/override/reactivate", - { - method: "POST", - headers: { "Content-Type": "application/json", Authorization: `Bearer ${jwt}` }, - body: JSON.stringify({ version: 1 }), - } - ); + const res = await server.webapp.fetch("/api/v1/prompts/some-slug/override/reactivate", { + method: "POST", + headers: { "Content-Type": "application/json", Authorization: `Bearer ${jwt}` }, + body: JSON.stringify({ version: 1 }), + }); expect(res.status).toBe(403); }); }); @@ -2252,9 +2245,12 @@ describe("API", () => { payload: { pub: true, sub: seed.environment.id, scopes: ["read:query:runs"] }, expirationTime: "15m", }); - const res = await post({ query: "SELECT * FROM runs" }, { - Authorization: `Bearer ${jwt}`, - }); + const res = await post( + { query: "SELECT * FROM runs" }, + { + Authorization: `Bearer ${jwt}`, + } + ); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); }); @@ -2290,8 +2286,7 @@ describe("API", () => { }); const res = await post( { - query: - "SELECT count() FROM runs UNION ALL SELECT count() FROM metrics", + query: "SELECT count() FROM runs UNION ALL SELECT count() FROM metrics", }, { Authorization: `Bearer ${jwt}` } ); @@ -2314,8 +2309,7 @@ describe("API", () => { }); const res = await post( { - query: - "SELECT count() FROM runs UNION ALL SELECT count() FROM metrics", + query: "SELECT count() FROM runs UNION ALL SELECT count() FROM metrics", }, { Authorization: `Bearer ${jwt}` } ); @@ -2335,9 +2329,12 @@ describe("API", () => { }, expirationTime: "15m", }); - const res = await post({ query: "SELECT * FROM runs" }, { - Authorization: `Bearer ${jwt}`, - }); + const res = await post( + { query: "SELECT * FROM runs" }, + { + Authorization: `Bearer ${jwt}`, + } + ); expect(res.status).toBe(403); }); @@ -2349,9 +2346,12 @@ describe("API", () => { payload: { pub: true, sub: seed.environment.id, scopes: ["admin"] }, expirationTime: "15m", }); - const res = await post({ query: "SELECT * FROM runs" }, { - Authorization: `Bearer ${jwt}`, - }); + const res = await post( + { query: "SELECT * FROM runs" }, + { + Authorization: `Bearer ${jwt}`, + } + ); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); }); @@ -2422,9 +2422,7 @@ describe("API", () => { // Old superScopes: ["read:sessions", "read:all", "admin"] describe("List sessions — GET /api/v1/sessions", () => { const path = (taskFilter?: string) => - taskFilter - ? `/api/v1/sessions?filter[taskIdentifier]=${taskFilter}` - : "/api/v1/sessions"; + taskFilter ? `/api/v1/sessions?filter[taskIdentifier]=${taskFilter}` : "/api/v1/sessions"; const fetchWithJwt = async (jwt: string, taskFilter?: string) => getTestServer().webapp.fetch(path(taskFilter), { @@ -2445,9 +2443,7 @@ describe("API", () => { it("read:tasks:foo on filter=foo: auth passes", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:tasks:foo", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:tasks:foo"]); const res = await fetchWithJwt(jwt, "foo"); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); @@ -2455,18 +2451,14 @@ describe("API", () => { it("read:tasks:bar on filter=foo: 403 (per-task narrowing)", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:tasks:bar", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:tasks:bar"]); const res = await fetchWithJwt(jwt, "foo"); expect(res.status).toBe(403); }); it("read:sessions on filter=foo: auth passes (was a superScope)", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:sessions"]); const res = await fetchWithJwt(jwt, "foo"); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); @@ -2474,9 +2466,7 @@ describe("API", () => { it("read:sessions on no-filter list: auth passes", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:sessions"]); const res = await fetchWithJwt(jwt); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); @@ -2502,18 +2492,14 @@ describe("API", () => { // No filter → resource is `{ type: "sessions" }` only. read:tasks // doesn't match the sessions type, so 403 — explicit narrowing. const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:tasks", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:tasks"]); const res = await fetchWithJwt(jwt); expect(res.status).toBe(403); }); it("write:tasks:foo (wrong action) on filter=foo: 403", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:tasks:foo", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:tasks:foo"]); const res = await fetchWithJwt(jwt, "foo"); expect(res.status).toBe(403); }); @@ -2549,9 +2535,7 @@ describe("API", () => { it("write:tasks:foo matching body: auth passes", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:tasks:foo", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:tasks:foo"]); const res = await post(jwt, "foo"); // Body validation / handler can fail later (404 if task is // missing, 400 for invalid body) — we only care that auth @@ -2562,18 +2546,14 @@ describe("API", () => { it("write:tasks:bar mismatching body: 403", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:tasks:bar", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:tasks:bar"]); const res = await post(jwt, "foo"); expect(res.status).toBe(403); }); it("write:sessions: auth passes (was a superScope)", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:sessions"]); const res = await post(jwt, "foo"); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); @@ -2597,9 +2577,7 @@ describe("API", () => { it("read:tasks:foo (wrong action): 403", async () => { const seed = await seedTestEnvironment(getTestServer().prisma); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:tasks:foo", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:tasks:foo"]); const res = await post(jwt, "foo"); expect(res.status).toBe(403); }); @@ -2649,9 +2627,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:sessions"]); const res = await get(session.friendlyId, jwt); expect(res.status).toBe(200); }); @@ -2705,9 +2681,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "admin:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["admin:sessions"]); const res = await patch(session.friendlyId, jwt); expect(res.status).toBe(200); }); @@ -2734,9 +2708,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:sessions"]); const res = await patch(session.friendlyId, jwt); expect(res.status).toBe(403); }); @@ -2747,17 +2719,14 @@ describe("API", () => { // action: "admin" — same matrix as PATCH. describe("Close session — POST /api/v1/sessions/:session/close", () => { const close = async (sessionParam: string, jwt: string) => - getTestServer().webapp.fetch( - `/api/v1/sessions/${sessionParam}/close`, - { - method: "POST", - headers: { - Authorization: `Bearer ${jwt}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ reason: "test" }), - } - ); + getTestServer().webapp.fetch(`/api/v1/sessions/${sessionParam}/close`, { + method: "POST", + headers: { + Authorization: `Bearer ${jwt}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ reason: "test" }), + }); const mintJwt = async (apiKey: string, envId: string, scopes: string[]) => generateJWT({ @@ -2770,9 +2739,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "admin:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["admin:sessions"]); const res = await close(session.friendlyId, jwt); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); @@ -2792,9 +2759,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:sessions"]); const res = await close(session.friendlyId, jwt); expect(res.status).toBe(403); }); @@ -2805,22 +2770,19 @@ describe("API", () => { // action: "write" — multi-key sessions resource. describe("End-and-continue — POST /api/v1/sessions/:session/end-and-continue", () => { const endAndContinue = async (sessionParam: string, jwt: string) => - getTestServer().webapp.fetch( - `/api/v1/sessions/${sessionParam}/end-and-continue`, - { - method: "POST", - headers: { - Authorization: `Bearer ${jwt}`, - "Content-Type": "application/json", - }, - // Body shape doesn't matter for auth — handler runs after - // the auth check so any 4xx here means auth passed. - body: JSON.stringify({ - reason: "test", - callingRunId: "run_does_not_exist", - }), - } - ); + getTestServer().webapp.fetch(`/api/v1/sessions/${sessionParam}/end-and-continue`, { + method: "POST", + headers: { + Authorization: `Bearer ${jwt}`, + "Content-Type": "application/json", + }, + // Body shape doesn't matter for auth — handler runs after + // the auth check so any 4xx here means auth passed. + body: JSON.stringify({ + reason: "test", + callingRunId: "run_does_not_exist", + }), + }); const mintJwt = async (apiKey: string, envId: string, scopes: string[]) => generateJWT({ @@ -2833,9 +2795,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:sessions"]); const res = await endAndContinue(session.friendlyId, jwt); expect(res.status).not.toBe(401); expect(res.status).not.toBe(403); @@ -2855,9 +2815,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:sessions"]); const res = await endAndContinue(session.friendlyId, jwt); expect(res.status).toBe(403); }); @@ -2869,8 +2827,7 @@ describe("API", () => { // multi-key sessions resource. No deep matrix here; one positive // test per old superScope per method is enough. describe("Realtime IO — /realtime/v1/sessions/:session/:io", () => { - const ioPath = (sessionParam: string) => - `/realtime/v1/sessions/${sessionParam}/in`; + const ioPath = (sessionParam: string) => `/realtime/v1/sessions/${sessionParam}/in`; const mintJwt = async (apiKey: string, envId: string, scopes: string[]) => generateJWT({ @@ -2883,9 +2840,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "read:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["read:sessions"]); const res = await server.webapp.fetch(ioPath(session.friendlyId), { method: "HEAD", headers: { Authorization: `Bearer ${jwt}` }, @@ -2911,9 +2866,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:sessions"]); const res = await server.webapp.fetch(ioPath(session.friendlyId), { method: "PUT", headers: { Authorization: `Bearer ${jwt}` }, @@ -2941,9 +2894,7 @@ describe("API", () => { const server = getTestServer(); const seed = await seedTestEnvironment(server.prisma); const session = await seedTestApiSession(server.prisma, seed.environment); - const jwt = await mintJwt(seed.apiKey, seed.environment.id, [ - "write:sessions", - ]); + const jwt = await mintJwt(seed.apiKey, seed.environment.id, ["write:sessions"]); const res = await server.webapp.fetch(appendPath(session.friendlyId), { method: "POST", headers: { diff --git a/apps/webapp/test/auth-dashboard.e2e.full.test.ts b/apps/webapp/test/auth-dashboard.e2e.full.test.ts index 948b9c6c0cf..728e9c220c5 100644 --- a/apps/webapp/test/auth-dashboard.e2e.full.test.ts +++ b/apps/webapp/test/auth-dashboard.e2e.full.test.ts @@ -26,11 +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/concurrency", "/admin/back-office"]; for (const path of adminRoutes) { describe(`GET ${path}`, () => { diff --git a/apps/webapp/test/bufferedTriggerPayload.test.ts b/apps/webapp/test/bufferedTriggerPayload.test.ts index 6280acd4c63..e685643eb97 100644 --- a/apps/webapp/test/bufferedTriggerPayload.test.ts +++ b/apps/webapp/test/bufferedTriggerPayload.test.ts @@ -90,7 +90,7 @@ describe("buildBufferedTriggerPayload", () => { expect(buildBufferedTriggerPayload(baseInput).parentRunFriendlyId).toBeNull(); expect( buildBufferedTriggerPayload({ ...baseInput, parentRunFriendlyId: "run_parent" }) - .parentRunFriendlyId, + .parentRunFriendlyId ).toBe("run_parent"); }); }); diff --git a/apps/webapp/test/chat-snapshot-integration.test.ts b/apps/webapp/test/chat-snapshot-integration.test.ts index c2a5dcce98d..c730e24b960 100644 --- a/apps/webapp/test/chat-snapshot-integration.test.ts +++ b/apps/webapp/test/chat-snapshot-integration.test.ts @@ -33,7 +33,9 @@ vi.setConfig({ testTimeout: 60_000 }); // ── Helpers ──────────────────────────────────────────────────────────── -function makeSnapshot(opts: { messages?: UIMessage[]; lastOutEventId?: string } = {}): ChatSnapshotV1 { +function makeSnapshot( + opts: { messages?: UIMessage[]; lastOutEventId?: string } = {} +): ChatSnapshotV1 { return { version: 1, savedAt: 1_700_000_000_000, @@ -109,126 +111,136 @@ describe("chat snapshot integration (MinIO + SDK helpers)", () => { expect(result).toEqual(snapshot); }); - postgresAndMinioTest("returns undefined for a fresh session with no snapshot", async ({ minioConfig }) => { - env.OBJECT_STORE_BASE_URL = minioConfig.baseUrl; - env.OBJECT_STORE_ACCESS_KEY_ID = minioConfig.accessKeyId; - env.OBJECT_STORE_SECRET_ACCESS_KEY = minioConfig.secretAccessKey; - env.OBJECT_STORE_REGION = minioConfig.region; - env.OBJECT_STORE_DEFAULT_PROTOCOL = undefined; - - stubApiClient({ projectRef: "proj_snap_404", envSlug: "dev" }); - - warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); - - // Session never had a snapshot written — read returns undefined. - const result = await readChatSnapshot("sess_never_existed"); - expect(result).toBeUndefined(); - }); - - postgresAndMinioTest("overwrites a prior snapshot in place (single-writer)", async ({ minioConfig }) => { - // The runtime guarantees one attempt alive at a time, and - // `writeChatSnapshot` runs awaited after `onTurnComplete`. Verify - // that a second write to the same key replaces the first cleanly — - // the read-after-write reflects the latest blob. - env.OBJECT_STORE_BASE_URL = minioConfig.baseUrl; - env.OBJECT_STORE_ACCESS_KEY_ID = minioConfig.accessKeyId; - env.OBJECT_STORE_SECRET_ACCESS_KEY = minioConfig.secretAccessKey; - env.OBJECT_STORE_REGION = minioConfig.region; - env.OBJECT_STORE_DEFAULT_PROTOCOL = undefined; - - stubApiClient({ projectRef: "proj_snap_overwrite", envSlug: "dev" }); - - const sessionId = "sess_overwrite"; - - const turn1 = makeSnapshot({ - messages: [ - { id: "u-1", role: "user", parts: [{ type: "text", text: "first" }] }, - ], - lastOutEventId: "evt-turn1", - }); - const turn2 = makeSnapshot({ - messages: [ - { id: "u-1", role: "user", parts: [{ type: "text", text: "first" }] }, - { id: "a-1", role: "assistant", parts: [{ type: "text", text: "reply-1" }] }, - { id: "u-2", role: "user", parts: [{ type: "text", text: "second" }] }, - { id: "a-2", role: "assistant", parts: [{ type: "text", text: "reply-2" }] }, - ], - lastOutEventId: "evt-turn2", - }); - - await writeChatSnapshot(sessionId, turn1); - await writeChatSnapshot(sessionId, turn2); - - const result = await readChatSnapshot(sessionId); - expect(result).toEqual(turn2); - expect(result?.messages).toHaveLength(4); - expect(result?.lastOutEventId).toBe("evt-turn2"); - }); - - postgresAndMinioTest("isolates snapshots by sessionId (no cross-talk)", async ({ minioConfig }) => { - env.OBJECT_STORE_BASE_URL = minioConfig.baseUrl; - env.OBJECT_STORE_ACCESS_KEY_ID = minioConfig.accessKeyId; - env.OBJECT_STORE_SECRET_ACCESS_KEY = minioConfig.secretAccessKey; - env.OBJECT_STORE_REGION = minioConfig.region; - env.OBJECT_STORE_DEFAULT_PROTOCOL = undefined; - - stubApiClient({ projectRef: "proj_snap_iso", envSlug: "dev" }); - - const sessA = "sess_iso_A"; - const sessB = "sess_iso_B"; - const snapA = makeSnapshot({ lastOutEventId: "evt-A" }); - const snapB = makeSnapshot({ lastOutEventId: "evt-B" }); - - await writeChatSnapshot(sessA, snapA); - await writeChatSnapshot(sessB, snapB); + postgresAndMinioTest( + "returns undefined for a fresh session with no snapshot", + async ({ minioConfig }) => { + env.OBJECT_STORE_BASE_URL = minioConfig.baseUrl; + env.OBJECT_STORE_ACCESS_KEY_ID = minioConfig.accessKeyId; + env.OBJECT_STORE_SECRET_ACCESS_KEY = minioConfig.secretAccessKey; + env.OBJECT_STORE_REGION = minioConfig.region; + env.OBJECT_STORE_DEFAULT_PROTOCOL = undefined; - const readA = await readChatSnapshot(sessA); - const readB = await readChatSnapshot(sessB); - - expect(readA?.lastOutEventId).toBe("evt-A"); - expect(readB?.lastOutEventId).toBe("evt-B"); - // Distinct objects — modifying one shouldn't affect the other. - expect(readA?.lastOutEventId).not.toBe(readB?.lastOutEventId); - }); - - postgresAndMinioTest("handles snapshots with large message lists (~50 messages)", async ({ minioConfig }) => { - // Stress test: a 50-turn chat snapshot. Plan F.4 mentions the - // pre-change baseline grew past 512 KiB around turn 10-30 with tool - // use; the post-slim wire keeps wire payloads small but the snapshot - // itself can still get large. Verify the helpers handle a realistic - // payload size. - env.OBJECT_STORE_BASE_URL = minioConfig.baseUrl; - env.OBJECT_STORE_ACCESS_KEY_ID = minioConfig.accessKeyId; - env.OBJECT_STORE_SECRET_ACCESS_KEY = minioConfig.secretAccessKey; - env.OBJECT_STORE_REGION = minioConfig.region; - env.OBJECT_STORE_DEFAULT_PROTOCOL = undefined; + stubApiClient({ projectRef: "proj_snap_404", envSlug: "dev" }); - stubApiClient({ projectRef: "proj_snap_big", envSlug: "dev" }); + warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); - const messages: UIMessage[] = []; - for (let i = 0; i < 50; i++) { - messages.push({ - id: `u-${i}`, - role: "user", - parts: [{ type: "text", text: `user message ${i}: ${"x".repeat(200)}` }], + // Session never had a snapshot written — read returns undefined. + const result = await readChatSnapshot("sess_never_existed"); + expect(result).toBeUndefined(); + } + ); + + postgresAndMinioTest( + "overwrites a prior snapshot in place (single-writer)", + async ({ minioConfig }) => { + // The runtime guarantees one attempt alive at a time, and + // `writeChatSnapshot` runs awaited after `onTurnComplete`. Verify + // that a second write to the same key replaces the first cleanly — + // the read-after-write reflects the latest blob. + env.OBJECT_STORE_BASE_URL = minioConfig.baseUrl; + env.OBJECT_STORE_ACCESS_KEY_ID = minioConfig.accessKeyId; + env.OBJECT_STORE_SECRET_ACCESS_KEY = minioConfig.secretAccessKey; + env.OBJECT_STORE_REGION = minioConfig.region; + env.OBJECT_STORE_DEFAULT_PROTOCOL = undefined; + + stubApiClient({ projectRef: "proj_snap_overwrite", envSlug: "dev" }); + + const sessionId = "sess_overwrite"; + + const turn1 = makeSnapshot({ + messages: [{ id: "u-1", role: "user", parts: [{ type: "text", text: "first" }] }], + lastOutEventId: "evt-turn1", }); - messages.push({ - id: `a-${i}`, - role: "assistant", - parts: [{ type: "text", text: `assistant reply ${i}: ${"y".repeat(500)}` }], + const turn2 = makeSnapshot({ + messages: [ + { id: "u-1", role: "user", parts: [{ type: "text", text: "first" }] }, + { id: "a-1", role: "assistant", parts: [{ type: "text", text: "reply-1" }] }, + { id: "u-2", role: "user", parts: [{ type: "text", text: "second" }] }, + { id: "a-2", role: "assistant", parts: [{ type: "text", text: "reply-2" }] }, + ], + lastOutEventId: "evt-turn2", }); + + await writeChatSnapshot(sessionId, turn1); + await writeChatSnapshot(sessionId, turn2); + + const result = await readChatSnapshot(sessionId); + expect(result).toEqual(turn2); + expect(result?.messages).toHaveLength(4); + expect(result?.lastOutEventId).toBe("evt-turn2"); } - const snapshot = makeSnapshot({ messages, lastOutEventId: "evt-50" }); - - await writeChatSnapshot("sess_big_chat", snapshot); - const result = await readChatSnapshot("sess_big_chat"); - - expect(result).toBeDefined(); - expect(result!.messages).toHaveLength(100); - expect(result!.lastOutEventId).toBe("evt-50"); - // Spot-check ordering integrity — the messages array round-tripped - // in the same order. - expect(result!.messages[0]!.id).toBe("u-0"); - expect(result!.messages[99]!.id).toBe("a-49"); - }); + ); + + postgresAndMinioTest( + "isolates snapshots by sessionId (no cross-talk)", + async ({ minioConfig }) => { + env.OBJECT_STORE_BASE_URL = minioConfig.baseUrl; + env.OBJECT_STORE_ACCESS_KEY_ID = minioConfig.accessKeyId; + env.OBJECT_STORE_SECRET_ACCESS_KEY = minioConfig.secretAccessKey; + env.OBJECT_STORE_REGION = minioConfig.region; + env.OBJECT_STORE_DEFAULT_PROTOCOL = undefined; + + stubApiClient({ projectRef: "proj_snap_iso", envSlug: "dev" }); + + const sessA = "sess_iso_A"; + const sessB = "sess_iso_B"; + const snapA = makeSnapshot({ lastOutEventId: "evt-A" }); + const snapB = makeSnapshot({ lastOutEventId: "evt-B" }); + + await writeChatSnapshot(sessA, snapA); + await writeChatSnapshot(sessB, snapB); + + const readA = await readChatSnapshot(sessA); + const readB = await readChatSnapshot(sessB); + + expect(readA?.lastOutEventId).toBe("evt-A"); + expect(readB?.lastOutEventId).toBe("evt-B"); + // Distinct objects — modifying one shouldn't affect the other. + expect(readA?.lastOutEventId).not.toBe(readB?.lastOutEventId); + } + ); + + postgresAndMinioTest( + "handles snapshots with large message lists (~50 messages)", + async ({ minioConfig }) => { + // Stress test: a 50-turn chat snapshot. Plan F.4 mentions the + // pre-change baseline grew past 512 KiB around turn 10-30 with tool + // use; the post-slim wire keeps wire payloads small but the snapshot + // itself can still get large. Verify the helpers handle a realistic + // payload size. + env.OBJECT_STORE_BASE_URL = minioConfig.baseUrl; + env.OBJECT_STORE_ACCESS_KEY_ID = minioConfig.accessKeyId; + env.OBJECT_STORE_SECRET_ACCESS_KEY = minioConfig.secretAccessKey; + env.OBJECT_STORE_REGION = minioConfig.region; + env.OBJECT_STORE_DEFAULT_PROTOCOL = undefined; + + stubApiClient({ projectRef: "proj_snap_big", envSlug: "dev" }); + + const messages: UIMessage[] = []; + for (let i = 0; i < 50; i++) { + messages.push({ + id: `u-${i}`, + role: "user", + parts: [{ type: "text", text: `user message ${i}: ${"x".repeat(200)}` }], + }); + messages.push({ + id: `a-${i}`, + role: "assistant", + parts: [{ type: "text", text: `assistant reply ${i}: ${"y".repeat(500)}` }], + }); + } + const snapshot = makeSnapshot({ messages, lastOutEventId: "evt-50" }); + + await writeChatSnapshot("sess_big_chat", snapshot); + const result = await readChatSnapshot("sess_big_chat"); + + expect(result).toBeDefined(); + expect(result!.messages).toHaveLength(100); + expect(result!.lastOutEventId).toBe("evt-50"); + // Spot-check ordering integrity — the messages array round-tripped + // in the same order. + expect(result!.messages[0]!.id).toBe("u-0"); + expect(result!.messages[99]!.id).toBe("a-49"); + } + ); }); diff --git a/apps/webapp/test/clickhouseFactory.test.ts b/apps/webapp/test/clickhouseFactory.test.ts index 7f19ea1f218..17e4502590a 100644 --- a/apps/webapp/test/clickhouseFactory.test.ts +++ b/apps/webapp/test/clickhouseFactory.test.ts @@ -13,17 +13,14 @@ const TEST_URL = "https://default:password@ch-org.example.com:8443"; const TEST_URL_2 = "https://default:password@ch-other.example.com:8443"; describe("ClickHouse Factory", () => { - postgresTest( - "returns default client when org has no data store", - async ({ prisma }) => { - const registry = new OrganizationDataStoresRegistry(prisma, prisma); - await registry.loadFromDatabase(); - - const factory = new ClickhouseFactory(registry); - const client = await factory.getClickhouseForOrganization("org-no-store", "standard"); - expect(client).toBeDefined(); - } - ); + postgresTest("returns default client when org has no data store", async ({ prisma }) => { + const registry = new OrganizationDataStoresRegistry(prisma, prisma); + await registry.loadFromDatabase(); + + const factory = new ClickhouseFactory(registry); + const client = await factory.getClickhouseForOrganization("org-no-store", "standard"); + expect(client).toBeDefined(); + }); postgresTest( "returns org-specific client when a data store is configured", diff --git a/apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts b/apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts index f5e56fb6e24..8032bc851c8 100644 --- a/apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts +++ b/apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts @@ -325,4 +325,3 @@ describe("createTSQLCompletion", () => { }); }); }); - diff --git a/apps/webapp/test/components/code/tsql/tsqlLinter.test.ts b/apps/webapp/test/components/code/tsql/tsqlLinter.test.ts index a669c1209df..efd0d0bfd10 100644 --- a/apps/webapp/test/components/code/tsql/tsqlLinter.test.ts +++ b/apps/webapp/test/components/code/tsql/tsqlLinter.test.ts @@ -28,9 +28,7 @@ describe("tsqlLinter", () => { true ); expect( - isValidTSQLQuery( - "SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id" - ) + isValidTSQLQuery("SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id") ).toBe(true); }); @@ -76,4 +74,3 @@ describe("tsqlLinter", () => { }); }); }); - diff --git a/apps/webapp/test/components/runs/v3/RunTag.test.ts b/apps/webapp/test/components/runs/v3/RunTag.test.ts index e2c2bec6464..244695a6bf4 100644 --- a/apps/webapp/test/components/runs/v3/RunTag.test.ts +++ b/apps/webapp/test/components/runs/v3/RunTag.test.ts @@ -52,17 +52,23 @@ describe("splitTag", () => { it("should handle special characters in values", () => { expect(splitTag("region:us-west-2")).toEqual({ key: "region", value: "us-west-2" }); - expect(splitTag("query:SELECT * FROM users")).toEqual({ key: "query", value: "SELECT * FROM users" }); + expect(splitTag("query:SELECT * FROM users")).toEqual({ + key: "query", + value: "SELECT * FROM users", + }); expect(splitTag("path:/api/v1/users")).toEqual({ key: "path", value: "/api/v1/users" }); }); it("should handle values containing numbers and special formats", () => { - expect(splitTag("uuid:123e4567-e89b-12d3-a456-426614174000")).toEqual({ - key: "uuid", - value: "123e4567-e89b-12d3-a456-426614174000" + expect(splitTag("uuid:123e4567-e89b-12d3-a456-426614174000")).toEqual({ + key: "uuid", + value: "123e4567-e89b-12d3-a456-426614174000", }); expect(splitTag("ip_192.168.1.1")).toEqual({ key: "ip", value: "192.168.1.1" }); - expect(splitTag("date:2023-04-01T12:00:00Z")).toEqual({ key: "date", value: "2023-04-01T12:00:00Z" }); + expect(splitTag("date:2023-04-01T12:00:00Z")).toEqual({ + key: "date", + value: "2023-04-01T12:00:00Z", + }); }); it("should handle keys with numbers", () => { @@ -71,7 +77,13 @@ describe("splitTag", () => { }); it("should handle particularly complex mixed cases", () => { - expect(splitTag("env:prod_us-west-2_replica")).toEqual({ key: "env", value: "prod_us-west-2_replica" }); - expect(splitTag("status_error:connection:timeout")).toEqual({ key: "status", value: "error:connection:timeout" }); + expect(splitTag("env:prod_us-west-2_replica")).toEqual({ + key: "env", + value: "prod_us-west-2_replica", + }); + expect(splitTag("status_error:connection:timeout")).toEqual({ + key: "status", + value: "error:connection:timeout", + }); }); -}); \ No newline at end of file +}); diff --git a/apps/webapp/test/computeMigration.test.ts b/apps/webapp/test/computeMigration.test.ts index 0c914f2e2ae..b828409cd34 100644 --- a/apps/webapp/test/computeMigration.test.ts +++ b/apps/webapp/test/computeMigration.test.ts @@ -16,12 +16,20 @@ describe("isOrgMigrated", () => { }); it("does not migrate when globally disabled", () => { expect( - isOrgMigrated({ ...base, orgId: "org_x", flags: { computeMigrationEnabled: false, computeMigrationFreePercentage: 100 } }) + isOrgMigrated({ + ...base, + orgId: "org_x", + flags: { computeMigrationEnabled: false, computeMigrationFreePercentage: 100 }, + }) ).toBe(false); }); it("per-org override false excludes even at 100%", () => { expect( - isOrgMigrated({ ...base, orgId: "org_x", orgFeatureFlags: { computeMigrationEnabled: false } }) + isOrgMigrated({ + ...base, + orgId: "org_x", + orgFeatureFlags: { computeMigrationEnabled: false }, + }) ).toBe(false); }); it("per-org override true enrolls even when globally off", () => { @@ -50,13 +58,22 @@ describe("isOrgMigrated", () => { planType: "enterprise", orgId: "org_x", orgFeatureFlags: {}, - flags: { computeMigrationEnabled: true, computeMigrationFreePercentage: 100, computeMigrationPaidPercentage: 100 }, + flags: { + computeMigrationEnabled: true, + computeMigrationFreePercentage: 100, + computeMigrationPaidPercentage: 100, + }, }) ).toBe(false); }); it("undefined planType is not enrolled", () => { expect( - isOrgMigrated({ planType: undefined, orgId: "org_x", orgFeatureFlags: {}, flags: { computeMigrationEnabled: true } }) + isOrgMigrated({ + planType: undefined, + orgId: "org_x", + orgFeatureFlags: {}, + flags: { computeMigrationEnabled: true }, + }) ).toBe(false); }); }); @@ -74,7 +91,12 @@ describe("resolveComputeMigration", () => { it("swaps to the compute backing for an enrolled free org", () => { expect( - resolveComputeMigration({ ...enrolled, baseWorkerQueue: "us-east-1", orgId: "org_x", backing }) + resolveComputeMigration({ + ...enrolled, + baseWorkerQueue: "us-east-1", + orgId: "org_x", + backing, + }) ).toEqual({ workerQueue: "us-east-1-next", region: "us-east-1", enableFastPath: true }); }); it("leaves the queue unchanged when there is no backing for the region (EU)", () => { @@ -90,17 +112,35 @@ describe("resolveComputeMigration", () => { }); it("does not migrate DEVELOPMENT", () => { expect( - resolveComputeMigration({ ...enrolled, baseWorkerQueue: "us-east-1", orgId: "org_x", backing, envType: "DEVELOPMENT" }) + resolveComputeMigration({ + ...enrolled, + baseWorkerQueue: "us-east-1", + orgId: "org_x", + backing, + envType: "DEVELOPMENT", + }) ).toEqual({ workerQueue: "us-east-1", region: "us-east-1", enableFastPath: false }); }); it("leaves a non-enrolled org untouched", () => { expect( - resolveComputeMigration({ ...enrolled, baseWorkerQueue: "us-east-1", orgId: "org_x", backing, flags: { computeMigrationEnabled: false } }) + resolveComputeMigration({ + ...enrolled, + baseWorkerQueue: "us-east-1", + orgId: "org_x", + backing, + flags: { computeMigrationEnabled: false }, + }) ).toEqual({ workerQueue: "us-east-1", region: "us-east-1", enableFastPath: false }); }); it("undefined baseWorkerQueue passes through", () => { expect( - resolveComputeMigration({ ...enrolled, baseWorkerQueue: undefined, region: undefined, orgId: "org_x", backing }) + resolveComputeMigration({ + ...enrolled, + baseWorkerQueue: undefined, + region: undefined, + orgId: "org_x", + backing, + }) ).toEqual({ workerQueue: undefined, region: undefined, enableFastPath: false }); }); }); diff --git a/apps/webapp/test/createDeploymentWithNextVersion.test.ts b/apps/webapp/test/createDeploymentWithNextVersion.test.ts index aa135b3a316..7b4fda2b82b 100644 --- a/apps/webapp/test/createDeploymentWithNextVersion.test.ts +++ b/apps/webapp/test/createDeploymentWithNextVersion.test.ts @@ -67,24 +67,21 @@ describe("createDeploymentWithNextVersion", () => { } ); - containerTest( - "propagates non-P2002 errors immediately without retrying", - async ({ prisma }) => { - const { environment } = await seedEnvironment(prisma); + containerTest("propagates non-P2002 errors immediately without retrying", async ({ prisma }) => { + const { environment } = await seedEnvironment(prisma); - let buildDataCalls = 0; - const buildData = () => { - buildDataCalls++; - throw new Error("builder boom"); - }; + let buildDataCalls = 0; + const buildData = () => { + buildDataCalls++; + throw new Error("builder boom"); + }; - await expect( - createDeploymentWithNextVersion(prisma, environment.id, buildData) - ).rejects.toThrow("builder boom"); + await expect( + createDeploymentWithNextVersion(prisma, environment.id, buildData) + ).rejects.toThrow("builder boom"); - expect(buildDataCalls).toBe(1); - } - ); + expect(buildDataCalls).toBe(1); + }); containerTest( "wraps exhausted retries in DeploymentVersionCollisionError with the P2002 as cause", @@ -113,9 +110,7 @@ describe("createDeploymentWithNextVersion", () => { ); const fulfilled = settled.filter((s) => s.status === "fulfilled"); - const rejected = settled.filter( - (s): s is PromiseRejectedResult => s.status === "rejected" - ); + const rejected = settled.filter((s): s is PromiseRejectedResult => s.status === "rejected"); expect(fulfilled.length).toBeGreaterThanOrEqual(1); expect(rejected.length).toBeGreaterThanOrEqual(1); diff --git a/apps/webapp/test/devBranchServices.test.ts b/apps/webapp/test/devBranchServices.test.ts index f8d333f5ad0..06c06f7c7e8 100644 --- a/apps/webapp/test/devBranchServices.test.ts +++ b/apps/webapp/test/devBranchServices.test.ts @@ -30,93 +30,109 @@ async function createDevRoot( } describe("UpsertBranchService — DEVELOPMENT parent", () => { - postgresTest("creates a child branch that inherits the parent's ownership", async ({ prisma }) => { - const { organization, project, user, orgMember } = await createTestOrgProjectWithMember(prisma); - const devRoot = await createDevRoot(prisma, project.id, organization.id, orgMember.id); - - const result = await new UpsertBranchService(prisma).call( - { type: "userMembership", userId: user.id }, - { projectId: project.id, env: "development", branchName: "my-feature" } - ); - - expect(result.success).toBe(true); - if (!result.success) return; - - const { branch } = result; - expect(branch.type).toBe("DEVELOPMENT"); - expect(branch.parentEnvironmentId).toBe(devRoot.id); - expect(branch.branchName).toBe("my-feature"); - // The key dev-vs-preview divergence: dev branches MUST copy the parent's - // orgMemberId (preview parents have none). - expect(branch.orgMemberId).toBe(orgMember.id); - // Children inherit the parent's concurrency limit at creation. - expect(branch.maximumConcurrencyLimit).toBe(17); - expect(branch.slug).toBe(slug(`${devRoot.slug}-my-feature`)); - }); - - postgresTest("is idempotent — upserting the same branch returns the existing row", async ({ prisma }) => { - const { organization, project, user, orgMember } = await createTestOrgProjectWithMember(prisma); - await createDevRoot(prisma, project.id, organization.id, orgMember.id); - const orgFilter = { type: "userMembership" as const, userId: user.id }; - const options = { projectId: project.id, env: "development" as const, branchName: "dup" }; - - const first = await new UpsertBranchService(prisma).call(orgFilter, options); - const second = await new UpsertBranchService(prisma).call(orgFilter, options); - - expect(first.success && second.success).toBe(true); - if (!first.success || !second.success) return; - expect(second.alreadyExisted).toBe(true); - expect(second.branch.id).toBe(first.branch.id); - }); - - postgresTest("rejects an invalid branch name without touching the database", async ({ prisma }) => { - const { organization, project, user, orgMember } = await createTestOrgProjectWithMember(prisma); - await createDevRoot(prisma, project.id, organization.id, orgMember.id); - - const result = await new UpsertBranchService(prisma).call( - { type: "userMembership", userId: user.id }, - { projectId: project.id, env: "development", branchName: "bad branch name!" } - ); - - expect(result.success).toBe(false); - }); + postgresTest( + "creates a child branch that inherits the parent's ownership", + async ({ prisma }) => { + const { organization, project, user, orgMember } = + await createTestOrgProjectWithMember(prisma); + const devRoot = await createDevRoot(prisma, project.id, organization.id, orgMember.id); + + const result = await new UpsertBranchService(prisma).call( + { type: "userMembership", userId: user.id }, + { projectId: project.id, env: "development", branchName: "my-feature" } + ); + + expect(result.success).toBe(true); + if (!result.success) return; + + const { branch } = result; + expect(branch.type).toBe("DEVELOPMENT"); + expect(branch.parentEnvironmentId).toBe(devRoot.id); + expect(branch.branchName).toBe("my-feature"); + // The key dev-vs-preview divergence: dev branches MUST copy the parent's + // orgMemberId (preview parents have none). + expect(branch.orgMemberId).toBe(orgMember.id); + // Children inherit the parent's concurrency limit at creation. + expect(branch.maximumConcurrencyLimit).toBe(17); + expect(branch.slug).toBe(slug(`${devRoot.slug}-my-feature`)); + } + ); + + postgresTest( + "is idempotent — upserting the same branch returns the existing row", + async ({ prisma }) => { + const { organization, project, user, orgMember } = + await createTestOrgProjectWithMember(prisma); + await createDevRoot(prisma, project.id, organization.id, orgMember.id); + const orgFilter = { type: "userMembership" as const, userId: user.id }; + const options = { projectId: project.id, env: "development" as const, branchName: "dup" }; + + const first = await new UpsertBranchService(prisma).call(orgFilter, options); + const second = await new UpsertBranchService(prisma).call(orgFilter, options); + + expect(first.success && second.success).toBe(true); + if (!first.success || !second.success) return; + expect(second.alreadyExisted).toBe(true); + expect(second.branch.id).toBe(first.branch.id); + } + ); + + postgresTest( + "rejects an invalid branch name without touching the database", + async ({ prisma }) => { + const { organization, project, user, orgMember } = + await createTestOrgProjectWithMember(prisma); + await createDevRoot(prisma, project.id, organization.id, orgMember.id); + + const result = await new UpsertBranchService(prisma).call( + { type: "userMembership", userId: user.id }, + { projectId: project.id, env: "development", branchName: "bad branch name!" } + ); + + expect(result.success).toBe(false); + } + ); }); describe("ArchiveBranchService — DEVELOPMENT", () => { - postgresTest("archives a dev branch and frees its slug/shortcode for reuse", async ({ prisma }) => { - const { organization, project, user, orgMember } = await createTestOrgProjectWithMember(prisma); - await createDevRoot(prisma, project.id, organization.id, orgMember.id); - const orgFilter = { type: "userMembership" as const, userId: user.id }; - - const created = await new UpsertBranchService(prisma).call(orgFilter, { - projectId: project.id, - env: "development", - branchName: "reuse-me", - }); - expect(created.success).toBe(true); - if (!created.success) return; - const originalSlug = created.branch.slug; - - const archived = await new ArchiveBranchService(prisma).call(orgFilter, { - environmentId: created.branch.id, - }); - expect(archived.success).toBe(true); - if (!archived.success) return; - expect(archived.branch.archivedAt).not.toBeNull(); - // Slug + shortcode are randomized on archive so the name can be reused. - expect(archived.branch.slug).not.toBe(originalSlug); - - // The same branch name can now be created again (new row, deterministic slug). - const recreated = await new UpsertBranchService(prisma).call(orgFilter, { - projectId: project.id, - env: "development", - branchName: "reuse-me", - }); - expect(recreated.success).toBe(true); - if (!recreated.success) return; - expect(recreated.branch.id).not.toBe(created.branch.id); - expect(recreated.branch.slug).toBe(originalSlug); - }); + postgresTest( + "archives a dev branch and frees its slug/shortcode for reuse", + async ({ prisma }) => { + const { organization, project, user, orgMember } = + await createTestOrgProjectWithMember(prisma); + await createDevRoot(prisma, project.id, organization.id, orgMember.id); + const orgFilter = { type: "userMembership" as const, userId: user.id }; + + const created = await new UpsertBranchService(prisma).call(orgFilter, { + projectId: project.id, + env: "development", + branchName: "reuse-me", + }); + expect(created.success).toBe(true); + if (!created.success) return; + const originalSlug = created.branch.slug; + + const archived = await new ArchiveBranchService(prisma).call(orgFilter, { + environmentId: created.branch.id, + }); + expect(archived.success).toBe(true); + if (!archived.success) return; + expect(archived.branch.archivedAt).not.toBeNull(); + // Slug + shortcode are randomized on archive so the name can be reused. + expect(archived.branch.slug).not.toBe(originalSlug); + + // The same branch name can now be created again (new row, deterministic slug). + const recreated = await new UpsertBranchService(prisma).call(orgFilter, { + projectId: project.id, + env: "development", + branchName: "reuse-me", + }); + expect(recreated.success).toBe(true); + if (!recreated.success) return; + expect(recreated.branch.id).not.toBe(created.branch.id); + expect(recreated.branch.slug).toBe(originalSlug); + } + ); postgresTest("refuses to archive the default branch (the dev root)", async ({ prisma }) => { const { organization, project, user, orgMember } = await createTestOrgProjectWithMember(prisma); diff --git a/apps/webapp/test/devPresenceRecency.test.ts b/apps/webapp/test/devPresenceRecency.test.ts index aafeb8bd701..6aac3c936bc 100644 --- a/apps/webapp/test/devPresenceRecency.test.ts +++ b/apps/webapp/test/devPresenceRecency.test.ts @@ -14,13 +14,16 @@ function ids() { const recentKey = (userId: string, projectId: string) => `dev-recent:${userId}:${projectId}`; describe("DevPresence — recency ZSET", () => { - redisTest("getRecentBranchIds returns an empty map when nothing has pinged", async ({ redisOptions }) => { - const presence = new DevPresence(redisOptions); - const { userId, projectId } = ids(); - - const result = await presence.getRecentBranchIds(userId, projectId); - expect(result.size).toBe(0); - }); + redisTest( + "getRecentBranchIds returns an empty map when nothing has pinged", + async ({ redisOptions }) => { + const presence = new DevPresence(redisOptions); + const { userId, projectId } = ids(); + + const result = await presence.getRecentBranchIds(userId, projectId); + expect(result.size).toBe(0); + } + ); redisTest("a ping records the branch as recently active", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); @@ -87,21 +90,24 @@ describe("DevPresence — recency ZSET", () => { await redis.quit(); }); - redisTest("caps cardinality at 50 even under a flood of distinct branches", async ({ redisOptions }) => { - const presence = new DevPresence(redisOptions); - const redis = new Redis(redisOptions); - const { userId, projectId } = ids(); + redisTest( + "caps cardinality at 50 even under a flood of distinct branches", + async ({ redisOptions }) => { + const presence = new DevPresence(redisOptions); + const redis = new Redis(redisOptions); + const { userId, projectId } = ids(); - // 60 distinct envs, each with its own debounce key, so each performs a ZADD. - for (let i = 0; i < 60; i++) { - // eslint-disable-next-line no-await-in-loop - await presence.setConnected({ userId, projectId, environmentId: `env_${i}`, ttl: 60 }); - } + // 60 distinct envs, each with its own debounce key, so each performs a ZADD. + for (let i = 0; i < 60; i++) { + // eslint-disable-next-line no-await-in-loop + await presence.setConnected({ userId, projectId, environmentId: `env_${i}`, ttl: 60 }); + } - expect(await redis.zcard(recentKey(userId, projectId))).toBe(50); + expect(await redis.zcard(recentKey(userId, projectId))).toBe(50); - await redis.quit(); - }); + await redis.quit(); + } + ); redisTest("returns recent branches in most-recent-first order", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); diff --git a/apps/webapp/test/duplicateTaskIds.test.ts b/apps/webapp/test/duplicateTaskIds.test.ts index f0f95806c56..f5e722e39ba 100644 --- a/apps/webapp/test/duplicateTaskIds.test.ts +++ b/apps/webapp/test/duplicateTaskIds.test.ts @@ -2,7 +2,12 @@ import { describe, it, expect } from "vitest"; import { ServiceValidationError } from "~/v3/services/common.server"; import { assertNoDuplicateTaskIds } from "~/v3/services/duplicateTaskIds.server"; -function task(partial: { id: string; filePath?: string; exportName?: string; triggerSource?: string }) { +function task(partial: { + id: string; + filePath?: string; + exportName?: string; + triggerSource?: string; +}) { return { filePath: "src/trigger/example.ts", exportName: "exampleTask", diff --git a/apps/webapp/test/engine/streamBatchItems.test.ts b/apps/webapp/test/engine/streamBatchItems.test.ts index c043a4e9e5d..70dba77661e 100644 --- a/apps/webapp/test/engine/streamBatchItems.test.ts +++ b/apps/webapp/test/engine/streamBatchItems.test.ts @@ -1790,17 +1790,17 @@ describe("StreamBatchItemsService", () => { `\n[streamBatchItems perf] runCount=${runCount}\n` + ` large payloads (${offloadLatencyMs}ms/item offload):\n` + ` concurrency=1 ${offloadSeqMs.toFixed(0)}ms\n` + - ` concurrency=10 ${offload10Ms.toFixed(0)}ms (${( - offloadSeqMs / offload10Ms - ).toFixed(1)}x faster)\n` + - ` concurrency=50 ${offload50Ms.toFixed(0)}ms (${( - offloadSeqMs / offload50Ms - ).toFixed(1)}x faster)\n` + + ` concurrency=10 ${offload10Ms.toFixed(0)}ms (${(offloadSeqMs / offload10Ms).toFixed( + 1 + )}x faster)\n` + + ` concurrency=50 ${offload50Ms.toFixed(0)}ms (${(offloadSeqMs / offload50Ms).toFixed( + 1 + )}x faster)\n` + ` small payloads (Redis enqueue only, no offload):\n` + ` concurrency=1 ${enqueueSeqMs.toFixed(0)}ms\n` + - ` concurrency=10 ${enqueue10Ms.toFixed(0)}ms (${( - enqueueSeqMs / enqueue10Ms - ).toFixed(1)}x faster)\n` + ` concurrency=10 ${enqueue10Ms.toFixed(0)}ms (${(enqueueSeqMs / enqueue10Ms).toFixed( + 1 + )}x faster)\n` ); // Sequential floor: N items each held for offloadLatencyMs cannot finish diff --git a/apps/webapp/test/engine/triggerTask.test.ts b/apps/webapp/test/engine/triggerTask.test.ts index c524d0a3b9c..ecd4db5e2b9 100644 --- a/apps/webapp/test/engine/triggerTask.test.ts +++ b/apps/webapp/test/engine/triggerTask.test.ts @@ -23,10 +23,7 @@ import { TaskRun } from "@trigger.dev/database"; import { Redis } from "ioredis"; import { IdempotencyKeyConcern } from "~/runEngine/concerns/idempotencyKeys.server"; import { DefaultQueueManager } from "~/runEngine/concerns/queues.server"; -import { - NoopTaskMetadataCache, - RedisTaskMetadataCache, -} from "~/services/taskMetadataCache.server"; +import { NoopTaskMetadataCache, RedisTaskMetadataCache } from "~/services/taskMetadataCache.server"; import { EntitlementValidationParams, MaxAttemptsValidationParams, @@ -97,9 +94,9 @@ class MockTraceEventConcern implements TraceEventConcern { spanId: MOCK_SPAN_ID, traceContext: { traceparent: MOCK_TRACEPARENT }, traceparent: undefined, - setAttribute: () => { }, - failWithError: () => { }, - stop: () => { }, + setAttribute: () => {}, + failWithError: () => {}, + stop: () => {}, }, "test" ); @@ -122,9 +119,9 @@ class MockTraceEventConcern implements TraceEventConcern { spanId: "test", traceContext: {}, traceparent: undefined, - setAttribute: () => { }, - failWithError: () => { }, - stop: () => { }, + setAttribute: () => {}, + failWithError: () => {}, + stop: () => {}, }, "test" ); @@ -147,9 +144,9 @@ class MockTraceEventConcern implements TraceEventConcern { spanId: "test", traceContext: {}, traceparent: undefined, - setAttribute: () => { }, - failWithError: () => { }, - stop: () => { }, + setAttribute: () => {}, + failWithError: () => {}, + stop: () => {}, }, "test" ); @@ -1425,83 +1422,80 @@ describe("RunEngineTriggerTaskService", () => { } ); - containerTest( - "should accept valid debounce.delay formats", - async ({ prisma, redisOptions }) => { - const engine = new RunEngine({ - prisma, - worker: { - redis: redisOptions, - workers: 1, - tasksPerWorker: 10, - pollIntervalMs: 100, - }, - queue: { - redis: redisOptions, - }, - runLock: { - redis: redisOptions, - }, + containerTest("should accept valid debounce.delay formats", async ({ prisma, redisOptions }) => { + const engine = new RunEngine({ + prisma, + worker: { + redis: redisOptions, + workers: 1, + tasksPerWorker: 10, + pollIntervalMs: 100, + }, + queue: { + redis: redisOptions, + }, + runLock: { + redis: redisOptions, + }, + machines: { + defaultMachine: "small-1x", machines: { - defaultMachine: "small-1x", - machines: { - "small-1x": { - name: "small-1x" as const, - cpu: 0.5, - memory: 0.5, - centsPerMs: 0.0001, - }, + "small-1x": { + name: "small-1x" as const, + cpu: 0.5, + memory: 0.5, + centsPerMs: 0.0001, }, - baseCostInCents: 0.0005, }, - tracer: trace.getTracer("test", "0.0.0"), - }); + baseCostInCents: 0.0005, + }, + tracer: trace.getTracer("test", "0.0.0"), + }); - const authenticatedEnvironment = await setupAuthenticatedEnvironment(prisma, "PRODUCTION"); - const taskIdentifier = "test-task"; + const authenticatedEnvironment = await setupAuthenticatedEnvironment(prisma, "PRODUCTION"); + const taskIdentifier = "test-task"; - await setupBackgroundWorker(engine, authenticatedEnvironment, taskIdentifier); + await setupBackgroundWorker(engine, authenticatedEnvironment, taskIdentifier); - const queuesManager = new DefaultQueueManager(prisma, engine); - const idempotencyKeyConcern = new IdempotencyKeyConcern( - prisma, - engine, - new MockTraceEventConcern() - ); + const queuesManager = new DefaultQueueManager(prisma, engine); + const idempotencyKeyConcern = new IdempotencyKeyConcern( + prisma, + engine, + new MockTraceEventConcern() + ); - const triggerTaskService = new RunEngineTriggerTaskService({ - engine, - prisma, - payloadProcessor: new MockPayloadProcessor(), - queueConcern: queuesManager, - idempotencyKeyConcern, - validator: new MockTriggerTaskValidator(), - traceEventConcern: new MockTraceEventConcern(), - tracer: trace.getTracer("test", "0.0.0"), - metadataMaximumSize: 1024 * 1024 * 1, - }); + const triggerTaskService = new RunEngineTriggerTaskService({ + engine, + prisma, + payloadProcessor: new MockPayloadProcessor(), + queueConcern: queuesManager, + idempotencyKeyConcern, + validator: new MockTriggerTaskValidator(), + traceEventConcern: new MockTraceEventConcern(), + tracer: trace.getTracer("test", "0.0.0"), + metadataMaximumSize: 1024 * 1024 * 1, + }); - // Valid debounce.delay format - const result = await triggerTaskService.call({ - taskId: taskIdentifier, - environment: authenticatedEnvironment, - body: { - payload: { test: "test" }, - options: { - debounce: { - key: "test-key", - delay: "5s", // Valid format - }, + // Valid debounce.delay format + const result = await triggerTaskService.call({ + taskId: taskIdentifier, + environment: authenticatedEnvironment, + body: { + payload: { test: "test" }, + options: { + debounce: { + key: "test-key", + delay: "5s", // Valid format }, }, - }); + }, + }); - expect(result).toBeDefined(); - expect(result?.run.friendlyId).toBeDefined(); + expect(result).toBeDefined(); + expect(result?.run.friendlyId).toBeDefined(); - await engine.quit(); - } - ); + await engine.quit(); + }); // ─── Mollifier integration ────────────────────────────────────────────────── // @@ -1517,14 +1511,26 @@ describe("RunEngineTriggerTaskService", () => { this.accepted.push(input); return true; } - async pop() { return null; } + async pop() { + return null; + } async ack() {} async requeue() {} - async fail() { return false; } - async getEntry() { return null; } - async listEnvs(): Promise { return []; } - async getEntryTtlSeconds(): Promise { return -1; } - async evaluateTrip() { return { tripped: false, count: 0 }; } + async fail() { + return false; + } + async getEntry() { + return null; + } + async listEnvs(): Promise { + return []; + } + async getEntryTtlSeconds(): Promise { + return -1; + } + async evaluateTrip() { + return { tripped: false, count: 0 }; + } async close() {} } @@ -1538,7 +1544,9 @@ describe("RunEngineTriggerTaskService", () => { runLock: { redis: redisOptions }, machines: { defaultMachine: "small-1x", - machines: { "small-1x": { name: "small-1x" as const, cpu: 0.5, memory: 0.5, centsPerMs: 0.0001 } }, + machines: { + "small-1x": { name: "small-1x" as const, cpu: 0.5, memory: 0.5, centsPerMs: 0.0001 }, + }, baseCostInCents: 0.0005, }, tracer: trace.getTracer("test", "0.0.0"), @@ -1558,16 +1566,28 @@ describe("RunEngineTriggerTaskService", () => { } const buffer = new CapturingMollifierBuffer(); - const evaluateGateSpy = vi.fn(async () => ({ action: "mollify" as const, decision: { - divert: true as const, reason: "per_env_rate" as const, count: 99, threshold: 1, windowMs: 200, holdMs: 500, - } })); + const evaluateGateSpy = vi.fn(async () => ({ + action: "mollify" as const, + decision: { + divert: true as const, + reason: "per_env_rate" as const, + count: 99, + threshold: 1, + windowMs: 200, + holdMs: 500, + }, + })); const triggerTaskService = new RunEngineTriggerTaskService({ engine, prisma, payloadProcessor: new MockPayloadProcessor(), queueConcern: new DefaultQueueManager(prisma, engine), - idempotencyKeyConcern: new IdempotencyKeyConcern(prisma, engine, new MockTraceEventConcern()), + idempotencyKeyConcern: new IdempotencyKeyConcern( + prisma, + engine, + new MockTraceEventConcern() + ), validator: new FailingMaxAttemptsValidator(), traceEventConcern: new MockTraceEventConcern(), tracer: trace.getTracer("test", "0.0.0"), @@ -1582,7 +1602,7 @@ describe("RunEngineTriggerTaskService", () => { taskId: taskIdentifier, environment: authenticatedEnvironment, body: { payload: { test: "x" } }, - }), + }) ).rejects.toThrow(/synthetic max-attempts failure/); // Critical: the gate must NEVER be consulted when validation fails. @@ -1593,7 +1613,7 @@ describe("RunEngineTriggerTaskService", () => { expect(buffer.accepted).toHaveLength(0); await engine.quit(); - }, + } ); containerTest( @@ -1615,7 +1635,9 @@ describe("RunEngineTriggerTaskService", () => { runLock: { redis: redisOptions }, machines: { defaultMachine: "small-1x", - machines: { "small-1x": { name: "small-1x" as const, cpu: 0.5, memory: 0.5, centsPerMs: 0.0001 } }, + machines: { + "small-1x": { name: "small-1x" as const, cpu: 0.5, memory: 0.5, centsPerMs: 0.0001 }, + }, baseCostInCents: 0.0005, }, tracer: trace.getTracer("test", "0.0.0"), @@ -1658,7 +1680,11 @@ describe("RunEngineTriggerTaskService", () => { prisma, payloadProcessor: new MockPayloadProcessor(), queueConcern: new DefaultQueueManager(prisma, engine), - idempotencyKeyConcern: new IdempotencyKeyConcern(prisma, engine, new MockTraceEventConcern()), + idempotencyKeyConcern: new IdempotencyKeyConcern( + prisma, + engine, + new MockTraceEventConcern() + ), validator: new MockTriggerTaskValidator(), traceEventConcern: traceConcern, tracer: trace.getTracer("test", "0.0.0"), @@ -1763,7 +1789,7 @@ describe("RunEngineTriggerTaskService", () => { expect(pgRun).toBeNull(); await engine.quit(); - }, + } ); containerTest( @@ -1776,7 +1802,9 @@ describe("RunEngineTriggerTaskService", () => { runLock: { redis: redisOptions }, machines: { defaultMachine: "small-1x", - machines: { "small-1x": { name: "small-1x" as const, cpu: 0.5, memory: 0.5, centsPerMs: 0.0001 } }, + machines: { + "small-1x": { name: "small-1x" as const, cpu: 0.5, memory: 0.5, centsPerMs: 0.0001 }, + }, baseCostInCents: 0.0005, }, tracer: trace.getTracer("test", "0.0.0"), @@ -1794,7 +1822,11 @@ describe("RunEngineTriggerTaskService", () => { prisma, payloadProcessor: new MockPayloadProcessor(), queueConcern: new DefaultQueueManager(prisma, engine), - idempotencyKeyConcern: new IdempotencyKeyConcern(prisma, engine, new MockTraceEventConcern()), + idempotencyKeyConcern: new IdempotencyKeyConcern( + prisma, + engine, + new MockTraceEventConcern() + ), validator: new MockTriggerTaskValidator(), traceEventConcern: new MockTraceEventConcern(), tracer: trace.getTracer("test", "0.0.0"), @@ -1824,7 +1856,7 @@ describe("RunEngineTriggerTaskService", () => { expect(result?.isMollified).toBeFalsy(); await engine.quit(); - }, + } ); containerTest( @@ -1850,7 +1882,9 @@ describe("RunEngineTriggerTaskService", () => { runLock: { redis: redisOptions }, machines: { defaultMachine: "small-1x", - machines: { "small-1x": { name: "small-1x" as const, cpu: 0.5, memory: 0.5, centsPerMs: 0.0001 } }, + machines: { + "small-1x": { name: "small-1x" as const, cpu: 0.5, memory: 0.5, centsPerMs: 0.0001 }, + }, baseCostInCents: 0.0005, }, tracer: trace.getTracer("test", "0.0.0"), @@ -1863,7 +1897,7 @@ describe("RunEngineTriggerTaskService", () => { const idempotencyKeyConcern = new IdempotencyKeyConcern( prisma, engine, - new MockTraceEventConcern(), + new MockTraceEventConcern() ); // Setup: normal trigger to create the cached run (no mollifier). @@ -1931,9 +1965,8 @@ describe("RunEngineTriggerTaskService", () => { expect(buffer.accepted).toHaveLength(0); await engine.quit(); - }, + } ); - }); describe("DefaultQueueManager task metadata cache", () => { @@ -1981,7 +2014,11 @@ describe("DefaultQueueManager task metadata cache", () => { prisma, payloadProcessor: new MockPayloadProcessor(), queueConcern: queuesManager, - idempotencyKeyConcern: new IdempotencyKeyConcern(prisma, engine, new MockTraceEventConcern()), + idempotencyKeyConcern: new IdempotencyKeyConcern( + prisma, + engine, + new MockTraceEventConcern() + ), validator: new MockTriggerTaskValidator(), traceEventConcern: new MockTraceEventConcern(), tracer: trace.getTracer("test", "0.0.0"), @@ -2037,7 +2074,11 @@ describe("DefaultQueueManager task metadata cache", () => { prisma, payloadProcessor: new MockPayloadProcessor(), queueConcern: queuesManager, - idempotencyKeyConcern: new IdempotencyKeyConcern(prisma, engine, new MockTraceEventConcern()), + idempotencyKeyConcern: new IdempotencyKeyConcern( + prisma, + engine, + new MockTraceEventConcern() + ), validator: new MockTriggerTaskValidator(), traceEventConcern: new MockTraceEventConcern(), tracer: trace.getTracer("test", "0.0.0"), @@ -2111,7 +2152,11 @@ describe("DefaultQueueManager task metadata cache", () => { prisma, payloadProcessor: new MockPayloadProcessor(), queueConcern: queuesManager, - idempotencyKeyConcern: new IdempotencyKeyConcern(prisma, engine, new MockTraceEventConcern()), + idempotencyKeyConcern: new IdempotencyKeyConcern( + prisma, + engine, + new MockTraceEventConcern() + ), validator: new MockTriggerTaskValidator(), traceEventConcern: new MockTraceEventConcern(), tracer: trace.getTracer("test", "0.0.0"), @@ -2194,7 +2239,11 @@ describe("DefaultQueueManager task metadata cache", () => { prisma, payloadProcessor: new MockPayloadProcessor(), queueConcern: queuesManager, - idempotencyKeyConcern: new IdempotencyKeyConcern(prisma, engine, new MockTraceEventConcern()), + idempotencyKeyConcern: new IdempotencyKeyConcern( + prisma, + engine, + new MockTraceEventConcern() + ), validator: new MockTriggerTaskValidator(), traceEventConcern: new MockTraceEventConcern(), tracer: trace.getTracer("test", "0.0.0"), diff --git a/apps/webapp/test/environmentSort.test.ts b/apps/webapp/test/environmentSort.test.ts index 5efed1235df..e47912fab01 100644 --- a/apps/webapp/test/environmentSort.test.ts +++ b/apps/webapp/test/environmentSort.test.ts @@ -15,12 +15,7 @@ describe("sortEnvironments", () => { { type: "STAGING" }, ]); - expect(sorted.map((e) => e.type)).toEqual([ - "DEVELOPMENT", - "STAGING", - "PREVIEW", - "PRODUCTION", - ]); + expect(sorted.map((e) => e.type)).toEqual(["DEVELOPMENT", "STAGING", "PREVIEW", "PRODUCTION"]); }); it("sorts same-type rows by lastActivity desc when both have it", () => { @@ -95,10 +90,7 @@ describe("filterOrphanedEnvironments", () => { { type: "PRODUCTION" } as any, ]); - expect(result).toEqual([ - { type: "DEVELOPMENT", orgMemberId: "om_1" }, - { type: "PRODUCTION" }, - ]); + expect(result).toEqual([{ type: "DEVELOPMENT", orgMemberId: "om_1" }, { type: "PRODUCTION" }]); }); it("keeps DEVELOPMENT envs whose orgMember relation is loaded", () => { @@ -121,9 +113,6 @@ describe("onlyDevEnvironments / exceptDevEnvironments", () => { it("partitions on the development type", () => { expect(onlyDevEnvironments([...envs])).toEqual([{ type: "DEVELOPMENT" }]); - expect(exceptDevEnvironments([...envs])).toEqual([ - { type: "PREVIEW" }, - { type: "PRODUCTION" }, - ]); + expect(exceptDevEnvironments([...envs])).toEqual([{ type: "PREVIEW" }, { type: "PRODUCTION" }]); }); }); diff --git a/apps/webapp/test/environmentVariablesEnvironments.test.ts b/apps/webapp/test/environmentVariablesEnvironments.test.ts index 2555bcae0f7..9a21506f7be 100644 --- a/apps/webapp/test/environmentVariablesEnvironments.test.ts +++ b/apps/webapp/test/environmentVariablesEnvironments.test.ts @@ -11,9 +11,7 @@ vi.mock("~/db.server", () => ({ fnOrOptions?: ((tx: unknown) => Promise) | unknown ) => { const fn = - typeof nameOrFn === "string" - ? (fnOrOptions as (tx: unknown) => Promise) - : nameOrFn; + typeof nameOrFn === "string" ? (fnOrOptions as (tx: unknown) => Promise) : nameOrFn; return prismaClient.$transaction(fn); }, @@ -45,7 +43,9 @@ describe("loadEnvironmentVariablesEnvironments", () => { }); expect(result.environments.map((environment) => environment.id)).toContain(production.id); - expect(result.environments.every((environment) => typeof environment.id === "string")).toBe(true); + expect(result.environments.every((environment) => typeof environment.id === "string")).toBe( + true + ); }); postgresTest("rejects users who are not project members", async ({ prisma }) => { @@ -125,20 +125,23 @@ describe("loadEnvironmentVariablesEnvironments", () => { expect(result.hasStaging).toBe(true); }); - postgresTest("returns hasStaging false when no staging environment exists", async ({ prisma }) => { - const { user, organization, project } = await createTestOrgProjectWithMember(prisma); + postgresTest( + "returns hasStaging false when no staging environment exists", + async ({ prisma }) => { + const { user, organization, project } = await createTestOrgProjectWithMember(prisma); - await createRuntimeEnvironment(prisma, { - projectId: project.id, - organizationId: organization.id, - type: "PRODUCTION", - }); + await createRuntimeEnvironment(prisma, { + projectId: project.id, + organizationId: organization.id, + type: "PRODUCTION", + }); - const result = await loadEnvironmentVariablesEnvironments(prisma, { - userId: user.id, - projectId: project.id, - }); + const result = await loadEnvironmentVariablesEnvironments(prisma, { + userId: user.id, + projectId: project.id, + }); - expect(result.hasStaging).toBe(false); - }); + expect(result.hasStaging).toBe(false); + } + ); }); diff --git a/apps/webapp/test/environmentVariablesRepository.test.ts b/apps/webapp/test/environmentVariablesRepository.test.ts index ead865eb5da..4025d8fd2eb 100644 --- a/apps/webapp/test/environmentVariablesRepository.test.ts +++ b/apps/webapp/test/environmentVariablesRepository.test.ts @@ -11,9 +11,7 @@ vi.mock("~/db.server", () => ({ fnOrOptions?: ((tx: unknown) => Promise) | unknown ) => { const fn = - typeof nameOrFn === "string" - ? (fnOrOptions as (tx: unknown) => Promise) - : nameOrFn; + typeof nameOrFn === "string" ? (fnOrOptions as (tx: unknown) => Promise) : nameOrFn; return prismaClient.$transaction(fn); }, @@ -58,46 +56,49 @@ describe("EnvironmentVariablesRepository.getVariableValuesForKeys", () => { expect(result.has(`${environment.id}:DOES_NOT_EXIST`)).toBe(false); }); - postgresTest("returns requested values with correct map keys and decrypted values", async ({ prisma }) => { - const { user, organization, project } = await createTestOrgProjectWithMember(prisma); - const environment = await createRuntimeEnvironment(prisma, { - projectId: project.id, - organizationId: organization.id, - type: "PRODUCTION", - }); - - const repository = new EnvironmentVariablesRepository(prisma, prisma); - - await createEnvironmentVariable(repository, project.id, { - environmentId: environment.id, - key: "VAR_A", - value: "value-a", - userId: user.id, - }); - await createEnvironmentVariable(repository, project.id, { - environmentId: environment.id, - key: "VAR_B", - value: "value-b", - userId: user.id, - }); - await createEnvironmentVariable(repository, project.id, { - environmentId: environment.id, - key: "VAR_C", - value: "value-c", - userId: user.id, - }); - - const result = await repository.getVariableValuesForKeys(project.id, [ - { environmentId: environment.id, key: "VAR_A" }, - { environmentId: environment.id, key: "VAR_C" }, - ]); - - expect(result).toBeInstanceOf(Map); - expect(result.size).toBe(2); - expect(result.get(`${environment.id}:VAR_A`)).toBe("value-a"); - expect(result.get(`${environment.id}:VAR_C`)).toBe("value-c"); - expect(result.has(`${environment.id}:VAR_B`)).toBe(false); - }); + postgresTest( + "returns requested values with correct map keys and decrypted values", + async ({ prisma }) => { + const { user, organization, project } = await createTestOrgProjectWithMember(prisma); + const environment = await createRuntimeEnvironment(prisma, { + projectId: project.id, + organizationId: organization.id, + type: "PRODUCTION", + }); + + const repository = new EnvironmentVariablesRepository(prisma, prisma); + + await createEnvironmentVariable(repository, project.id, { + environmentId: environment.id, + key: "VAR_A", + value: "value-a", + userId: user.id, + }); + await createEnvironmentVariable(repository, project.id, { + environmentId: environment.id, + key: "VAR_B", + value: "value-b", + userId: user.id, + }); + await createEnvironmentVariable(repository, project.id, { + environmentId: environment.id, + key: "VAR_C", + value: "value-c", + userId: user.id, + }); + + const result = await repository.getVariableValuesForKeys(project.id, [ + { environmentId: environment.id, key: "VAR_A" }, + { environmentId: environment.id, key: "VAR_C" }, + ]); + + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(2); + expect(result.get(`${environment.id}:VAR_A`)).toBe("value-a"); + expect(result.get(`${environment.id}:VAR_C`)).toBe("value-c"); + expect(result.has(`${environment.id}:VAR_B`)).toBe(false); + } + ); postgresTest("deduplicates duplicate environmentId and key requests", async ({ prisma }) => { const { user, organization, project } = await createTestOrgProjectWithMember(prisma); @@ -117,7 +118,11 @@ describe("EnvironmentVariablesRepository.getVariableValuesForKeys", () => { }); const request = { environmentId: environment.id, key: "DEDUP_KEY" }; - const result = await repository.getVariableValuesForKeys(project.id, [request, request, request]); + const result = await repository.getVariableValuesForKeys(project.id, [ + request, + request, + request, + ]); expect(result.size).toBe(1); expect(result.get(`${environment.id}:DEDUP_KEY`)).toBe("dedup-value"); diff --git a/apps/webapp/test/errorFingerprinting.test.ts b/apps/webapp/test/errorFingerprinting.test.ts index 4d68fd87e85..b4788252933 100644 --- a/apps/webapp/test/errorFingerprinting.test.ts +++ b/apps/webapp/test/errorFingerprinting.test.ts @@ -156,18 +156,13 @@ describe("normalizeErrorMessage", () => { }); it("message with both a URL and a timestamp", () => { - const message = - "Request to https://api.example.com/data failed at 2025-06-15T10:30:00Z"; - expect(normalizeErrorMessage(message)).toBe( - "Request to failed at " - ); + const message = "Request to https://api.example.com/data failed at 2025-06-15T10:30:00Z"; + expect(normalizeErrorMessage(message)).toBe("Request to failed at "); }); it("message with a URL and a unix timestamp", () => { const message = "Callback to https://example.com/hook timed out after 1700000000"; - expect(normalizeErrorMessage(message)).toBe( - "Callback to timed out after " - ); + expect(normalizeErrorMessage(message)).toBe("Callback to timed out after "); }); it("path-like string that is NOT a URL should still become ", () => { diff --git a/apps/webapp/test/fairDequeuingStrategy.test.ts b/apps/webapp/test/fairDequeuingStrategy.test.ts index 0d8b708161a..f5b96cf5e88 100644 --- a/apps/webapp/test/fairDequeuingStrategy.test.ts +++ b/apps/webapp/test/fairDequeuingStrategy.test.ts @@ -527,14 +527,17 @@ describe("FairDequeuingStrategy", () => { 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); + 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 diff --git a/apps/webapp/test/findEnvironmentByApiKey.test.ts b/apps/webapp/test/findEnvironmentByApiKey.test.ts index 22477207b56..5bbc1e97834 100644 --- a/apps/webapp/test/findEnvironmentByApiKey.test.ts +++ b/apps/webapp/test/findEnvironmentByApiKey.test.ts @@ -40,54 +40,57 @@ async function createEnv( } describe("findEnvironmentByApiKey — DEVELOPMENT branch resolution", () => { - postgresTest("resolves the full dev auth matrix from the parent's api key", async ({ prisma }) => { - const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma); - - // The existing per-member dev env IS the default branch (no branchName). - const devRoot = await createEnv(prisma, project.id, organization.id, { - type: "DEVELOPMENT", - orgMemberId: orgMember.id, - }); - - const namedBranch = await createEnv(prisma, project.id, organization.id, { - type: "DEVELOPMENT", - orgMemberId: orgMember.id, - parentEnvironmentId: devRoot.id, - branchName: "my-feature", - }); - - await createEnv(prisma, project.id, organization.id, { - type: "DEVELOPMENT", - orgMemberId: orgMember.id, - parentEnvironmentId: devRoot.id, - branchName: "archived-feature", - archivedAt: new Date(), - }); - - // No header → the root dev env (unchanged, day-one behaviour). - const noHeader = await findEnvironmentByApiKey(devRoot.apiKey, undefined, prisma); - expect(noHeader?.id).toBe(devRoot.id); - - // "default" sentinel → also the root dev env. - const defaultHeader = await findEnvironmentByApiKey(devRoot.apiKey, "default", prisma); - expect(defaultHeader?.id).toBe(devRoot.id); - - // A named branch that exists → the child env... - const child = await findEnvironmentByApiKey(devRoot.apiKey, "my-feature", prisma); - expect(child?.id).toBe(namedBranch.id); - expect(child?.branchName).toBe("my-feature"); - // ...but carrying the PARENT's api key and ownership, not the child's own key. - expect(child?.apiKey).toBe(devRoot.apiKey); - expect(child?.orgMemberId).toBe(orgMember.id); - - // A named branch that doesn't exist → null (not a silent fall-through to root). - const missing = await findEnvironmentByApiKey(devRoot.apiKey, "does-not-exist", prisma); - expect(missing).toBeNull(); - - // An archived branch → null (archivedAt filter on the child include). - const archived = await findEnvironmentByApiKey(devRoot.apiKey, "archived-feature", prisma); - expect(archived).toBeNull(); - }); + postgresTest( + "resolves the full dev auth matrix from the parent's api key", + async ({ prisma }) => { + const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma); + + // The existing per-member dev env IS the default branch (no branchName). + const devRoot = await createEnv(prisma, project.id, organization.id, { + type: "DEVELOPMENT", + orgMemberId: orgMember.id, + }); + + const namedBranch = await createEnv(prisma, project.id, organization.id, { + type: "DEVELOPMENT", + orgMemberId: orgMember.id, + parentEnvironmentId: devRoot.id, + branchName: "my-feature", + }); + + await createEnv(prisma, project.id, organization.id, { + type: "DEVELOPMENT", + orgMemberId: orgMember.id, + parentEnvironmentId: devRoot.id, + branchName: "archived-feature", + archivedAt: new Date(), + }); + + // No header → the root dev env (unchanged, day-one behaviour). + const noHeader = await findEnvironmentByApiKey(devRoot.apiKey, undefined, prisma); + expect(noHeader?.id).toBe(devRoot.id); + + // "default" sentinel → also the root dev env. + const defaultHeader = await findEnvironmentByApiKey(devRoot.apiKey, "default", prisma); + expect(defaultHeader?.id).toBe(devRoot.id); + + // A named branch that exists → the child env... + const child = await findEnvironmentByApiKey(devRoot.apiKey, "my-feature", prisma); + expect(child?.id).toBe(namedBranch.id); + expect(child?.branchName).toBe("my-feature"); + // ...but carrying the PARENT's api key and ownership, not the child's own key. + expect(child?.apiKey).toBe(devRoot.apiKey); + expect(child?.orgMemberId).toBe(orgMember.id); + + // A named branch that doesn't exist → null (not a silent fall-through to root). + const missing = await findEnvironmentByApiKey(devRoot.apiKey, "does-not-exist", prisma); + expect(missing).toBeNull(); + + // An archived branch → null (archivedAt filter on the child include). + const archived = await findEnvironmentByApiKey(devRoot.apiKey, "archived-feature", prisma); + expect(archived).toBeNull(); + } + ); postgresTest("a branch name is sanitized before lookup", async ({ prisma }) => { const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma); @@ -114,39 +117,45 @@ describe("findEnvironmentByApiKey — DEVELOPMENT branch resolution", () => { }); describe("findEnvironmentByApiKey — PREVIEW (regression guard)", () => { - postgresTest("preview still requires a branch and never resolves the parent", async ({ prisma }) => { - const { organization, project } = await createTestOrgProjectWithMember(prisma); - - const previewParent = await createEnv(prisma, project.id, organization.id, { - type: "PREVIEW", - isBranchableEnvironment: true, - }); - const previewBranch = await createEnv(prisma, project.id, organization.id, { - type: "PREVIEW", - parentEnvironmentId: previewParent.id, - branchName: "pr-123", - }); - - // No header on a preview key → null (preview has no default). - const noHeader = await findEnvironmentByApiKey(previewParent.apiKey, undefined, prisma); - expect(noHeader).toBeNull(); - - // With a branch → the child, carrying the parent's api key. - const resolved = await findEnvironmentByApiKey(previewParent.apiKey, "pr-123", prisma); - expect(resolved?.id).toBe(previewBranch.id); - expect(resolved?.apiKey).toBe(previewParent.apiKey); - }); + postgresTest( + "preview still requires a branch and never resolves the parent", + async ({ prisma }) => { + const { organization, project } = await createTestOrgProjectWithMember(prisma); + + const previewParent = await createEnv(prisma, project.id, organization.id, { + type: "PREVIEW", + isBranchableEnvironment: true, + }); + const previewBranch = await createEnv(prisma, project.id, organization.id, { + type: "PREVIEW", + parentEnvironmentId: previewParent.id, + branchName: "pr-123", + }); + + // No header on a preview key → null (preview has no default). + const noHeader = await findEnvironmentByApiKey(previewParent.apiKey, undefined, prisma); + expect(noHeader).toBeNull(); + + // With a branch → the child, carrying the parent's api key. + const resolved = await findEnvironmentByApiKey(previewParent.apiKey, "pr-123", prisma); + expect(resolved?.id).toBe(previewBranch.id); + expect(resolved?.apiKey).toBe(previewParent.apiKey); + } + ); }); describe("findEnvironmentByApiKey — non-branchable", () => { - postgresTest("a production key ignores the branch header and returns itself", async ({ prisma }) => { - const { organization, project } = await createTestOrgProjectWithMember(prisma); + postgresTest( + "a production key ignores the branch header and returns itself", + async ({ prisma }) => { + const { organization, project } = await createTestOrgProjectWithMember(prisma); - const prod = await createEnv(prisma, project.id, organization.id, { type: "PRODUCTION" }); + const prod = await createEnv(prisma, project.id, organization.id, { type: "PRODUCTION" }); - const resolved = await findEnvironmentByApiKey(prod.apiKey, "some-branch", prisma); - expect(resolved?.id).toBe(prod.id); - }); + const resolved = await findEnvironmentByApiKey(prod.apiKey, "some-branch", prisma); + expect(resolved?.id).toBe(prod.id); + } + ); postgresTest("an unknown api key returns null", async ({ prisma }) => { const resolved = await findEnvironmentByApiKey("tr_dev_nonexistent", undefined, prisma); diff --git a/apps/webapp/test/healthcheck-require-plugins.e2e.test.ts b/apps/webapp/test/healthcheck-require-plugins.e2e.test.ts index 17e02ce8d93..e16801babb2 100644 --- a/apps/webapp/test/healthcheck-require-plugins.e2e.test.ts +++ b/apps/webapp/test/healthcheck-require-plugins.e2e.test.ts @@ -23,13 +23,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import type { TestServer } from "@internal/testcontainers/webapp"; import { startTestServer } from "@internal/testcontainers/webapp"; -const LINKED_PLUGIN_PATH = resolve( - __dirname, - "..", - "node_modules", - "@triggerdotdev", - "plugins" -); +const LINKED_PLUGIN_PATH = resolve(__dirname, "..", "node_modules", "@triggerdotdev", "plugins"); const pluginLocallyLinked = existsSync(LINKED_PLUGIN_PATH); vi.setConfig({ testTimeout: 180_000 }); @@ -63,10 +57,7 @@ describe("/healthcheck with REQUIRE_PLUGINS", () => { describe.runIf(pluginLocallyLinked)( "REQUIRE_PLUGINS=1 + plugin LOCALLY LINKED (cross-repo dev setup)", () => { - it.skip( - `skipped because ${LINKED_PLUGIN_PATH} exists — plugin would load successfully. Run \`pnpm dev:unlink-webapp\` to exercise this case locally; CI runs it without the link.`, - () => {} - ); + it.skip(`skipped because ${LINKED_PLUGIN_PATH} exists — plugin would load successfully. Run \`pnpm dev:unlink-webapp\` to exercise this case locally; CI runs it without the link.`, () => {}); } ); diff --git a/apps/webapp/test/helpers/seedTestApiSession.ts b/apps/webapp/test/helpers/seedTestApiSession.ts index cb98c1798c9..b20c21b9df9 100644 --- a/apps/webapp/test/helpers/seedTestApiSession.ts +++ b/apps/webapp/test/helpers/seedTestApiSession.ts @@ -10,7 +10,9 @@ import type { PrismaClient, Session } from "@trigger.dev/database"; import { randomBytes } from "node:crypto"; function randomHex(len = 12): string { - return randomBytes(Math.ceil(len / 2)).toString("hex").slice(0, len); + return randomBytes(Math.ceil(len / 2)) + .toString("hex") + .slice(0, len); } export async function seedTestApiSession( @@ -32,9 +34,7 @@ export async function seedTestApiSession( // (single-id auth resource); omit the override to get a unique // externalId for the multi-key path. externalId: - overrides?.externalId === null - ? null - : overrides?.externalId ?? `ext_${suffix}`, + overrides?.externalId === null ? null : (overrides?.externalId ?? `ext_${suffix}`), type: "chat.agent", projectId: env.projectId, runtimeEnvironmentId: env.id, diff --git a/apps/webapp/test/helpers/seedTestEnvironment.ts b/apps/webapp/test/helpers/seedTestEnvironment.ts index 670927cc626..beca226134c 100644 --- a/apps/webapp/test/helpers/seedTestEnvironment.ts +++ b/apps/webapp/test/helpers/seedTestEnvironment.ts @@ -2,7 +2,9 @@ import type { PrismaClient } from "@trigger.dev/database"; import { randomBytes } from "crypto"; function randomHex(len = 12): string { - return randomBytes(Math.ceil(len / 2)).toString("hex").slice(0, len); + return randomBytes(Math.ceil(len / 2)) + .toString("hex") + .slice(0, len); } export async function seedTestEnvironment(prisma: PrismaClient) { diff --git a/apps/webapp/test/helpers/seedTestUserProject.ts b/apps/webapp/test/helpers/seedTestUserProject.ts index 3512054ec1f..cf8ae862613 100644 --- a/apps/webapp/test/helpers/seedTestUserProject.ts +++ b/apps/webapp/test/helpers/seedTestUserProject.ts @@ -3,7 +3,9 @@ import { randomBytes } from "node:crypto"; import { seedTestPAT, seedTestUser } from "./seedTestPAT"; function randomHex(len = 12): string { - return randomBytes(Math.ceil(len / 2)).toString("hex").slice(0, len); + return randomBytes(Math.ceil(len / 2)) + .toString("hex") + .slice(0, len); } // Composite test fixture: a User, an Organization with that user as a diff --git a/apps/webapp/test/metadataRouteOperationsLogging.test.ts b/apps/webapp/test/metadataRouteOperationsLogging.test.ts index ab96c9b9b23..b7e5f860198 100644 --- a/apps/webapp/test/metadataRouteOperationsLogging.test.ts +++ b/apps/webapp/test/metadataRouteOperationsLogging.test.ts @@ -26,7 +26,10 @@ vi.mock("~/services/apiAuth.server", () => ({ })); vi.mock("~/v3/services/common.server", () => ({ ServiceValidationError: class extends Error { - constructor(public override message: string, public status?: number) { + constructor( + public override message: string, + public status?: number + ) { super(message); } }, @@ -77,7 +80,7 @@ describe("routeOperationsToRun — non-throw buffer outcome logging", () => { expect(warnSpy).toHaveBeenCalledWith( "metadata route: parent/root buffer op did not apply", - expect.objectContaining({ targetRunId: "run_buffered_1", kind }), + expect.objectContaining({ targetRunId: "run_buffered_1", kind }) ); }); } @@ -95,7 +98,7 @@ describe("routeOperationsToRun — non-throw buffer outcome logging", () => { expect(warnSpy).not.toHaveBeenCalledWith( "metadata route: parent/root buffer op did not apply", - expect.anything(), + expect.anything() ); }); @@ -110,11 +113,11 @@ describe("routeOperationsToRun — non-throw buffer outcome logging", () => { // early on bufferError). expect(warnSpy).toHaveBeenCalledWith( "metadata route: buffer fallback for parent/root op failed", - expect.objectContaining({ targetRunId: "run_buffered_1" }), + expect.objectContaining({ targetRunId: "run_buffered_1" }) ); expect(warnSpy).not.toHaveBeenCalledWith( "metadata route: parent/root buffer op did not apply", - expect.anything(), + expect.anything() ); }); diff --git a/apps/webapp/test/mollifierApplyMetadataMutation.test.ts b/apps/webapp/test/mollifierApplyMetadataMutation.test.ts index 5995f6969f3..474c9864cd9 100644 --- a/apps/webapp/test/mollifierApplyMetadataMutation.test.ts +++ b/apps/webapp/test/mollifierApplyMetadataMutation.test.ts @@ -50,11 +50,13 @@ function makeBufferStub(initialPayload: Record = {}): BufferStu }; const buffer: MollifierBuffer = { - getEntry: vi.fn(async (): Promise => ({ - ...entryTemplate, - metadataVersion: state.version, - payload: JSON.stringify({ ...initialPayload, metadata: JSON.stringify(state.metadata) }), - })), + getEntry: vi.fn( + async (): Promise => ({ + ...entryTemplate, + metadataVersion: state.version, + payload: JSON.stringify({ ...initialPayload, metadata: JSON.stringify(state.metadata) }), + }) + ), casSetMetadata: vi.fn( async (input: { runId: string; @@ -75,7 +77,7 @@ function makeBufferStub(initialPayload: Record = {}): BufferStu state.metadata = JSON.parse(input.newMetadata) as Record; state.version += 1; return { kind: "applied", newVersion: state.version }; - }, + } ), } as unknown as MollifierBuffer; @@ -119,7 +121,10 @@ describe("applyMetadataMutationToBufferedRun — retry behaviour", () => { const { buffer } = makeBufferStub(); const setStateConflicts = (n: number) => { // Re-read state from the closure - const state = (buffer as unknown as { __state__?: never; getEntry: () => Promise }); + const state = buffer as unknown as { + __state__?: never; + getEntry: () => Promise; + }; void state; }; void setStateConflicts; @@ -342,7 +347,7 @@ describe("applyMetadataMutationToBufferedRun — retry behaviour", () => { maximumSize: 1024 * 1024, body: { operations: [{ type: "increment", key: "counter", value: 1 }] }, buffer: sharedStub.buffer, - }), + }) ); const results = await Promise.all(calls); const applied = results.filter((r) => r.kind === "applied").length; diff --git a/apps/webapp/test/mollifierClaimResolution.test.ts b/apps/webapp/test/mollifierClaimResolution.test.ts index f61cda0d04e..7a2a0c1e546 100644 --- a/apps/webapp/test/mollifierClaimResolution.test.ts +++ b/apps/webapp/test/mollifierClaimResolution.test.ts @@ -31,7 +31,7 @@ function makeConcern(prisma: { findFirst: () => Promise }) { return new IdempotencyKeyConcern( { taskRun: { findFirst: prisma.findFirst } } as never, {} as never, // engine — unused on this path - {} as never, // traceEventConcern — unused on this path + {} as never // traceEventConcern — unused on this path ); } diff --git a/apps/webapp/test/mollifierDecisionLabels.test.ts b/apps/webapp/test/mollifierDecisionLabels.test.ts index 8206edabb0b..69c67c5af75 100644 --- a/apps/webapp/test/mollifierDecisionLabels.test.ts +++ b/apps/webapp/test/mollifierDecisionLabels.test.ts @@ -27,7 +27,7 @@ describe("decisionLabels", () => { // Enrolled: org label present. expect( - decisionLabels("mollify", { enrolled: true, orgId: "org_1", reason: "per_env_rate" }), + decisionLabels("mollify", { enrolled: true, orgId: "org_1", reason: "per_env_rate" }) ).toEqual({ outcome: "mollify", enrolled: "true", @@ -45,10 +45,10 @@ describe("decisionLabels", () => { it("includes `reason` only when supplied", () => { expect(decisionLabels("pass_through", { enrolled: true, orgId: "org_1" })).not.toHaveProperty( - "reason", + "reason" + ); + expect(decisionLabels("shadow_log", { enrolled: false, reason: "per_env_rate" })).toMatchObject( + { reason: "per_env_rate" } ); - expect( - decisionLabels("shadow_log", { enrolled: false, reason: "per_env_rate" }), - ).toMatchObject({ reason: "per_env_rate" }); }); }); diff --git a/apps/webapp/test/mollifierDrainerHandler.test.ts b/apps/webapp/test/mollifierDrainerHandler.test.ts index 085fab6418b..c4897f61651 100644 --- a/apps/webapp/test/mollifierDrainerHandler.test.ts +++ b/apps/webapp/test/mollifierDrainerHandler.test.ts @@ -138,7 +138,7 @@ describe("createDrainerHandler", () => { payload: { taskIdentifier: "t" }, attempts: 0, createdAt: new Date(), - } as any), + } as any) ).rejects.toThrow("Can't reach database server"); // Retryable: we do NOT write a SYSTEM_FAILURE row, the entry should // be requeued for another shot. @@ -173,7 +173,7 @@ describe("createDrainerHandler", () => { payload: { taskIdentifier: "t", environment: envFixture }, attempts: 0, createdAt: new Date(), - } as any), + } as any) ).resolves.toBeUndefined(); expect(trigger).toHaveBeenCalledOnce(); @@ -213,7 +213,7 @@ describe("createDrainerHandler", () => { }, attempts: 0, createdAt: new Date(), - } as any), + } as any) ).resolves.toBeUndefined(); expect(createFailedTaskRun).toHaveBeenCalledOnce(); @@ -244,7 +244,7 @@ describe("createDrainerHandler", () => { payload: { taskIdentifier: "t", environment: envFixture }, attempts: 0, createdAt: new Date(), - } as any), + } as any) ).rejects.toThrow("engine rejected the snapshot"); // Drainer's outer drainOne loop now decides retry vs buffer.fail. expect(createFailedTaskRun).toHaveBeenCalledOnce(); @@ -301,9 +301,7 @@ describe("createDrainerHandler", () => { // while the run keeps executing. const friendlyId = RunId.generate().friendlyId; const createCancelledRun = vi.fn(async () => { - throw new Error( - `createCancelledRun conflict: existing run ${friendlyId} has status PENDING` - ); + throw new Error(`createCancelledRun conflict: existing run ${friendlyId} has status PENDING`); }); const cancelRun = vi.fn(async () => ({ alreadyFinished: false })); const handler = createDrainerHandler({ @@ -462,7 +460,7 @@ describe("createDrainerHandler", () => { payload: { taskIdentifier: "t" /* no environment */ }, attempts: 0, createdAt: new Date(), - } as any), + } as any) ).rejects.toThrow("engine rejected the snapshot"); expect(createFailedTaskRun).not.toHaveBeenCalled(); }); diff --git a/apps/webapp/test/mollifierDrainerWorker.test.ts b/apps/webapp/test/mollifierDrainerWorker.test.ts index 0d4e931fd83..bf17b0e992c 100644 --- a/apps/webapp/test/mollifierDrainerWorker.test.ts +++ b/apps/webapp/test/mollifierDrainerWorker.test.ts @@ -28,7 +28,7 @@ import { initMollifierDrainerWorker } from "~/v3/mollifierDrainerWorker.server"; describe("initMollifierDrainerWorker error classification", () => { it("rethrows MollifierConfigurationError so the process can crash on misconfig", () => { const misconfig = new MollifierConfigurationError( - "TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS must be at least 1000ms below GRACEFUL_SHUTDOWN_TIMEOUT", + "TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS must be at least 1000ms below GRACEFUL_SHUTDOWN_TIMEOUT" ); expect(() => @@ -37,7 +37,7 @@ describe("initMollifierDrainerWorker error classification", () => { getDrainer: () => { throw misconfig; }, - }), + }) ).toThrow(MollifierConfigurationError); }); @@ -56,7 +56,7 @@ describe("initMollifierDrainerWorker error classification", () => { getDrainer: () => { throw cousin; }, - }), + }) ).toThrow(cousin); }); @@ -67,7 +67,7 @@ describe("initMollifierDrainerWorker error classification", () => { getDrainer: () => { throw new Error("transient redis blip during buffer init"); }, - }), + }) ).not.toThrow(); }); diff --git a/apps/webapp/test/mollifierGate.test.ts b/apps/webapp/test/mollifierGate.test.ts index 5f3f28e668c..1cc4626177b 100644 --- a/apps/webapp/test/mollifierGate.test.ts +++ b/apps/webapp/test/mollifierGate.test.ts @@ -26,7 +26,10 @@ import type { DecisionOutcome, DecisionReason } from "~/v3/mollifier/mollifierTe type Spies = { evaluatorCalls: number; logShadowCalls: Array<{ inputs: GateInputs; decision: Extract }>; - logMollifiedCalls: Array<{ inputs: GateInputs; decision: Extract }>; + logMollifiedCalls: Array<{ + inputs: GateInputs; + decision: Extract; + }>; recordDecisionCalls: Array<{ outcome: DecisionOutcome; reason?: DecisionReason; @@ -118,26 +121,250 @@ type Row = { // each row exercises so reviewers can map row → code at a glance. const cascade: Row[] = [ // enabled=F → kill-switch wins; evaluator+flag never consulted (rows 1-8) - { id: 1, enabled: false, shadow: false, flag: false, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 2, enabled: false, shadow: false, flag: false, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 3, enabled: false, shadow: false, flag: true, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 4, enabled: false, shadow: false, flag: true, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 5, enabled: false, shadow: true, flag: false, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 6, enabled: false, shadow: true, flag: false, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 7, enabled: false, shadow: true, flag: true, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 8, enabled: false, shadow: true, flag: true, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, + { + id: 1, + enabled: false, + shadow: false, + flag: false, + divert: false, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 2, + enabled: false, + shadow: false, + flag: false, + divert: true, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 3, + enabled: false, + shadow: false, + flag: true, + divert: false, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 4, + enabled: false, + shadow: false, + flag: true, + divert: true, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 5, + enabled: false, + shadow: true, + flag: false, + divert: false, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 6, + enabled: false, + shadow: true, + flag: false, + divert: true, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 7, + enabled: false, + shadow: true, + flag: true, + divert: false, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 8, + enabled: false, + shadow: true, + flag: true, + divert: true, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, // enabled=T, flag=F, shadow=F → both opt-ins off; evaluator never called (rows 9-10) - { id: 9, enabled: true, shadow: false, flag: false, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 10, enabled: true, shadow: false, flag: false, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, + { + id: 9, + enabled: true, + shadow: false, + flag: false, + divert: false, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 10, + enabled: true, + shadow: false, + flag: false, + divert: true, + expected: { + action: "pass_through", + evaluatorCalls: 0, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, // enabled=T, flag=F, shadow=T → shadow path; divert routes outcome (rows 11-12) - { id: 11, enabled: true, shadow: true, flag: false, divert: false, expected: { action: "pass_through", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 12, enabled: true, shadow: true, flag: false, divert: true, expected: { action: "shadow_log", evaluatorCalls: 1, logShadowCalls: 1, logMollifiedCalls: 0, recordedOutcome: "shadow_log", expectedReason: "per_env_rate" } }, + { + id: 11, + enabled: true, + shadow: true, + flag: false, + divert: false, + expected: { + action: "pass_through", + evaluatorCalls: 1, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 12, + enabled: true, + shadow: true, + flag: false, + divert: true, + expected: { + action: "shadow_log", + evaluatorCalls: 1, + logShadowCalls: 1, + logMollifiedCalls: 0, + recordedOutcome: "shadow_log", + expectedReason: "per_env_rate", + }, + }, // enabled=T, flag=T, shadow=F → mollify path (rows 13-14) - { id: 13, enabled: true, shadow: false, flag: true, divert: false, expected: { action: "pass_through", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 14, enabled: true, shadow: false, flag: true, divert: true, expected: { action: "mollify", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 1, recordedOutcome: "mollify", expectedReason: "per_env_rate" } }, + { + id: 13, + enabled: true, + shadow: false, + flag: true, + divert: false, + expected: { + action: "pass_through", + evaluatorCalls: 1, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 14, + enabled: true, + shadow: false, + flag: true, + divert: true, + expected: { + action: "mollify", + evaluatorCalls: 1, + logShadowCalls: 0, + logMollifiedCalls: 1, + recordedOutcome: "mollify", + expectedReason: "per_env_rate", + }, + }, // enabled=T, flag=T, shadow=T → flag wins over shadow (rows 15-16) - { id: 15, enabled: true, shadow: true, flag: true, divert: false, expected: { action: "pass_through", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined } }, - { id: 16, enabled: true, shadow: true, flag: true, divert: true, expected: { action: "mollify", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 1, recordedOutcome: "mollify", expectedReason: "per_env_rate" } }, + { + id: 15, + enabled: true, + shadow: true, + flag: true, + divert: false, + expected: { + action: "pass_through", + evaluatorCalls: 1, + logShadowCalls: 0, + logMollifiedCalls: 0, + recordedOutcome: "pass_through", + expectedReason: undefined, + }, + }, + { + id: 16, + enabled: true, + shadow: true, + flag: true, + divert: true, + expected: { + action: "mollify", + evaluatorCalls: 1, + logShadowCalls: 0, + logMollifiedCalls: 1, + recordedOutcome: "mollify", + expectedReason: "per_env_rate", + }, + }, ]; describe("evaluateGate cascade — exhaustive truth table", () => { @@ -168,7 +395,7 @@ describe("evaluateGate cascade — exhaustive truth table", () => { // mollifierDecisionLabels.test.ts). expect(spies.recordDecisionCalls[0].enrolled).toBe(row.flag); expect(spies.recordDecisionCalls[0].orgId).toBe(inputs.orgId); - }, + } ); it("divert log carries the full decision (envId, orgId, taskId, reason, count, threshold, windowMs, holdMs)", async () => { @@ -345,9 +572,10 @@ describe("evaluateGate — fail open on resolveOrgFlag error", () => { }); describe("evaluateGate — per-org isolation via Organization.featureFlags", () => { - function makeIsolationDeps( - resolveOrgFlag: GateDependencies["resolveOrgFlag"], - ): { deps: Partial; spies: Spies } { + function makeIsolationDeps(resolveOrgFlag: GateDependencies["resolveOrgFlag"]): { + deps: Partial; + spies: Spies; + } { const spies: Spies = { evaluatorCalls: 0, logShadowCalls: [], @@ -486,10 +714,7 @@ describe("evaluateGate — debounce / OTU / triggerAndWait bypasses", () => { flag: true, decision: trippedDecision, }); - const outcome = await evaluateGate( - { ...inputs, options: { debounce: { key: "k" } } }, - deps, - ); + const outcome = await evaluateGate({ ...inputs, options: { debounce: { key: "k" } } }, deps); expect(outcome).toEqual({ action: "pass_through" }); expect(spies.evaluatorCalls).toBe(0); }); @@ -503,7 +728,7 @@ describe("evaluateGate — debounce / OTU / triggerAndWait bypasses", () => { }); const outcome = await evaluateGate( { ...inputs, options: { oneTimeUseToken: "jwt-otu" } }, - deps, + deps ); expect(outcome).toEqual({ action: "pass_through" }); expect(spies.evaluatorCalls).toBe(0); @@ -521,7 +746,7 @@ describe("evaluateGate — debounce / OTU / triggerAndWait bypasses", () => { ...inputs, options: { parentTaskRunId: "run_parent", resumeParentOnCompletion: true }, }, - deps, + deps ); expect(outcome).toEqual({ action: "pass_through" }); expect(spies.evaluatorCalls).toBe(0); @@ -536,7 +761,7 @@ describe("evaluateGate — debounce / OTU / triggerAndWait bypasses", () => { }); const outcome = await evaluateGate( { ...inputs, options: { parentTaskRunId: "run_parent" } }, - deps, + deps ); expect(outcome.action).toBe("mollify"); expect(spies.evaluatorCalls).toBe(1); diff --git a/apps/webapp/test/mollifierIdempotencyClaim.test.ts b/apps/webapp/test/mollifierIdempotencyClaim.test.ts index 87c009cb1f7..1b093250f1e 100644 --- a/apps/webapp/test/mollifierIdempotencyClaim.test.ts +++ b/apps/webapp/test/mollifierIdempotencyClaim.test.ts @@ -2,15 +2,8 @@ import { describe, expect, it, vi } from "vitest"; vi.mock("~/db.server", () => ({ prisma: {}, $replica: {} })); -import { - claimOrAwait, - publishClaim, - releaseClaim, -} from "~/v3/mollifier/idempotencyClaim.server"; -import type { - IdempotencyClaimResult, - MollifierBuffer, -} from "@trigger.dev/redis-worker"; +import { claimOrAwait, publishClaim, releaseClaim } from "~/v3/mollifier/idempotencyClaim.server"; +import type { IdempotencyClaimResult, MollifierBuffer } from "@trigger.dev/redis-worker"; type ClaimState = { value: string | null; @@ -190,7 +183,7 @@ describe("publishClaim", () => { it("no-op when buffer is null", async () => { await expect( - publishClaim({ ...baseInput, token: "owner-token", runId: "run_X", buffer: null }), + publishClaim({ ...baseInput, token: "owner-token", runId: "run_X", buffer: null }) ).resolves.toBeUndefined(); }); @@ -201,7 +194,7 @@ describe("publishClaim", () => { }), } as unknown as MollifierBuffer; await expect( - publishClaim({ ...baseInput, token: "owner-token", runId: "run_X", buffer }), + publishClaim({ ...baseInput, token: "owner-token", runId: "run_X", buffer }) ).resolves.toBeUndefined(); }); }); @@ -214,7 +207,9 @@ describe("releaseClaim", () => { }); it("no-op when buffer is null", async () => { - await expect(releaseClaim({ ...baseInput, token: "owner-token", buffer: null })).resolves.toBeUndefined(); + await expect( + releaseClaim({ ...baseInput, token: "owner-token", buffer: null }) + ).resolves.toBeUndefined(); }); }); @@ -250,7 +245,7 @@ describe("claim ownership token wiring", () => { expect.objectContaining({ token: "owner-token-xyz", runId: "run_X", - }), + }) ); }); @@ -262,7 +257,7 @@ describe("claim ownership token wiring", () => { buffer, }); expect(buffer.releaseClaim).toHaveBeenCalledWith( - expect.objectContaining({ token: "owner-token-xyz" }), + expect.objectContaining({ token: "owner-token-xyz" }) ); }); }); diff --git a/apps/webapp/test/mollifierMollify.test.ts b/apps/webapp/test/mollifierMollify.test.ts index ec7a30b49c2..fce4b93f64d 100644 --- a/apps/webapp/test/mollifierMollify.test.ts +++ b/apps/webapp/test/mollifierMollify.test.ts @@ -10,7 +10,7 @@ import { RunId } from "@trigger.dev/core/v3/isomorphic"; import type { MollifierBuffer } from "@trigger.dev/redis-worker"; function fakeBuffer( - acceptResult: Awaited> = { kind: "accepted" }, + acceptResult: Awaited> = { kind: "accepted" } ): { buffer: MollifierBuffer; accept: ReturnType } { const accept = vi.fn(async () => acceptResult); return { diff --git a/apps/webapp/test/mollifierMutateWithFallback.test.ts b/apps/webapp/test/mollifierMutateWithFallback.test.ts index 1102229f568..1414772a1a8 100644 --- a/apps/webapp/test/mollifierMutateWithFallback.test.ts +++ b/apps/webapp/test/mollifierMutateWithFallback.test.ts @@ -6,11 +6,7 @@ vi.mock("~/db.server", () => ({ })); import { mutateWithFallback } from "~/v3/mollifier/mutateWithFallback.server"; -import type { - BufferEntry, - MollifierBuffer, - MutateSnapshotResult, -} from "@trigger.dev/redis-worker"; +import type { BufferEntry, MollifierBuffer, MutateSnapshotResult } from "@trigger.dev/redis-worker"; import type { TaskRun } from "@trigger.dev/database"; type FindFirst = ReturnType; diff --git a/apps/webapp/test/mollifierReadFallback.test.ts b/apps/webapp/test/mollifierReadFallback.test.ts index feef6a420ad..2c439ea0bca 100644 --- a/apps/webapp/test/mollifierReadFallback.test.ts +++ b/apps/webapp/test/mollifierReadFallback.test.ts @@ -21,7 +21,7 @@ describe("findRunByIdWithMollifierFallback", () => { it("returns null when buffer is unavailable (mollifier disabled)", async () => { const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => null }, + { getBuffer: () => null } ); expect(result).toBeNull(); }); @@ -29,7 +29,7 @@ describe("findRunByIdWithMollifierFallback", () => { it("returns null when no buffer entry exists", async () => { const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(null) }, + { getBuffer: () => fakeBuffer(null) } ); expect(result).toBeNull(); }); @@ -46,7 +46,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result).toBeNull(); }); @@ -63,7 +63,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result).toBeNull(); }); @@ -80,7 +80,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.friendlyId).toBe("run_1"); @@ -101,7 +101,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.status).toBe("QUEUED"); }); @@ -119,7 +119,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.status).toBe("FAILED"); expect(result!.error).toEqual({ code: "VALIDATION", message: "task not found" }); @@ -154,7 +154,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.payloadType).toBe("application/json"); @@ -188,7 +188,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.traceId).toBe("trace_abc"); expect(result!.spanId).toBe("span_xyz"); @@ -222,7 +222,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.idempotencyKeyOptions).toEqual({ @@ -256,7 +256,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.idempotencyKeyOptions).toBeUndefined(); @@ -274,7 +274,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.payloadType).toBeUndefined(); expect(result!.metadata).toBeUndefined(); @@ -310,7 +310,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.id).toBeTypeOf("string"); @@ -344,7 +344,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.batchId).toBe("batch_internal_cuid"); }); @@ -361,7 +361,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.batchId).toBeUndefined(); }); @@ -383,7 +383,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.status).toBe("QUEUED"); @@ -410,7 +410,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.status).toBe("CANCELED"); expect(result!.cancelledAt).toEqual(new Date(cancelledAtIso)); @@ -430,7 +430,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.runtimeEnvironmentId).toBe("env_a"); expect(result!.workerQueue).toBeUndefined(); @@ -457,7 +457,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.batchId).toBe("batch_internal_xyz"); }); @@ -479,7 +479,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.batchId).toBeUndefined(); }); @@ -509,7 +509,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.parentTaskRunFriendlyId).toBe(parent.friendlyId); expect(result!.rootTaskRunFriendlyId).toBe(root.friendlyId); @@ -527,7 +527,7 @@ describe("findRunByIdWithMollifierFallback", () => { }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, - { getBuffer: () => fakeBuffer(entry) }, + { getBuffer: () => fakeBuffer(entry) } ); expect(result!.parentTaskRunFriendlyId).toBeUndefined(); expect(result!.rootTaskRunFriendlyId).toBeUndefined(); diff --git a/apps/webapp/test/mollifierReplayPayloadShape.test.ts b/apps/webapp/test/mollifierReplayPayloadShape.test.ts index d2f098d7086..e95fabaaf5a 100644 --- a/apps/webapp/test/mollifierReplayPayloadShape.test.ts +++ b/apps/webapp/test/mollifierReplayPayloadShape.test.ts @@ -51,9 +51,7 @@ describe("mollifier replay payload shape", () => { payloadType: "application/json", }; - const roundTripped = deserialiseMollifierSnapshot( - serialiseMollifierSnapshot(triggerInput), - ); + const roundTripped = deserialiseMollifierSnapshot(serialiseMollifierSnapshot(triggerInput)); // This is exactly the call the replay loader makes: // prettyPrintPacket(run.payload, run.payloadType) @@ -63,7 +61,7 @@ describe("mollifier replay payload shape", () => { // JSON. const pretty = await prettyPrintPacket( roundTripped.payload, - roundTripped.payloadType as string, + roundTripped.payloadType as string ); expect(pretty).toBe(JSON.stringify(original, null, 2)); diff --git a/apps/webapp/test/mollifierResetIdempotencyKey.test.ts b/apps/webapp/test/mollifierResetIdempotencyKey.test.ts index 4909087d70c..82280444c6b 100644 --- a/apps/webapp/test/mollifierResetIdempotencyKey.test.ts +++ b/apps/webapp/test/mollifierResetIdempotencyKey.test.ts @@ -90,7 +90,7 @@ describe("ResetIdempotencyKeyService — buffer-outage handling", () => { const error = await service.call("ikey", "task", env).then( () => null, - (err) => err, + (err) => err ); expect(error).toBeInstanceOf(ServiceValidationError); expect(error.status).toBe(503); diff --git a/apps/webapp/test/mollifierStaleSweep.test.ts b/apps/webapp/test/mollifierStaleSweep.test.ts index 94928611119..eee1da2501e 100644 --- a/apps/webapp/test/mollifierStaleSweep.test.ts +++ b/apps/webapp/test/mollifierStaleSweep.test.ts @@ -101,7 +101,7 @@ describe("runStaleSweepOnce — unit", () => { const spies = spyDeps(); const result = await runStaleSweepOnce( { staleThresholdMs: 1000 }, - { ...spies.deps, getBuffer: () => null, state: makeFakeState() }, + { ...spies.deps, getBuffer: () => null, state: makeFakeState() } ); expect(result).toEqual({ orgsScanned: 0, @@ -157,8 +157,8 @@ describe("runStaleSweepOnce — unit", () => { state: failingState, getBuffer: () => buffer, now: () => Date.now(), - }, - ), + } + ) ).rejects.toThrow("Redis read failed"); expect(readAttempts).toBe(1); @@ -220,7 +220,7 @@ describe("runStaleSweepOnce — testcontainers", () => { getBuffer: () => buffer, state, now: () => futureNow, - }, + } ); expect(result.envsScanned).toBe(2); @@ -250,7 +250,7 @@ describe("runStaleSweepOnce — testcontainers", () => { } finally { await buffer.close(); } - }, + } ); redisTest( @@ -278,7 +278,7 @@ describe("runStaleSweepOnce — testcontainers", () => { const spies = spyDeps(); await runStaleSweepOnce( { staleThresholdMs: 60 * 1000 }, - { ...spies.deps, getBuffer: () => buffer, state }, + { ...spies.deps, getBuffer: () => buffer, state } ); expect(spies.snapshots).toHaveLength(1); // env_a has entries but none stale → not in the snapshot. @@ -287,7 +287,7 @@ describe("runStaleSweepOnce — testcontainers", () => { await state.close(); await buffer.close(); } - }, + } ); redisTest( @@ -310,7 +310,7 @@ describe("runStaleSweepOnce — testcontainers", () => { const spies = spyDeps(); const result = await runStaleSweepOnce( { staleThresholdMs: 60 * 1000 }, - { ...spies.deps, getBuffer: () => buffer, state }, + { ...spies.deps, getBuffer: () => buffer, state } ); expect(result.staleCount).toBe(0); expect(spies.staleEntryCount).toBe(0); @@ -319,7 +319,7 @@ describe("runStaleSweepOnce — testcontainers", () => { await state.close(); await buffer.close(); } - }, + } ); redisTest( @@ -376,7 +376,7 @@ describe("runStaleSweepOnce — testcontainers", () => { await state.close(); await buffer.close(); } - }, + } ); redisTest( @@ -430,7 +430,7 @@ describe("runStaleSweepOnce — testcontainers", () => { await state.close(); await buffer.close(); } - }, + } ); redisTest( @@ -501,48 +501,44 @@ describe("runStaleSweepOnce — testcontainers", () => { await state.close(); await buffer.close(); } - }, + } ); - redisTest( - "scans across multiple orgs", - { timeout: 20_000 }, - async ({ redisOptions }) => { - // The drainer pops with org-level fairness, so the sweep must - // walk every org/env to surface stale entries across all of them - // — not just stop at the first env it finds. If a future refactor - // collapsed listOrgs/listEnvsForOrg into a single env-flat list, - // this test catches a regression there. - const buffer = new MollifierBuffer({ redisOptions }); - const state = new MollifierStaleSweepState({ redisOptions }); - try { - await buffer.accept({ - runId: "run_x", - envId: "env_x", - orgId: "org_x", - payload: JSON.stringify(SNAPSHOT), - }); - await buffer.accept({ - runId: "run_y", - envId: "env_y", - orgId: "org_y", - payload: JSON.stringify(SNAPSHOT), - }); - const futureNow = Date.now() + 5 * 60 * 1000; - const spies = spyDeps(); - const result = await runStaleSweepOnce( - { staleThresholdMs: 60 * 1000 }, - { ...spies.deps, getBuffer: () => buffer, state, now: () => futureNow }, - ); - expect(result.orgsScanned).toBe(2); - expect(result.envsScanned).toBe(2); - expect(result.staleCount).toBe(2); - } finally { - await state.close(); - await buffer.close(); - } - }, - ); + redisTest("scans across multiple orgs", { timeout: 20_000 }, async ({ redisOptions }) => { + // The drainer pops with org-level fairness, so the sweep must + // walk every org/env to surface stale entries across all of them + // — not just stop at the first env it finds. If a future refactor + // collapsed listOrgs/listEnvsForOrg into a single env-flat list, + // this test catches a regression there. + const buffer = new MollifierBuffer({ redisOptions }); + const state = new MollifierStaleSweepState({ redisOptions }); + try { + await buffer.accept({ + runId: "run_x", + envId: "env_x", + orgId: "org_x", + payload: JSON.stringify(SNAPSHOT), + }); + await buffer.accept({ + runId: "run_y", + envId: "env_y", + orgId: "org_y", + payload: JSON.stringify(SNAPSHOT), + }); + const futureNow = Date.now() + 5 * 60 * 1000; + const spies = spyDeps(); + const result = await runStaleSweepOnce( + { staleThresholdMs: 60 * 1000 }, + { ...spies.deps, getBuffer: () => buffer, state, now: () => futureNow } + ); + expect(result.orgsScanned).toBe(2); + expect(result.envsScanned).toBe(2); + expect(result.staleCount).toBe(2); + } finally { + await state.close(); + await buffer.close(); + } + }); redisTest( "state survives process restart: a second state instance picks up the cursor and counts", @@ -611,7 +607,7 @@ describe("runStaleSweepOnce — testcontainers", () => { await state2.close(); await buffer.close(); } - }, + } ); redisTest( @@ -672,7 +668,7 @@ describe("runStaleSweepOnce — testcontainers", () => { await state.close(); await buffer.close(); } - }, + } ); redisTest( @@ -690,7 +686,7 @@ describe("runStaleSweepOnce — testcontainers", () => { const spies = spyDeps(); const result = await runStaleSweepOnce( { staleThresholdMs: 60 * 1000, maxOrgsPerPass: 10 }, - { ...spies.deps, getBuffer: () => buffer, state }, + { ...spies.deps, getBuffer: () => buffer, state } ); expect(result).toEqual({ orgsScanned: 0, @@ -706,7 +702,7 @@ describe("runStaleSweepOnce — testcontainers", () => { await state.close(); await buffer.close(); } - }, + } ); redisTest( @@ -754,19 +750,23 @@ describe("runStaleSweepOnce — testcontainers", () => { await state.close(); await buffer.close(); } - }, + } ); }); describe("MollifierStaleSweepState — direct unit tests", () => { - redisTest("readCursor returns 0 when the key is absent", { timeout: 20_000 }, async ({ redisOptions }) => { - const state = new MollifierStaleSweepState({ redisOptions }); - try { - expect(await state.readCursor()).toBe(0); - } finally { - await state.close(); + redisTest( + "readCursor returns 0 when the key is absent", + { timeout: 20_000 }, + async ({ redisOptions }) => { + const state = new MollifierStaleSweepState({ redisOptions }); + try { + expect(await state.readCursor()).toBe(0); + } finally { + await state.close(); + } } - }); + ); redisTest( "writeCursor + readCursor round-trip; readCursor parses a non-numeric value as 0", @@ -784,7 +784,7 @@ describe("MollifierStaleSweepState — direct unit tests", () => { } finally { await state.close(); } - }, + } ); redisTest( @@ -812,7 +812,7 @@ describe("MollifierStaleSweepState — direct unit tests", () => { } finally { await state.close(); } - }, + } ); redisTest( @@ -834,7 +834,7 @@ describe("MollifierStaleSweepState — direct unit tests", () => { } finally { await state.close(); } - }, + } ); redisTest( @@ -855,7 +855,7 @@ describe("MollifierStaleSweepState — direct unit tests", () => { } finally { await state.close(); } - }, + } ); }); @@ -940,7 +940,7 @@ describe("startStaleSweepInterval — lifecycle", () => { reportStaleEntrySnapshot: () => {}, logger: { warn: () => {} }, now: () => Date.now(), - }, + } ); // Wait for the interval to fire one tick. The tick will start, call @@ -969,8 +969,6 @@ describe("startStaleSweepInterval — lifecycle", () => { // The tick's readCursor:end MUST appear before the close — otherwise // we closed the Redis client out from under an in-flight tick. expect(callOrder.indexOf("readCursor:end")).toBeGreaterThan(-1); - expect(callOrder.indexOf("close")).toBeGreaterThan( - callOrder.indexOf("readCursor:end"), - ); + expect(callOrder.indexOf("close")).toBeGreaterThan(callOrder.indexOf("readCursor:end")); }); }); diff --git a/apps/webapp/test/mollifierSynthesiseFoundRun.test.ts b/apps/webapp/test/mollifierSynthesiseFoundRun.test.ts index 4e2d6a61632..5c175e88898 100644 --- a/apps/webapp/test/mollifierSynthesiseFoundRun.test.ts +++ b/apps/webapp/test/mollifierSynthesiseFoundRun.test.ts @@ -120,9 +120,7 @@ describe("synthesiseFoundRunFromBuffer", () => { }); it("passes through an explicit workerQueue from the snapshot unchanged", () => { - const found = synthesiseFoundRunFromBuffer( - makeSyntheticRun({ workerQueue: "us-east-1" }) - ); + const found = synthesiseFoundRunFromBuffer(makeSyntheticRun({ workerQueue: "us-east-1" })); expect(found.workerQueue).toBe("us-east-1"); }); diff --git a/apps/webapp/test/mollifierSyntheticApiResponses.test.ts b/apps/webapp/test/mollifierSyntheticApiResponses.test.ts index 94ee67c8584..37be1dfac7a 100644 --- a/apps/webapp/test/mollifierSyntheticApiResponses.test.ts +++ b/apps/webapp/test/mollifierSyntheticApiResponses.test.ts @@ -77,9 +77,7 @@ describe("buildSyntheticSpanDetailBody", () => { }); it("defaults message to '' when the buffered run has no taskIdentifier", () => { - const body = buildSyntheticSpanDetailBody( - makeSyntheticRun({ taskIdentifier: undefined }) - ); + const body = buildSyntheticSpanDetailBody(makeSyntheticRun({ taskIdentifier: undefined })); expect(body.message).toBe(""); }); diff --git a/apps/webapp/test/mollifierSyntheticRedirectInfo.test.ts b/apps/webapp/test/mollifierSyntheticRedirectInfo.test.ts index a996b9de693..4ad1b6efe2a 100644 --- a/apps/webapp/test/mollifierSyntheticRedirectInfo.test.ts +++ b/apps/webapp/test/mollifierSyntheticRedirectInfo.test.ts @@ -22,36 +22,39 @@ function fakePrisma(member: { id: string } | null) { } describe("findBufferedRunRedirectInfo (testcontainers)", () => { - redisTest("returns slugs + spanId for a real buffer entry when user is a member", async ({ redisOptions }) => { - const buffer = new MollifierBuffer({ redisOptions }); - try { - await buffer.accept({ - runId: "run_real_1", - envId: "env_a", - orgId: "org_1", - payload: JSON.stringify(SNAPSHOT), - }); - const info = await findBufferedRunRedirectInfo( - { runFriendlyId: "run_real_1", userId: "user_1" }, - { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) }, - ); - expect(info).toEqual({ - organizationSlug: "references-6120", - projectSlug: "hello-world-bN7m", - environmentSlug: "dev", - spanId: "span_1", - }); - } finally { - await buffer.close(); + redisTest( + "returns slugs + spanId for a real buffer entry when user is a member", + async ({ redisOptions }) => { + const buffer = new MollifierBuffer({ redisOptions }); + try { + await buffer.accept({ + runId: "run_real_1", + envId: "env_a", + orgId: "org_1", + payload: JSON.stringify(SNAPSHOT), + }); + const info = await findBufferedRunRedirectInfo( + { runFriendlyId: "run_real_1", userId: "user_1" }, + { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) } + ); + expect(info).toEqual({ + organizationSlug: "references-6120", + projectSlug: "hello-world-bN7m", + environmentSlug: "dev", + spanId: "span_1", + }); + } finally { + await buffer.close(); + } } - }); + ); redisTest("returns null when no buffer entry exists for the runId", async ({ redisOptions }) => { const buffer = new MollifierBuffer({ redisOptions }); try { const info = await findBufferedRunRedirectInfo( { runFriendlyId: "run_missing", userId: "user_1" }, - { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) }, + { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) } ); expect(info).toBeNull(); } finally { @@ -59,48 +62,56 @@ describe("findBufferedRunRedirectInfo (testcontainers)", () => { } }); - redisTest("returns null when the user is not an org member (default check enforced)", async ({ redisOptions }) => { - const buffer = new MollifierBuffer({ redisOptions }); - try { - await buffer.accept({ - runId: "run_real_2", - envId: "env_a", - orgId: "org_1", - payload: JSON.stringify(SNAPSHOT), - }); - const info = await findBufferedRunRedirectInfo( - { runFriendlyId: "run_real_2", userId: "user_other" }, - { getBuffer: () => buffer, prismaClient: fakePrisma(null) }, - ); - expect(info).toBeNull(); - } finally { - await buffer.close(); + redisTest( + "returns null when the user is not an org member (default check enforced)", + async ({ redisOptions }) => { + const buffer = new MollifierBuffer({ redisOptions }); + try { + await buffer.accept({ + runId: "run_real_2", + envId: "env_a", + orgId: "org_1", + payload: JSON.stringify(SNAPSHOT), + }); + const info = await findBufferedRunRedirectInfo( + { runFriendlyId: "run_real_2", userId: "user_other" }, + { getBuffer: () => buffer, prismaClient: fakePrisma(null) } + ); + expect(info).toBeNull(); + } finally { + await buffer.close(); + } } - }); + ); - redisTest("skips the org-membership check when skipOrgMembershipCheck is set (admin path)", async ({ redisOptions }) => { - const buffer = new MollifierBuffer({ redisOptions }); - try { - await buffer.accept({ - runId: "run_real_3", - envId: "env_a", - orgId: "org_1", - payload: JSON.stringify(SNAPSHOT), - }); - const findFirst = vi.fn(); - const info = await findBufferedRunRedirectInfo( - { runFriendlyId: "run_real_3", userId: "user_admin", skipOrgMembershipCheck: true }, - { - getBuffer: () => buffer, - prismaClient: { orgMember: { findFirst } } as unknown as Parameters[1]["prismaClient"], - }, - ); - expect(info?.organizationSlug).toBe("references-6120"); - expect(findFirst).not.toHaveBeenCalled(); - } finally { - await buffer.close(); + redisTest( + "skips the org-membership check when skipOrgMembershipCheck is set (admin path)", + async ({ redisOptions }) => { + const buffer = new MollifierBuffer({ redisOptions }); + try { + await buffer.accept({ + runId: "run_real_3", + envId: "env_a", + orgId: "org_1", + payload: JSON.stringify(SNAPSHOT), + }); + const findFirst = vi.fn(); + const info = await findBufferedRunRedirectInfo( + { runFriendlyId: "run_real_3", userId: "user_admin", skipOrgMembershipCheck: true }, + { + getBuffer: () => buffer, + prismaClient: { orgMember: { findFirst } } as unknown as Parameters< + typeof findBufferedRunRedirectInfo + >[1]["prismaClient"], + } + ); + expect(info?.organizationSlug).toBe("references-6120"); + expect(findFirst).not.toHaveBeenCalled(); + } finally { + await buffer.close(); + } } - }); + ); redisTest("returns null when snapshot is malformed JSON", async ({ redisOptions }) => { const buffer = new MollifierBuffer({ redisOptions }); @@ -113,7 +124,7 @@ describe("findBufferedRunRedirectInfo (testcontainers)", () => { }); const info = await findBufferedRunRedirectInfo( { runFriendlyId: "run_real_4", userId: "user_1" }, - { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) }, + { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) } ); expect(info).toBeNull(); } finally { @@ -132,7 +143,7 @@ describe("findBufferedRunRedirectInfo (testcontainers)", () => { }); const info = await findBufferedRunRedirectInfo( { runFriendlyId: "run_real_5", userId: "user_1" }, - { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) }, + { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) } ); expect(info).toBeNull(); } finally { @@ -140,25 +151,28 @@ describe("findBufferedRunRedirectInfo (testcontainers)", () => { } }); - redisTest("returns info with undefined spanId when snapshot has no spanId", async ({ redisOptions }) => { - const buffer = new MollifierBuffer({ redisOptions }); - try { - await buffer.accept({ - runId: "run_real_6", - envId: "env_a", - orgId: "org_1", - payload: JSON.stringify({ environment: SNAPSHOT.environment }), - }); - const info = await findBufferedRunRedirectInfo( - { runFriendlyId: "run_real_6", userId: "user_1" }, - { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) }, - ); - expect(info?.spanId).toBeUndefined(); - expect(info?.environmentSlug).toBe("dev"); - } finally { - await buffer.close(); + redisTest( + "returns info with undefined spanId when snapshot has no spanId", + async ({ redisOptions }) => { + const buffer = new MollifierBuffer({ redisOptions }); + try { + await buffer.accept({ + runId: "run_real_6", + envId: "env_a", + orgId: "org_1", + payload: JSON.stringify({ environment: SNAPSHOT.environment }), + }); + const info = await findBufferedRunRedirectInfo( + { runFriendlyId: "run_real_6", userId: "user_1" }, + { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) } + ); + expect(info?.spanId).toBeUndefined(); + expect(info?.environmentSlug).toBe("dev"); + } finally { + await buffer.close(); + } } - }); + ); redisTest( "rejects snapshots where a slug is the wrong type (Zod guard, not just typeof)", @@ -186,12 +200,12 @@ describe("findBufferedRunRedirectInfo (testcontainers)", () => { }); const info = await findBufferedRunRedirectInfo( { runFriendlyId: "run_real_7", userId: "user_1" }, - { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) }, + { getBuffer: () => buffer, prismaClient: fakePrisma({ id: "member_1" }) } ); expect(info).toBeNull(); } finally { await buffer.close(); } - }, + } ); }); diff --git a/apps/webapp/test/mollifierSyntheticTrace.test.ts b/apps/webapp/test/mollifierSyntheticTrace.test.ts index e711eb0ffe2..69ce8111e4c 100644 --- a/apps/webapp/test/mollifierSyntheticTrace.test.ts +++ b/apps/webapp/test/mollifierSyntheticTrace.test.ts @@ -78,9 +78,7 @@ describe("buildSyntheticTraceForBufferedRun", () => { }); it("falls back to an empty-string span id when the snapshot has no spanId", () => { - const trace = buildSyntheticTraceForBufferedRun( - makeSyntheticRun({ spanId: undefined }) - ); + const trace = buildSyntheticTraceForBufferedRun(makeSyntheticRun({ spanId: undefined })); expect(trace.events[0].id).toBe(""); // Empty id still marks as root (it matches the rootId fallback). expect(trace.events[0].data.isRoot).toBe(true); diff --git a/apps/webapp/test/mollifierTripEvaluator.test.ts b/apps/webapp/test/mollifierTripEvaluator.test.ts index 14ac0cc55bc..00092332429 100644 --- a/apps/webapp/test/mollifierTripEvaluator.test.ts +++ b/apps/webapp/test/mollifierTripEvaluator.test.ts @@ -26,7 +26,7 @@ describe("createRealTripEvaluator", () => { } finally { await buffer.close(); } - }, + } ); redisTest( @@ -57,7 +57,7 @@ describe("createRealTripEvaluator", () => { } finally { await buffer.close(); } - }, + } ); redisTest("returns divert=false when getBuffer returns null (fail-open)", async () => { @@ -70,21 +70,18 @@ describe("createRealTripEvaluator", () => { expect(decision).toEqual({ divert: false }); }); - redisTest( - "returns divert=false when buffer throws (fail-open)", - async ({ redisOptions }) => { - const buffer = new MollifierBuffer({ redisOptions }); - // Closing the client up front means evaluateTrip will throw on the first - // Redis command — a real failure mode, not a stub. - await buffer.close(); + redisTest("returns divert=false when buffer throws (fail-open)", async ({ redisOptions }) => { + const buffer = new MollifierBuffer({ redisOptions }); + // Closing the client up front means evaluateTrip will throw on the first + // Redis command — a real failure mode, not a stub. + await buffer.close(); - const evaluator = createRealTripEvaluator({ - getBuffer: () => buffer, - options: () => ({ windowMs: 200, threshold: 100, holdMs: 500 }), - }); + const evaluator = createRealTripEvaluator({ + getBuffer: () => buffer, + options: () => ({ windowMs: 200, threshold: 100, holdMs: 500 }), + }); - const decision = await evaluator(inputs); - expect(decision).toEqual({ divert: false }); - }, - ); + const decision = await evaluator(inputs); + expect(decision).toEqual({ divert: false }); + }); }); diff --git a/apps/webapp/test/objectStore.test.ts b/apps/webapp/test/objectStore.test.ts index bdfd1f7cfb1..617e6b08b9c 100644 --- a/apps/webapp/test/objectStore.test.ts +++ b/apps/webapp/test/objectStore.test.ts @@ -181,7 +181,9 @@ describe("Object Storage", () => { const key = `packets/proj_ref/dev/${path}`; const normalized = normalizeObjectStoreLogicalKeyPathname(key); expect(normalized).toBe(`/packets/proj_ref/dev/${path}`); - expect(() => assertPacketObjectStoreKeyUnderPrefix(key, "packets/proj_ref/dev")).not.toThrow(); + expect(() => + assertPacketObjectStoreKeyUnderPrefix(key, "packets/proj_ref/dev") + ).not.toThrow(); }); }); @@ -206,7 +208,9 @@ describe("Object Storage", () => { it("rejects env-level traversal via encoded parent segments", () => { const traversalKey = `${prefix}/%2e%2e/secret.json`; - expect(normalizeObjectStoreLogicalKeyPathname(traversalKey)).toBe("/packets/proj_ref/secret.json"); + expect(normalizeObjectStoreLogicalKeyPathname(traversalKey)).toBe( + "/packets/proj_ref/secret.json" + ); expect(() => assertPacketObjectStoreKeyUnderPrefix(traversalKey, prefix)).toThrow( ServiceValidationError @@ -217,9 +221,7 @@ describe("Object Storage", () => { describe("normalizeObjectStoreLogicalKeyPathname (Aws4FetchClient behavior)", () => { it("decodes %2e%2e segments into parent directory traversal", () => { const key = "packets/proj_ref/dev/run/%2e%2e/secret.json"; - expect(normalizeObjectStoreLogicalKeyPathname(key)).toBe( - "/packets/proj_ref/dev/secret.json" - ); + expect(normalizeObjectStoreLogicalKeyPathname(key)).toBe("/packets/proj_ref/dev/secret.json"); }); }); @@ -241,7 +243,8 @@ describe("Object Storage", () => { }); expect(response.status).toBe(500); expect(await response.json()).toEqual({ - error: "Failed to generate presigned URL: Object store is not configured for protocol: default", + error: + "Failed to generate presigned URL: Object store is not configured for protocol: default", }); }); @@ -265,16 +268,13 @@ describe("Object Storage", () => { "run/%2e%2e/secret.json", "%2e%2e/secret.json", "%2E%2E/secret.json", - ])( - "returns 400 failure for unsafe path %s without calling object store", - async (filename) => { - const result = await generatePresignedUrl("proj_test", "dev", filename, "PUT"); - expect(result.success).toBe(false); - if (result.success) throw new Error("expected presign to fail"); - expect(result.error).toBe(INVALID_PACKET_STORAGE_PATH); - expect(result.status).toBe(400); - } - ); + ])("returns 400 failure for unsafe path %s without calling object store", async (filename) => { + const result = await generatePresignedUrl("proj_test", "dev", filename, "PUT"); + expect(result.success).toBe(false); + if (result.success) throw new Error("expected presign to fail"); + expect(result.error).toBe(INVALID_PACKET_STORAGE_PATH); + expect(result.status).toBe(400); + }); it("allows presign for valid packet paths when object store is not configured", async () => { env.OBJECT_STORE_BASE_URL = undefined; @@ -309,7 +309,9 @@ describe("Object Storage", () => { it("PUT with forceNoPrefix skips OBJECT_STORE_DEFAULT_PROTOCOL for unprefixed keys", () => { env.OBJECT_STORE_DEFAULT_PROTOCOL = "s3"; - expect(resolveStoreProtocolForPacketPresign("a/b.json", "PUT", true).storeProtocol).toBeUndefined(); + expect( + resolveStoreProtocolForPacketPresign("a/b.json", "PUT", true).storeProtocol + ).toBeUndefined(); }); it("explicit protocol in key wins for PUT with forceNoPrefix", () => { @@ -687,7 +689,12 @@ describe("Object Storage", () => { }); expect(putResponse.ok).toBe(true); - const getResult = await generatePresignedUrl(projectRef, envSlug, putResult.storagePath!, "GET"); + const getResult = await generatePresignedUrl( + projectRef, + envSlug, + putResult.storagePath!, + "GET" + ); expect(getResult.success).toBe(true); if (!getResult.success) throw new Error(getResult.error); diff --git a/apps/webapp/test/organizationDataStoresRegistry.test.ts b/apps/webapp/test/organizationDataStoresRegistry.test.ts index 9d94e0447fe..87fdeced321 100644 --- a/apps/webapp/test/organizationDataStoresRegistry.test.ts +++ b/apps/webapp/test/organizationDataStoresRegistry.test.ts @@ -62,23 +62,26 @@ describe("OrganizationDataStoresRegistry", () => { expect(secret).not.toBeNull(); }); - postgresTest("loadFromDatabase resolves secrets and makes orgs available via get", async ({ prisma }) => { - const registry = new OrganizationDataStoresRegistry(prisma, prisma); + postgresTest( + "loadFromDatabase resolves secrets and makes orgs available via get", + async ({ prisma }) => { + const registry = new OrganizationDataStoresRegistry(prisma, prisma); - await registry.addDataStore({ - key: "hipaa-store", - kind: "CLICKHOUSE", - organizationIds: ["org-hipaa"], - config: ClickhouseConnectionSchema.parse({ url: TEST_URL }), - }); + await registry.addDataStore({ + key: "hipaa-store", + kind: "CLICKHOUSE", + organizationIds: ["org-hipaa"], + config: ClickhouseConnectionSchema.parse({ url: TEST_URL }), + }); - await registry.loadFromDatabase(); + await registry.loadFromDatabase(); - const result = registry.get("org-hipaa", "CLICKHOUSE"); - expect(result).not.toBeNull(); - expect(result?.kind).toBe("CLICKHOUSE"); - expect(result?.url).toBe(TEST_URL); - }); + const result = registry.get("org-hipaa", "CLICKHOUSE"); + expect(result).not.toBeNull(); + expect(result?.kind).toBe("CLICKHOUSE"); + expect(result?.url).toBe(TEST_URL); + } + ); postgresTest("get returns null for orgs not in any data store", async ({ prisma }) => { const registry = new OrganizationDataStoresRegistry(prisma, prisma); @@ -144,36 +147,38 @@ describe("OrganizationDataStoresRegistry", () => { await registry.loadFromDatabase(); - const expectedUrl = - winner!.key === "dup-overlap-first" ? TEST_URL : TEST_URL_2; + const expectedUrl = winner!.key === "dup-overlap-first" ? TEST_URL : TEST_URL_2; expect(registry.get(sharedOrg, "CLICKHOUSE")?.url).toBe(expectedUrl); } ); - postgresTest("updateDataStore updates organizationIds and rotates the secret", async ({ prisma }) => { - const registry = new OrganizationDataStoresRegistry(prisma, prisma); + postgresTest( + "updateDataStore updates organizationIds and rotates the secret", + async ({ prisma }) => { + const registry = new OrganizationDataStoresRegistry(prisma, prisma); - await registry.addDataStore({ - key: "update-store", - kind: "CLICKHOUSE", - organizationIds: ["org-old"], - config: ClickhouseConnectionSchema.parse({ url: TEST_URL }), - }); + await registry.addDataStore({ + key: "update-store", + kind: "CLICKHOUSE", + organizationIds: ["org-old"], + config: ClickhouseConnectionSchema.parse({ url: TEST_URL }), + }); - await registry.updateDataStore({ - key: "update-store", - kind: "CLICKHOUSE", - organizationIds: ["org-new-1", "org-new-2"], - config: ClickhouseConnectionSchema.parse({ url: TEST_URL_2 }), - }); + await registry.updateDataStore({ + key: "update-store", + kind: "CLICKHOUSE", + organizationIds: ["org-new-1", "org-new-2"], + config: ClickhouseConnectionSchema.parse({ url: TEST_URL_2 }), + }); - const row = await prisma.organizationDataStore.findFirst({ where: { key: "update-store" } }); - expect(row?.organizationIds).toEqual(["org-new-1", "org-new-2"]); + const row = await prisma.organizationDataStore.findFirst({ where: { key: "update-store" } }); + expect(row?.organizationIds).toEqual(["org-new-1", "org-new-2"]); - await registry.loadFromDatabase(); - expect(registry.get("org-new-1", "CLICKHOUSE")?.url).toBe(TEST_URL_2); - expect(registry.get("org-old", "CLICKHOUSE")).toBeNull(); - }); + await registry.loadFromDatabase(); + expect(registry.get("org-new-1", "CLICKHOUSE")?.url).toBe(TEST_URL_2); + expect(registry.get("org-old", "CLICKHOUSE")).toBeNull(); + } + ); postgresTest("reload picks up changes made after initial load", async ({ prisma }) => { const registry = new OrganizationDataStoresRegistry(prisma, prisma); @@ -205,8 +210,12 @@ describe("OrganizationDataStoresRegistry", () => { await registry.deleteDataStore({ key: "delete-store", kind: "CLICKHOUSE" }); - expect(await prisma.organizationDataStore.findFirst({ where: { key: "delete-store" } })).toBeNull(); - expect(await prisma.secretStore.findFirst({ where: { key: "data-store:delete-store:clickhouse" } })).toBeNull(); + expect( + await prisma.organizationDataStore.findFirst({ where: { key: "delete-store" } }) + ).toBeNull(); + expect( + await prisma.secretStore.findFirst({ where: { key: "data-store:delete-store:clickhouse" } }) + ).toBeNull(); }); postgresTest("after delete and reload, org no longer has a data store", async ({ prisma }) => { diff --git a/apps/webapp/test/presenters/ApiBatchResultsPresenter.test.ts b/apps/webapp/test/presenters/ApiBatchResultsPresenter.test.ts index d0888ba6a18..93c4fc59d49 100644 --- a/apps/webapp/test/presenters/ApiBatchResultsPresenter.test.ts +++ b/apps/webapp/test/presenters/ApiBatchResultsPresenter.test.ts @@ -1,5 +1,10 @@ import { containerTest } from "@internal/testcontainers"; -import type { Organization, PrismaClient, Project, RuntimeEnvironment } from "@trigger.dev/database"; +import type { + Organization, + PrismaClient, + Project, + RuntimeEnvironment, +} from "@trigger.dev/database"; import { customAlphabet } from "nanoid"; import { expect, vi } from "vitest"; import { ApiBatchResultsPresenter } from "~/presenters/v3/ApiBatchResultsPresenter.server"; @@ -31,7 +36,10 @@ type SeedContext = { queueId: string; }; -async function seedWorker(prisma: PrismaClient, ctx: Omit) { +async function seedWorker( + prisma: PrismaClient, + ctx: Omit +) { const queue = await prisma.taskQueue.create({ data: { friendlyId: `queue_${idGenerator()}`, @@ -136,7 +144,7 @@ containerTest( // A successful run, a failed run, and an executing run (no terminal attempt → undefined). const successRun = await seedRunWithAttempt(prisma, ctx, { status: "COMPLETED_SUCCESSFULLY", - attempt: { status: "COMPLETED", output: "\"hello\"", outputType: "application/json" }, + attempt: { status: "COMPLETED", output: '"hello"', outputType: "application/json" }, }); const failedRun = await seedRunWithAttempt(prisma, ctx, { status: "COMPLETED_WITH_ERRORS", @@ -170,7 +178,10 @@ containerTest( } const presenter = new ApiBatchResultsPresenter(prisma); - const result = await presenter.call(batchFriendlyId, authEnv(environment, project, organization)); + const result = await presenter.call( + batchFriendlyId, + authEnv(environment, project, organization) + ); expect(result).toBeDefined(); expect(result?.id).toBe(batchFriendlyId); @@ -182,7 +193,7 @@ containerTest( expect(first.ok).toBe(true); expect(first.id).toBe(successRun.friendlyId); if (first.ok) { - expect(first.output).toBe("\"hello\""); + expect(first.output).toBe('"hello"'); expect(first.taskIdentifier).toBe("test-task"); } @@ -212,7 +223,7 @@ containerTest( const pendingRun = await seedRunWithAttempt(prisma, ctx, { status: "EXECUTING" }); const successRun = await seedRunWithAttempt(prisma, ctx, { status: "COMPLETED_SUCCESSFULLY", - attempt: { status: "COMPLETED", output: "\"ok\"", outputType: "application/json" }, + attempt: { status: "COMPLETED", output: '"ok"', outputType: "application/json" }, }); const batchInternalId = idGenerator(); @@ -233,7 +244,10 @@ containerTest( } const presenter = new ApiBatchResultsPresenter(prisma); - const result = await presenter.call(batchFriendlyId, authEnv(environment, project, organization)); + const result = await presenter.call( + batchFriendlyId, + authEnv(environment, project, organization) + ); expect(result?.items).toHaveLength(1); expect(result?.items[0]?.id).toBe(successRun.friendlyId); diff --git a/apps/webapp/test/prismaInfrastructureErrorCapture.test.ts b/apps/webapp/test/prismaInfrastructureErrorCapture.test.ts index 63170c9e286..892befb97f2 100644 --- a/apps/webapp/test/prismaInfrastructureErrorCapture.test.ts +++ b/apps/webapp/test/prismaInfrastructureErrorCapture.test.ts @@ -21,64 +21,65 @@ function capturingLogger() { } describe("captureInfrastructureErrors", () => { - postgresTest("P2025 (not found) passes through with code intact and unlogged", async ({ - prisma, - }) => { - const log = capturingLogger(); - const client = captureInfrastructureErrors(prisma, log); - - const error = await client.secretStore - .update({ where: { key: "does-not-exist" }, data: { version: "2" } }) - .then(() => undefined) - .catch((e) => e); - - expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError); - expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2025"); - expect(log.captured).toHaveLength(0); - }); + postgresTest( + "P2025 (not found) passes through with code intact and unlogged", + async ({ prisma }) => { + const log = capturingLogger(); + const client = captureInfrastructureErrors(prisma, log); - postgresTest("P2002 (unique violation) passes through with code intact and unlogged", async ({ - prisma, - }) => { - const log = capturingLogger(); - const client = captureInfrastructureErrors(prisma, log); + const error = await client.secretStore + .update({ where: { key: "does-not-exist" }, data: { version: "2" } }) + .then(() => undefined) + .catch((e) => e); - await client.secretStore.create({ data: { key: "dup-key", value: { a: 1 } } }); + expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError); + expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2025"); + expect(log.captured).toHaveLength(0); + } + ); - const error = await client.secretStore - .create({ data: { key: "dup-key", value: { a: 2 } } }) - .then(() => undefined) - .catch((e) => e); + postgresTest( + "P2002 (unique violation) passes through with code intact and unlogged", + async ({ prisma }) => { + const log = capturingLogger(); + const client = captureInfrastructureErrors(prisma, log); - expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError); - expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2002"); - expect(log.captured).toHaveLength(0); - }); + await client.secretStore.create({ data: { key: "dup-key", value: { a: 1 } } }); - postgresTest("errors raised inside an interactive $transaction keep their code", async ({ - prisma, - }) => { - const log = capturingLogger(); - const client = captureInfrastructureErrors(prisma, log); + const error = await client.secretStore + .create({ data: { key: "dup-key", value: { a: 2 } } }) + .then(() => undefined) + .catch((e) => e); - // Proves $allOperations fires per-statement inside a transaction — the - // basis for transaction retry logic (which branches on error.code) staying - // intact. - const error = await client - .$transaction(async (tx) => { - await tx.secretStore.update({ where: { key: "missing-in-tx" }, data: { version: "2" } }); - }) - .then(() => undefined) - .catch((e) => e); + expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError); + expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2002"); + expect(log.captured).toHaveLength(0); + } + ); + + postgresTest( + "errors raised inside an interactive $transaction keep their code", + async ({ prisma }) => { + const log = capturingLogger(); + const client = captureInfrastructureErrors(prisma, log); + + // Proves $allOperations fires per-statement inside a transaction — the + // basis for transaction retry logic (which branches on error.code) staying + // intact. + const error = await client + .$transaction(async (tx) => { + await tx.secretStore.update({ where: { key: "missing-in-tx" }, data: { version: "2" } }); + }) + .then(() => undefined) + .catch((e) => e); - expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError); - expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2025"); - expect(log.captured).toHaveLength(0); - }); + expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError); + expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2025"); + expect(log.captured).toHaveLength(0); + } + ); - postgresTest("raw queries (model undefined) are wrapped without crashing", async ({ - prisma, - }) => { + postgresTest("raw queries (model undefined) are wrapped without crashing", async ({ prisma }) => { const log = capturingLogger(); const client = captureInfrastructureErrors(prisma, log); diff --git a/apps/webapp/test/rbacFallbackBranch.test.ts b/apps/webapp/test/rbacFallbackBranch.test.ts index cb19f610d91..f8e90e444ce 100644 --- a/apps/webapp/test/rbacFallbackBranch.test.ts +++ b/apps/webapp/test/rbacFallbackBranch.test.ts @@ -82,27 +82,30 @@ describe("RBAC fallback — DEVELOPMENT branch pivot", () => { expect(result.environment.apiKey).toBe(devRoot.apiKey); }); - postgresTest("the 'default' sentinel resolves the root dev env (no pivot)", async ({ prisma }) => { - const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma); - const rbac = makeController(prisma); - - const devRoot = await createEnv(prisma, project.id, organization.id, { - type: "DEVELOPMENT", - orgMemberId: orgMember.id, - }); - await createEnv(prisma, project.id, organization.id, { - type: "DEVELOPMENT", - orgMemberId: orgMember.id, - parentEnvironmentId: devRoot.id, - branchName: "my-feature", - }); - - const result = await rbac.authenticateBearer(bearerRequest(devRoot.apiKey, "default")); - - expect(result.ok).toBe(true); - if (!result.ok) return; - expect(result.environment.id).toBe(devRoot.id); - }); + postgresTest( + "the 'default' sentinel resolves the root dev env (no pivot)", + async ({ prisma }) => { + const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma); + const rbac = makeController(prisma); + + const devRoot = await createEnv(prisma, project.id, organization.id, { + type: "DEVELOPMENT", + orgMemberId: orgMember.id, + }); + await createEnv(prisma, project.id, organization.id, { + type: "DEVELOPMENT", + orgMemberId: orgMember.id, + parentEnvironmentId: devRoot.id, + branchName: "my-feature", + }); + + const result = await rbac.authenticateBearer(bearerRequest(devRoot.apiKey, "default")); + + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.environment.id).toBe(devRoot.id); + } + ); postgresTest("no branch header resolves the root dev env", async ({ prisma }) => { const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma); @@ -120,23 +123,24 @@ describe("RBAC fallback — DEVELOPMENT branch pivot", () => { expect(result.environment.id).toBe(devRoot.id); }); - postgresTest("a named branch that doesn't exist is rejected (not a fall-through)", async ({ - prisma, - }) => { - const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma); - const rbac = makeController(prisma); + postgresTest( + "a named branch that doesn't exist is rejected (not a fall-through)", + async ({ prisma }) => { + const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma); + const rbac = makeController(prisma); - const devRoot = await createEnv(prisma, project.id, organization.id, { - type: "DEVELOPMENT", - orgMemberId: orgMember.id, - }); + const devRoot = await createEnv(prisma, project.id, organization.id, { + type: "DEVELOPMENT", + orgMemberId: orgMember.id, + }); - const result = await rbac.authenticateBearer(bearerRequest(devRoot.apiKey, "nope")); + const result = await rbac.authenticateBearer(bearerRequest(devRoot.apiKey, "nope")); - expect(result.ok).toBe(false); - if (result.ok) return; - expect(result.status).toBe(401); - }); + expect(result.ok).toBe(false); + if (result.ok) return; + expect(result.status).toBe(401); + } + ); }); describe("RBAC fallback — branch header guards", () => { @@ -145,29 +149,30 @@ describe("RBAC fallback — branch header guards", () => { // PREVIEW branch literally named "default" is reachable and the request pivots // to it like any other branch. (Preview branch names are normally PR refs, so // a branch named "default" is unusual — but it's supported, not a collision.) - postgresTest("preview + 'default' pivots to the branch named 'default' (sentinel is dev-only)", async ({ - prisma, - }) => { - const { organization, project } = await createTestOrgProjectWithMember(prisma); - const rbac = makeController(prisma); - - const previewParent = await createEnv(prisma, project.id, organization.id, { - type: "PREVIEW", - isBranchableEnvironment: true, - }); - const previewDefaultBranch = await createEnv(prisma, project.id, organization.id, { - type: "PREVIEW", - parentEnvironmentId: previewParent.id, - branchName: "default", - }); - - const result = await rbac.authenticateBearer(bearerRequest(previewParent.apiKey, "default")); - - expect(result.ok).toBe(true); - if (!result.ok) return; - // Pivots to the branch named "default", carrying the parent's api key. - expect(result.environment.id).toBe(previewDefaultBranch.id); - expect(result.environment.id).not.toBe(previewParent.id); - expect(result.environment.apiKey).toBe(previewParent.apiKey); - }); + postgresTest( + "preview + 'default' pivots to the branch named 'default' (sentinel is dev-only)", + async ({ prisma }) => { + const { organization, project } = await createTestOrgProjectWithMember(prisma); + const rbac = makeController(prisma); + + const previewParent = await createEnv(prisma, project.id, organization.id, { + type: "PREVIEW", + isBranchableEnvironment: true, + }); + const previewDefaultBranch = await createEnv(prisma, project.id, organization.id, { + type: "PREVIEW", + parentEnvironmentId: previewParent.id, + branchName: "default", + }); + + const result = await rbac.authenticateBearer(bearerRequest(previewParent.apiKey, "default")); + + expect(result.ok).toBe(true); + if (!result.ok) return; + // Pivots to the branch named "default", carrying the parent's api key. + expect(result.environment.id).toBe(previewDefaultBranch.id); + expect(result.environment.id).not.toBe(previewParent.id); + expect(result.environment.apiKey).toBe(previewParent.apiKey); + } + ); }); diff --git a/apps/webapp/test/realtime/envChangeRouter.test.ts b/apps/webapp/test/realtime/envChangeRouter.test.ts index 022b5827cd6..123146dd128 100644 --- a/apps/webapp/test/realtime/envChangeRouter.test.ts +++ b/apps/webapp/test/realtime/envChangeRouter.test.ts @@ -166,10 +166,7 @@ describe("EnvChangeRouter", () => { const { router, src } = makeRouter(rows); const reg = router.register("env_1", { kind: "batch", batchId: "batch_1" }, []); const wait = reg.waitForMatch(undefined, 1_000); - src.push("env_1", [ - record("rX", { batchId: "other" }), - record("r1", { batchId: "batch_1" }), - ]); + src.push("env_1", [record("rX", { batchId: "other" }), record("r1", { batchId: "batch_1" })]); const result = await wait; expect(result.rows.map((m) => m.row.id)).toEqual(["r1"]); reg.close(); @@ -186,7 +183,10 @@ describe("EnvChangeRouter", () => { // r_one shares a tag (routes as a candidate via the index) but lacks "b" — must be // culled by the authoritative row check. r_both carries both and wakes the feed. - src.push("env_1", [record("r_one", { tags: ["a"] }), record("r_both", { tags: ["a", "b", "c"] })]); + src.push("env_1", [ + record("r_one", { tags: ["a"] }), + record("r_both", { tags: ["a", "b", "c"] }), + ]); const result = await wait; expect(result.reason).toBe("notify"); @@ -197,7 +197,11 @@ describe("EnvChangeRouter", () => { it("drops a tag match created before the feed's createdAt floor", async () => { const rows = new Map([["r1", row("r1", { tags: ["a"], createdAtMs: FLOOR_MS - 10_000 })]]); const { router, src } = makeRouter(rows); - const reg = router.register("env_1", { kind: "tag", tags: ["a"], createdAtFloorMs: FLOOR_MS }, []); + const reg = router.register( + "env_1", + { kind: "tag", tags: ["a"], createdAtFloorMs: FLOOR_MS }, + [] + ); let settled = false; const wait = reg.waitForMatch(undefined, 60).then((r) => { settled = true; @@ -375,8 +379,7 @@ describe("EnvChangeRouter read-your-writes gate", () => { marginMs: 0, maxDelayMs: 1_000, staleRetries: 3, - onStaleHydrate: (outcome: string, runCount: number) => - outcomes.push({ outcome, runCount }), + onStaleHydrate: (outcome: string, runCount: number) => outcomes.push({ outcome, runCount }), ...overrides, }, }; diff --git a/apps/webapp/test/realtime/nativeHoldOnEmpty.test.ts b/apps/webapp/test/realtime/nativeHoldOnEmpty.test.ts index 615abc90394..98d21f870a7 100644 --- a/apps/webapp/test/realtime/nativeHoldOnEmpty.test.ts +++ b/apps/webapp/test/realtime/nativeHoldOnEmpty.test.ts @@ -5,10 +5,7 @@ import { type RealtimeListEnvironment, } from "~/services/realtime/nativeRealtimeClient.server"; import { type RealtimeRunRow } from "~/services/realtime/electricStreamProtocol.server"; -import { - EnvChangeRouter, - type EnvChangeSource, -} from "~/services/realtime/envChangeRouter.server"; +import { EnvChangeRouter, type EnvChangeSource } from "~/services/realtime/envChangeRouter.server"; import { type ChangeRecord } from "~/services/realtime/runChangeNotifier.server"; import { describe, expect, it, vi } from "vitest"; @@ -70,7 +67,7 @@ function makeClient(overrides: Record = {}) { hydrator: { hydrateByIds: hydrateSpy }, replayWindowMs: 0, unsubscribeLingerMs: 0, - ...(overrides.routerOptions as Record ?? {}), + ...((overrides.routerOptions as Record) ?? {}), }); delete overrides.routerOptions; @@ -87,7 +84,13 @@ function makeClient(overrides: Record = {}) { ...overrides, }); - return { client, src, hydrateSpy, resolveSpy, setRows: (rows: RealtimeRunRow[]) => (rowsToReturn = rows) }; + return { + client, + src, + hydrateSpy, + resolveSpy, + setRows: (rows: RealtimeRunRow[]) => (rowsToReturn = rows), + }; } function liveRuns(client: NativeRealtimeClient) { @@ -163,7 +166,9 @@ describe("NativeRealtimeClient multi-run live path over the router", () => { it("a matching run created before the window floor is hydrated but dropped (keeps holding)", async () => { // Generous backstop so the "still holding" assertion can't race a timeout in slow CI. - const { client, src, hydrateSpy, resolveSpy, setRows } = makeClient({ livePollTimeoutMs: 1500 }); + const { client, src, hydrateSpy, resolveSpy, setRows } = makeClient({ + livePollTimeoutMs: 1500, + }); setRows([row("run_1", FLOOR_MS + 5_000, { createdAtMs: FLOOR_MS - 10_000, tags: ["t"] })]); const responsePromise = liveRuns(client); @@ -233,7 +238,14 @@ describe("NativeRealtimeClient multi-run live path over the router", () => { const url = `http://localhost:3030/realtime/v1/runs/run_1?offset=${FLOOR_MS + 1_000}_1&handle=run-run_1&live=true`; // First poll subscribes the env, then drains via its backstop. - const first = await client.streamRun(url, ENV, "run_1", CURRENT_API_VERSION, undefined, "1.0.0"); + const first = await client.streamRun( + url, + ENV, + "run_1", + CURRENT_API_VERSION, + undefined, + "1.0.0" + ); expect(first.status).toBe(200); // The record lands between polls; the lingering env subscription buffers it. diff --git a/apps/webapp/test/realtime/nativeRunSetCache.test.ts b/apps/webapp/test/realtime/nativeRunSetCache.test.ts index 2389fd78080..a2337ac7b88 100644 --- a/apps/webapp/test/realtime/nativeRunSetCache.test.ts +++ b/apps/webapp/test/realtime/nativeRunSetCache.test.ts @@ -176,7 +176,10 @@ describe("NativeRealtimeClient resolve admission gate (mass-reconnect stampede)" }; } - function makeGatedClient(resolveAdmissionLimit: number, resolver: ReturnType) { + function makeGatedClient( + resolveAdmissionLimit: number, + resolver: ReturnType + ) { const hydrateSpy = vi.fn(async (_env: string, ids: string[]) => ids.map(row)); return new NativeRealtimeClient({ runReader: { getRunById: async () => null, hydrateByIds: hydrateSpy } as any, diff --git a/apps/webapp/test/realtime/replayCursorStore.test.ts b/apps/webapp/test/realtime/replayCursorStore.test.ts index b66bc72df9c..4a31cc62ae8 100644 --- a/apps/webapp/test/realtime/replayCursorStore.test.ts +++ b/apps/webapp/test/realtime/replayCursorStore.test.ts @@ -42,24 +42,25 @@ describe("RedisReplayCursorStore", () => { } }); - redisTest("a second store instance reads the first's cursor (fleet sharing)", async ({ - redisOptions, - }) => { - const a = new RedisReplayCursorStore({ - redis: { ...redisOptions, tlsDisabled: true }, - ttlMs: 60_000, - }); - const b = new RedisReplayCursorStore({ - redis: { ...redisOptions, tlsDisabled: true }, - ttlMs: 60_000, - }); - try { - a.set("env_1:h2", 42_000); - await vi.waitFor(async () => expect(await b.get("env_1:h2")).toBe(42_000)); - } finally { - await Promise.all([a.quit(), b.quit()]); + redisTest( + "a second store instance reads the first's cursor (fleet sharing)", + async ({ redisOptions }) => { + const a = new RedisReplayCursorStore({ + redis: { ...redisOptions, tlsDisabled: true }, + ttlMs: 60_000, + }); + const b = new RedisReplayCursorStore({ + redis: { ...redisOptions, tlsDisabled: true }, + ttlMs: 60_000, + }); + try { + a.set("env_1:h2", 42_000); + await vi.waitFor(async () => expect(await b.get("env_1:h2")).toBe(42_000)); + } finally { + await Promise.all([a.quit(), b.quit()]); + } } - }); + ); it("degrades to undefined within the read deadline when Redis is unreachable", async () => { const results: Array<[string, boolean]> = []; @@ -79,7 +80,11 @@ describe("RedisReplayCursorStore", () => { }); describe("NativeRealtimeClient replay-cursor threading", () => { - const ENV: RealtimeListEnvironment = { id: "env_1", organizationId: "org_1", projectId: "proj_1" }; + const ENV: RealtimeListEnvironment = { + id: "env_1", + organizationId: "org_1", + projectId: "proj_1", + }; const FLOOR_MS = Date.UTC(2026, 5, 7, 12, 0, 0); it("passes the stored cursor to register and stamps the store after responding", async () => { diff --git a/apps/webapp/test/realtime/runChangeNotifier.test.ts b/apps/webapp/test/realtime/runChangeNotifier.test.ts index 96d7fd56a45..d11ecfd7c36 100644 --- a/apps/webapp/test/realtime/runChangeNotifier.test.ts +++ b/apps/webapp/test/realtime/runChangeNotifier.test.ts @@ -29,7 +29,9 @@ describe("RunChangeNotifier", () => { const notifier = new RunChangeNotifier({ redis: toRedisOptions(redisOptions) }); try { const received: ChangeRecord[] = []; - const unsubscribe = notifier.subscribeToEnv("env_1", (records) => received.push(...records)); + const unsubscribe = notifier.subscribeToEnv("env_1", (records) => + received.push(...records) + ); expect(notifier.activeSubscriptionCount).toBe(1); await sleep(SUBSCRIBE_SETTLE_MS); diff --git a/apps/webapp/test/realtime/shadowCompare.test.ts b/apps/webapp/test/realtime/shadowCompare.test.ts index 0d5f431f0bf..7bbc985edc8 100644 --- a/apps/webapp/test/realtime/shadowCompare.test.ts +++ b/apps/webapp/test/realtime/shadowCompare.test.ts @@ -120,7 +120,12 @@ describe("RealtimeShadowComparator serialization", () => { expect(out.serializationDiverged).toBe(1); expect(out.serializationMatched).toBe(0); expect(out.diffs).toEqual([ - { runId: "run_a", column: "payload", electric: '{"hello":"TAMPERED"}', native: '{"hello":"world"}' }, + { + runId: "run_a", + column: "payload", + electric: '{"hello":"TAMPERED"}', + native: '{"hello":"world"}', + }, ]); }); @@ -145,7 +150,9 @@ describe("RealtimeShadowComparator serialization", () => { it("records skew when the row advanced between emit and refetch", async () => { const row = sampleRow(); // Electric emitted an older version; the refetched row is newer. - const value = { ...serializeRunRow(sampleRow({ updatedAt: new Date("2026-06-07T10:00:00.000Z") })) }; + const value = { + ...serializeRunRow(sampleRow({ updatedAt: new Date("2026-06-07T10:00:00.000Z") })), + }; const body = JSON.stringify([insert(value), UP_TO_DATE]); const cmp = makeComparator({ run_a: row }); @@ -179,10 +186,10 @@ describe("RealtimeShadowComparator membership", () => { } it("matches when Electric's set equals the native resolver's set", async () => { - const cmp = makeComparator( - { a: sampleRow({ id: "a" }), b: sampleRow({ id: "b" }) }, - ["a", "b"] - ); + const cmp = makeComparator({ a: sampleRow({ id: "a" }), b: sampleRow({ id: "b" }) }, [ + "a", + "b", + ]); const out = await cmp.compare({ feed: "runs", electricBody: bodyFor(["a", "b"]), diff --git a/apps/webapp/test/replay-after-crash.test.ts b/apps/webapp/test/replay-after-crash.test.ts index fdd5274b5e7..6133bda843f 100644 --- a/apps/webapp/test/replay-after-crash.test.ts +++ b/apps/webapp/test/replay-after-crash.test.ts @@ -62,11 +62,7 @@ function textTurn(id: string, text: string): UIMessageChunk[] { * the schema now declares `data: z.unknown()` and consumers use it * without an extra `JSON.parse` step. */ -function stubApiClient(opts: { - projectRef: string; - envSlug: string; - sessionOutChunks: unknown[]; -}) { +function stubApiClient(opts: { projectRef: string; envSlug: string; sessionOutChunks: unknown[] }) { const records = opts.sessionOutChunks.map((chunk, i) => ({ data: chunk, id: `evt-${i + 1}`, @@ -220,7 +216,11 @@ describe("replay after crash (MinIO + SDK helpers)", () => { sessionOutChunks: [ ...textTurn("a-complete", "I finished step 1"), // Partial tool turn — no tool-input-end, no finish. - { type: "start", messageId: "a-orphan", messageMetadata: { role: "assistant" } } as UIMessageChunk, + { + type: "start", + messageId: "a-orphan", + messageMetadata: { role: "assistant" }, + } as UIMessageChunk, { type: "tool-input-start", id: "tc-cut", toolName: "search" } as UIMessageChunk, { type: "tool-input-delta", id: "tc-cut", delta: '{"q":"x"}' } as UIMessageChunk, ], @@ -233,9 +233,9 @@ describe("replay after crash (MinIO + SDK helpers)", () => { // Orphaned tool-call never surfaces in `input-streaming` state. const orphan = replayed.find((m) => m.id === "a-orphan"); if (orphan) { - const stillStreaming = (orphan.parts as Array<{ toolCallId?: string; state?: string }>).find( - (p) => p.toolCallId === "tc-cut" && p.state === "input-streaming" - ); + const stillStreaming = ( + orphan.parts as Array<{ toolCallId?: string; state?: string }> + ).find((p) => p.toolCallId === "tc-cut" && p.state === "input-streaming"); expect(stillStreaming).toBeUndefined(); } } @@ -282,9 +282,8 @@ describe("replay after crash (MinIO + SDK helpers)", () => { envSlug: "dev", sessionOutChunks: [], }); - const { __writeChatSnapshotProductionPathForTests: writeSnapshot } = await import( - "@trigger.dev/sdk/ai" - ); + const { __writeChatSnapshotProductionPathForTests: writeSnapshot } = + await import("@trigger.dev/sdk/ai"); await writeSnapshot(sessionId, snapshot); // Restubbing for the boot phase: replay tail carries the fresh diff --git a/apps/webapp/test/runsRepositoryCursor.test.ts b/apps/webapp/test/runsRepositoryCursor.test.ts index f7e02763ad9..ec447245da2 100644 --- a/apps/webapp/test/runsRepositoryCursor.test.ts +++ b/apps/webapp/test/runsRepositoryCursor.test.ts @@ -290,10 +290,7 @@ describe("RunsRepository cursor pagination", () => { }); const returned = page.runs.map((r) => r.id).sort(); - expect(returned).toEqual([ - "aaaaaaaaaaaaaaaaaaaaaaaa", - "bbbbbbbbbbbbbbbbbbbbbbbb", - ]); + expect(returned).toEqual(["aaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbb"]); } ); diff --git a/apps/webapp/test/sanitizeRowsOnParseError.test.ts b/apps/webapp/test/sanitizeRowsOnParseError.test.ts index 6e0de52aa66..e3353176624 100644 --- a/apps/webapp/test/sanitizeRowsOnParseError.test.ts +++ b/apps/webapp/test/sanitizeRowsOnParseError.test.ts @@ -90,7 +90,12 @@ describe("sanitizeUnknownInPlace", () => { }); it("walks arrays recursively", () => { - const value = ["ok", `bad ${LOW_SURROGATE} value`, "also ok", { nested: `also bad ${HIGH_SURROGATE}` }]; + const value = [ + "ok", + `bad ${LOW_SURROGATE} value`, + "also ok", + { nested: `also bad ${HIGH_SURROGATE}` }, + ]; const result = sanitizeUnknownInPlace(value); expect(result.fixed).toBe(2); expect(value[1]).toBe(INVALID_UTF16_SENTINEL); @@ -196,9 +201,9 @@ describe("sanitizeUnknownInPlace", () => { }; const result = sanitizeUnknownInPlace(row); expect(result.fixed).toBe(1); - expect( - (row.output.data.profiles[1].spec_format![0].platform_variables[0] as any).value - ).toBe("117039831458782870000"); + expect((row.output.data.profiles[1].spec_format![0].platform_variables[0] as any).value).toBe( + "117039831458782870000" + ); // Untouched neighbours expect(row.output.data.profiles[0].module).toBe("linktree"); expect(row.output.data.profiles[1].spec_format![0].platform_variables[0].type).toBe("int"); diff --git a/apps/webapp/test/sessionDuration.test.ts b/apps/webapp/test/sessionDuration.test.ts index e10b4b5f21c..a066865a4b0 100644 --- a/apps/webapp/test/sessionDuration.test.ts +++ b/apps/webapp/test/sessionDuration.test.ts @@ -138,19 +138,16 @@ describe("getOrganizationSessionCap", () => { }); describe("getEffectiveSessionDuration", () => { - containerTest( - "returns the user setting when no org cap is set", - async ({ prisma }) => { - const user = await createUser(prisma, "effective-no-cap@test.com", oneDay); - await createOrgWithMember(prisma, "effective-no-cap-org", user.id, null); + containerTest("returns the user setting when no org cap is set", async ({ prisma }) => { + const user = await createUser(prisma, "effective-no-cap@test.com", oneDay); + await createOrgWithMember(prisma, "effective-no-cap-org", user.id, null); - const result = await getEffectiveSessionDuration(user.id, prisma); - expect(result.userSettingSeconds).toBe(oneDay); - expect(result.orgCapSeconds).toBeNull(); - expect(result.cappingOrgId).toBeNull(); - expect(result.durationSeconds).toBe(oneDay); - } - ); + const result = await getEffectiveSessionDuration(user.id, prisma); + expect(result.userSettingSeconds).toBe(oneDay); + expect(result.orgCapSeconds).toBeNull(); + expect(result.cappingOrgId).toBeNull(); + expect(result.durationSeconds).toBe(oneDay); + }); containerTest("caps the user setting at the most restrictive org cap", async ({ prisma }) => { const user = await createUser(prisma, "effective-capped@test.com", oneYear); diff --git a/apps/webapp/test/shouldRevalidateRunsList.test.ts b/apps/webapp/test/shouldRevalidateRunsList.test.ts index 2274ddddd21..5a9ace30892 100644 --- a/apps/webapp/test/shouldRevalidateRunsList.test.ts +++ b/apps/webapp/test/shouldRevalidateRunsList.test.ts @@ -30,7 +30,10 @@ describe("shouldRevalidateRunsList", () => { it("returns false when only bulk inspector UI params change", () => { expect( shouldRevalidateRunsList( - args("?tasks=hello", `?tasks=hello&bulkInspector=${RUNS_BULK_INSPECTOR_OPEN_VALUE}&action=replay&mode=selected`) + args( + "?tasks=hello", + `?tasks=hello&bulkInspector=${RUNS_BULK_INSPECTOR_OPEN_VALUE}&action=replay&mode=selected` + ) ) ).toBe(false); }); @@ -44,14 +47,14 @@ describe("shouldRevalidateRunsList", () => { }); it("returns false when list-data params are reordered", () => { - expect( - shouldRevalidateRunsList(args("?tasks=a&runtime=b", "?runtime=b&tasks=a")) - ).toBe(false); + expect(shouldRevalidateRunsList(args("?tasks=a&runtime=b", "?runtime=b&tasks=a"))).toBe(false); }); it("returns default when list filters and bulk inspector UI params change together", () => { expect( - shouldRevalidateRunsList(args("?tasks=a", `?tasks=b&bulkInspector=${RUNS_BULK_INSPECTOR_OPEN_VALUE}`)) + shouldRevalidateRunsList( + args("?tasks=a", `?tasks=b&bulkInspector=${RUNS_BULK_INSPECTOR_OPEN_VALUE}`) + ) ).toBe(true); }); @@ -61,9 +64,7 @@ describe("shouldRevalidateRunsList", () => { it("returns default when pagination params change", () => { expect( - shouldRevalidateRunsList( - args("?tasks=hello", "?tasks=hello&cursor=abc&direction=forward") - ) + shouldRevalidateRunsList(args("?tasks=hello", "?tasks=hello&cursor=abc&direction=forward")) ).toBe(true); }); @@ -81,9 +82,7 @@ describe("shouldRevalidateRunsList", () => { }); it("respects defaultShouldRevalidate when false", () => { - expect( - shouldRevalidateRunsList(args("?tasks=hello", "?tasks=world", false)) - ).toBe(false); + expect(shouldRevalidateRunsList(args("?tasks=hello", "?tasks=world", false))).toBe(false); }); }); @@ -98,10 +97,7 @@ function makeLocation(search: string): Location { }; } -function navigation( - state: Navigation["state"], - nextSearch?: string -): Navigation { +function navigation(state: Navigation["state"], nextSearch?: string): Navigation { return { state, location: nextSearch ? makeLocation(nextSearch) : undefined, @@ -121,7 +117,10 @@ describe("isRunsListLoading", () => { it("returns false when only bulk inspector UI params change", () => { expect( isRunsListLoading( - navigation("loading", `?tasks=hello&bulkInspector=${RUNS_BULK_INSPECTOR_OPEN_VALUE}&action=replay`), + navigation( + "loading", + `?tasks=hello&bulkInspector=${RUNS_BULK_INSPECTOR_OPEN_VALUE}&action=replay` + ), "?tasks=hello" ) ).toBe(false); @@ -135,14 +134,15 @@ describe("isRunsListLoading", () => { it("returns true when list filters and bulk inspector UI params change together", () => { expect( - isRunsListLoading(navigation("loading", `?tasks=b&bulkInspector=${RUNS_BULK_INSPECTOR_OPEN_VALUE}`), "?tasks=a") + isRunsListLoading( + navigation("loading", `?tasks=b&bulkInspector=${RUNS_BULK_INSPECTOR_OPEN_VALUE}`), + "?tasks=a" + ) ).toBe(true); }); it("returns true when list filters change", () => { - expect( - isRunsListLoading(navigation("loading", "?tasks=world"), "?tasks=hello") - ).toBe(true); + expect(isRunsListLoading(navigation("loading", "?tasks=world"), "?tasks=hello")).toBe(true); }); it("returns true when pagination params change", () => { diff --git a/apps/webapp/test/slackErrorAlerts.test.ts b/apps/webapp/test/slackErrorAlerts.test.ts index b86856adc4c..3a326b21ac2 100644 --- a/apps/webapp/test/slackErrorAlerts.test.ts +++ b/apps/webapp/test/slackErrorAlerts.test.ts @@ -67,8 +67,7 @@ function createMockErrorPayload( // Skip tests if Slack credentials not configured const hasSlackCredentials = - !!process.env.TEST_SLACK_CHANNEL_ID && - !!process.env.TEST_SLACK_BOT_TOKEN; + !!process.env.TEST_SLACK_CHANNEL_ID && !!process.env.TEST_SLACK_BOT_TOKEN; describe.skipIf(!hasSlackCredentials)("Slack Error Alert Visual Tests", () => { beforeAll(async () => { @@ -76,9 +75,7 @@ describe.skipIf(!hasSlackCredentials)("Slack Error Alert Visual Tests", () => { prisma = dbModule.prisma; const secretModule = await import("../app/services/secrets/secretStore.server.js"); getSecretStore = secretModule.getSecretStore; - const alertModule = await import( - "../app/v3/services/alerts/deliverErrorGroupAlert.server.js" - ); + const alertModule = await import("../app/v3/services/alerts/deliverErrorGroupAlert.server.js"); DeliverErrorGroupAlertService = alertModule.DeliverErrorGroupAlertService; const organization = await prisma.organization.create({ diff --git a/apps/webapp/test/timelineSpanEvents.test.ts b/apps/webapp/test/timelineSpanEvents.test.ts index 11b62cf5afd..4118be07557 100644 --- a/apps/webapp/test/timelineSpanEvents.test.ts +++ b/apps/webapp/test/timelineSpanEvents.test.ts @@ -27,8 +27,7 @@ describe("createTimelineSpanEventsFromSpanEvents", () => { file: "src/trigger/chat.ts", event: "import", duration: 67, - entryPoint: - "/project/.trigger/tmp/build-AL7zTl/src/trigger/chat.mjs", + entryPoint: "/project/.trigger/tmp/build-AL7zTl/src/trigger/chat.mjs", }, }, ]; @@ -52,8 +51,7 @@ describe("createTimelineSpanEventsFromSpanEvents", () => { file: "src/trigger/chat.ts", event: "import", duration: 67, - entryPoint: - "/project/.trigger/tmp/build-AL7zTl/src/trigger/chat.mjs", + entryPoint: "/project/.trigger/tmp/build-AL7zTl/src/trigger/chat.mjs", }, }, ]; diff --git a/apps/webapp/test/traceExport.test.ts b/apps/webapp/test/traceExport.test.ts index a05187795d2..35816b20273 100644 --- a/apps/webapp/test/traceExport.test.ts +++ b/apps/webapp/test/traceExport.test.ts @@ -45,7 +45,8 @@ function sampleEvents(): StreamedTraceEvent[] { level: "ERROR", message: "task failed", isError: true, - propertiesText: '{"error":{"message":"boom: it failed","name":"Error","stackTrace":"Error: boom\\n at fn"}}', + propertiesText: + '{"error":{"message":"boom: it failed","name":"Error","stackTrace":"Error: boom\\n at fn"}}', }, { spanId: "quiet1", @@ -75,7 +76,9 @@ async function drain(gen: AsyncIterable): Promise { } function render(formatName: string, items = sampleEvents(), opts = {}): Promise { - return drain(streamTraceExport(toAsyncIterable(items), getTraceExportFormat(formatName), CTX, opts)); + return drain( + streamTraceExport(toAsyncIterable(items), getTraceExportFormat(formatName), CTX, opts) + ); } describe("getTraceExportFormat", () => { @@ -142,7 +145,9 @@ describe("markdown format", () => { expect(text).toContain("task: agent-workflow"); expect(text).toContain("url: https://app.example.com/orgs/o/projects/p/env/dev/runs/run_x"); expect(text).toContain("# Trace for run_x"); - expect(text).toContain("[View in dashboard](https://app.example.com/orgs/o/projects/p/env/dev/runs/run_x)"); + expect(text).toContain( + "[View in dashboard](https://app.example.com/orgs/o/projects/p/env/dev/runs/run_x)" + ); expect(text).toContain("| time | level | event | duration | span ← parent | properties |"); expect(text).not.toContain("```json"); expect(text).toContain("`log1 ← root1`"); diff --git a/apps/webapp/test/utils/testReplicationClickhouseFactory.ts b/apps/webapp/test/utils/testReplicationClickhouseFactory.ts index 2422a461d61..5108ae79109 100644 --- a/apps/webapp/test/utils/testReplicationClickhouseFactory.ts +++ b/apps/webapp/test/utils/testReplicationClickhouseFactory.ts @@ -1,8 +1,5 @@ import type { ClickHouse } from "@internal/clickhouse"; -import { - ClickhouseFactory, - type ClientType, -} from "~/services/clickhouse/clickhouseFactory.server"; +import { ClickhouseFactory, type ClientType } from "~/services/clickhouse/clickhouseFactory.server"; import type { OrganizationDataStoresRegistry } from "~/services/dataStores/organizationDataStoresRegistry.server"; const testReplicationRegistryStub = { diff --git a/apps/webapp/test/workerRegions.test.ts b/apps/webapp/test/workerRegions.test.ts index 7befdfee842..9645e8a9ff2 100644 --- a/apps/webapp/test/workerRegions.test.ts +++ b/apps/webapp/test/workerRegions.test.ts @@ -1,10 +1,32 @@ import { describe, it, expect } from "vitest"; -import { regionForQueue, backingForQueue, type WorkerGroupRegionRow } from "~/v3/workerRegions.server"; +import { + regionForQueue, + backingForQueue, + type WorkerGroupRegionRow, +} from "~/v3/workerRegions.server"; const groups: WorkerGroupRegionRow[] = [ - { masterQueue: "us-east-1", region: "us-east-1", workloadType: "CONTAINER", hidden: false, enableFastPath: false }, - { masterQueue: "us-east-1-next", region: "us-east-1", workloadType: "MICROVM", hidden: false, enableFastPath: true }, - { masterQueue: "eu-central-1", region: "eu-central-1", workloadType: "CONTAINER", hidden: false, enableFastPath: false }, + { + masterQueue: "us-east-1", + region: "us-east-1", + workloadType: "CONTAINER", + hidden: false, + enableFastPath: false, + }, + { + masterQueue: "us-east-1-next", + region: "us-east-1", + workloadType: "MICROVM", + hidden: false, + enableFastPath: true, + }, + { + masterQueue: "eu-central-1", + region: "eu-central-1", + workloadType: "CONTAINER", + hidden: false, + enableFastPath: false, + }, ]; describe("regionForQueue", () => { @@ -18,24 +40,59 @@ describe("regionForQueue", () => { expect(regionForQueue("mystery", groups)).toBe("mystery"); }); it("passes through when a group has no region set", () => { - expect(regionForQueue("x", [{ masterQueue: "x", region: null, workloadType: "CONTAINER", hidden: false, enableFastPath: false }])).toBe("x"); + expect( + regionForQueue("x", [ + { + masterQueue: "x", + region: null, + workloadType: "CONTAINER", + hidden: false, + enableFastPath: false, + }, + ]) + ).toBe("x"); }); }); describe("backingForQueue", () => { it("finds the MICROVM backing for a region with one", () => { - expect(backingForQueue("us-east-1", groups)).toEqual({ workerQueue: "us-east-1-next", enableFastPath: true }); + expect(backingForQueue("us-east-1", groups)).toEqual({ + workerQueue: "us-east-1-next", + enableFastPath: true, + }); }); it("returns undefined for a region with no compute backing (EU)", () => { expect(backingForQueue("eu-central-1", groups)).toBeUndefined(); }); it("returns undefined when the queue's group has no region", () => { - expect(backingForQueue("x", [{ masterQueue: "x", region: null, workloadType: "CONTAINER", hidden: false, enableFastPath: false }])).toBeUndefined(); + expect( + backingForQueue("x", [ + { + masterQueue: "x", + region: null, + workloadType: "CONTAINER", + hidden: false, + enableFastPath: false, + }, + ]) + ).toBeUndefined(); }); it("ignores hidden MICROVM groups", () => { const g: WorkerGroupRegionRow[] = [ - { masterQueue: "us-east-1", region: "us-east-1", workloadType: "CONTAINER", hidden: false, enableFastPath: false }, - { masterQueue: "us-east-1-next", region: "us-east-1", workloadType: "MICROVM", hidden: true, enableFastPath: true }, + { + masterQueue: "us-east-1", + region: "us-east-1", + workloadType: "CONTAINER", + hidden: false, + enableFastPath: false, + }, + { + masterQueue: "us-east-1-next", + region: "us-east-1", + workloadType: "MICROVM", + hidden: true, + enableFastPath: true, + }, ]; expect(backingForQueue("us-east-1", g)).toBeUndefined(); });