Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vite-ignore-optional-imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---

Annotate the optional `@ai-sdk/otel` dynamic import with `@vite-ignore` so Vite-based bundlers don't warn about an unanalyzable import.
Comment thread
kathiekiwi marked this conversation as resolved.
3 changes: 1 addition & 2 deletions apps/webapp/app/components/primitives/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from "@heroicons/react/20/solid";
import type { Prisma } from "@trigger.dev/database";
import { z } from "zod";
import { logger } from "~/services/logger.server";
import { cn } from "~/utils/cn";

export const AvatarType = z.enum(["icon", "letters", "image"]);
Expand Down Expand Up @@ -45,7 +44,7 @@ export function parseAvatar(json: Prisma.JsonValue, defaultAvatar: Avatar): Avat
const parsed = AvatarData.safeParse(json);

if (!parsed.success) {
logger.error("Invalid org avatar", { json, error: parsed.error });
console.error("Invalid org avatar", { json, error: parsed.error });
return defaultAvatar;
}

Expand Down
5 changes: 4 additions & 1 deletion apps/webapp/app/presenters/v3/UsagePresenter.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { DataPoint } from "regression";
import { linear } from "regression";
// Default-import: regression is CJS and its named exports aren't statically
// analyzable under ESM interop.
import regression from "regression";
const { linear } = regression;
import type { PrismaClientOrTransaction } from "~/db.server";
import { env } from "~/env.server";
import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server";
Expand Down
8 changes: 5 additions & 3 deletions apps/webapp/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { LinksFunction, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
import { type UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson";
import { ExternalScripts } from "remix-utils/external-scripts";
import type { ToastMessage } from "~/models/message.server";
import { commitSession, getSession } from "~/models/message.server";
import tailwindStylesheetUrl from "~/tailwind.css";
// Fonts imported here so Vite rebases the urls and emits the woff2 assets
import "non.geist";
import "non.geist/mono";
Comment thread
kathiekiwi marked this conversation as resolved.
import tailwindStylesheetUrl from "~/tailwind.css?url";
import { RouteErrorDisplay } from "./components/ErrorDisplay";
import { AppContainer, MainCenteredContainer } from "./components/layout/AppLayout";
import { ShortcutsProvider } from "./components/primitives/ShortcutsProvider";
Expand Down Expand Up @@ -137,7 +140,6 @@ export default function App() {
<ScrollRestoration />
<ExternalScripts />
<Scripts />
<LiveReload />
</body>
</html>
</>
Expand Down
5 changes: 4 additions & 1 deletion apps/webapp/app/routes/api.v1.errors.$errorId.ignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const ParamsSchema = z.object({
errorId: z.string(),
});

export const { action, loader } = createActionApiRoute(
const route = createActionApiRoute(
{
params: ParamsSchema,
body: IgnoreErrorRequestBody,
Expand Down Expand Up @@ -56,3 +56,6 @@ export const { action, loader } = createActionApiRoute(
return json(updated);
}
);

export const action = route.action;
export const loader = route.loader;
5 changes: 4 additions & 1 deletion apps/webapp/app/routes/api.v1.errors.$errorId.resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const ParamsSchema = z.object({
errorId: z.string(),
});

export const { action, loader } = createActionApiRoute(
const route = createActionApiRoute(
{
params: ParamsSchema,
body: ResolveErrorRequestBody,
Expand Down Expand Up @@ -48,3 +48,6 @@ export const { action, loader } = createActionApiRoute(
return json(updated);
}
);

export const action = route.action;
export const loader = route.loader;
5 changes: 4 additions & 1 deletion apps/webapp/app/routes/api.v1.errors.$errorId.unresolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const ParamsSchema = z.object({
errorId: z.string(),
});

export const { action, loader } = createActionApiRoute(
const route = createActionApiRoute(
{
params: ParamsSchema,
method: "POST",
Expand Down Expand Up @@ -40,3 +40,6 @@ export const { action, loader } = createActionApiRoute(
return json(updated);
}
);

export const action = route.action;
export const loader = route.loader;
6 changes: 5 additions & 1 deletion apps/webapp/app/routes/api.v1.idempotencyKeys.$key.reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const BodySchema = z.object({
taskIdentifier: z.string().min(1, "Task identifier is required"),
});

export const { action } = createActionApiRoute(
const route = createActionApiRoute(
{
params: ParamsSchema,
body: BodySchema,
Expand Down Expand Up @@ -50,3 +50,7 @@ export const { action } = createActionApiRoute(
}
}
);

export const action = route.action;
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
// The builder's loader handles CORS OPTIONS preflight
export const loader = route.loader;
3 changes: 2 additions & 1 deletion apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { prisma } from "~/db.server";
import { createProject } from "~/models/project.server";
import { logger } from "~/services/logger.server";
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
import { isCuid } from "cuid";
import cuid from "cuid";
const { isCuid } = cuid;

const ParamsSchema = z.object({
orgParam: z.string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const BodySchema = z.object({
concurrencyLimit: z.number().int().min(0).max(100000),
});

export const { action } = createActionApiRoute(
const route = createActionApiRoute(
{
body: BodySchema,
params: z.object({
Expand Down Expand Up @@ -73,3 +73,7 @@ export const { action } = createActionApiRoute(
);
}
);

export const action = route.action;
// The builder's loader handles CORS OPTIONS preflight
export const loader = route.loader;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const BodySchema = z.object({
type: RetrieveQueueType.default("id"),
});

export const { action } = createActionApiRoute(
const route = createActionApiRoute(
{
body: BodySchema,
params: z.object({
Expand Down Expand Up @@ -73,3 +73,7 @@ export const { action } = createActionApiRoute(
);
}
);

export const action = route.action;
// The builder's loader handles CORS OPTIONS preflight
export const loader = route.loader;
6 changes: 5 additions & 1 deletion apps/webapp/app/routes/api.v1.queues.$queueParam.pause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const BodySchema = z.object({
action: z.enum(["pause", "resume"]),
});

export const { action } = createActionApiRoute(
const route = createActionApiRoute(
{
body: BodySchema,
params: z.object({
Expand Down Expand Up @@ -44,3 +44,7 @@ export const { action } = createActionApiRoute(
return json(q);
}
);

export const action = route.action;
// The builder's loader handles CORS OPTIONS preflight
export const loader = route.loader;
Comment thread
kathiekiwi marked this conversation as resolved.
115 changes: 2 additions & 113 deletions apps/webapp/app/routes/api.v1.runs.$runId.metadata.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { tryCatch } from "@trigger.dev/core/utils";
import type { RunMetadataChangeOperation } from "@trigger.dev/core/v3/schemas";
import { UpdateMetadataRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { $replica } from "~/db.server";
// Aliased to avoid shadowing the local `env: AuthenticatedEnvironment`
// parameter the route handler and `routeOperationsToRun` use.
// Aliased to avoid shadowing the local `env` parameter in the handler.
import { env as appEnv } from "~/env.server";
import type { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { updateMetadataService } from "~/services/metadata/updateMetadataInstance.server";
import { publishChangeRecord } from "~/services/realtime/runChangeNotifierInstance.server";
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
import { ServiceValidationError } from "~/v3/services/common.server";
import { applyMetadataMutationToBufferedRun } from "~/v3/mollifier/applyMetadataMutation.server";
import { routeOperationsToRun } from "~/v3/mollifier/routeOperationsToRun.server";
import { findRunByIdWithMollifierFallback } from "~/v3/mollifier/readFallback.server";
import { runStore } from "~/v3/runStore.server";

Expand Down Expand Up @@ -67,114 +64,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
return json({ error: "Run not found" }, { status: 404 });
}

// Route parent/root operations to the existing PG service by directly
// invoking it against the parent/root runId. The service ingests via
// its batching worker, which targets PG by id. If the parent/root is
// itself buffered we recurse through our buffered-mutation helper.
// `_ingestion_only` flag: a synthetic body that has the operations
// promoted to top-level `operations` so the service applies them to
// `targetRunId` directly.
// Exported so the silent-failure logging behaviour can be unit-tested.
// The route handler itself isn't an attractive test target (createActionApiRoute
// wraps it in auth + body parsing + error-handler middleware), but the
// fan-out helper carries the load-bearing logic — including the ops-
// visibility branch this change adds.
export async function routeOperationsToRun(
targetRunId: string | undefined,
operations: RunMetadataChangeOperation[] | undefined,
env: AuthenticatedEnvironment
): Promise<void> {
if (!targetRunId || !operations || operations.length === 0) return;

// Try PG first via the existing service (this is how parent/root
// operations have always landed; preserve that). Accepts the full
// AuthenticatedEnvironment so we don't have to recover the unsafe
// `as unknown` cast that the previous narrowed `{ id, organizationId }`
// signature forced on us.
//
// Two non-success outcomes from `call`:
// * throws — PG threw (e.g. "Cannot update metadata for a completed
// run", or a transient PG outage).
// * resolves with undefined — PG row didn't exist (the target may be
// buffered, not yet materialised).
// Either way we want to try the buffer fallback below; treating the
// undefined-return as success would make the fallback unreachable.
const [error, result] = await tryCatch(
updateMetadataService.call(targetRunId, { operations }, env)
);
if (!error && result !== undefined) {
// The parent/root run changed too — wake its live feeds (only when something was
// actually written here; buffered writes publish from the flusher).
if (result.updatedAtMs !== undefined) {
publishChangeRecord({
runId: result.runId,
envId: env.id,
tags: result.runTags,
batchId: result.batchId,
updatedAtMs: result.updatedAtMs,
});
}
return;
}

if (error) {
// PG threw — auxiliary op, stay best-effort and don't surface this
// to the caller (the caller's primary mutation already landed). But
// warn so a genuine PG outage on these ops isn't invisible.
logger.warn("metadata route: parent/root PG op failed", {
targetRunId,
error: error instanceof Error ? error.message : String(error),
});
}

// Buffer fallback only makes sense for friendlyId-keyed entries. The
// PG-side parent/root IDs are internal cuids; the buffer keys entries
// by friendlyId, so passing the internal id would silently no-op.
// Skip explicitly — a buffered child's parent is always materialised
// in PG already (a buffered run hasn't executed, so it can't have
// triggered the child), so the buffered-parent branch isn't actually
// reachable. Treating the no-op as intentional rather than incidental.
if (!targetRunId.startsWith("run_")) return;

// Best-effort buffer fallback. Wrap so a transient Redis throw on
// this auxiliary op can't 500 the request after the primary mutation
// already succeeded.
const [bufferError, bufferOutcome] = await tryCatch(
applyMetadataMutationToBufferedRun({
runId: targetRunId,
environmentId: env.id,
organizationId: env.organizationId,
maximumSize: appEnv.TASK_RUN_METADATA_MAXIMUM_SIZE,
maxRetries: appEnv.TRIGGER_MOLLIFIER_METADATA_MAX_RETRIES,
backoffBaseMs: appEnv.TRIGGER_MOLLIFIER_METADATA_BACKOFF_BASE_MS,
backoffStepMs: appEnv.TRIGGER_MOLLIFIER_METADATA_BACKOFF_STEP_MS,
body: { operations },
})
);
if (bufferError) {
logger.warn("metadata route: buffer fallback for parent/root op failed", {
targetRunId,
error: bufferError instanceof Error ? bufferError.message : String(bufferError),
});
return;
}
// `applyMetadataMutationToBufferedRun` reports non-throw failures via
// its returned outcome kind: `not_found`, `busy`, `version_exhausted`,
// `metadata_too_large`. Without inspecting `.kind`, the parent/root
// operation can silently disappear — no PG row landed it (handled
// above) and the buffer rejected it for one of these reasons but the
// helper returned cleanly. Surface a warn log per non-success branch
// so ops can trace why a parent/root op went missing. The customer's
// primary mutation has already succeeded by this point; this remains
// best-effort, so we still don't bubble these to the response.
if (bufferOutcome && bufferOutcome.kind !== "applied") {
logger.warn("metadata route: parent/root buffer op did not apply", {
targetRunId,
kind: bufferOutcome.kind,
});
}
}

const { action } = createActionApiRoute(
{
params: ParamsSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function sessionResource(
return anyResource([...ids].map((id) => ({ type: "sessions" as const, id })));
}

export const { action } = createActionApiRoute(
const route = createActionApiRoute(
{
...routeConfig,
method: "PUT",
Expand Down Expand Up @@ -94,3 +94,5 @@ export const loader = createLoaderApiRoute(
return json({ presignedUrl: signed.url });
}
);

export const action = route.action;
4 changes: 2 additions & 2 deletions apps/webapp/app/routes/auth.github.callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { commitAuthenticatedSession } from "~/services/sessionDuration.server";
import { trackAndClearReferralSource } from "~/services/referralSource.server";
import { appendRedirectTo, ssoRedirectFromAuthError } from "~/services/ssoAutoDiscovery.server";
import type { AuthUser } from "~/services/authUser";
import { redirectCookie } from "./auth.github";
import { githubRedirectCookie } from "~/services/redirectCookies.server";
import { sanitizeRedirectPath } from "~/utils";

export let loader: LoaderFunction = async ({ request }) => {
const cookie = request.headers.get("Cookie");
const redirectValue = await redirectCookie.parse(cookie);
const redirectValue = await githubRedirectCookie.parse(cookie);
const redirectTo = sanitizeRedirectPath(redirectValue);

// The SSO auto-discovery gate runs inside the strategy's verify
Expand Down
13 changes: 3 additions & 10 deletions apps/webapp/app/routes/auth.github.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ActionFunction, type LoaderFunction, redirect, createCookie } from "@remix-run/node";
import { type ActionFunction, type LoaderFunction, redirect } from "@remix-run/node";
import { authenticator } from "~/services/auth.server";
import { env } from "~/env.server";
import { githubRedirectCookie } from "~/services/redirectCookies.server";
import { sanitizeRedirectPath } from "~/utils";

export let loader: LoaderFunction = () => redirect("/login");
Expand All @@ -23,15 +23,8 @@ export let action: ActionFunction = async ({ request }) => {
if (error instanceof Response) {
// we need to append a Set-Cookie header with a cookie storing the
// returnTo value (store the sanitized path)
error.headers.append("Set-Cookie", await redirectCookie.serialize(safeRedirect));
error.headers.append("Set-Cookie", await githubRedirectCookie.serialize(safeRedirect));
}
throw error;
}
};

export const redirectCookie = createCookie("redirect-to", {
maxAge: 60 * 60, // 1 hour
httpOnly: true,
sameSite: "lax",
secure: env.NODE_ENV === "production",
});
4 changes: 2 additions & 2 deletions apps/webapp/app/routes/auth.google.callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { commitAuthenticatedSession } from "~/services/sessionDuration.server";
import { trackAndClearReferralSource } from "~/services/referralSource.server";
import { appendRedirectTo, ssoRedirectFromAuthError } from "~/services/ssoAutoDiscovery.server";
import type { AuthUser } from "~/services/authUser";
import { redirectCookie } from "./auth.google";
import { googleRedirectCookie } from "~/services/redirectCookies.server";
import { sanitizeRedirectPath } from "~/utils";

export let loader: LoaderFunction = async ({ request }) => {
const cookie = request.headers.get("Cookie");
const redirectValue = await redirectCookie.parse(cookie);
const redirectValue = await googleRedirectCookie.parse(cookie);
const redirectTo = sanitizeRedirectPath(redirectValue);

// The SSO auto-discovery gate runs inside the strategy's verify
Expand Down
Loading