-
{run.idempotencyKey ? run.idempotencyKey : "–"}
+ {run.idempotencyKey ? (
+
+ ) : (
+
–
+ )}
{run.idempotencyKey && (
Expires:{" "}
@@ -587,7 +592,9 @@ function RunBody({
{run.idempotencyKey && (
diff --git a/apps/webapp/app/services/clickhouseInstance.server.ts b/apps/webapp/app/services/clickhouseInstance.server.ts
index 32fc9bc0d4..156448c005 100644
--- a/apps/webapp/app/services/clickhouseInstance.server.ts
+++ b/apps/webapp/app/services/clickhouseInstance.server.ts
@@ -12,6 +12,28 @@ function initializeClickhouseClient() {
console.log(`🗃️ Clickhouse service enabled to host ${url.host}`);
+ // Build logs query settings from environment variables
+ const logsQuerySettings = {
+ list: {
+ max_memory_usage: env.CLICKHOUSE_LOGS_LIST_MAX_MEMORY_USAGE.toString(),
+ max_bytes_before_external_sort: env.CLICKHOUSE_LOGS_LIST_MAX_BYTES_BEFORE_EXTERNAL_SORT.toString(),
+ max_threads: env.CLICKHOUSE_LOGS_LIST_MAX_THREADS,
+ ...(env.CLICKHOUSE_LOGS_LIST_MAX_ROWS_TO_READ && {
+ max_rows_to_read: env.CLICKHOUSE_LOGS_LIST_MAX_ROWS_TO_READ.toString(),
+ }),
+ ...(env.CLICKHOUSE_LOGS_LIST_MAX_EXECUTION_TIME && {
+ max_execution_time: env.CLICKHOUSE_LOGS_LIST_MAX_EXECUTION_TIME,
+ }),
+ },
+ detail: {
+ max_memory_usage: env.CLICKHOUSE_LOGS_DETAIL_MAX_MEMORY_USAGE.toString(),
+ max_threads: env.CLICKHOUSE_LOGS_DETAIL_MAX_THREADS,
+ ...(env.CLICKHOUSE_LOGS_DETAIL_MAX_EXECUTION_TIME && {
+ max_execution_time: env.CLICKHOUSE_LOGS_DETAIL_MAX_EXECUTION_TIME,
+ }),
+ },
+ };
+
const clickhouse = new ClickHouse({
url: url.toString(),
name: "clickhouse-instance",
@@ -24,6 +46,7 @@ function initializeClickhouseClient() {
request: true,
},
maxOpenConnections: env.CLICKHOUSE_MAX_OPEN_CONNECTIONS,
+ logsQuerySettings,
});
return clickhouse;
diff --git a/apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts b/apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts
index 8b5cee04ca..9d3a92e911 100644
--- a/apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts
+++ b/apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts
@@ -52,6 +52,29 @@ export class ClickHouseRunsRepository implements IRunsRepository {
return runIds;
}
+ async listFriendlyRunIds(options: ListRunsOptions) {
+ // First get internal IDs from ClickHouse
+ const internalIds = await this.listRunIds(options);
+
+ if (internalIds.length === 0) {
+ return [];
+ }
+
+ // Then get friendly IDs from Prisma
+ const runs = await this.options.prisma.taskRun.findMany({
+ where: {
+ id: {
+ in: internalIds,
+ },
+ },
+ select: {
+ friendlyId: true,
+ },
+ });
+
+ return runs.map((run) => run.friendlyId);
+ }
+
async listRuns(options: ListRunsOptions) {
const runIds = await this.listRunIds(options);
diff --git a/apps/webapp/app/services/runsRepository/postgresRunsRepository.server.ts b/apps/webapp/app/services/runsRepository/postgresRunsRepository.server.ts
index 93edbd9349..eaf2242090 100644
--- a/apps/webapp/app/services/runsRepository/postgresRunsRepository.server.ts
+++ b/apps/webapp/app/services/runsRepository/postgresRunsRepository.server.ts
@@ -31,6 +31,18 @@ export class PostgresRunsRepository implements IRunsRepository {
return runs.map((run) => run.id);
}
+ async listFriendlyRunIds(options: ListRunsOptions) {
+ const filterOptions = await convertRunListInputOptionsToFilterRunsOptions(
+ options,
+ this.options.prisma
+ );
+
+ const query = this.#buildFriendlyRunIdsQuery(filterOptions, options.page);
+ const runs = await this.options.prisma.$queryRaw<{ friendlyId: string }[]>(query);
+
+ return runs.map((run) => run.friendlyId);
+ }
+
async listRuns(options: ListRunsOptions) {
const filterOptions = await convertRunListInputOptionsToFilterRunsOptions(
options,
@@ -146,6 +158,21 @@ export class PostgresRunsRepository implements IRunsRepository {
`;
}
+ #buildFriendlyRunIdsQuery(
+ filterOptions: FilterRunsOptions,
+ page: { size: number; cursor?: string; direction?: "forward" | "backward" }
+ ) {
+ const whereConditions = this.#buildWhereConditions(filterOptions, page.cursor, page.direction);
+
+ return Prisma.sql`
+ SELECT tr."friendlyId"
+ FROM ${sqlDatabaseSchema}."TaskRun" tr
+ WHERE ${whereConditions}
+ ORDER BY ${page.direction === "backward" ? Prisma.sql`tr.id ASC` : Prisma.sql`tr.id DESC`}
+ LIMIT ${page.size + 1}
+ `;
+ }
+
#buildRunsQuery(
filterOptions: FilterRunsOptions,
page: { size: number; cursor?: string; direction?: "forward" | "backward" }
diff --git a/apps/webapp/app/services/runsRepository/runsRepository.server.ts b/apps/webapp/app/services/runsRepository/runsRepository.server.ts
index 7bf81a4aa5..895c8b5fe5 100644
--- a/apps/webapp/app/services/runsRepository/runsRepository.server.ts
+++ b/apps/webapp/app/services/runsRepository/runsRepository.server.ts
@@ -7,7 +7,7 @@ import { type Prisma, TaskRunStatus } from "@trigger.dev/database";
import parseDuration from "parse-duration";
import { z } from "zod";
import { timeFilters } from "~/components/runs/v3/SharedFilters";
-import { type PrismaClient } from "~/db.server";
+import { type PrismaClient, type PrismaClientOrTransaction } from "~/db.server";
import { FEATURE_FLAG, makeFlags } from "~/v3/featureFlags.server";
import { startActiveSpan } from "~/v3/tracer.server";
import { logger } from "../logger.server";
@@ -16,7 +16,7 @@ import { PostgresRunsRepository } from "./postgresRunsRepository.server";
export type RunsRepositoryOptions = {
clickhouse: ClickHouse;
- prisma: PrismaClient;
+ prisma: PrismaClientOrTransaction;
logger?: Logger;
logLevel?: LogLevel;
tracer?: Tracer;
@@ -127,6 +127,8 @@ export type TagList = {
export interface IRunsRepository {
name: string;
listRunIds(options: ListRunsOptions): Promise
;
+ /** Returns friendly IDs (e.g., run_xxx) instead of internal UUIDs. Used for ClickHouse task_events queries. */
+ listFriendlyRunIds(options: ListRunsOptions): Promise;
listRuns(options: ListRunsOptions): Promise<{
runs: ListedRun[];
pagination: {
@@ -223,6 +225,48 @@ export class RunsRepository implements IRunsRepository {
);
}
+ async listFriendlyRunIds(options: ListRunsOptions): Promise {
+ const repository = await this.#getRepository();
+ return startActiveSpan(
+ "runsRepository.listFriendlyRunIds",
+ async () => {
+ try {
+ return await repository.listFriendlyRunIds(options);
+ } catch (error) {
+ // If ClickHouse fails, retry with Postgres
+ if (repository.name === "clickhouse") {
+ this.logger?.warn("ClickHouse failed, retrying with Postgres", { error });
+ return startActiveSpan(
+ "runsRepository.listFriendlyRunIds.fallback",
+ async () => {
+ return await this.postgresRunsRepository.listFriendlyRunIds(options);
+ },
+ {
+ attributes: {
+ "repository.name": "postgres",
+ "fallback.reason": "clickhouse_error",
+ "fallback.error": error instanceof Error ? error.message : String(error),
+ organizationId: options.organizationId,
+ projectId: options.projectId,
+ environmentId: options.environmentId,
+ },
+ }
+ );
+ }
+ throw error;
+ }
+ },
+ {
+ attributes: {
+ "repository.name": repository.name,
+ organizationId: options.organizationId,
+ projectId: options.projectId,
+ environmentId: options.environmentId,
+ },
+ }
+ );
+ }
+
async listRuns(options: ListRunsOptions): Promise<{
runs: ListedRun[];
pagination: {
diff --git a/apps/webapp/app/utils/logUtils.ts b/apps/webapp/app/utils/logUtils.ts
new file mode 100644
index 0000000000..b4a130b8e5
--- /dev/null
+++ b/apps/webapp/app/utils/logUtils.ts
@@ -0,0 +1,155 @@
+import { createElement, Fragment, type ReactNode } from "react";
+import { z } from "zod";
+
+export const LogLevelSchema = z.enum(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "CANCELLED"]);
+export type LogLevel = z.infer;
+
+export const validLogLevels: LogLevel[] = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "CANCELLED"];
+
+// Default styles for search highlighting
+const DEFAULT_HIGHLIGHT_STYLES: React.CSSProperties = {
+ backgroundColor: "#facc15", // yellow-400
+ color: "#000000",
+ fontWeight: "500",
+ borderRadius: "0.25rem",
+ padding: "0 0.125rem",
+} as const;
+
+/**
+ * Highlights all occurrences of a search term in text with consistent styling.
+ * Case-insensitive search with regex special character escaping.
+ *
+ * @param text - The text to search within
+ * @param searchTerm - The term to highlight (optional)
+ * @param style - Optional custom inline styles for highlights
+ * @returns React nodes with highlighted matches, or the original text if no matches
+ */
+export function highlightSearchText(
+ text: string,
+ searchTerm?: string,
+ style: React.CSSProperties = DEFAULT_HIGHLIGHT_STYLES
+): ReactNode {
+ if (!searchTerm || searchTerm.trim() === "") {
+ return text;
+ }
+
+ // Defense in depth: limit search term length to prevent ReDoS and performance issues
+ const MAX_SEARCH_LENGTH = 500;
+ if (searchTerm.length > MAX_SEARCH_LENGTH) {
+ return text;
+ }
+
+ // Escape special regex characters in search term
+ const escapedSearch = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const regex = new RegExp(escapedSearch, "gi");
+
+ const parts: ReactNode[] = [];
+ let lastIndex = 0;
+ let match;
+ let matchCount = 0;
+
+ while ((match = regex.exec(text)) !== null) {
+ // Add text before match
+ if (match.index > lastIndex) {
+ parts.push(text.substring(lastIndex, match.index));
+ }
+ // Add highlighted match
+ parts.push(
+ createElement("span", { key: `match-${matchCount}`, style }, match[0])
+ );
+ lastIndex = regex.lastIndex;
+ matchCount++;
+ }
+
+ // Add remaining text
+ if (lastIndex < text.length) {
+ parts.push(text.substring(lastIndex));
+ }
+
+ return parts.length > 0 ? parts : text;
+}
+
+// Convert ClickHouse kind to display level
+export function kindToLevel(kind: string, status: string): LogLevel {
+ if (status === "CANCELLED") {
+ return "CANCELLED";
+ }
+
+ // ERROR can come from either kind or status
+ if (kind === "LOG_ERROR" || status === "ERROR") {
+ return "ERROR";
+ }
+
+ switch (kind) {
+ case "DEBUG_EVENT":
+ case "LOG_DEBUG":
+ return "DEBUG";
+ case "LOG_INFO":
+ return "INFO";
+ case "LOG_WARN":
+ return "WARN";
+ case "LOG_LOG":
+ return "INFO"; // Changed from "LOG"
+ case "SPAN":
+ case "ANCESTOR_OVERRIDE":
+ case "SPAN_EVENT":
+ default:
+ return "TRACE";
+ }
+}
+
+// Level badge color styles
+export function getLevelColor(level: LogLevel): string {
+ switch (level) {
+ case "ERROR":
+ return "text-error bg-error/10 border-error/20";
+ case "WARN":
+ return "text-warning bg-warning/10 border-warning/20";
+ case "DEBUG":
+ return "text-charcoal-400 bg-charcoal-700 border-charcoal-600";
+ case "INFO":
+ return "text-blue-400 bg-blue-500/10 border-blue-500/20";
+ case "TRACE":
+ return "text-charcoal-500 bg-charcoal-800 border-charcoal-700";
+ case "CANCELLED":
+ return "text-charcoal-400 bg-charcoal-700 border-charcoal-600";
+ default:
+ return "text-text-dimmed bg-charcoal-750 border-charcoal-700";
+ }
+}
+
+// Event kind badge color styles
+export function getKindColor(kind: string): string {
+ if (kind === "SPAN") {
+ return "text-purple-400 bg-purple-500/10 border-purple-500/20";
+ }
+ if (kind === "SPAN_EVENT") {
+ return "text-amber-400 bg-amber-500/10 border-amber-500/20";
+ }
+ if (kind.startsWith("LOG_")) {
+ return "text-blue-400 bg-blue-500/10 border-blue-500/20";
+ }
+ return "text-charcoal-400 bg-charcoal-700 border-charcoal-600";
+}
+
+// Get human readable kind label
+export function getKindLabel(kind: string): string {
+ switch (kind) {
+ case "SPAN":
+ return "Span";
+ case "SPAN_EVENT":
+ return "Event";
+ case "LOG_DEBUG":
+ case "LOG_INFO":
+ case "LOG_WARN":
+ case "LOG_ERROR":
+ case "LOG_LOG":
+ return "Log";
+ case "DEBUG_EVENT":
+ return "Debug";
+ case "ANCESTOR_OVERRIDE":
+ return "Override";
+ default:
+ return kind;
+ }
+}
diff --git a/apps/webapp/app/utils/pathBuilder.ts b/apps/webapp/app/utils/pathBuilder.ts
index ebdec3401a..a2756f7e5b 100644
--- a/apps/webapp/app/utils/pathBuilder.ts
+++ b/apps/webapp/app/utils/pathBuilder.ts
@@ -455,6 +455,14 @@ export function v3ProjectSettingsPath(
return `${v3EnvironmentPath(organization, project, environment)}/settings`;
}
+export function v3LogsPath(
+ organization: OrgForPath,
+ project: ProjectForPath,
+ environment: EnvironmentForPath,
+) {
+ return `${v3EnvironmentPath(organization, project, environment)}/logs`;
+}
+
export function v3DeploymentsPath(
organization: OrgForPath,
project: ProjectForPath,
diff --git a/apps/webapp/tailwind.config.js b/apps/webapp/tailwind.config.js
index 7ca81fd8ee..9f4e4381b8 100644
--- a/apps/webapp/tailwind.config.js
+++ b/apps/webapp/tailwind.config.js
@@ -160,6 +160,7 @@ const batches = colors.pink[500];
const schedules = colors.yellow[500];
const queues = colors.purple[500];
const deployments = colors.green[500];
+const logs = colors.blue[500];
const tests = colors.lime[500];
const apiKeys = colors.amber[500];
const environmentVariables = colors.pink[500];
@@ -236,6 +237,7 @@ module.exports = {
schedules,
queues,
deployments,
+ logs,
tests,
apiKeys,
environmentVariables,
diff --git a/internal-packages/clickhouse/src/client/queryBuilder.ts b/internal-packages/clickhouse/src/client/queryBuilder.ts
index 78383fd270..30aad98486 100644
--- a/internal-packages/clickhouse/src/client/queryBuilder.ts
+++ b/internal-packages/clickhouse/src/client/queryBuilder.ts
@@ -98,6 +98,7 @@ export class ClickhouseQueryFastBuilder> {
private columns: Array;
private reader: ClickhouseReader;
private settings: ClickHouseSettings | undefined;
+ private prewhereClauses: string[] = [];
private whereClauses: string[] = [];
private params: QueryParams = {};
private orderByClause: string | null = null;
@@ -118,6 +119,25 @@ export class ClickhouseQueryFastBuilder> {
this.settings = settings;
}
+ /**
+ * Add a PREWHERE clause - filters applied before reading columns.
+ * Use for primary key columns (environment_id, start_time) to reduce I/O.
+ */
+ prewhere(clause: string, params?: QueryParams): this {
+ this.prewhereClauses.push(clause);
+ if (params) {
+ Object.assign(this.params, params);
+ }
+ return this;
+ }
+
+ prewhereIf(condition: any, clause: string, params?: QueryParams): this {
+ if (condition) {
+ this.prewhere(clause, params);
+ }
+ return this;
+ }
+
where(clause: string, params?: QueryParams): this {
this.whereClauses.push(clause);
if (params) {
@@ -163,6 +183,9 @@ export class ClickhouseQueryFastBuilder> {
build(): { query: string; params: QueryParams } {
let query = `SELECT ${this.buildColumns().join(", ")} FROM ${this.table}`;
+ if (this.prewhereClauses.length > 0) {
+ query += " PREWHERE " + this.prewhereClauses.join(" AND ");
+ }
if (this.whereClauses.length > 0) {
query += " WHERE " + this.whereClauses.join(" AND ");
}
diff --git a/internal-packages/clickhouse/src/index.ts b/internal-packages/clickhouse/src/index.ts
index 03b8b81e13..ca28a0a022 100644
--- a/internal-packages/clickhouse/src/index.ts
+++ b/internal-packages/clickhouse/src/index.ts
@@ -22,6 +22,8 @@ import {
getTraceSummaryQueryBuilderV2,
insertTaskEvents,
insertTaskEventsV2,
+ getLogsListQueryBuilder,
+ getLogDetailQueryBuilder,
} from "./taskEvents.js";
import { Logger, type LogLevel } from "@trigger.dev/core/logger";
import type { Agent as HttpAgent } from "http";
@@ -44,6 +46,11 @@ export {
} from "./client/tsql.js";
export type { OutputColumnMetadata } from "@internal/tsql";
+export type LogsQuerySettings = {
+ list?: ClickHouseSettings;
+ detail?: ClickHouseSettings;
+};
+
export type ClickhouseCommonConfig = {
keepAlive?: {
enabled?: boolean;
@@ -58,6 +65,7 @@ export type ClickhouseCommonConfig = {
response?: boolean;
};
maxOpenConnections?: number;
+ logsQuerySettings?: LogsQuerySettings;
};
export type ClickHouseConfig =
@@ -81,9 +89,11 @@ export class ClickHouse {
public readonly writer: ClickhouseWriter;
private readonly logger: Logger;
private _splitClients: boolean;
+ private readonly logsQuerySettings?: LogsQuerySettings;
constructor(config: ClickHouseConfig) {
this.logger = config.logger ?? new Logger("ClickHouse", config.logLevel ?? "debug");
+ this.logsQuerySettings = config.logsQuerySettings;
if (config.url) {
const url = new URL(config.url);
@@ -195,6 +205,8 @@ export class ClickHouse {
traceSummaryQueryBuilder: getTraceSummaryQueryBuilderV2(this.reader),
traceDetailedSummaryQueryBuilder: getTraceDetailedSummaryQueryBuilderV2(this.reader),
spanDetailsQueryBuilder: getSpanDetailsQueryBuilderV2(this.reader),
+ logsListQueryBuilder: getLogsListQueryBuilder(this.reader, this.logsQuerySettings?.list),
+ logDetailQueryBuilder: getLogDetailQueryBuilder(this.reader, this.logsQuerySettings?.detail),
};
}
}
diff --git a/internal-packages/clickhouse/src/taskEvents.ts b/internal-packages/clickhouse/src/taskEvents.ts
index d8c1b8b7f6..f526cdf0b6 100644
--- a/internal-packages/clickhouse/src/taskEvents.ts
+++ b/internal-packages/clickhouse/src/taskEvents.ts
@@ -230,3 +230,98 @@ export function getSpanDetailsQueryBuilderV2(
settings,
});
}
+
+// ============================================================================
+// Logs List Query Builders (for aggregated logs page)
+// ============================================================================
+
+export const LogsListResult = z.object({
+ environment_id: z.string(),
+ organization_id: z.string(),
+ project_id: z.string(),
+ task_identifier: z.string(),
+ run_id: z.string(),
+ start_time: z.string(),
+ trace_id: z.string(),
+ span_id: z.string(),
+ parent_span_id: z.string(),
+ message: z.string(),
+ kind: z.string(),
+ status: z.string(),
+ duration: z.number().or(z.string()),
+ metadata: z.string(),
+ attributes: z.any(),
+});
+
+export type LogsListResult = z.output;
+
+export function getLogsListQueryBuilder(ch: ClickhouseReader, settings?: ClickHouseSettings) {
+ return ch.queryBuilderFast({
+ name: "getLogsList",
+ table: "trigger_dev.task_events_v2",
+ columns: [
+ "environment_id",
+ "organization_id",
+ "project_id",
+ "task_identifier",
+ "run_id",
+ "start_time",
+ "trace_id",
+ "span_id",
+ "parent_span_id",
+ { name: "message", expression: "LEFT(message, 512)" },
+ "kind",
+ "status",
+ "duration",
+ "metadata",
+ "attributes"
+ ],
+ settings,
+ });
+}
+
+// Single log detail query builder (for side panel)
+export const LogDetailV2Result = z.object({
+ environment_id: z.string(),
+ organization_id: z.string(),
+ project_id: z.string(),
+ task_identifier: z.string(),
+ run_id: z.string(),
+ start_time: z.string(),
+ trace_id: z.string(),
+ span_id: z.string(),
+ parent_span_id: z.string(),
+ message: z.string(),
+ kind: z.string(),
+ status: z.string(),
+ duration: z.number().or(z.string()),
+ metadata: z.string(),
+ attributes: z.any()
+});
+
+export type LogDetailV2Result = z.output;
+
+export function getLogDetailQueryBuilder(ch: ClickhouseReader, settings?: ClickHouseSettings) {
+ return ch.queryBuilderFast({
+ name: "getLogDetail",
+ table: "trigger_dev.task_events_v2",
+ columns: [
+ "environment_id",
+ "organization_id",
+ "project_id",
+ "task_identifier",
+ "run_id",
+ "start_time",
+ "trace_id",
+ "span_id",
+ "parent_span_id",
+ "message",
+ "kind",
+ "status",
+ "duration",
+ "metadata",
+ "attributes",
+ ],
+ settings,
+ });
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 185ae798c3..1fc310de96 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2715,6 +2715,40 @@ importers:
specifier: ^5
version: 5.5.4
+ references/seed:
+ dependencies:
+ '@sinclair/typebox':
+ specifier: ^0.34.3
+ version: 0.34.38
+ '@trigger.dev/build':
+ specifier: workspace:*
+ version: link:../../packages/build
+ '@trigger.dev/sdk':
+ specifier: workspace:*
+ version: link:../../packages/trigger-sdk
+ arktype:
+ specifier: ^2.0.0
+ version: 2.1.20
+ openai:
+ specifier: ^4.97.0
+ version: 4.97.0(encoding@0.1.13)(ws@8.18.3(bufferutil@4.0.9))(zod@3.25.76)
+ puppeteer-core:
+ specifier: ^24.15.0
+ version: 24.15.0(bufferutil@4.0.9)
+ replicate:
+ specifier: ^1.0.1
+ version: 1.0.1
+ yup:
+ specifier: ^1.6.1
+ version: 1.7.0
+ zod:
+ specifier: 3.25.76
+ version: 3.25.76
+ devDependencies:
+ trigger.dev:
+ specifier: workspace:*
+ version: link:../../packages/cli-v3
+
references/telemetry:
dependencies:
'@opentelemetry/resources':
@@ -11587,9 +11621,6 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
- bare-events@2.5.4:
- resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==}
-
bare-events@2.8.2:
resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
peerDependencies:
@@ -23623,7 +23654,7 @@ snapshots:
'@hono/node-ws@1.0.4(@hono/node-server@1.12.2(hono@4.5.11))(bufferutil@4.0.9)':
dependencies:
'@hono/node-server': 1.12.2(hono@4.5.11)
- ws: 8.18.0(bufferutil@4.0.9)
+ ws: 8.18.3(bufferutil@4.0.9)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -32012,17 +32043,14 @@ snapshots:
balanced-match@1.0.2: {}
- bare-events@2.5.4:
- optional: true
-
bare-events@2.8.2:
optional: true
bare-fs@4.5.1:
dependencies:
- bare-events: 2.5.4
+ bare-events: 2.8.2
bare-path: 3.0.0
- bare-stream: 2.6.5(bare-events@2.5.4)
+ bare-stream: 2.6.5(bare-events@2.8.2)
bare-url: 2.3.2
fast-fifo: 1.3.2
transitivePeerDependencies:
@@ -32037,11 +32065,11 @@ snapshots:
bare-os: 3.6.1
optional: true
- bare-stream@2.6.5(bare-events@2.5.4):
+ bare-stream@2.6.5(bare-events@2.8.2):
dependencies:
streamx: 2.22.0
optionalDependencies:
- bare-events: 2.5.4
+ bare-events: 2.8.2
transitivePeerDependencies:
- bare-abort-controller
optional: true
@@ -32094,7 +32122,7 @@ snapshots:
dependencies:
buffer: 5.7.1
inherits: 2.0.4
- readable-stream: 3.6.0
+ readable-stream: 3.6.2
body-parser@1.20.3:
dependencies:
@@ -40724,7 +40752,7 @@ snapshots:
end-of-stream: 1.4.4
fs-constants: 1.0.0
inherits: 2.0.4
- readable-stream: 3.6.0
+ readable-stream: 3.6.2
tar-stream@3.1.7:
dependencies:
diff --git a/references/nextjs-realtime/.eslintrc.json b/references/nextjs-realtime/.eslintrc.json
new file mode 100644
index 0000000000..6b10a5b739
--- /dev/null
+++ b/references/nextjs-realtime/.eslintrc.json
@@ -0,0 +1,6 @@
+{
+ "extends": [
+ "next/core-web-vitals",
+ "next/typescript"
+ ]
+}
diff --git a/references/seed/.gitignore b/references/seed/.gitignore
new file mode 100644
index 0000000000..6524f048dc
--- /dev/null
+++ b/references/seed/.gitignore
@@ -0,0 +1 @@
+.trigger
\ No newline at end of file
diff --git a/references/seed/package.json b/references/seed/package.json
new file mode 100644
index 0000000000..aa788c467a
--- /dev/null
+++ b/references/seed/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "references-seed",
+ "private": true,
+ "type": "module",
+ "devDependencies": {
+ "trigger.dev": "workspace:*"
+ },
+ "dependencies": {
+ "@trigger.dev/build": "workspace:*",
+ "@trigger.dev/sdk": "workspace:*",
+ "arktype": "^2.0.0",
+ "openai": "^4.97.0",
+ "puppeteer-core": "^24.15.0",
+ "replicate": "^1.0.1",
+ "yup": "^1.6.1",
+ "zod": "3.25.76",
+ "@sinclair/typebox": "^0.34.3"
+ },
+ "scripts": {
+ "dev": "trigger dev",
+ "deploy": "trigger deploy"
+ }
+}
diff --git a/references/seed/src/index.ts b/references/seed/src/index.ts
new file mode 100644
index 0000000000..cb0ff5c3b5
--- /dev/null
+++ b/references/seed/src/index.ts
@@ -0,0 +1 @@
+export {};
diff --git a/references/seed/src/trigger/logSpammer.ts b/references/seed/src/trigger/logSpammer.ts
new file mode 100644
index 0000000000..7156b55602
--- /dev/null
+++ b/references/seed/src/trigger/logSpammer.ts
@@ -0,0 +1,109 @@
+import { logger, task, wait } from "@trigger.dev/sdk/v3";
+
+const LONG_TEXT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
+
+const SEARCHABLE_TERMS = [
+ "authentication_failed",
+ "database_connection_error",
+ "payment_processed",
+ "user_registration_complete",
+ "api_rate_limit_exceeded",
+ "cache_invalidation",
+ "webhook_delivery_success",
+ "session_expired",
+ "file_upload_complete",
+ "email_sent_successfully",
+];
+
+function generateLargeJson(index: number) {
+ return {
+ requestId: `req_${Date.now()}_${index}`,
+ timestamp: new Date().toISOString(),
+ metadata: {
+ source: "log-spammer-task",
+ environment: "development",
+ version: "1.0.0",
+ region: ["us-east-1", "eu-west-1", "ap-southeast-1"][index % 3],
+ },
+ user: {
+ id: `user_${1000 + index}`,
+ email: `testuser${index}@example.com`,
+ name: `Test User ${index}`,
+ preferences: {
+ theme: index % 2 === 0 ? "dark" : "light",
+ notifications: { email: true, push: false, sms: index % 3 === 0 },
+ language: ["en", "es", "fr", "de"][index % 4],
+ },
+ },
+ payload: {
+ items: Array.from({ length: 5 }, (_, i) => ({
+ itemId: `item_${index}_${i}`,
+ name: `Product ${i}`,
+ price: Math.random() * 100,
+ quantity: Math.floor(Math.random() * 10) + 1,
+ tags: ["electronics", "sale", "featured"].slice(0, (i % 3) + 1),
+ })),
+ totals: {
+ subtotal: Math.random() * 500,
+ tax: Math.random() * 50,
+ shipping: Math.random() * 20,
+ discount: Math.random() * 30,
+ },
+ },
+ debugInfo: {
+ stackTrace: `Error: ${SEARCHABLE_TERMS[index % SEARCHABLE_TERMS.length]}\n at processRequest (/app/src/handlers/main.ts:${100 + index}:15)\n at handleEvent (/app/src/events/processor.ts:${50 + index}:8)\n at async Runtime.handler (/app/src/index.ts:25:3)`,
+ memoryUsage: { heapUsed: 45000000 + index * 1000, heapTotal: 90000000 },
+ cpuTime: Math.random() * 1000,
+ },
+ longDescription: LONG_TEXT.repeat(2),
+ };
+}
+
+export const logSpammerTask = task({
+ id: "log-spammer",
+ maxDuration: 300,
+ run: async () => {
+ logger.info("Starting log spammer task for search testing");
+
+ for (let i = 0; i < 50; i++) {
+ const term = SEARCHABLE_TERMS[i % SEARCHABLE_TERMS.length];
+ const jsonPayload = generateLargeJson(i);
+
+ logger.log(`Processing event: ${term}`, { data: jsonPayload });
+
+ if (i % 5 === 0) {
+ logger.warn(`Warning triggered for ${term}`, {
+ warningCode: `WARN_${i}`,
+ details: jsonPayload,
+ longMessage: LONG_TEXT,
+ });
+ }
+
+ if (i % 10 === 0) {
+ logger.error(`Error encountered: ${term}`, {
+ errorCode: `ERR_${i}`,
+ stack: jsonPayload.debugInfo.stackTrace,
+ context: jsonPayload,
+ });
+ }
+
+ logger.debug(`Debug info for iteration ${i}`, {
+ iteration: i,
+ searchTerm: term,
+ fullPayload: jsonPayload,
+ additionalText: `${LONG_TEXT} --- Iteration ${i} complete with term ${term}`,
+ });
+
+ if (i % 10 === 0) {
+ await wait.for({ seconds: 0.5 });
+ }
+ }
+
+ logger.info("Log spammer task completed", {
+ totalLogs: 50 * 4,
+ searchableTerms: SEARCHABLE_TERMS,
+ });
+
+ return { success: true, logsGenerated: 200 };
+ },
+});
diff --git a/references/seed/src/trigger/seedTask.ts b/references/seed/src/trigger/seedTask.ts
new file mode 100644
index 0000000000..2fb4305492
--- /dev/null
+++ b/references/seed/src/trigger/seedTask.ts
@@ -0,0 +1,32 @@
+import { task, batch } from "@trigger.dev/sdk/v3";
+import { ErrorTask } from "./throwError.js";
+import { SpanSpammerTask } from "./spanSpammer.js";
+import { logSpammerTask } from "./logSpammer.js";
+
+export const seedTask = task({
+ id: "seed-task",
+ run: async (payload: any, { ctx }) => {
+ let tasksToRun = [];
+
+ for (let i = 0; i < 10; i++) {
+ tasksToRun.push({
+ id: "simple-throw-error",
+ payload: {},
+ options: { delay: `${i}s` },
+ });
+ }
+
+ tasksToRun.push({
+ id: "span-spammer",
+ payload: {},
+ });
+
+ tasksToRun.push({
+ id: "log-spammer",
+ payload: {},
+ });
+
+ await batch.triggerAndWait(tasksToRun);
+ return;
+ },
+});
diff --git a/references/seed/src/trigger/spanSpammer.ts b/references/seed/src/trigger/spanSpammer.ts
new file mode 100644
index 0000000000..b16f00c4c2
--- /dev/null
+++ b/references/seed/src/trigger/spanSpammer.ts
@@ -0,0 +1,42 @@
+import { logger, task, wait } from "@trigger.dev/sdk/v3";
+
+const CONFIG = {
+ delayBetweenBatchesSeconds: 0.2,
+ logsPerBatch: 30,
+ totalBatches: 100,
+ initialDelaySeconds: 5,
+} as const;
+
+export const SpanSpammerTask = task({
+ id: "span-spammer",
+ maxDuration: 300,
+ run: async (payload: any, { ctx }) => {
+ const context = { payload, ctx };
+ let logCount = 0;
+
+ logger.info("Starting span spammer task", context);
+ logger.warn("This will generate a lot of logs", context);
+
+
+ const emitBatch = (prefix: string) => {
+ logger.debug("Started spam batch emit!", context);
+
+ for (let i = 0; i < CONFIG.logsPerBatch; i++) {
+ logger.log(`${prefix} ${++logCount}`, context);
+ }
+
+ logger.debug('Completed spam batch emit!', context);
+ };
+
+ emitBatch("Log number");
+ await wait.for({ seconds: CONFIG.initialDelaySeconds });
+
+ for (let batch = 0; batch < CONFIG.totalBatches; batch++) {
+ await wait.for({ seconds: CONFIG.delayBetweenBatchesSeconds });
+ emitBatch("This is a test log!!! Log number: ");
+ }
+
+ logger.info("Completed span spammer task", context);
+ return { message: `Created ${logCount} logs` };
+ },
+});
diff --git a/references/seed/src/trigger/throwError.ts b/references/seed/src/trigger/throwError.ts
new file mode 100644
index 0000000000..5f2d623a01
--- /dev/null
+++ b/references/seed/src/trigger/throwError.ts
@@ -0,0 +1,16 @@
+import { logger, task, wait } from "@trigger.dev/sdk/v3";
+
+
+export const ErrorTask = task({
+ id: "simple-throw-error",
+ maxDuration: 60,
+ run: async (payload: any, { ctx }) => {
+ logger.log("This task is about to throw an error!", { payload, ctx });
+
+ await wait.for({ seconds: 9 });
+ throw new Error("This is an expected test error from ErrorTask!");
+ },
+ onFailure: async ({ payload, error, ctx }) => {
+ logger.warn("ErrorTask failed!", { payload, error, ctx });
+ }
+});
diff --git a/references/seed/trigger.config.ts b/references/seed/trigger.config.ts
new file mode 100644
index 0000000000..f87620cd78
--- /dev/null
+++ b/references/seed/trigger.config.ts
@@ -0,0 +1,56 @@
+import { defineConfig } from "@trigger.dev/sdk/v3";
+import { syncEnvVars } from "@trigger.dev/build/extensions/core";
+import { lightpanda } from "@trigger.dev/build/extensions/lightpanda";
+
+export default defineConfig({
+ compatibilityFlags: ["run_engine_v2"],
+ project: process.env.TRIGGER_PROJECT_REF!,
+ experimental_processKeepAlive: {
+ enabled: true,
+ maxExecutionsPerProcess: 20,
+ },
+ logLevel: "debug",
+ maxDuration: 3600,
+ retries: {
+ enabledInDev: true,
+ default: {
+ maxAttempts: 3,
+ minTimeoutInMs: 1000,
+ maxTimeoutInMs: 10000,
+ factor: 2,
+ randomize: true,
+ },
+ },
+ machine: "small-2x",
+ build: {
+ extensions: [
+ lightpanda(),
+ syncEnvVars(async (ctx) => {
+ return [
+ { name: "SYNC_ENV", value: ctx.environment },
+ { name: "BRANCH", value: ctx.branch ?? "NO_BRANCH" },
+ { name: "BRANCH", value: "PARENT", isParentEnv: true },
+ { name: "SECRET_KEY", value: "secret-value" },
+ { name: "ANOTHER_SECRET", value: "another-secret-value" },
+ ];
+ }),
+ {
+ name: "npm-token",
+ onBuildComplete: async (context, manifest) => {
+ if (context.target === "dev") {
+ return;
+ }
+
+ context.addLayer({
+ id: "npm-token",
+ build: {
+ env: {
+ NPM_TOKEN: manifest.deploy.env?.NPM_TOKEN,
+ },
+ },
+ });
+ },
+ },
+ ],
+ },
+});
diff --git a/references/seed/tsconfig.json b/references/seed/tsconfig.json
new file mode 100644
index 0000000000..9a5ee0b9d6
--- /dev/null
+++ b/references/seed/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2023",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "esModuleInterop": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "customConditions": ["@triggerdotdev/source"],
+ "jsx": "preserve",
+ "lib": ["DOM", "DOM.Iterable"],
+ "noEmit": true
+ },
+ "include": ["./src/**/*.ts", "trigger.config.ts"]
+}