chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
import { type ClickHouse } from "@internal/clickhouse";
|
||||
import { type RunStore } from "@internal/run-store";
|
||||
import { type Tracer } from "@internal/tracing";
|
||||
import { type Logger, type LogLevel } from "@trigger.dev/core/logger";
|
||||
import { MachinePresetName } from "@trigger.dev/core/v3";
|
||||
import { BulkActionId, RunId } from "@trigger.dev/core/v3/isomorphic";
|
||||
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 PrismaClientOrTransaction } from "~/db.server";
|
||||
import { runStore as defaultRunStore } from "~/v3/runStore.server";
|
||||
import { startActiveSpan } from "~/v3/tracer.server";
|
||||
import { ClickHouseRunsRepository } from "./clickhouseRunsRepository.server";
|
||||
|
||||
export type RunsRepositoryOptions = {
|
||||
clickhouse: ClickHouse;
|
||||
prisma: PrismaClientOrTransaction;
|
||||
logger?: Logger;
|
||||
logLevel?: LogLevel;
|
||||
tracer?: Tracer;
|
||||
|
||||
// Injectable run-ops store; defaults to the `~/v3/runStore.server` singleton
|
||||
// (passthrough). The list-hydrate fan-out below does not depend on the store
|
||||
// routing mixed-residency id sets — it applies the read-through fan-out itself.
|
||||
runStore?: RunStore;
|
||||
|
||||
// Run-ops read-through wiring for the list hydrate. Omitted => passthrough.
|
||||
readThrough?: {
|
||||
// `legacyReplica` is a READ REPLICA handle only — there is no legacy-primary field.
|
||||
newClient?: PrismaClientOrTransaction;
|
||||
legacyReplica?: PrismaClientOrTransaction;
|
||||
// Resolved boot constant; when false the split branch is never entered.
|
||||
splitEnabled?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const RunStatus = z.enum(Object.values(TaskRunStatus) as [TaskRunStatus, ...TaskRunStatus[]]);
|
||||
|
||||
const RunListInputOptionsSchema = z.object({
|
||||
organizationId: z.string(),
|
||||
projectId: z.string(),
|
||||
environmentId: z.string(),
|
||||
//filters
|
||||
tasks: z.array(z.string()).optional(),
|
||||
versions: z.array(z.string()).optional(),
|
||||
statuses: z.array(RunStatus).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
// "any" (default) = run has at least one of `tags`; "all" = run has every tag.
|
||||
tagsMatch: z.enum(["any", "all"]).optional(),
|
||||
scheduleId: z.string().optional(),
|
||||
period: z.string().optional(),
|
||||
from: z.number().optional(),
|
||||
to: z.number().optional(),
|
||||
isTest: z.boolean().optional(),
|
||||
rootOnly: z.boolean().optional(),
|
||||
batchId: z.string().optional(),
|
||||
runId: z.array(z.string()).optional(),
|
||||
bulkId: z.string().optional(),
|
||||
queues: z.array(z.string()).optional(),
|
||||
regions: z.array(z.string()).optional(),
|
||||
machines: MachinePresetName.array().optional(),
|
||||
errorId: z.string().optional(),
|
||||
taskKinds: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export type RunListInputOptions = z.infer<typeof RunListInputOptionsSchema>;
|
||||
export type RunListInputFilters = Omit<
|
||||
RunListInputOptions,
|
||||
"organizationId" | "projectId" | "environmentId"
|
||||
>;
|
||||
|
||||
export type ParsedRunFilters = RunListInputFilters & {
|
||||
cursor?: string;
|
||||
direction?: "forward" | "backward";
|
||||
sources?: string[];
|
||||
};
|
||||
|
||||
export type FilterRunsOptions = Omit<RunListInputOptions, "period"> & {
|
||||
period: number | undefined;
|
||||
};
|
||||
|
||||
type Pagination = {
|
||||
page: {
|
||||
size: number;
|
||||
cursor?: string;
|
||||
direction?: "forward" | "backward";
|
||||
};
|
||||
};
|
||||
|
||||
type OffsetPagination = {
|
||||
offset: number;
|
||||
limit: number;
|
||||
};
|
||||
|
||||
export type ListedRun = Prisma.TaskRunGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
friendlyId: true;
|
||||
taskIdentifier: true;
|
||||
taskVersion: true;
|
||||
runtimeEnvironmentId: true;
|
||||
status: true;
|
||||
createdAt: true;
|
||||
startedAt: true;
|
||||
lockedAt: true;
|
||||
delayUntil: true;
|
||||
updatedAt: true;
|
||||
completedAt: true;
|
||||
isTest: true;
|
||||
spanId: true;
|
||||
idempotencyKey: true;
|
||||
ttl: true;
|
||||
expiredAt: true;
|
||||
costInCents: true;
|
||||
baseCostInCents: true;
|
||||
usageDurationMs: true;
|
||||
runTags: true;
|
||||
depth: true;
|
||||
rootTaskRunId: true;
|
||||
batchId: true;
|
||||
metadata: true;
|
||||
metadataType: true;
|
||||
machinePreset: true;
|
||||
queue: true;
|
||||
workerQueue: true;
|
||||
region: true;
|
||||
annotations: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type ListRunsOptions = RunListInputOptions & Pagination;
|
||||
|
||||
export type TagListOptions = {
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
period?: string;
|
||||
from?: number;
|
||||
to?: number;
|
||||
/** Performs a case insensitive contains search on the tag name */
|
||||
query?: string;
|
||||
} & OffsetPagination;
|
||||
|
||||
export type TagList = {
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
export type CursorPagination = {
|
||||
nextCursor: string | null;
|
||||
previousCursor: string | null;
|
||||
};
|
||||
|
||||
export type RunIdsPage = {
|
||||
runIds: string[];
|
||||
pagination: CursorPagination;
|
||||
};
|
||||
|
||||
export interface IRunsRepository {
|
||||
name: string;
|
||||
/**
|
||||
* A keyset-paginated page of run ids plus the cursors to navigate
|
||||
* forward/backward. The cursors are opaque composite `(created_at, run_id)`
|
||||
* tokens, so pagination can't duplicate or skip runs. This is the single
|
||||
* cursor-aware list primitive — `listRuns` and bulk actions build on it.
|
||||
*/
|
||||
listRunIds(options: ListRunsOptions): Promise<RunIdsPage>;
|
||||
/** Returns friendly IDs (e.g., run_xxx) instead of internal UUIDs. Used for ClickHouse task_events queries. */
|
||||
listFriendlyRunIds(options: ListRunsOptions): Promise<string[]>;
|
||||
listRuns(options: ListRunsOptions): Promise<{
|
||||
runs: ListedRun[];
|
||||
pagination: {
|
||||
nextCursor: string | null;
|
||||
previousCursor: string | null;
|
||||
};
|
||||
}>;
|
||||
countRuns(options: RunListInputOptions): Promise<number>;
|
||||
listTags(options: TagListOptions): Promise<TagList>;
|
||||
runExistsInEnvironment(options: {
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
createdAtLowerBoundMs?: number;
|
||||
}): Promise<boolean>;
|
||||
}
|
||||
|
||||
export class RunsRepository implements IRunsRepository {
|
||||
private readonly clickHouseRunsRepository: ClickHouseRunsRepository;
|
||||
|
||||
constructor(private readonly options: RunsRepositoryOptions) {
|
||||
this.clickHouseRunsRepository = new ClickHouseRunsRepository(options);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "runsRepository";
|
||||
}
|
||||
|
||||
async runExistsInEnvironment(options: {
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
createdAtLowerBoundMs?: number;
|
||||
}): Promise<boolean> {
|
||||
return startActiveSpan(
|
||||
"runsRepository.runExistsInEnvironment",
|
||||
async () => this.clickHouseRunsRepository.runExistsInEnvironment(options),
|
||||
{
|
||||
attributes: {
|
||||
"repository.name": "clickhouse",
|
||||
organizationId: options.organizationId,
|
||||
projectId: options.projectId,
|
||||
environmentId: options.environmentId,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async listRunIds(options: ListRunsOptions): Promise<RunIdsPage> {
|
||||
return startActiveSpan(
|
||||
"runsRepository.listRunIds",
|
||||
async () => this.clickHouseRunsRepository.listRunIds(options),
|
||||
{
|
||||
attributes: {
|
||||
"repository.name": "clickhouse",
|
||||
organizationId: options.organizationId,
|
||||
projectId: options.projectId,
|
||||
environmentId: options.environmentId,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async listFriendlyRunIds(options: ListRunsOptions): Promise<string[]> {
|
||||
return startActiveSpan(
|
||||
"runsRepository.listFriendlyRunIds",
|
||||
async () => this.clickHouseRunsRepository.listFriendlyRunIds(options),
|
||||
{
|
||||
attributes: {
|
||||
"repository.name": "clickhouse",
|
||||
"readThrough.split": Boolean(this.options.readThrough?.splitEnabled),
|
||||
organizationId: options.organizationId,
|
||||
projectId: options.projectId,
|
||||
environmentId: options.environmentId,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async listRuns(options: ListRunsOptions): Promise<{
|
||||
runs: ListedRun[];
|
||||
pagination: {
|
||||
nextCursor: string | null;
|
||||
previousCursor: string | null;
|
||||
};
|
||||
}> {
|
||||
return startActiveSpan(
|
||||
"runsRepository.listRuns",
|
||||
async () => this.clickHouseRunsRepository.listRuns(options),
|
||||
{
|
||||
attributes: {
|
||||
"repository.name": "clickhouse",
|
||||
"readThrough.split": Boolean(this.options.readThrough?.splitEnabled),
|
||||
organizationId: options.organizationId,
|
||||
projectId: options.projectId,
|
||||
environmentId: options.environmentId,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async countRuns(options: RunListInputOptions): Promise<number> {
|
||||
return startActiveSpan(
|
||||
"runsRepository.countRuns",
|
||||
async () => this.clickHouseRunsRepository.countRuns(options),
|
||||
{
|
||||
attributes: {
|
||||
"repository.name": "clickhouse",
|
||||
organizationId: options.organizationId,
|
||||
projectId: options.projectId,
|
||||
environmentId: options.environmentId,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async listTags(options: TagListOptions): Promise<TagList> {
|
||||
return startActiveSpan(
|
||||
"runsRepository.listTags",
|
||||
async () => this.clickHouseRunsRepository.listTags(options),
|
||||
{
|
||||
attributes: {
|
||||
"repository.name": "clickhouse",
|
||||
organizationId: options.organizationId,
|
||||
projectId: options.projectId,
|
||||
environmentId: options.environmentId,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRunListInputOptions(data: any): RunListInputOptions {
|
||||
return RunListInputOptionsSchema.parse(data);
|
||||
}
|
||||
|
||||
export async function convertRunListInputOptionsToFilterRunsOptions(
|
||||
options: RunListInputOptions,
|
||||
prisma: RunsRepositoryOptions["prisma"],
|
||||
store: RunStore = defaultRunStore
|
||||
): Promise<FilterRunsOptions> {
|
||||
const convertedOptions: FilterRunsOptions = {
|
||||
...options,
|
||||
period: undefined,
|
||||
};
|
||||
|
||||
// Convert time period to ms
|
||||
const time = timeFilters({
|
||||
period: options.period,
|
||||
from: options.from,
|
||||
to: options.to,
|
||||
});
|
||||
convertedOptions.period = time.period ? (parseDuration(time.period) ?? undefined) : undefined;
|
||||
|
||||
// Cross-DB resolution: BatchTaskRun is a RUN-OPS table. A run-ops batch resident on the
|
||||
// dedicated run-ops DB must resolve via the store's NEW->LEGACY probe — a single control-plane
|
||||
// client would miss it and leave the friendlyId in the ClickHouse `batch_id` filter, matching
|
||||
// nothing. Split off / self-host: the store is a passthrough over the one client.
|
||||
if (options.batchId && options.batchId.startsWith("batch_")) {
|
||||
const batch = await store.findBatchTaskRunByFriendlyId(options.batchId, options.environmentId);
|
||||
|
||||
if (batch) {
|
||||
convertedOptions.batchId = batch.id;
|
||||
}
|
||||
}
|
||||
|
||||
// ScheduleId can be a friendlyId. TaskSchedule is a CONTROL-PLANE table, so this stays on
|
||||
// the passed `prisma` (the control-plane client) in both single-DB and split modes.
|
||||
if (options.scheduleId && options.scheduleId.startsWith("sched_")) {
|
||||
const schedule = await prisma.taskSchedule.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
friendlyId: options.scheduleId,
|
||||
projectId: options.projectId,
|
||||
},
|
||||
});
|
||||
|
||||
if (schedule) {
|
||||
convertedOptions.scheduleId = schedule?.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.bulkId && options.bulkId.startsWith("bulk_")) {
|
||||
convertedOptions.bulkId = BulkActionId.toId(options.bulkId);
|
||||
}
|
||||
|
||||
if (options.runId) {
|
||||
// Convert to friendlyId
|
||||
convertedOptions.runId = options.runId.map((r) => RunId.toFriendlyId(r));
|
||||
}
|
||||
|
||||
// batchId/runId/scheduleId target specific runs, so rootOnly is meaningless and forced off.
|
||||
// tasks is intentionally excluded so rootOnly can narrow a task filter to root runs only.
|
||||
if (options.batchId || options.runId?.length || options.scheduleId) {
|
||||
convertedOptions.rootOnly = false;
|
||||
}
|
||||
|
||||
return convertedOptions;
|
||||
}
|
||||
Reference in New Issue
Block a user