Files
triggerdotdev--trigger.dev/apps/webapp/test/realtime/runReaderProjection.test.ts
Daniel Sutton 65c545da4e refactor(run-store,webapp,run-engine): route Postgres TaskRun reads through the run store (#3990)
## Summary

Adds read methods to `RunStore` (`findRun`, `findRunOrThrow`,
`findRuns`) and routes every Postgres read of `TaskRun` through them,
mirroring how writes already go through the store. Behavior-preserving:
each relocated read keeps its exact query, field selection, and database
client (writer, replica, or transaction). This lets `TaskRun` reads be
retargeted to a different backing store later without touching call
sites.

Stacked on #3981 (the write adapter); that PR is the base of this one.

## Scope

In scope: the run engine, webapp services, presenters, and route
loaders. Three reads that pulled `TaskRun` in through a parent model's
relation `include` (alert delivery, batch results, attempt-dependency
cancellation) are decomposed to fetch the run(s) through the store and
stitch them back, since a relation include would not follow `TaskRun` to
a new table.

Left reading the existing table (out of scope): the legacy MarQS paths,
the legacy trigger idempotency read, and one raw-SQL recovery script
(commented for revisiting at cutover).

## Notes

Reads default to the read replica; callers pass the writer or a
transaction client wherever the original read did, so writer-vs-replica
behavior is unchanged.
2026-06-22 10:02:57 +01:00

78 lines
2.9 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { PostgresRunStore } from "@internal/run-store";
import { buildHydratorSelect, RunHydrator } from "~/services/realtime/runReader.server";
describe("buildHydratorSelect", () => {
it("returns the full select when nothing is skipped", () => {
const select = buildHydratorSelect([]);
expect(select.id).toBe(true);
expect(select.payload).toBe(true);
expect(select.output).toBe(true);
expect(select.metadata).toBe(true);
expect(select.error).toBe(true);
});
it("keeps protocol-reserved columns even when asked to skip them", () => {
// Reserved columns are always emitted by the serializer, so hydration must keep
// them regardless of skipColumns or the output is null/incorrect.
const select = buildHydratorSelect([
"status",
"taskIdentifier",
"createdAt",
"friendlyId",
"payload",
]);
expect(select.status).toBe(true);
expect(select.taskIdentifier).toBe(true);
expect(select.createdAt).toBe(true);
expect(select.friendlyId).toBe(true);
// A non-reserved skipped column is still dropped.
expect(select.payload).toBeUndefined();
});
it("drops skipped columns but always keeps id + updatedAt", () => {
const select = buildHydratorSelect(["payload", "output", "metadata", "error"]);
expect(select.payload).toBeUndefined();
expect(select.output).toBeUndefined();
expect(select.metadata).toBeUndefined();
expect(select.error).toBeUndefined();
// Needed internally regardless of skipColumns (keys the row, drives the diff/offset).
expect(select.id).toBe(true);
expect(select.updatedAt).toBe(true);
// A non-skipped column survives.
expect(select.status).toBe(true);
});
});
describe("RunHydrator.hydrateByIds column projection", () => {
function makeHydrator() {
let capturedSelect: Record<string, boolean> | undefined;
const replica = {
taskRun: {
findMany: vi.fn(async ({ select }: { select: Record<string, boolean> }) => {
capturedSelect = select;
return [];
}),
},
} as any;
const runStore = new PostgresRunStore({ prisma: replica, readOnlyPrisma: replica });
return { hydrator: new RunHydrator({ replica, runStore }), getSelect: () => capturedSelect };
}
it("projects the SELECT by skipColumns", async () => {
const { hydrator, getSelect } = makeHydrator();
await hydrator.hydrateByIds("env_1", ["run_1"], ["payload", "output"]);
const select = getSelect()!;
expect(select.payload).toBeUndefined();
expect(select.output).toBeUndefined();
expect(select.id).toBe(true);
expect(select.updatedAt).toBe(true);
});
it("selects the full column set when no skipColumns are given", async () => {
const { hydrator, getSelect } = makeHydrator();
await hydrator.hydrateByIds("env_1", ["run_1"]);
expect(getSelect()!.payload).toBe(true);
});
});