diff --git a/.oxfmtrc.json b/.oxfmtrc.json index b41db4dda..71809f50e 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 76175b9eb..99babf86a 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 65f6fdb2f..146edd0e6 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 2be3fe38e..418cdeff5 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 de2a886b9..4b6c7f036 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 2defe9c2f..11b630ac2 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 73cc480f4..8deb7a19c 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 d25faf6d0..cffb0aa9d 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 3275c5442..5b5b64042 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 814d4649c..bf76654cc 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 fc926266f..7b5bdc631 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 7c67d5689..27d969547 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 3e42cd689..323faf4ea 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 accb2f65a..d219b0be7 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 4702f3723..63b5fddf7 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 4855c4c24..7de475bda 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 b27956f43..6b13bac8b 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 83a368094..3891e4fc4 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 0e52124d2..64ee6a05d 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 bf8ecf831..8aa4b5c93 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 2c3f1c9f2..97013c192 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 73ca07180..8e10f8e6f 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 2faed6eaa..c9dd2f12e 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 bda244e30..71c543161 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 b53c551a4..03d75f179 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 8acc81d66..8ecc21a16 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 72794e3e9..42f5288fb 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 a0a96ecb5..2fef1f3de 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 32ed778f8..8a823ee78 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 d8e9f3fe3..8449d342f 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 7ddf039ab..856b74eba 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 21734c5c0..7ae12b20d 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]); + }, [ + 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]); + 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 33818fa60..2c117af9b 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 4dae92731..41c51cdd7 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 48fc59cfa..bf485bbf1 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 f4aafd5ca..b434bb1d3 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 c116205ae..45915c252 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 2e4305d42..d9a955609 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]; + 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; + 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 a84e44553..62a14ec22 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 782d2eef8..e27b11b75 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 c913623eb..f0ecd687a 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 f4248aa64..94b4bfc31 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 2b86eb1cd..328fb9c44 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 f0c3c1d61..05b4f4d9b 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 35acfd266..bfe7bf08f 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 41c17e6d3..e8ce98f1d 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 b6afe46be..9696e5f68 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 2d754309a..d006a853e 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 +
+
+
+ Current default +
+
+ {currentDefaultRegion?.name ?? "–"} +
+
+ + {currentDefaultRegion?.cloudProvider ? ( + <> + + {cloudProviderTitle(currentDefaultRegion.cloudProvider)} + + ) : ( + "–" + )} + +
+
+ + {currentDefaultRegion?.location ? ( + + ) : null} + {currentDefaultRegion?.description ?? "–"} + +
-
- {currentDefaultRegion?.name ?? "–"} + + {/* Middle column with arrow */} +
+
+ +
-
- - {currentDefaultRegion?.cloudProvider ? ( - <> - - {cloudProviderTitle(currentDefaultRegion.cloudProvider)} - - ) : ( - "–" - )} - -
-
- - {currentDefaultRegion?.location ? ( - - ) : null} - {currentDefaultRegion?.description ?? "–"} - + + {/* Right column */} +
+
+ New default +
+
+ {newDefaultRegion.name} +
+
+ + {newDefaultRegion.cloudProvider ? ( + <> + + {cloudProviderTitle(newDefaultRegion.cloudProvider)} + + ) : ( + "–" + )} + +
+
+ + {newDefaultRegion.location ? ( + + ) : null} + {newDefaultRegion.description ?? "–"} + +
- {/* Middle column with arrow */} -
-
- -
-
- - {/* 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 de23b935c..462c5ef29 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 69e0ce6b6..e8c1f1914 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 3ba1c9724..a8d26ac35 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 b9bd38715..7d07cb928 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 d66bdb0e0..e56162731 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 cc85dbb4a..1777d50e1 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 65363a3ef..2d9ac6cd8 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 d82ebea7a..548bf6bf5 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 a6608488e..1e3d75005 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 0e0e0feab..c5ec45b4e 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 00dcb37f4..77cd3bf63 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" - /> - - 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} - -
- - 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 && ( +
+ + {task.taskIdentifier} +
+
- + + + + 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} + + + - {/* 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. */} + + 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} + +
+ + Options enable you to control the execution behavior of your task.{" "} + Read the docs. + + + + - {isDev ? ( - Region is not available in the development environment. - ) : ( - Overrides the region for this run. - )} - {region.error} + Overrides the machine preset. + {machine.error} - )} - - - {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(); + + + {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) } - }} - 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} - -
+ 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 54102b86b..7f762fd03 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 0eb496880..74ab93911 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 1ad36854d..b80af0568 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 39cde0cf2..a7581c974 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 d2ef3b5f8..b7919adc8 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 7e5f559a4..e5c51f9e1 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 6753f37dc..1d8136ca3 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 847828d98..61eb5a69b 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 caa36e521..ffebea96d 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 6081febb5..ea0dd757c 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 e2226aebb..df4d97376 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 627c8bd52..23ffeb51b 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 3ec9e99b2..bac76f76b 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 630bc100b..f91f02bd6 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 8dafeb9b2..5e698c941 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 3c63ce09f..56dc3f4ca 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 60b74d104..d9bab1480 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 8441d4d19..51cd95523 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 503181721..d46743d05 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 5c0c65baf..b485dc860 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 89aaad430..78488bfee 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 f9c5ac0b6..feec6dd65 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 6fc85d69c..0dbde4482 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 fe7845c54..ad869c7a4 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 7f52d9a52..90bebf0af 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 6b4b0518d..6215b9c8e 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 e9977211e..d99e43c23 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 e09bc510d..7a2bb5b7e 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 ffc82e39b..1cc565f95 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 5940bcdda..dd4ce9e2e 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 2a00ceac1..f31ade052 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 3d8de30c2..316701581 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 6ef8a014f..d8c64834a 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 8ba10c660..87939ea87 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 3fb6b04ec..d729e9e9e 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 42468f676..43b034087 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 7ec10835c..58cf572f4 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 061199f33..5f4c35c15 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 c3a99fcec..be717efa3 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 f1aa4d589..82c356630 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 4b238869d..df684946b 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 cbdd9807d..dbc521a74 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 4cbd689f6..21b7b5785 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 8f19194a5..79ad6091c 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 15c2e8dc6..b52a58c8c 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 cc1a6d4f9..35770928c 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 58208de35..6a92db279 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 80140f05d..ba70f08da 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 ec8c171fc..1a10e6c6f 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 eb9e5d974..2b3f308df 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 16b3ef160..2c33e1b4a 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 a4a3027cf..deb3b7882 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 974b1ed29..4845bcf47 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 8787ae470..a42706432 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 00ea71025..5debce703 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 932186b51..593b9d40f 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 125fd8ec9..95fb4ff7b 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 2430af764..ab84f9c34 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 23970ac96..bda6466ce 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 247984d45..713a344d0 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 111cab601..92a6645a7 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 7b234d412..aa28eff0e 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 9c7df4f91..ca418e2d7 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 c309f27db..eccd889f1 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 803dc3da2..a73442ff5 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 add50434d..07906292d 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 f2268989b..297190bc4 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 2e3617800..c0600b392 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 0bdcceb71..ddd4ab4ac 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 ab6462175..e846e851b 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 81784f9bc..19e56e5e0 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 7cb813a6d..12400e8d0 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 c71ad48d1..01a2b550d 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 0a69d4461..8e1aad9ae 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 3b7f178da..105a21644 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 62b13b518..3a7e5bd34 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 f55307c7c..501b4a8ad 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 66f5aae80..c2a4bf59f 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 51bbded59..ee3f56147 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 1e33b3b68..ef5ed4fa1 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 be4fdba7f..418cee805 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 38b7dd390..8e918ec5f 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 826618277..9485f5ff4 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 038e053a8..0d1586593 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 e54bfe357..317bd38a7 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 e468438cd..989751403 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 3a0dfca56..c001d66d5 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 cec6c3c4e..6c50b0f14 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 d6d39ebbb..450b9c696 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 24e7a7337..ee5eda4c3 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 616aa7288..44def23a8 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 1f7a2add4..157462a86 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 f765da353..8f09c6e4e 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 08dcad356..b87c1bfc4 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 13f442aa6..94db448b5 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 7662a88b4..6b5140342 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 38e17531f..e85da45bb 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 27ffec567..afc26a06e 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 49c2dbcf7..a068f6fa7 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 0bd205fa6..e43015a36 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 2f136bddb..f30be72b6 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 2b5f4c4cd..dee5aa3e2 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 24a1382fe..e5606c159 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 6a188acfa..cbd2ee935 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 25b919717..a9b4f4a15 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 faf43ca35..6534e87d0 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 8187ff928..94fd8f534 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 9d808dd75..3b5da8808 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 c885a8035..e598cbdca 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 02d0ec957..3e049ceb3 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 7e1efbdf3..17e52109d 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 5fea4eedd..5a4f18d1d 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 3df2dfb00..e45005779 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 d0d13adfe..fd229777c 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 031411844..6e3b73220 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 89a938da8..cee833e96 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 c0c5de1d2..339f1a36e 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 07e6160ee..2ad5bffb5 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 8297f95aa..8e9a37f55 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 727a5b9ab..c26eca8e3 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 431a0062d..6eb3bb08b 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 95fe22088..0a097458e 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 273b16edd..8e8b8884e 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 ff9039f3e..d30f8da7a 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 07a4f1d35..bf495e885 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 e06ad05cb..61b53a4d6 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 49d745dda..11d401125 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 4363c6805..e67895fe0 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 288fd635a..a4f3ee59c 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 76c39caba..f317a4c58 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 75c1467ab..ebf7e7a76 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 acd395991..a0821d660 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 fdc709fb1..9d1021015 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 6425b7bdb..19faa0382 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 b623f018b..a25b95451 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 4a576c2bf..57b877ed8 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 c6aab9eb9..b69c63ee7 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 efe711a72..fc3a3285a 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 6c5969e02..95e25b7d0 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 00e50ed9f..8b49e52cf 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 1b6fdb3b0..414ffec9f 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 69ca81cf2..4b2207fe1 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 7300d081b..2e032bd15 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 98ce4dc35..952280e77 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 b91f47c6b..22d2ead23 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 b227f382c..341680b32 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 a523111b5..a7129830e 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 35333f963..7453b1078 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 e29aeb168..3cb30ba6a 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 f09507997..3121f0e18 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 593c916f7..49c909f7f 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 b009c2184..a46038959 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 31d8a3844..f76a3c7b8 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 d32652a0b..9bf673147 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 d40e5cf8c..a4543a26d 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 58af8e86f..10405f975 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 311216782..7b53042d8 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 b66faf4d3..10086c52f 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 5a8080510..4c15d0423 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 308f16fde..b20df4ebd 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 2d582dac1..a8f2cb15c 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 074c9b4c3..6a1842b52 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 ad67871c1..71ca0a0e6 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 25bbb2763..0bc88a9af 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 286e99740..326d43e58 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 2f8cdf7e5..680c2a0c9 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 53ec64bc5..997042c31 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 be7cde397..c58d11a7e 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 71ec44534..b6f3681d0 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 cb5f97693..708734dcb 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 3b65fde13..ca167e216 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 ee205374e..72b205884 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 ddbdb3cef..6c128b259 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 65a658501..81eb67234 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 bc2d7d1e4..739b284c2 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 0d8592d27..0c8ecc41a 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 0caf5c591..0c5696810 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 afa3e1332..ddf11c210 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 3bd9d9035..39a802521 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 1e091944e..79fe5a4f9 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 268c955fd..46ca8cbe4 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 c59be0f3f..ca0fb85f0 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 d1e9f5b6f..c0a736c60 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 7a381063b..7e4458b01 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 016662e1d..64171f366 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 3143e40f0..3b6c1f444 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 e9205cd00..1e88a5348 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 5348f228a..c1beced9a 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 518b64666..19d50faac 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 bef87afa3..25fe6d73d 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 c520847ce..dacefaac7 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 6e829baa5..fd9daa5d6 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 eda8f45eb..85a2acd65 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 461faa8d3..8e237b8e7 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 a8b0b1511..6ebcf4a24 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 d13582403..c2c2a2d03 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 a6f3ff3e4..9be3c171c 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 4bd2c45eb..6310ad9d5 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 6ed2d9a1e..8460fbe54 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 d68474121..188face64 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 fc251469c..258c0da21 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 e316846d7..4bc6b9f9b 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 9b137f87f..4b505fcb5 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 ae274aac3..d5d682426 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 03b6e03ca..e36aaa19e 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 b79b9f08e..1ffa48f1c 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 e147422bf..2247a6bdf 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 16aa9d71e..159f2f41c 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 9999a40b7..790530de3 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 975e4aed4..148109a03 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 304664800..4784ad756 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 06cb591a0..3f9cd603b 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 e2285a4fe..a9c9df583 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 cf3dbd57c..6b416b45b 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 49f464d6d..247efb5d4 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 647a0b0cf..b9c1e7ddf 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 c001932ba..a9d3a8f2f 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 76d550c70..579f9a42b 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 22a9047c3..dc51150a8 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 8342291a3..7bc03e9d5 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 64d978609..b5b6207a7 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 7a2dcb06c..d73ee46ed 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 dbc4c576b..0c0503b35 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 ab2729fc2..56e6b8fa6 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 6b9907bba..98034761b 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 a2f0b9de2..5ccee9e5c 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 7f52e32d5..da1caa866 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 ed580b40d..093f12713 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 024ac75a9..9173038db 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 2c081bc36..264be0489 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 f34f0b64c..39be3f97d 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 31f42acc8..e6bfbb036 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 95cff1a48..1399b87fc 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 957e0d290..9656148a1 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 5e86f1dd4..f682868e5 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 28c49eceb..265e42da2 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 6ada9fb32..5297171f9 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 2571f8679..7e67e616e 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 b5172bd59..ba4daea89 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", - }); + 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); + 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, - }); + 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 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"); + 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"); - }); + 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 31e365d6d..9e0cc2b55 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 e66cb1e80..279cbeb98 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 948b9c6c0..728e9c220 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 6280acd4c..e685643eb 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 c2a5dcce9..c730e24b9 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; + 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" }); + stubApiClient({ projectRef: "proj_snap_404", envSlug: "dev" }); - warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + 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); - - 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)}` }], - }); + // Session never had a snapshot written — read returns undefined. + const result = await readChatSnapshot("sess_never_existed"); + expect(result).toBeUndefined(); } - const snapshot = makeSnapshot({ messages, lastOutEventId: "evt-50" }); + ); - await writeChatSnapshot("sess_big_chat", snapshot); - const result = await readChatSnapshot("sess_big_chat"); + 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; - 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"); - }); + 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); + + 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 7f19ea1f2..17e450259 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(); + 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(); - } - ); + 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 f5e56fb6e..8032bc851 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 a669c1209..efd0d0bfd 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 e2c2bec64..244695a6b 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 0c914f2e2..b828409cd 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 aa135b3a3..7b4fda2b8 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 f8d333f5a..06c06f7c7 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); + 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" } - ); + 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; + 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`)); - }); + 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" }; + 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); + 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); - }); + 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); + 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!" } - ); + 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); - }); + 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 }; + 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 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); + 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); - }); + // 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 aafeb8bd7..6aac3c936 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(); + 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); - }); + 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); + + await redis.quit(); } - - expect(await redis.zcard(recentKey(userId, projectId))).toBe(50); - - 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 f0f95806c..f5e722e39 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 c043a4e9e..70dba7766 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 c524d0a3b..ecd4db5e2 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, - }, - }, - baseCostInCents: 0.0005, - }, - tracer: trace.getTracer("test", "0.0.0"), - }); - - const authenticatedEnvironment = await setupAuthenticatedEnvironment(prisma, "PRODUCTION"); - const taskIdentifier = "test-task"; - - await setupBackgroundWorker(engine, authenticatedEnvironment, taskIdentifier); - - 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, - }); - - // 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 - }, + "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"), + }); - expect(result).toBeDefined(); - expect(result?.run.friendlyId).toBeDefined(); + const authenticatedEnvironment = await setupAuthenticatedEnvironment(prisma, "PRODUCTION"); + const taskIdentifier = "test-task"; - await engine.quit(); - } - ); + await setupBackgroundWorker(engine, authenticatedEnvironment, taskIdentifier); + + 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, + }); + + // 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(); + + 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 5efed1235..e47912fab 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 2555bcae0..9a21506f7 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 ead865eb5..4025d8fd2 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", - }); + 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); + 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, - }); + 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" }, - ]); + 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); - }); + 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 4d68fd87e..b47882529 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 0d8b70816..f5b96cf5e 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 22477207b..5bbc1e978 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); + 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, - }); + // 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", - }); + 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(), - }); + 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); + // 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); + // "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 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(); + // 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(); - }); + // 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); + 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", - }); + 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(); + // 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); - }); + // 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 17e02ce8d..e16801bab 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 cb98c1798..b20c21b9d 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 670927cc6..beca22613 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 3512054ec..cf8ae8626 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 ab96c9b9b..b7e5f8601 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 5995f6969..474c9864c 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 f61cda0d0..7a2a0c1e5 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 8206edabb..69c67c5af 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 085fab641..c4897f616 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 0d4e931fd..bf17b0e99 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 5f3f28e66..1cc462617 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 87c009cb1..1b093250f 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 ec7a30b49..fce4b93f6 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 1102229f5..1414772a1 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 feef6a420..2c439ea0b 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 d2f098d70..e95fabaaf 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 4909087d7..82280444c 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 949286111..eee1da250 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 4e2d6a616..5c175e888 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 94ee67c85..37be1dfac 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 a996b9de6..4ad1b6efe 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 e711eb0ff..69ce8111e 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 14ac0cc55..000923324 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 bdfd1f7cf..617e6b08b 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 9d94e0447..87fdeced3 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 d0888ba6a..93c4fc59d 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 63170c9e2..892befb97 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); + 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); + 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); - }); + expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError); + expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2025"); + expect(log.captured).toHaveLength(0); + } + ); - postgresTest("P2002 (unique violation) 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); - await client.secretStore.create({ data: { key: "dup-key", value: { a: 1 } } }); + await client.secretStore.create({ data: { key: "dup-key", value: { a: 1 } } }); - const error = await client.secretStore - .create({ data: { key: "dup-key", value: { a: 2 } } }) - .then(() => undefined) - .catch((e) => e); + const error = await client.secretStore + .create({ data: { key: "dup-key", value: { a: 2 } } }) + .then(() => undefined) + .catch((e) => e); - expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError); - expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2002"); - expect(log.captured).toHaveLength(0); - }); + 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); + 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); + // 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 cb19f610d..f8e90e444 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); + 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 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")); + 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); - }); + 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); + 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 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")); + 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); - }); + 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 022b5827c..123146dd1 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 615abc903..98d21f870 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 2389fd780..a2337ac7b 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 b66bc72df..4a31cc62a 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 96d7fd56a..d11ecfd7c 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 0d5f431f0..7bbc985ed 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 fdd5274b5..6133bda84 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 f7e02763a..ec447245d 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 6e0de52aa..e33531766 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 e10b4b5f2..a066865a4 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 2274ddddd..5a9ace308 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 b86856adc..3a326b21a 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 11b62cf5a..4118be075 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 a05187795..35816b202 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 2422a461d..5108ae791 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 7befdfee8..9645e8a9f 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(); });